resolved 177b04ad-5381-4c49-892a-8431f94e760c
MOTIVATION
The renderer has been stripped of all reserved payload keys. _embed was
removed in ticket 8d949ef3 and _links was removed in 6dafb799. Both
removals were deliberate — the data model should describe what exists,
and the UI layer should decide how to present what exists. Payload
directives violated that principle and are gone.
The consequence of those removals: the world-UI pages at /w/:slug/...
currently render without inline links (entity ids and turn refs are
plain text) and without a breadcrumb navigation strip. Separately, the
/dashboard page lists worlds but provides no clickable entry point
into any specific world's session page — the slug and name cells are
plain text, so discovery requires copying a slug and typing a URL.
This parent ticket bundles the work to restore all three capabilities, built on the clean metadata rather than on top of it. Every restoration derives its output from context passed explicitly from route handler to renderer, or from concrete fields on the world registry. Payloads stay pure data. No reserved keys reintroduced.
UI chrome — linking, navigation, discovery affordances — is derived from route context and registry data, not from payload directives. The renderer gets told what page it's rendering and where that page sits in the hierarchy. It produces chrome from that information plus static rules. Payloads describe the world, turn, or entity being displayed and contain no presentation instructions.
Both Child A and Child B consume a single context struct, constructed
by the route handler in src/server.rs:
pub struct PageContext { pub page_type: PageType, // Session | Turn | Entity pub world_slug: String, // always present for /w/... routes pub turn: Option, // Some for Turn pages pub entity_id: Option, // Some for Entity pages pub chain_range: Option<(u64, u64)>, // min/max turn for prev/next gating }
pub enum PageType { Session, Turn, Entity }
One struct, two consumers. The handler builds it once per request and
passes it to render::render_page alongside the payload. The renderer
calls into linking (Child A) and nav (Child B) with the same context.
The context shape is established here as documentation. The struct
itself lives in src/render.rs and is introduced by whichever child
lands first; the second child imports it. No struct definition merges
from this parent — the parent is documentation and ticketing only.
Child C does NOT need PageContext. It operates at the dashboard layer, which is outside the world-UI render flow.
Every point-to-point navigation is covered once all three land.
Child A — Pattern rules for inline linking (PageContext). Adds
src/linking.rs with a rules table, threads PageContext into the
renderer, reuses replace_exact_skipping_tags (retained from 8d949ef3
for exactly this purpose). Entity ids and turn refs in rendered
content become clickable anchors.
Child B — Breadcrumb navigation (PageContext). Adds a breadcrumb
generator (in src/render.rs), per-page-type match arm, threads
PageContext into the renderer, emits <nav> above the title. Dashboard
link on every world page, session link on turn and entity pages, prev
and next turn arrows on turn pages.
Child C — Dashboard entry points. Makes the dashboard a functional
entry point: slug and name cells on each row link to /w/:slug; rows
sort by last_activity newest-first. Operates on src/html.rs and
src/server.rs, independent of the other two children.
Same pattern as the 8894dd80 model-adjustments parent: shared principle,
independent children, no sequence dependency. No depends_on edges
between the three children.
File overlap analysis:
src/linking.rs (new), src/render.rs (adds
linking call in render_page), and src/server.rs (constructs
PageContext in handlers).src/render.rs (adds breadcrumb generator and
nav emission before title) and src/server.rs (constructs
PageContext in handlers, same as Child A).src/html.rs (dashboard row rendering) and
src/server.rs (possibly adds last_activity to WorldRow
construction).A and B both modify src/render.rs in adjacent but distinct line
ranges (linking as a post-pass, nav as a pre-title emission), and
both modify src/server.rs to construct PageContext. Normal rebase
hygiene handles this. C touches html.rs and a different part of
server.rs; zero overlap risk.
Parent accepts when all three children resolve. No cascade needed; children close themselves.
Post-parent live check against the deployed ant-verify world:
/dashboard, click on the ant-verify row → land on
/w/ant-verify without typing the URL./w/ant-verify, see a breadcrumb with dashboard link./w/ant-verify, entity ids (ant, crumb, etc.) in the rendered
content are <a> wrapped links./w/ant-verify, turn_NNNNNN refs in the turn list are <a>
wrapped links./w/ant-verify/turn/2, see breadcrumb with
dashboard + session + ← turn 1 (no next, since 2 is the current
max)./w/ant-verify/turn/2, entity refs in events are clickable./w/ant-verify/entity/ant, see breadcrumb with
dashboard + session. Turn refs in the event history are clickable.?format=json on every page returns pure data: no _links,
no _embed, no new reserved top-level keys._embed or _links as escape hatches.All three model-adjustment children implemented, merged, deployed, and smoke-verified against the live ant-verify world.
replace_exact_skipping_tags consumer (the helper retained through the last three tickets now has a live user; dead_code warning is gone). 8 new unit tests.All verified against live ant-verify world on pod chukwa-6db8f6888f-tfxbj:
turn_NNNNNN anchors in the turn list (Child A regex rule).ant entity anchors (Child A entities-map rule).<nav class="links"> above <h1> on all three page types (Child B).?format=json on every /w/... route returns a top-level object with NO reserved keys — no _links, no _embed, no new keys. The data model describes what exists; the UI layer decides how to present what exists. Achieved without payload directives as the parent's principle demanded.
replace_exact_skipping_tags now has its first real consumer (Child A). If future ticket wants to add more rules, the linking module is the natural home.Per standing guidance I am not confirming this parent ticket — only proposing. The three children are also in proposed_resolution; accepting them resolves their individual contributions, and closing this parent afterward reflects the bundle is complete. Cascade not needed at acceptance time since children close independently.
All three children (5db770f0 Child A linking, ba39e5d1 Child B breadcrumbs, 06dab5e7 Child C dashboard) in proposed_resolution with full receipts. Bundled into one deploy at main HEAD 83def6f on pod chukwa-6db8f6888f-tfxbj. End-to-end navigation chain works per parent's acceptance checklist. Parent ready to close once caller accepts each child.
Caller accepted: All three children resolved and individually audited (5db770f0 linking, ba39e5d1 breadcrumbs, 06dab5e7 dashboard entry points). End-to-end navigation chain is the one the parent spec called for: Landing → Dashboard → Session (Child C click-through) → Turn/Entity (Child A inline links) → adjacent via breadcrumb (Child B), with ?format=json still returning pure data and no reserved top-level keys. The data-model / UI-chrome split holds: every chrome element is derived from PageContext or from registry metadata, never from a payload directive. The retained replace_exact_skipping_tags helper finally has a real consumer and the dead_code warning is gone. Accepting parent to clear the queue.
Sign in as a human to drive this ticket from the page, or use the MCP tools.
Ticket created: Context-driven UI chrome: restore linking, navigation, and dashboard entry points