(motir-core) The rail's `Docs` row is a DEAD link — it still hard-codes `/docs` after MOTIR-3932 moved that surface to motir-marketing, while the `Legal` row beside it got the configured-url treatment
Found by motir run MOTIR-4130 while drawing the rail's bottom section (2026-09-02). It holds nothing up and is filed forward. Not absorbed into MOTIR-4130, whose Boundary is explicit that no .tsx is edited.
The defect
app/(authed)/_components/SidebarNav.tsx's bottom section, on origin/main 8d80ac8db:
{
// The documentation area's front door (MOTIR-2570) — `/docs`, not
// `/docs/api`: the index IS the area …
icon: <BookOpen />,
label: t('nav.docs'),
href: '/docs',
},
Nothing in this repository serves /docs. Measured at 8d80ac8db:
| check | command | result |
|---|---|---|
| a route | git ls-tree -r --name-only HEAD app | grep -i docs | only ApiDocsLinkPanel.tsx — no page |
| a redirect | grep -n "source: '/docs'" next.config.ts | no match (DOCS_REDIRECTS maps /api-docs/* → /docs/*, i.e. /docs is a DESTINATION, never a source) |
| a rewrite | grep -n "rewrites" next.config.ts | none defined — only async redirects() |
| middleware | grep -n docs middleware.ts | no match |
So a signed-in user who clicks Docs in the rail gets a 404. The public reading surface moved to motir-marketing under MOTIR-3932; the row that points at it did not move with it.
Independent corroboration, from a guard that already knew. tests/design-asset-addresses.test.ts's KNOWN table carries eight /docs entries, every one reasoned "The public reading surface moved to motir-marketing (MOTIR-3932); this asset is a point-in-time record of the route as it was on app.motir.co." The guard has been recording that /docs is dead for every design asset that names it — while the shipped rail row kept pointing there, because no guard reads a .tsx href against the route tree.
Why the row beside it is the answer
The Legal row had exactly this problem and was fixed three hours before this was found. MOTIR-3909 / MOTIR-4010 moved the legal documents out of the repository and gave the row a resolver — lib/legal/links.ts's legalIndexUrl() — which returns the operator's configured absolute url, or null, and the row does not render at all when it is null:
...(legalIndexUrl
? [{ icon: <Scale />, label: t('nav.legal'), href: legalIndexUrl }]
: []),
That is the shape this row wants, for the same reason: "a door pointing nowhere is worse than no door" (lib/legal/links.ts's own docstring). The two rows are adjacent in the same section, they lost their destination to the same architectural split, and only one of them was repaired.
Boundary
- It does not move the docs surface. That already happened (MOTIR-3932).
- It does not decide the hosting arrangement. Where
motir-marketingpublishes the docs is settled; this card reads it, it does not choose it.
Acceptance criteria
- The rail's
Docsrow resolves to a page a signed-in user can reach, or it does not render — never a link to a 404. Which of the two is a function of configuration, exactly as theLegalrow's is. - The mechanism matches the
Legalrow's unless there is a stated reason to differ: a server-side resolver returningstring | null, resolved inapp/(authed)/layout.tsxand passed to the client component as a prop, with the row spread-conditional on it. Do not invent a second pattern for the same problem in the same section. - Whatever the resolver reads is named in
docs/decisions/public-surface-hosts.md, beside the legal manifest it sits next to. - A test asserts the row is absent when the resolver returns
null, mirroringtests/components/SidebarNav-legal-door.test.tsx. design/shell/rail-bottom-section.mock.htmlis updated to draw theDocsrow's two arms, and itsKNOWNentry intests/design-asset-addresses.test.tsis removed — that entry exists only to record this defect, and it is asserted tight in both directions, so leaving it fails the suite once the address stops being drawn.- The other seven
/docsKNOWNentries are left alone: they are point-in-time records of assets drawn before the move, which is a different thing from a live row.
Context refs
motir-core/app/(authed)/_components/SidebarNav.tsx— the dead rowmotir-core/lib/legal/links.ts—legalIndexUrl(), the pattern to mirrormotir-core/app/(authed)/layout.tsx— where the sibling resolver is called and threadedmotir-core/tests/components/SidebarNav-legal-door.test.tsx— the test to mirrormotir-core/tests/design-asset-addresses.test.ts— theKNOWNtable, and the entry this card removesmotir-core/design/shell/rail-bottom-section.mock.html— the asset to update- MOTIR-3932 — the move that stranded the row
- MOTIR-4130 — the card that found it;
relates_to
Advisory dispositions
- No
blocked_byis owed to MOTIR-4130, and here is the command that settles it rather than the assertion alone: this card editsSidebarNav.tsx,lib/,app/(authed)/layout.tsxandtests/, none of which MOTIR-4130 touches (git show --staton its branch isdesign/**plus onetests/design-asset-addresses.test.tsentry). The one file both name is that guard'sKNOWNtable, where MOTIR-4130 ADDS the entry this card REMOVES — a dependency in the ordering sense, which is whatrelates_toplus the criterion above records. If this card runs first, the entry is simply never added.