(motir-core) The Public address pane, part 1 — the rail entry and its doors, the pane shell, and the workspace subdomain card: claim, rename with the retained-alias warning, copy and open; en + zh
Opened by Zhu Yue ·
The first half of the settings surface a customer configures their address from: the rail entry, both doors into it, the pane's shell, and the workspace-subdomain card — built to the design panels 0, 1, 2, 8 and 9, over the subdomain service. The custom-domain list renders INSIDE this pane and is part 2; this card ships the room it renders in. The pane is split in two on purpose: one card for both would exceed the estimation gate, and the design's allocation gives each card its own panels.
What ships
- The rail entry —
lib/settings/projectSettingsNav.tsgains{ id: 'public-address', group: 'access', href: '/settings/project/public-address', icon: Globe, labelKey: 'nav.publicAddress', permission: 'project:manage_access' }betweenmembersandroles(the design's panel 0), with the VERIFIED-permission comment the file's other entries carry; the registry totality test and the destination guard (guardSettingsPage('public-address', ctx)) cover it by construction. Cloud-only: the entry is absent and the pagenotFound()s whenisCloud()is false — theBuildInPublicPromoCardpattern (app/(authed)/settings/project/page.tsx). - The second door — the share-link row in
ProjectMembersSettings.tsx(design/public-projects Panel 6, rendered only while the project is public) gains the Set up your own address → link to the pane, exactly as panel 0 draws it. A small edit to a shipped component: read its access gate before adding the link. - The page —
app/(authed)/settings/project/public-address/page.tsxin the shape of the members page: session → active project →guardSettingsPage→ the pane's reads (the workspace subdomain DTO, and the addresses list part 2 will render) behindSuspensewithSettingsPaneFrame. - The subdomain card —
_components/PublicSubdomainCard.tsx: panel 1 (unclaimed: theInputwith the livehelperTextpreview<label>.<base>, grammar hint, inline reserved / taken / invalid errors mapped from the route's codes), panel 2 (claimed: the address row with copy + open ↗, Rename with the retained-alias warning andrenamesLeft, the rename confirmModal), panel 8 (non-admin read-only — a workspacemembersees the address and no controls, using the samecanManage-style gate the members pane uses but for the WORKSPACE role, which is what the service asserts). Optimistic writes throughPUT /api/workspaces/{id}/public-subdomainwith the members pane's toast + rollback pattern. - A slot for part 2 — the page composes
<CustomDomainsSection />from a file part 2 creates; this card ships it as a typed stub that renders nothing, so part 2 is an addition, not an edit. - Copy and i18n — every string under
settings.publicAddress.*inmessages/en.jsonANDmessages/zh.json(the repository ships both; a key present in one and not the other fails the i18n parity test — run it). - Tests: the registry entry (totality, permission, group order); the page's cloud gate and destination guard; the card's four states rendered from fixtures; the rename confirm; the error mapping for each route code; i18n parity.
Boundary
No custom-domain UI (part 2), no service or route change (the subdomain service's are complete), no design change — a deviation from the asset is recorded ON the asset, not left in code. Nothing outside motir-core.
Acceptance criteria
- Public address appears in the settings rail in the
accessgroup between Members & access and Roles for a user holdingproject:manage_accesson a cloud build, and is absent — with the route answering404— whenMOTIR_CLOUDis unset; the registry totality test and the destination-guard test both cover the new entry without an exemption. - The share-link row on the members pane links to the new pane while the project is public, and not otherwise.
- The subdomain card renders the unclaimed, claimed and read-only states as the design's panels 1, 2 and 8 draw them; claiming a reserved label shows the inline error from the route's code without a page reload; renaming shows the retained-alias warning and the remaining count, and the old address appears under the card as an alias after confirm.
- Every string is in
en.jsonandzh.json; the parity test is green. pnpm testcovers the card at the repository's ≥90% per-file floor; no file outsidemotir-coreis touched.
Context refs
- the design — panels 0, 1, 2, 8, 9 and the access-path table;
motir-core/design/projects/public-address.mock.html - the subdomain service — the DTO and route this card calls; part 2 — what renders in the slot
motir-core/lib/settings/projectSettingsNav.ts·app/(authed)/settings/project/_guard.tsx·app/(authed)/settings/project/members/page.tsx+_components/ProjectMembersSettings.tsx— the pane grammar, the gate, the share-link rowmotir-core/app/(authed)/settings/project/page.tsx—isCloud()gating a settings affordance (BuildInPublicPromoCard)motir-core/messages/en.json·messages/zh.json— the two cataloguesmotir-core/components/ui/*—Card,Input,Button,Modal,Pill,Tooltip;motir-core/CLAUDE.md— component + token reuse
Discussion
No comments yet.
Adding to this discussion signs you in on app.motir.co and brings you back to this request.