MOTIR-4229Blocked
(motir-core) The Public address pane, part 2 — the custom-domain list: add with DNS instructions, verify, every certificate state, make primary, remove, the tier upsell; en + zh
The second half of the pane — the custom-domain list that renders in the slot part 1 left: add a domain and see the records to create, verify it, watch every certificate state with an action beside it, make one primary, remove one, and meet the tier gate honestly. Built to the design panels 3, 3b, 4, 5, 6, 7 and 9, over the lifecycle's routes, showing what the status job last wrote.
What ships
_components/CustomDomainsSection.tsxreplaces part 1's stub: theCardwith the list ofPublicAddressDtorows, Add a domain, and — per row — the hostname, a statusPill,lastCheckedAt, and the row's actions.- The state set, total (panel 5): a
Record<PublicAddressStatus, { tone, labelKey, hintKey, actions }>— a compile error when the enum grows — mappingunverified(show the records · re-check · remove),verifying(re-check · remove),pending_certificate(waiting copy, re-check · remove),issued(make primary · remove),failed(thefailureReason· show the records · re-check · remove),expiredandrevoked(the reason · re-check · remove);activeandaliasnever appear in this list (they are the subdomain's) and the map says so. - Add (panel 4): a
Modal— hostnameInput→POST …/public-addresses→ the DNS-instruction block from the DTO (verificationTXT anddnsrecords, each name / value with a copy button; the apex-vs-subdomain shape follows what the DTO returns), Verify now →POST …/verify, I'll do this later closes with the row leftunverified. - Verify / re-check →
POST …/verify; Make primary (panel 6) →POST …/primary, enabled only onissuedrows with theTooltipotherwise, and the every other address redirects here consequence line; Remove (panel 7) →DELETE, the archive-confirm grammar. - The tier gate (panel 3b): when
POSTanswers the billing surface's entitlement-exceeded shape withentitlement: 'custom_domains', render the shipped upgrade prompt (the 8.1.7 / 8.1.8 component — compose it, do not redraw it) — and, when the org's tier allows zero, render Add a domain disabled with the same prompt before any request. - Liveness: while any row is
verifyingorpending_certificate, re-read the list every 30 s (the client-island tick —CLAUDE.md's page-state-after-mutation routing) so the pane shows the job's writes without a reload; stop when no row is pending. - Copy and i18n —
settings.publicAddress.domains.*inen.jsonandzh.json; the parity test. - Tests: every status value rendered from a fixture (a table-driven test over the enum, asserting the map is total); the add flow's three exits; make-primary disabled on non-issued; the entitlement prompt from the route's shape; the 30 s tick starts and stops with the pending set; i18n parity.
Boundary
No service or route change, no subdomain UI (part 1), no design change. Nothing outside motir-core.
Acceptance criteria
- Each of the seven customer-domain statuses renders the pill, hint and action set the design's panel 5 draws, from fixtures, in a table-driven test; the map is a total
RecordoverPublicAddressStatus. - Adding a domain shows the exact TXT and pointing records the DTO carries with working copy buttons, and Verify now moves the row's state without a reload; a cap refusal renders the shipped upgrade prompt.
- Make primary is enabled only for
issuedrows; after it, that row reads primary and the consequence line is shown; Remove asks for confirmation and removes the row. - While a row is pending, the list re-reads on the tick and reflects a state the job wrote; the tick stops once nothing is pending (asserted with fake timers).
- Every string is in both catalogues; the parity test is green; the per-file coverage floor holds; no file outside
motir-coreis touched.
Context refs
- the design — panels 3, 3b, 4, 5, 6, 7, 9;
motir-core/design/projects/public-address.mock.html - the lifecycle — the DTO and the five routes; the entitlement — the refusal shape; the status job — where the states come from
motir-core/app/(authed)/settings/project/members/_components/ProjectMembersSettings.tsx— optimistic writes, toasts, the confirm grammar; the billing upgrade-prompt component (8.1.7 / 8.1.8)motir-core/messages/en.json·messages/zh.json;motir-core/CLAUDE.md— component + token reuse, the client-island tick