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)
- Subdomain:
onoma.rdco.dev. - Cloudflare Access gated — same posture as Ink ([[2026-07-21-ink-build-spec]]). Not public.
- 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.
- Structured output. Founder: "an array that contains details for each name, then a description that brings together the whole name."
- 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-apiskill 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):
- Key input is
type="password",autocomplete="off". - Key is read from
localStorageat call time only; never held in a module-scope variable longer than the call. - NEVER
console.logthe key, put it in a query string or URL fragment, or send it to anything butapi.anthropic.com. - No third-party scripts, no CDN fetches, no analytics in the bundle.
- Error handlers must not serialize request headers into user-visible errors or notes.
- The key must never appear in IMPLEMENTATION-NOTES or any committed file.
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:
- Real roots. Actual source language and root form, not vibes.
- One non-obvious detail. The thing the user didn't know and will repeat to someone.
- 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 readingCAPABILITIES.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. Dittodesign-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:
- App folder exists at
~/rdco-vault/01-projects/onoma/app/and the page loads under Playwright with 0 console errors. grep -rE 'sk-ant|ANTHROPIC_API_KEY' ~/rdco-vault/01-projects/onoma/app/returns nothing.- All six radio values render and are selectable; changing type changes the request payload's
name_type. - With
window.fetchstubbed to return the fixture atapp/fixtures/benjamin.json, the editorial render shows one card perpartsentry, the compression as a large serif three-beat, and the synthesis as body prose. - A second fixture (
app/fixtures/place.json) renders correctly — proves the render isn't hardcoded to three parts or to person-shaped data. - Key round-trips: entered → persists across reload → cleared by the settings "clear" control.
- Stubbed error responses (401, 429, malformed JSON) each surface an honest user-visible error; none silently no-op; none serialize request headers.
/design-criticreturns PASS againstDESIGN-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:
- "Benjamin Andrew Wilson" /
person→ correct roots (Hebrew ben-yamin, Greek andros, Germanic wil + helm). - A place name and an organization name → analysis frame visibly differs; a city is not decomposed as if it were a person.
- A nonsense string →
confidence: speculative, caveat states it, no fabricated confident root.
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.
- Implementation agent maintains
IMPLEMENTATION-NOTES-2026-07-30-onoma-v0.mdin01-projects/onoma/— one level UP from the app folder, per the implementation-notes SOP. - App folder during the build:
~/rdco-vault/01-projects/onoma/app/. The build agent writes here and nowhere else — no git remote, no PR gate, nothing to stall on mid-run. - Final home (founder ruling, 2026-07-30): its own PRIVATE repo,
RayDataCo/onoma— created,maindefault. Migration is the parent's job AFTER the build agent's acceptance contract passes, not the build agent's: moveapp/into the repo, branch, PR per the PR-only workflow, then deploy from there. The build agent must not create~/Projects/onoma, must notgit init, and must not push. - IMPLEMENTATION-NOTES stays in the vault at
01-projects/onoma/, following the Ink precedent — app code goes to the repo, RDCO working memory stays in the vault. - Load the
claude-apiskill before writing any API call. Do not write model IDs, header names, or the structured-output parameter shape from memory. - CAPS: if you hit an auto-mode permission denial, STOP after one attempt and report it — do not route around it. If a spec instruction and an observed reality conflict, report the conflict rather than silently choosing.
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)