# demarkus-library — Debt & Deferred Work Technical debt and consciously deferred improvements. ## Reading-room rendering upgrades (inspired by markdownviewer.pages.dev) Capabilities to absorb, NOT the tool (it's a client-side editor — inverse architecture). Split along ADR 0003: - **Server-side, zero JS (do first):** syntax highlighting via goldmark-highlighting/chroma; GFM alerts; emoji extension. Bluemonday policy must allow chroma classes/spans. - **JS islands (tracked ADR 0003 concessions):** mermaid.js island (degrades to `
` code block); KaTeX island (degrades to TeX source).
  Vendored like htmx, no CDN.
- **Editor/split/export = Phase 3 cataloging desk** (CodeMirror island
  already in the plan) — not a bolt-on.

## Existing

- Per-reader MCP session pool: idle eviction is lazy/inline; if pool churn
  ever matters, consider background sweep + metrics.
- Broker `rateLimit.tokens` raised to 60/min during 429 incident — drop to
  ~30/min now that reads are 1 request (deploy-side).
- `mark_worlds` enumeration tool on the broker would enable a generated
  floor page (today the root hub serves by convention).
- Frontmatter strip is render-side only; the publishers that wrote metadata
  fences into bodies should be found and fixed at source.


## Related documents

- [ADR 0003 HTMX SSR philosophy](/demarkus-library/adr/0003-htmx-ssr-philosophy.md): rendering split and JS island concessions
- [Phase 3 cataloging desk](/demarkus-library/plans/phase-3-cataloging-desk.md): editor, split, export deferred here
- [Audit 2026-06-18](/demarkus-library/audit-2026-06-18.md): prioritized hardening backlog
- [Handler decomposition plan](/demarkus-library/plans/handler-decomposition.md): the sequence that pays this page down


## Backlinks depend on a document having been rendered

The referenced-by block reads the R3 observed-links map, which is populated when a document pane renders, not by crawling. The map is in-memory and per-pod, so every library restart empties it and a document shows no backlinks until each linking document is opened again. That is what made the edition bug so visible: editions were browsed and recorded, while some real sources had not been opened since the last roll.

Worth deciding whether the margin should fall back to the hub `/graph.md` (which the floor already parses) when the observed map has nothing for a document, so a cold pod shows the durable inbound edges rather than an empty list. Recorded 2026-08-18 alongside the fix in [/demarkus-library/debugging.md](/demarkus-library/debugging.md).


## The comma-ok exception is unwritten

The shared guidelines say never return more than two values unless one is
`error`. Four functions in the web adapter return two heterogeneous values
plus a trailing `bool`: `returnTrail`, `rewriteHref`, `previewableAnchor`
and `paneAddrFromRoute`. That is Go's comma-ok idiom, and rewriting them
into result structs would read worse than what they replace.

They are the only remaining hits of a strict AST sweep, so the decision to
make is on the guideline, not the code: either it names comma-ok as an
exception, or the sweep keeps reporting four false positives forever. Worth
raising against `mark://latebit/projects/demarkus/guidelines.md` rather than
patching around locally.

Recorded 2026-09-10, after the parameter-struct branch cleared every other
hit. Note that both the 2026-09-09 sweep and the handler-decomposition plan
had recorded the return-value rule as already holding; it did not, and nine
functions were in breach at the time.


## Resolved

The `ReadingHandler` breadth entry was paid off on 2026-09-10 (PR 73): the shared nav chrome moved to one type built once per request, and each surface family got its own handler over the slice of the port it drives. Steps 1 to 3 of the handler decomposition plan.

The wide-parameter-list entry was paid off the same day on
`refactor/param-structs` (steps 4 and 5). All three clusters converted
together: `domain.PublishRequest` and `domain.LookupRequest` across the
port, the service and the broker, world and federated adapters; `edgeEnd`,
`edgeStyle`, `ellipse`, `wmDraw`, `wmAgg` and `wmRanking` across the graph,
world map and floor builders; and `paneRequest` for the web adapter's
`paneView`. The sweep that found 22 wide signatures now finds none. SVG
output was byte-pinned before and after, and the route registration set
still matches main.

Three-value returns went with them on the same branch. `wmRankDocs` returns
a `wmRanking` plus the linked count and `floorRing` returns a
`floorRingGeom`, both fixed in passing because they sat in the files being
refactored. The OAuth flow's `Begin` was the one worth its own decision —
three interchangeable strings plus an error, spanning the web adapter's
port, the broker implementation in `cmd/`, and one fake — and now returns a
`web.LoginStart`. `TestFullLoginFlow` covers the converted path.


## The broker's Publish comment describes behavior it no longer has

`Gateway.Publish` in `internal/adapter/outbound/broker/broker.go` carries a
doc comment saying `on_conflict` is `"fail"`, that an expected-version
mismatch surfaces as a reload prompt, that the structural-merge flow is "a
later slice", and that the method returns the new version.

None of that is true now. The code picks `"merge"` for any edit and only
uses `"fail"` on a create (version 0), the merge flow shipped as
`PublishResult.Merge` plus `MergeCandidate`, and the method returns a
`PublishResult`. The inline comment a few lines lower already explains the
real choice by intent, so the header is the only wrong part.

Caught reviewing PR 74, which rewrote that function's signature. The fix was
written and then dropped from the branch to keep the merge signature-only, so
it is still on main. Small and self-contained: replace the last two sentences
of the header and let the inline comment carry the rule.


## Every error response is JSON, against the no-JSON rule

ADR 0003 says the room serves HTML and no JSON. Ten handlers return
`echo.NewHTTPError`, and nothing assigns `HTTPErrorHandler`, so Echo's default
writes a JSON body for every 400, 401, 404, 410, 500, 502 and 503 the room
produces. The one place this is already acknowledged is `page.html`, whose
`hx-status` rule suppresses 4xx and 5xx swaps precisely because "Echo's default
error body is JSON".

The fix is one custom `HTTPErrorHandler` rendering an HTML error page, plus
letting htmx swap error bodies again once it does. Both halves belong together;
doing the handler alone leaves the suppression rule lying about why it exists.

Raised by CodeRabbit against the 410 archived response on PR 76 and declined
there, because that response follows the same shape as the 404 beside it and
fixing one status while nine others stay JSON would be worse than the current
consistent state. Recorded 2026-09-11.


### Correction: the read path now answers in HTML

The entry above recorded the JSON error bodies as accepted debt after declining
the finding on PR 76. CodeRabbit re-raised it as an outside-diff comment,
narrowed to the trail boundary, and the narrow framing was the useful part: the
dead ends a reader actually reaches by clicking a link all pass through one
function.

`presentError` now renders an `error` template with the right status instead of
returning `echo.NewHTTPError`, so every read failure on the trail, document,
tag, palette, spatial and edit surfaces answers in HTML. The page carries no nav
or palette on purpose; a dead end needs the reason and a way back, not chrome.

What remains JSON is the routes outside the read path: a malformed trail, a
missing theme asset, an off-duty librarian, an invalid edit version. Those are
machine boundaries rather than reader-facing ones, so the remaining work is
smaller and less urgent than this entry first implied.

The original decline reasoning was wrong in one respect worth keeping: it argued
that fixing one status while others stayed JSON would be incoherent. True, and
the answer was to fix the whole read path rather than the single 410, which is
what shipped. Scoping to one shared presenter made a change that looked
repo-wide into a local one.