Skip to content

moooon

Motir

Vibe your whole project. Bring an idea — Motir's three AI layers plan it, track it, and ship it, end to end. You're looking at Motir, built in Motir.

  • Vibe Project
  • Open Source
  • AI Agent
  • AI Loop
1
requests
0
upvotes
145
planned
1,361
shipped

Motir · Roadmap

MOTIR-4010Done

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.md AMENDMENT 2 §D decided it, and the reason is that legal.signUpNotice is *"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

filetodayafter
app/(auth)/sign-up/_components/SignUpCard.tsx:308,313two <Link href="/legal/terms"> / "/legal/privacy" inside one sentencethe 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:495a rail row, href: '/legal', Scale icon, no active armthe manifest's index target; no manifest ⇒ no row
app/(auth)/re-consent/_components/LegalDocumentRow.tsx:60href={/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

  1. The links become ABSOLUTE and CROSS-ORIGIN. They are <Link> today because the target was same-origin. A configured URL points at another host, so next/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.
  2. OutstandingDocument may need a url. lib/legal/consent.ts's outstandingReconsent produces the rows the interstitial renders. Trace the field end to end — manifest entry → LegalDocumentOutstandingDocument → the row's prop — and if url does 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 in messages/en.json splits: the keys the /legal PAGES 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 en key needs its zh twin 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.md AMENDMENT 2 §D decided the unconfigured notice is ABSENT rather than re-flowed, so this card adds no legal.* key and the parity gate has nothing new to check. legal.signUpNotice is 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:19 carries 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 /legal page 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 is done before 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 url for the document it names, and a test asserts the rendered href equals 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.md AMENDMENT 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.
  • url reaches the re-consent row's prop end to end, or the PR body shows the trace proving it already did.
  • Every new en key has its zh twin; 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.ts pins stay at their thresholds.
  • pnpm lint · format:check · typecheck · test pass; 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 links
  • motir-core/app/(authed)/_components/SidebarNav.tsx — the rail row and its no-active comment
  • motir-core/app/(auth)/re-consent/_components/LegalDocumentRow.tsx · ReconsentCard.tsx · ReconsentDeferred.tsx — the interstitial, and the comment that goes stale
  • motir-core/lib/legal/consent.tsOutstandingDocument, the row's shape
  • motir-core/messages/en.json · messages/zh.json — the legal.* namespace
  • motir-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 url field this consumes; blocked_by
  • the design amendment — the unconfigured arm this builds to; blocked_by

Advisory dispositions

  • The remaining reference advisories 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.

Add a comment