01-projects/onoma

onoma.rdco.dev — build spec v0

2026-07-30·build-spec·status: ready-for-dispatch
rdcoclaude-apibyokquick-sitesetymology

A single-page RDCO surface: type in a name, pick what kind of name it is, get a structured etymological decomposition back from Claude. First RDCO surface that makes a live model call.

Named for Greek ὄνομα (onoma), "name" — and onomancy, divination by names.

Founder rulings (locked 2026-07-30)

  1. Subdomain: onoma.rdco.dev.
  2. Cloudflare Access gated — same posture as Ink ([[2026-07-21-ink-build-spec]]). Not public.
  3. Scope beyond personal names. Founder: "I was thinking personal names only, but if onomancy applies to naming of anything that could be fun to increase the scope. May need a radio button type input for the user to say what kind of name they are providing." → radio input for name type, analysis frame varies by type.
  4. Structured output. Founder: "an array that contains details for each name, then a description that brings together the whole name."
  5. BYOK — per-user token, following the Ink v0 pattern.

Stack

Static page deployed via quick-sites → onoma.rdco.dev behind Cloudflare Access. Proven rails; no new infra.

BYOK, exactly as Ink v0 does it ([[IMPLEMENTATION-NOTES-2026-07-21-ink-v0]]): user pastes their own Anthropic API key once, stored ONLY in localStorage, calls go browser-direct to the Anthropic API (CORS-enabled). No Worker, no shared secret, nothing to drain. Settings panel carries the key field with a "stored only on this device" note.

The build agent MUST load the claude-api skill for exact headers, params, model IDs, and the structured-output parameter shape rather than working from memory. Model: claude-opus-5.

Browser-direct calls require the header anthropic-dangerous-direct-browser-access: true — confirm the exact header name via the claude-api skill before writing it.

Key-handling rules (non-negotiable):

DO NOT DEPLOY. Do not run quick-publish, quick-share, wrangler, or any Cloudflare API call. Deploy is the parent session's job via ~/Projects/quick-sites/bin/quick-publish. Write only inside ~/rdco-vault/01-projects/onoma/. Do not touch settings.json. Access grants at deploy (parent's step): ben@raydata.co + ray@raydata.co.

Name types (radio)

The type selects the analysis frame. Ship the first five; other is the escape hatch.

Value Label Frame
person Person Given name(s) + surname; patronymics, maiden-name-as-middle, saint/biblical sources
place Place Topographic and habitational elements, language layers, who named it and when
organization Company or organization Founder surnames, coinages, portmanteaus, latinate constructions, what the name claims
vessel Ship or vessel Naming convention of the fleet/era, honorifics, mythological and virtue names
work Book, album, or film Allusion, quotation, register, what the title is signalling to its audience
other Something else Infer the category, say what you inferred

Structured output schema

Enforce the schema server-side via the API's structured-output parameter — get the exact parameter name and shape from the claude-api skill, not from this spec. If that skill is unavailable in your context, STOP and report rather than guessing. Do not parse prose.

{
  "input": "string — the name as entered",
  "name_type": "string — echoed back, or inferred when type was 'other'",
  "parts": [
    {
      "token": "string — the single element being analysed, e.g. 'Benjamin'",
      "role": "enum — exactly one of: given | middle | surname | patronymic | element | article",
      "language": "string — source language, e.g. 'Hebrew'",
      "root": "string — the root form, e.g. 'ben-yamin'",
      "literal": "string — the literal gloss, e.g. 'son of the right hand'",
      "note": "string — one non-obvious fact about THIS element. May be empty."
    }
  ],
  "compression": ["string — one punchy phrase per part, in order"],
  "reading": "string — the compression read as a single sentence",
  "synthesis": "string — 2-4 sentences tying the whole name together",
  "confidence": "enum — exactly one of: high | mixed | speculative",
  "caveat": "string — what is documented vs. interpretive. May be empty."
}

parts is the array; synthesis is the description that brings the whole name together. compression + reading are the three-beat payoff that makes the output feel like a finding rather than a dictionary entry.

Invariant: compression.length MUST equal parts.length, in the same order — one phrase per part. Enforce in the schema and assert it in the render.

The voice bar — this is the actual product

Generic prompting yields Wikipedia-grade blandness and the site is worthless. Every output needs three things:

  1. Real roots. Actual source language and root form, not vibes.
  2. One non-obvious detail. The thing the user didn't know and will repeat to someone.
  3. A synthesis that earns itself. An observation about the whole, not a restatement of the parts.

Encode this with few-shot examples in the system prompt, covering DIFFERENT name types — person AND place AND organization. Examples that are all the same type teach pattern-matching on that type instead of generalising the method.

Two worked exemplars to seed from (both real, both from live sessions):

