domain-settings
Skill527 starsChanged 4 months agoMITView on GitHub
Keep the three custom-domain surfaces in sync — profile domain settings, publication domain settings, and the leaflet share-menu domain picker. Use whenever you change how domains are listed, verified, assigned, unassigned, or deleted in any one of them, since each surface hand-rolls its own row UI over the same data and drifts silently.
In this repository12 other files it gives its agents.
- CLAUDE.mdCLAUDE.md
- blueprint-generateSkill
- blueprintSkill
- leaflet-ai-apiSkill
- lexiconsSkill
- mirror-editor-block-stylesSkill
- notificationsSkill
- publication-settingsSkill
- specSkill
- tests-postsSkill
- tests-subscribeSkill
- update-default-themeSkill
The file
We split this skill into the rules it gives an agent
1 rule. About 0 are common, 0 are distinctive, and 1 is very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈0
Distinctive · estimate
This author's own choices. Worth a look.
1
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 50
Returns
nullwhen not pending: Never make a row that opens it unconditionally, or a verified domain opens an empty modal/view.
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Custom domain settings
A custom domain is one row of custom_domains, reachable from three places that each
render their own row markup over the same identity data and the same
useDomainStatus / DomainVerification pieces. Nothing is shared below the data layer,
so a change to one surface leaves the other two looking and behaving differently, with no
type error and nothing failing in CI.
The three surfaces
| # | Surface | Entry point | Rows |
| 1 | Profile domain settings (/settings?tab=domains) | app/(app)/(identity)/(home-pages)/(writer)/settings/domains/DomainTab.tsx | PublicationDomain.tsx, LeafletDomain.tsx, UnassignedDomain.tsx |
| 2 | Publication domain settings (dashboard → settings) | app/(app)/(identity)/lish/[did]/[publication]/dashboard/settings/PubDomainSettings.tsx | PubDomainRow (current/default + alternates), UnassignedDomainRow (available) |
| 3 | Leaflet share menu → custom domain | app/(app)/(editor)/[leaflet_id]/actions/ShareOptions/DomainOptions.tsx | LinkedDomainOption, NonLinkedDomainRow, plus VerifyDomainView |
Surface 1 is the only place a domain is added (AddDomainForm) or deleted
(DeleteDomainButton). Surfaces 2 and 3 only assign/unassign an existing domain.
Surfaces 1 and 2 open verification through DomainVerificationModal, exported from
settings/domains/DomainVerification.tsx — pass it domain and a trigger; it owns
the modal title, width, and DomainVerification body. Don't re-wrap Modal +
DomainVerification by hand in either place.
Surface 3 deliberately does not use that modal: it lives in the share popover and swaps
the menu to its own VerifyDomainView via
setDomainMenuState({ state: "domain-settings", domain }). Leave it that way unless asked.
So an "open the verification UI" change is expressed twice — once as a
DomainVerificationModal trigger, once as a menu-state transition.
What they share
useDomainStatus(domain)(settings/domains/useDomainStatus.ts) — SWR onget_domain_status;pendingis truthy while the domain is misconfigured or awaiting DNS verification. One hook call per row, so it must be called from the row component.useDomainStatuses(domains)(same file) — the list-level version, one SWR key for the whole list, used when a surface needs to bucket or order rows by verification state (surface 3 separates unverified rows from available ones with it). It seeds the per-domaindomain-status-${domain}keys, so a row or view that later callsuseDomainStatusreads from cache instead of refetching. Don't calluseDomainStatusin a loop to get the same thing — the hook count has to stay stable as domains are added and deleted.DomainVerification(settings/domains/DomainVerification.tsx) — the DNS record table. Returnsnullwhen not pending. Never make a row that opens it unconditionally, or a verified domain opens an empty modal/view.DomainVerificationModal(same file) — the modal wrapper around it, used by surfaces 1 and 2 (see above).getDomainAssignment/describeAssignment(settings/domains/domainAssignment.ts) — classifies a domain aspublication/document/unassigned. Surface 1 buckets its sections with it; surface 2 uses it for the reassign confirmation. Surface 3 filters by hand (publication_domains.length === 0, then route match onpermission_token.id) instead of using it — so a change to what counts as "assigned" needs applying there too.- Server actions in
actions/domains/index.ts:addDomain,assignDomainToDocument,assignDomainToPublication,removeDomainAssignment,removeDomainRoute,deleteDomain. - Data source:
identity.custom_domainsviauseIdentityData(surfaces 1, 2, 3) pluspublication_domainsfromusePublicationData(surface 2) anduseLeafletDomains(surface 3). Optimistic updates go throughmutateIdentityData(mutateIdentity, draft => …); after a write, mutate every cache the surface reads, or the row snaps back.
Current conventions (keep these consistent across all three)
- An unverified domain row: the domain name pulses (
animate-pulse) and isfont-normal(verified domains are the bold ones); the affordance readsVerifyintext-accent-contrast font-bold— not "pending", not "unverified", not a spinner, and the label itself doesn't pulse. - The whole row is the trigger: a single
<button type="button">wrapping name + label, passed toModalwithasChild(surfaces 1, 2) or wired to the menu-state transition (surface 3). Per project convention, a button used as aModal/Popovertrigger always getsasChild. - A row that also holds its own control (surface 1's
DeleteDomainButton) keeps that control outside the trigger button — don't nest buttons, and don't rely onstopPropagationto untangle two nested triggers. - Verified-but-unassigned rows are not clickable-to-verify; they show the assign
affordance (surface 2's
assign, surface 3's route input) or plain text (surface 1). - Grouping unverified rows above the rest, separated by an
hr, is surface 3 only by decision. Surfaces 1 and 2 leave unverified rows interleaved in list order.
Required step: ask about the other two
Any change to domain row UI or behavior in one surface is a three-surface change until
the user says otherwise. After making the change, use AskUserQuestion to ask whether
the same change should be applied to the other two surfaces — name them specifically
(e.g. "also apply to the leaflet share menu picker and the publication dashboard rows?")
and say what the equivalent would be in each, since the mechanics differ (modal vs.
menu-state, assign vs. publish-to-route). Don't silently do all three, and don't silently
do only one.
This applies to: label text, pending/verified states, colors, pulse/animation, what part of the row is clickable, where verification opens, assign/unassign affordances, empty states, and the copy in each.
Verify before calling it done
npx tsc. Then, for each surface you touched:
- An unverified domain — name pulses,
Verifyreads as clickable, clicking anywhere on the row opens the DNS record table for that domain. - A verified domain — no
Verify, no pulse, and the assign/publish affordance works. - Surface 1's delete icon still opens the delete confirmation and doesn't also open verification.
- Surface 3 inside the share popover — the row click swaps to
VerifyDomainViewand the back arrow returns to the picker (rows there useonMouseDown, which the popover needs; don't switch it toonClickwithout checking the popover doesn't eat it).
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

