The three legal-link surfaces render from configuration — sign-up, the rail row and the re-consent document rows, with the unconfigured arm
Opened by Zhu Yue ·
Type: code · Executor: coding_agent · Repo: motir-core · ONE PR.
Three shipped surfaces hard-code a /legal path. This card points all three at the configured manifest the registry card built, and implements the UNCONFIGURED arm the design amendment draws. It is the story's feature-finishing card on the application host — after it lands, a person can click through sign-up, the rail and the re-consent screen and see the whole change.
⚠️ AMENDED 2026-09-01 by MOTIR-4004 — the SIGN-UP NOTICE IS ABSENT WHEN UNCONFIGURED, not re-flowed.
docs/decisions/public-surface-hosts.mdAMENDMENT 2 §D decided it, and the reason is thatlegal.signUpNoticeis *"By creating a Motir account you agree to our<terms>Terms of Service</terms>and<privacy>Privacy Policy</privacy>."* — a sentence entirely ABOUT two documents. Unlinking it does not weaken the notice, it makes it FALSE: an assertion that the reader agreed to documents nobody published. A self-hoster has no Terms of Service, and the honest sign-up form is one that does not claim otherwise. **The whole<p>does not render.** **This REMOVES work rather than adding it**: no new copy string, nozhtwin, no parity risk.legal.signUpNotice` survives unchanged for the CONFIGURED case.
The three surfaces, at origin/main e76d1afdd
| file | today | after |
|---|---|---|
app/(auth)/sign-up/_components/SignUpCard.tsx:308,313 | two <Link href="/legal/terms"> / "/legal/privacy" inside one sentence | the manifest's url for each; absent ⇒ LegalNotice renders NOTHING (⚠️ AMENDED 2026-09-01 — was "renders in its unconfigured form"; docs/decisions/public-surface-hosts.md AMENDMENT 2 §D) |
app/(authed)/_components/SidebarNav.tsx:495 | a rail row, href: '/legal', Scale icon, no active arm | the manifest's index target; no manifest ⇒ no row |
app/(auth)/re-consent/_components/LegalDocumentRow.tsx:60 | href={/legal/${slug}} | that document's url from the outstanding row; absent ⇒ the row renders without its link |
Two things that change SHAPE, not just a string
- The links become ABSOLUTE and CROSS-ORIGIN. They are
<Link>today because the target was same-origin. A configured URL points at another host, sonext/link's prefetching and client navigation are wrong for it — use a plain anchor with the treatment the design amendment draws, and reconcile with the shipped external-link treatment rather than inventing one. OutstandingDocumentmay need aurl.lib/legal/consent.ts'soutstandingReconsentproduces the rows the interstitial renders. Trace the field end to end — manifest entry →LegalDocument→OutstandingDocument→ the row's prop — and ifurldoes not survive that path today, carrying it is part of this card. A value that exists in the manifest and reaches no prop is, from the row's side, indistinguishable from one that was never configured.
The copy, and the zh twin
- The
legal.*namespace inmessages/en.jsonsplits: the keys the/legalPAGES use leave with those pages (the deletion card); the keys these three surfaces use STAY. This card adds whatever the unconfigured arm needs. - Every new
enkey needs itszhtwin in the same PR — the catalogue parity gate.That includes the re-flowed sign-up sentence, which is the one piece of new copy here— ⚠️ AMENDED 2026-09-01: there is NO new copy.docs/decisions/public-surface-hosts.mdAMENDMENT 2 §D decided the unconfigured notice is ABSENT rather than re-flowed, so this card adds nolegal.*key and the parity gate has nothing new to check.legal.signUpNoticeis unchanged and still used for the configured case. If you find yourself authoring a string for the unconfigured arm, you are building the superseded shape. app/(auth)/re-consent/_components/ReconsentDeferred.tsx:19carries a comment asserting "the document stays readable —/legal/<slug>is in the(public)group". That sentence becomes false. Correct it here rather than leaving a comment that outranks the code in a reader's mind.
The 4-layer contract
Nothing here is a route, a service or a repository change: it is three render surfaces reading a module that already exists. Reuse the shipped primitives and the --el-* element tokens plus element-semantic shape tokens; add no new container, no raw rounded-* / p-*, and no component a primitive already covers.
Boundary
- It renders no
/legalpage and deletes none.app/(public)/legal/is still served when this merges; removing it is the deletion card's. - It does not change the re-consent FLOW — the gate, the materiality rule, the single Agree-and-continue act and the deferral screen are unchanged.
- It does not change
lib/legal/documents.ts— that module is the registry card's and isdonebefore this starts. - It provisions nothing. In every environment this card runs in, the manifest is unset unless a developer sets it locally; that is the arm this card is mostly about.
Acceptance criteria
git grep -n "'/legal\|\"/legal\|\/legal" app/ components/returns **no hard-coded legal path** outsideapp/(public)/legal/` itself.- Configured: each of the three surfaces renders the manifest's
urlfor the document it names, and a test asserts the renderedhrefequals the configured value rather than a path. - Unconfigured: sign-up renders no legal paragraph at all (⚠️ AMENDED 2026-09-01 —
docs/decisions/public-surface-hosts.mdAMENDMENT 2 §D); the rail row is absent; the re-consent row renders without its link. All three are asserted, and the sign-up assertion asserts the ABSENCE OF THE PARAGRAPH — not merely the absence of an anchor, and not the text of a re-flowed sentence that no longer exists. - The cross-origin links are plain anchors with the drawn treatment, not
next/link, and a test asserts the element type. urlreaches the re-consent row's prop end to end, or the PR body shows the trace proving it already did.- Every new
enkey has itszhtwin; the catalogue parity gate passes. ReconsentDeferred.tsx's(public)-group comment is corrected.- ≥90% branch/function/line coverage on every file this PR modifies, and the five files
vitest.config.tspins stay at their thresholds. pnpm lint · format:check · typecheck · testpass; AA contrast holds in both themes for anything the unconfigured arm introduces.
Context refs
motir-core/app/(auth)/sign-up/_components/SignUpCard.tsx— the sentence and its two linksmotir-core/app/(authed)/_components/SidebarNav.tsx— the rail row and its no-activecommentmotir-core/app/(auth)/re-consent/_components/LegalDocumentRow.tsx·ReconsentCard.tsx·ReconsentDeferred.tsx— the interstitial, and the comment that goes stalemotir-core/lib/legal/consent.ts—OutstandingDocument, the row's shapemotir-core/messages/en.json·messages/zh.json— thelegal.*namespacemotir-core/tests/components/signup-legal-notice.test.tsx·tests/components/reconsent-card.test.tsx— the shipped component tests these surfaces already have- the registry card — the
urlfield this consumes;blocked_by - the design amendment — the unconfigured arm this builds to;
blocked_by
Advisory dispositions
- The remaining
referenceadvisories name cards this one's Boundary excludes. A boundary exists to say a deliverable is somebody else's, and naming the owner is what makes the exclusion checkable — so the reference is deliberate and no edge is owed.
Discussion
No comments yet.
Adding to this discussion signs you in on app.motir.co and brings you back to this request.