Benjamin Andrew Wilson — the non-obvious detail is that Benjamin was born Ben-Oni, "son of my sorrow," named by Rachel as she died in childbirth, and renamed by Jacob. The name is a father overwriting a mother's last word. Compression: Right hand. Courage. Son of will. Synthesis notes Wilson is literally a question — "whose son?".

Augustus Olney Miller — the non-obvious detail is that augere means to increase and molere means to grind down, opposites in one name, and a mill is exactly the machine that reconciles them: you grind grain down to multiply what it feeds. Also that Augustus began as a Senate-granted title, not a name.

Honesty rule: when the derivation is contested or the analysis is interpretive rather than documented, confidence must say so and caveat must name which part is the reach. A confident-sounding fabrication is the failure mode that kills this surface. Same-name and folk-etymology contamination are real — folk etymology is interesting and must be labelled as folk etymology, never presented as the root.

Design

Contract: ~/rdco-vault/02-sops/DESIGN-rdco.md (umbrella) — the contract is authoritative; take tokens from it, not from this spec.

v2 Tampa Bay sky+sun palette (2026-05-24). Surface sky #eaf2f8, elevated #dde9f2, soft #f3f7fb; navy #0D2438 for body type and headlines; sun #f0b820 as the 10% accent (CTA, hero <em>); coral #e08570 sparingly as the SC sibling-cameo; bay #4d8aac hairlines. Source Serif 4 headlines, Inter body, JetBrains Mono for root forms and language labels.

Corrected 2026-07-30. An earlier draft of this spec specified the v1 cream/lavender/terracotta palette. That is wrong: DESIGN-rdco.md marks those legacy-* tokens as "preserved for legacy memo templates only. Do not use on new umbrella surfaces." The error came from reading CAPABILITIES.md (last updated 2026-05-23) instead of the DESIGN contract (2026-05-24) — the index is one day stale on this and still lists the v1 palette. Ditto design-samples/rdco-sample.html, which is a v1 artifact. Use v2.

The output wants to read as an editorial artifact — a card per part, then the compression as a large serif three-beat, then the synthesis as body prose. Not a chat transcript, not a JSON dump.

ACCEPTANCE CONTRACT

You do not have an Anthropic API key. This is a BYOK app — the key is the user's, supplied at runtime in their browser. Do not attempt any live model call, do not source a key from the environment or 1Password, and do not claim a live-output test passed. Test against a stubbed window.fetch, exactly as the Ink v0 build did.

DONE — agent-checkable, no API key required. Every item must be objectively verified and reported PASS/FAIL individually:

  1. App folder exists at ~/rdco-vault/01-projects/onoma/app/ and the page loads under Playwright with 0 console errors.
  2. grep -rE 'sk-ant|ANTHROPIC_API_KEY' ~/rdco-vault/01-projects/onoma/app/ returns nothing.
  3. All six radio values render and are selectable; changing type changes the request payload's name_type.
  4. With window.fetch stubbed to return the fixture at app/fixtures/benjamin.json, the editorial render shows one card per parts entry, the compression as a large serif three-beat, and the synthesis as body prose.
  5. A second fixture (app/fixtures/place.json) renders correctly — proves the render isn't hardcoded to three parts or to person-shaped data.
  6. Key round-trips: entered → persists across reload → cleared by the settings "clear" control.
  7. Stubbed error responses (401, 429, malformed JSON) each surface an honest user-visible error; none silently no-op; none serialize request headers.
  8. /design-critic returns PASS against DESIGN-rdco.md.

LIVE-OUTPUT ACCEPTANCE — parent-run gate, NOT yours. After deploy, the parent session runs these with a real key. Do not attempt them and do not report on them:

FAILURE COST → GATES: reversible-cheap (Access-gated static page, no shared secret, nothing to drain) → one verifier (/design-critic) plus the parent's live-output spot-check. No second verifier needed.

STATE OWNER: the app folder on disk and the Playwright run output — not your summary. If a DONE item is unverified, report it as unverified. A false pass is worse than a reported failure.

Out of scope for v0

Sharing/permalinks, history of past lookups, RDCO-held tokens with metering (the Ink option-4 productization path applies here too if it ever becomes a product), non-Latin script input, audio pronunciation.

Build discipline

PRE-DECOMP — do this before writing any code. Return a first message containing your sub-task breakdown (expect roughly 7-8: page shell + design tokens, radio + settings panel, API call layer, system prompt + few-shot, editorial render, error degradation, Playwright harness + fixtures, design-critic pass) with per-task acceptance criteria. Then WAIT for parent go-ahead before implementing.

RETURN FORMAT to parent when done: (a) every path you touched, (b) the IMPLEMENTATION-NOTES path, (c) PASS/FAIL/UNVERIFIED per numbered DONE item above, (d) anything you deviated from in this spec and why.

Related: [[2026-07-21-ink-build-spec]] (BYOK pattern, quick-sites rails) · [[2026-07-30-cca-foundations-diagnostic-baseline]] (the few-shot-must-span-varied-scenarios lesson applied above)