input · the_problem TXT

2026 — Design & Full-Stack Build (solo)

Proof you can watch

Claim a domain and prove you control it with one DNS TXT record. The check asks your nameservers directly and shows its five steps as they land. Every failure has a name, a reason and one next action.

ownership_data DATA
RESEARCH
DESIGN
FRONTEND
BACKEND
SOLO. SEPTEMBER 2026.
signin_view · VERIFY.scn VIEW
VERIFY.scn · the product's home-page demoLIVE — REAL PRODUCT CODE, SYNTHETIC DATA
txt · what_goes_wrong TXT

What goes wrong

I started with the ways verification fails, before any screen: a resolver that caches "no record" for an hour, DNS panels that add the domain twice, half-pasted values, the wrong record type, wildcards, dead nameservers, expired tokens. Each one needed its own answer.

research_in DATA
  • IETF domain verification draftA scoped underscore label, the expiry inside the value, and asking the zone’s own servers.
  • RFC 2308How long a resolver keeps saying "no record" after it looked too early.
  • RFC 8555 DNS-01How certificate authorities prove control of a domain.
  • RFC 8552Why an underscore label can’t collide with a real hostname.
  • Five productsHow Google, GitHub, Vercel, Atlassian and Cloudflare verify, and what their errors say.
  • A deployed probeDirect queries from the host, the doubled name, split TXT values and timeouts, measured.
decision_01 · ask_the_source TXT

Ask the source

The check asks the zone’s own nameservers, which don’t cache. A record is found as soon as the DNS host publishes it, so there is no "wait 48 hours" anywhere in the product.

decision_02 · whose_move TXT

Whose move is next

The first check on a new claim always misses, and a red cross there would call that a fault. So every step sorts by whose move is next: yours (amber), time’s (cyan), or done (green). There is no red.

decision_03 · claim_then_prove TXT

Claim, then prove

A pending claim holds nothing, so anyone can start one. One account holds a name at a time, and the database enforces it, so a race can’t get around it.

decision_04 · progressive_disclosure TXT

Progressive disclosure

The claim page shows the step the person is on and nothing after it. The check’s steps land one at a time. Finished cards dim and stay above, one scroll away.

section_break SYS
01 THE LIVE DEMO
txt · try_it TXT

Try it

It’s the live app, and everything is clickable. The buttons underneath load each demo state.

claim_view · DEMO_NAMES.scn VIEW
DEMO_NAMES.scn · appended-zone.testLIVE — REAL PRODUCT CODE, SYNTHETIC DATA
YOUR MOVE05 / 13

The domain was added to the name twice

appended-zone.test
Most DNS panels add your domain to the name you type, so a full name gets saved twice. The check looks one level down, finds it, and gives the short name to use.
Measured on Squarespace, Sep 12. Its panel also refuses the trailing dot the usual advice suggests.
TESTS diagnose.test.ts · evaluate.test.ts
PICK ONE · 13 STATES
txt · only_real_states TXT

Only real states

Every state in the design was checked against the backend’s real types. What the app can’t reach was cut, and a promise in the copy became build work or the copy changed. The stack came last, with one rule: pages never load data.

rules_out DATA
  • Waiting is never drawn as a fault.
  • Cables only where there is a sequence, and straight.
  • Past cards dim. Future cards are absent.
  • The pulse runs only where the check is.
  • Copied is a word, never a tick.
  • Every state shown is one the app can reach.
  • On a phone, a row that scrolls sideways never wraps.
section_break SYS
02 THE CODE
txt · for_the_code_reader TXT

For the code reader

DNS sits behind an interface with a scripted fake, so no test touches the network. Failures are values in a typed union, and the message switch is exhaustive: a new failure breaks the build until it has words. An RFC leads the code, and a friction log records every problem I hit using it against real domains.

Each claim shows its site’s real favicon. The server fetches it under tight limits: public addresses, two redirects, raster images, since an SVG can run script. A site without one gets a globe. In the demo, the rail shows real icons next to the .test names, which get the globe.

export type FailureReason =
  | { code: 'record_not_found'; queriedName: string;
      nameservers: string[]; negativeTtlSeconds: number | null }
  | { code: 'no_txt_at_name'; queriedName: string }
  | { code: 'appended_zone_suspected'; queriedName: string; foundAt: string }
  | { code: 'value_mismatch'; expected: string; found: string[] }
  | { code: 'token_expired'; expiredAt: Date }
  | { code: 'nameservers_unreachable'; attempted: string[]; timeoutMs: number }
  | { code: 'zone_not_found'; walked: string[] };
src/lib/claims/state.ts · every failure the check can report
read_first DATA
  • src/lib/dns/trace.tsAsks DNS and reports what each server said.
  • src/lib/claims/evaluate.tsCompares that with the claim and decides what the row should do.
  • src/lib/claims/steps.tsTurns a check into the five steps.
  • src/lib/claims/messages.tsEvery failure as title, value, action and reason.
  • src/lib/dns/testNames.tsScripts the demo names.
SEE ON GITHUBCarlton-L/domainclaim
section_break SYS
03 THE PROCESS
txt · two_passes TXT

Two passes

The design came in two passes. The first was close to a wireframe: what the product shows, when, and how each step behaves. I built the engine under it and tested it against real domains.

The second pass happened away from the codebase, in HTML prototypes only, and was then applied to the working product. The screens changed. The DNS layer and the claim rules stayed as they were.

before · first_pass VIEW
The first pass of the claim screen: the five steps as a list, with the failing step open to show the value found, the value to use, and why they differ
FIRST PASS · THE CHECK AS A LIST OF STEPS
concepts · 01_02_03 VIEW
Concept 01, patch network: the claim screen at desktop width
NOT PICKED OPEN THE PROTOTYPE ↗
txt · one_change_at_a_time TXT

I drew three directions from how DNS fails and where the person acts or waits. The timeline won. A later version applied a whole list of changes at once and lost what made it work, so from then on each prototype changed one thing.

Prototype 04: every change applied at once, with collapsed cards and a pin button
04 · EVERYTHING AT ONCE
Prototype 05: restarted from the timeline, one change
05 · BACK TO 02, ONE CHANGE
out · outcome TXT

DomainClaim is live, and the demo names reach every state above. Designed and not built yet: checking on a schedule while nobody has the claim open, the grace window and the email behind it, transfers between accounts, and a second vantage point.

▸ VISIT LIVE · domainclaim-pi.vercel.app ↗
out · next_network DATA
EVERY FAILURE HAS AN ANSWER.
NEXT · GRID_LAMPa lamp you sculpt by touch