Sign in to edit tickets from this page.

← all tickets · home

Breadcrumb navigation via PageContext

resolved ba39e5d1-46cd-496d-9ad7-08aeca4cb9e8

created_at
2026-04-24
updated_at
2026-04-24
code_context
src/render.rs, src/server.rs
priority
P2
ticket_type
feature
parent
177b04ad
resolved_at
2026-04-24
resolution
accepted

Body

CONTEXT

Child of 177b04ad. See the parent for motivation, the principle (UI chrome derived from context, not payload), and the PageContext struct definition shared with Child A. This ticket is narrowly scoped to breadcrumb navigation.

================================================================ THE CHANGE

After this ticket: every /w/:slug/... page renders a breadcrumb navigation strip above the <h1> title, containing links appropriate to the page type (dashboard, session, prev/next turn). The strip is derived from PageContext — no payload directives, no reserved keys.

================================================================ BREADCRUMB GENERATOR

One function, top of src/render.rs:

fn render_breadcrumbs(context: &PageContext) -> String;

Returns <nav class="links">...</nav> markup with per-link anchors separated by ·. Returns empty string if the context is somehow internally inconsistent (e.g. Turn page type with turn: None), but that should never happen — handlers construct the context and are trusted.

Per-page-type rules:

Session page (page_type = Session):

That's it. No self link (you're on the page), no up link (session is the top of a world's hierarchy from the UI's POV).

Turn page (page_type = Turn):

When chain_range is None (shouldn't happen for a real turn page; defensive), omit prev and next.

Entity page (page_type = Entity):

No prev/next for entities.

================================================================ CSS

The .links CSS class was defined in wrap_document's inline stylesheet as part of the original web-UI feature. When the _links renderer was removed in 6dafb799, the CSS rule was left in place (nobody deleted it; it just became orphaned). Verify in implementation: grep for .links { in wrap_document's stylesheet. If the rule is still there, reuse it — that was its original purpose and the styling is appropriate. If the rule was swept away, re-add it matching the original shape:

.links { display: flex; flex-wrap: wrap; gap: 0.6rem; font-size: 0.9rem; padding: 0.4rem 0.6rem; background: color-mix(in srgb, currentColor 6%, transparent); border-radius: 6px; margin-bottom: 0.8rem; } .links a { color: #2b6cb0; text-decoration: none; } .links a:hover { text-decoration: underline; } .links .sep { color: #bbb; }

Do NOT add new CSS selectors beyond .links and its descendants. The CSS ticket's discipline from 6438ac5b applies: no scope creep.

================================================================ MODIFICATIONS

src/render.rs:

src/server.rs:

Each of the three world-UI handlers constructs PageContext from URL path params and the world handle. If Child A lands first, the construction is already there — this child just passes the existing Some(&ctx) through. If B lands first, the handlers add the construction.

The turn-page handler specifically needs chain_range for the prev/next gating. Compute via rt.turns.list() and fold — same computation the old build_turn_payload did before 6dafb799 removed it. Lift it into the handler, pass through PageContext, do not reintroduce it to the payload.

For the session and entity page handlers, chain_range can be None — neither page needs it.

src/views.rs:

No change. Builders stay pure-data.

================================================================ TESTS

All in src/render.rs::tests. No live-router needed.

================================================================ ACCEPTANCE

================================================================ EXPLICITLY OUT OF SCOPE

================================================================ NO OPEN KNOBS

Spec is prescriptive. Handler should not need to make judgment calls. If a question arises during implementation, leave a comment rather than guessing.

Proposed resolution

Implemented per spec.

Source (src/render.rs + src/server.rs):

Tests (8 new in render::tests):

Existing 14 render-module tests updated to pass None for the new context arg; all still pass.

Verification:

Production smoke:

Scope discipline:

Per standing guidance I am not confirming — only proposing.

History (4 events)

Sign in as a human to drive this ticket from the page, or use the MCP tools.