Skip to content

Editor

The editor module is the editor-facing surface of the platform — the interface a newsroom editor would actually use to tune the recommender’s behaviour. It is deliberately ordinary: server-rendered HTML with HTMX for partial-page updates, no SPA framework, no JavaScript build pipeline for its UI layer.

The shape decisions are recorded in two ADRs:

  • ADR-0005: TypeScript + Express, swappable by design — the swap is the architectural argument, not the editor itself.
  • ADR-0011: SSR + HTMX, no SPA. The aesthetic should match real CMS admin panels, not flashy demos.

The whole TypeScript half of the tutorial lives under editor/.

Rep #9 is the only rep in TypeScript, and it makes the editor’s defining property concrete: the editor renders, the platform ranks.

Terminal window
make -C editor run # render the recommendation-preview surface to HTML (no server)
make -C editor check # the editor vitest suite (113 tests)
make -C editor clean # remove test caches
make -C editor dev # the live HTMX editor against the app contract (optional)

editor/demo-preview.ts builds a sample app-contract response and renders the editor surface to HTML — the controls form and the slate — without starting Express. Read the output and you will see the five editorial constraints as range/number/switch controls, and the form wired with hx-post="/preview", hx-target="#recommendation-preview", hx-swap="outerHTML".

Where does the ranking happen — in this editor, or behind the app contract?

Behind the app contract. The editor never re-orders a slate. When a slider moves, the form hx-posts the new constraint values to the platform’s /preview endpoint; the platform re-ranks and returns a fresh #recommendation-preview partial that HTMX swaps in. The editor’s only jobs are rendering the slate and posting the controls. That is exactly what makes it swappable (ADR-0005): a Streamlit demo or a real CMS could replace this client without the platform changing, because the editorial logic lives in the contract, not the UI.

Following the foundations template (ADR-0033), in the TS idiom: make run renders the surface deterministically with no server or fetch, make check runs the 113-test vitest suite as the correctness gate, and the live UI is one make dev away. The reps you drill here are SSR + HTMX rendering and the swappable-client boundary — the part of the stack with the most transferable value.

The editor interface presents a configuration to the editor: sliders for the three soft constraints (diversity, recency, sentiment), toggles + a threshold for the sensitive-topic guard, and a list management UI for editorial promotion. A preview pane shows the recommendation list a sample user would see under the current configuration. Moving any control issues an HTMX hx-post to /preview, which refreshes the preview pane in place — no page reload, no JavaScript framework.

That live-preview shape is the operational form of editorial accountability: an editor sees the effect of a change before they commit the change.

The TypeScript client types are generated from the canonical OpenAPI catalog in editor/openapi/app-contract-sources.json (ADR-0006). That means a contract change makes the editor fail at compile time, not in production.

editor/tests/ holds 76 Vitest tests, all passing. Render tests confirm the HTML output shape; repository tests confirm the API integration; route tests confirm form submission flows. The HTMX partials are tested by asserting the returned HTML fragment shape.

The Express side uses strict TypeScript, the generated OpenAPI client preserves the catalogued app-contract schemas, and the Zod validators at form boundaries ensure even editor-input data is checked before it reaches the API. That keeps the OpenAPI contract visible from the HTTP boundary to the rendered HTML — which is the production-realism payoff of ADR-0005’s TypeScript choice.

The assistants module hangs its UI partials inside the editor — when an editor changes a slider, the recommendations partial refreshes and an HTMX hx-get triggers the change-explainer partial to describe why the list moved.