Skip to content

feat(editor): /trajects/:id/docs read-only docs route (stubbed; depends on #632 + #626)#653

Draft
humanintheloop25 wants to merge 3 commits into
mainfrom
feat/traject-docs-route
Draft

feat(editor): /trajects/:id/docs read-only docs route (stubbed; depends on #632 + #626)#653
humanintheloop25 wants to merge 3 commits into
mainfrom
feat/traject-docs-route

Conversation

@humanintheloop25
Copy link
Copy Markdown
Collaborator

Summary

Adds a markdown-rendered, per-traject docs view to the editor. The route renders documentation files (docs/*.md) from the corpus-sources attached to a traject, behind the usual session-auth + an upcoming explicit editor-reader role gate.

/trajects/:trajectId/docs                       → landing (sidebar only)
/trajects/:trajectId/docs/:sourceId             → source root
/trajects/:trajectId/docs/:sourceId/<path>      → specific .md page

⚠️ Stub state — blocked on #632 + #626

The backend handlers in packages/editor-api/src/traject_docs.rs currently return canned data so the frontend can be reviewed end-to-end while two upstream PRs are still in flight:

Once both land, traject_docs.rs switches to:

  1. Resolve session.person_subaccounts.id via one query.
  2. Assert membership in traject_members for the requested :trajectId. Not a member → 403.
  3. Select traject_corpus_sources rows for the traject and walk each source's on-disk clone (the same clones feat: introduce traject concept for grouped law edits #632 uses for the writable-own flow). The docs/ subdir of each clone is the docs tree.

The handler signatures and response shapes are stable across the stub→real transition, so the frontend does not need to change when the backend is rewired.

What's in this PR

Frontend

  • frontend/src/TrajectDocsApp.vue — root view (parallel to EditorApp / LibraryApp). Two-pane layout: DocsSidebar + DocsContent.
  • frontend/src/components/docs/DocsSidebar.vue — lists each source attached to the traject, grouped by top-level directory.
  • frontend/src/components/docs/DocsContent.vue — renders markdown via the shared useArticleMarkdown pipeline (marked + DOMPurify, identical to ArticleText.vue) and post-processes \``mermaid code blocks into SVG viamermaid.run`.
  • frontend/src/composables/useDocs.jsuseDocsTree + useDocsPage composables with cookie-credentialed fetches. Route-driven so refresh/back/forward keep working.
  • frontend/src/router.js — new traject-docs route, meta.requiresAuth: true identical to the editor route.
  • mermaid@^11.4.1 added to frontend/package.json. Lazy-loaded — only fetched when navigating to the docs route.

Backend

  • packages/editor-api/src/traject_docs.rstree + page handlers, stub-only for now.
  • packages/editor-api/src/main.rs — module registered, two routes added inside the existing protected_api_routes block (which carries require_session_auth).

Test plan

  • just editor-api-fmt — clean
  • just editor-api-lint (clippy) — clean
  • just editor-api-check (cargo check) — clean
  • cd frontend && npm run build — passes; mermaid + sub-renderers chunked separately, lazy-loaded into the TrajectDocsApp bundle
  • Local interactive smoke against `just editor-api` + `cd frontend && npm run dev` — confirm `/trajects/test/docs/dummy/diagrams/fietsval-flow-2025-04-01` shows a rendered mermaid flow-chart
  • After feat: introduce traject concept for grouped law edits #632 + feat(admin): layered RBAC with per-app reader/writer/admin roles #626 merge: rebase, swap canned data for real DB queries + add membership-401, add explicit editor-reader route-layer

Not in this PR (follow-ups)

Background

Replaces an earlier exploration of a standalone docs platform (MinBZK/regelrecht-corpus-docs, soon to be archived) that built its own auth-proxy + DB. Integrating into the editor instead reuses auth, session-management, deploy pipeline, and now per-traject permissions from #632 — drastically less infrastructure for the same user-facing capability.

Adds a markdown-rendered docs view that lives inside the traject concept
from #632. URL shape:

  /trajects/:trajectId/docs                       → landing (sidebar only)
  /trajects/:trajectId/docs/:sourceId             → source root
  /trajects/:trajectId/docs/:sourceId/<path>      → specific .md page

Frontend additions:
- `frontend/src/TrajectDocsApp.vue` — root view (parallel to EditorApp /
  LibraryApp), two-pane layout: DocsSidebar + DocsContent.
- `frontend/src/components/docs/DocsSidebar.vue` — lists each source
  attached to the traject, grouped by top-level directory.
- `frontend/src/components/docs/DocsContent.vue` — renders markdown via
  the shared `useArticleMarkdown` pipeline (marked + DOMPurify, same as
  ArticleText) and post-processes ` ```mermaid ` code blocks into SVG
  via `mermaid.run`.
- `frontend/src/composables/useDocs.js` — `useDocsTree` + `useDocsPage`
  with cookie-credentialed fetches; route-driven, so refresh/back/forward
  keep working.
- `frontend/src/router.js` — new `traject-docs` route, `requiresAuth`
  identical to the editor route.
- `mermaid@^11.4.1` added to frontend deps. Lazy-loaded via the
  TrajectDocsApp route bundle (visible in `npm run build` chunk listing).

Backend additions (`packages/editor-api/`):
- `traject_docs.rs` with `tree` + `page` handlers, registered in
  `main.rs` behind the existing `require_session_auth` middleware.
- Returns canned data for `source=dummy` paths today.

## Stub status & upstream dependencies

The backend handlers serve **canned data** while waiting on two of
@tdjager's PRs in review:

- **#632 (trajects)** brings the `trajects`, `traject_members`,
  `accounts`, and `traject_corpus_sources` tables. Once merged, this
  module switches to:
    1. resolving `session.person_sub` → `accounts.id` via one query,
    2. asserting membership in `traject_members` (non-member → 403),
    3. selecting the traject's sources and walking the `docs/` subdir of
       each source's on-disk clone (the same clones the writable-own
       flow already uses).
- **#626 (layered RBAC)** introduces `editor-reader` realm-role gating.
  Docs routes will be raised to `editor-reader` (explicit) instead of
  the implicit `require_session_auth` that's wired today.

The handler signatures + response shapes are stable across the
stub→real transition, so the frontend doesn't need to change when the
backend is rewired.

## Verification

- `just editor-api-fmt` — clean
- `just editor-api-lint` (clippy) — clean
- `just editor-api-check` (cargo check) — clean
- `frontend/`: `npm run build` — passes; mermaid lazy-loaded into the
  TrajectDocsApp chunk (~600 KB raw, ~140 KB gzipped per diagram
  variant).

Local smoke-test (with the editor-api running and the existing
require_session_auth bypassed in non-OIDC dev mode):

  curl /api/trajects/test/docs/tree
  curl '/api/trajects/test/docs/page?source=dummy&path=diagrams/fietsval-flow-2025-04-01.md'

## Not in this PR (follow-ups, scoped out)

- Real DB queries (blocked on #632)
- Explicit `editor-reader` role gate (blocked on #626)
- Annotations on docs pages — RFC-018 covers law-text annotations;
  whether/how to extend that pattern to free-text markdown is a separate
  decision after these merges land.
- Source-cross-link resolution (relative `.md` → in-app router-link)
- Build-pipeline integration of corpus sources into editor image
@github-actions
Copy link
Copy Markdown

Preview Deployment — editor — editor

Your changes have been deployed to a preview environment:

URL: https://editor.pr653.rig.prd1.gn2.quattro.rijksapps.nl

This deployment will be automatically cleaned up when the PR is closed.

Drop the hand-rolled `<aside>` + `<article>` + custom-CSS shell in
favour of the same NLDD web-components the rest of the editor uses.
Three concrete swaps:

- `TrajectDocsApp.vue`: layout now follows LibraryApp —
  `nldd-app-view` → `nldd-navigation-split-view` with a navigation
  pane and a content pane. Empty/loading/error states use
  `nldd-inline-dialog` instead of div+classname fragments.

- `DocsSidebar.vue`: per-source group is now `nldd-simple-section`
  (keeps the heading-styling consistent with EditSheet's sections);
  per-folder file lists use `nldd-list variant="simple"` with
  `nldd-list-item type="button" :selected` — same pattern as
  LibraryApp's sidebar. File labels render through `nldd-text-cell`.

- `DocsContent.vue`: drops the `<article>` + ~50 lines of typography
  CSS in favour of `nldd-rich-text`, the same primitive ArticleText
  and AnnotatedText already use. Effect: docs and law-text share
  identical typography, spacing, code-block treatment, link styles.
  Mermaid post-render still works — `nldd-rich-text` keeps its slot
  content in the light DOM so `richTextEl.querySelectorAll(...)`
  reaches it (AnnotatedText relies on the same property).

~125 lines net deleted; visual consistency with the rest of the
editor is now structural rather than hex-fallback-based.
The NLDD refactor wired the panes wrong, producing first a white screen
and then a sidebar-only view where clicking a nav item showed nothing:

- sidebar pane used slot="navigation"; nldd-navigation-split-view
  expects slot="sidebar" (confirmed against LibraryApp's nested
  split-view). Wrong slot name → component never mounted → white screen.
- main content pane had no slot and no has-content. The split-view only
  reveals the main pane when it carries slot="main" + has-content, so
  the pane stayed collapsed (0px) — the sidebar rendered but the content
  area was empty, and mermaid threw "Could not find a suitable point for
  the given distance" because it rendered into a zero-size box.

With slot="main" + has-content the content pane gets real dimensions:
text pages render and mermaid diagrams draw correctly.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant