PURE interface standards — the full suite
One visual language for every surface: tabs, top bar, tables, BI, controls, and the console pattern that drives spines and hubs. Every specimen below is rendered by the same CSS a page would ship, and every section is deep-linkable. The written rules live in docs/interface-standards.md and on this page itself.
Set by Mike · 8/21/26 · supersedes ad-hoc tab styles
The themes are rows, not CSS. Add one or change a token in Palette & Tokens and every surface follows without a deploy — this strip included.
Tabs — the folder standard
Mike's rule, 8/21: an unselected tab must still look like a tab. A folder shape sitting on the baseline — cream fill, hairline border, rounded top corners — never a bare text label you have to hover to discover. The selected tab joins its panel: white fill, navy border, a 3px gold rule inside its top edge, and the baseline opens under it.
var(--cream,#F8F4EF), 1px var(--hair,#E3DDD2) border, radius
8px 8px 0 0, ink var(--ink,#404040). Selected: white, navy border, inset 0 3px 0 var(--gold,#DCCAAF),
navy text. Baseline: 2px navy. Hover: #ece7da. The tab group you are looking at IS the specimen.Give every tab the folder shape; deep-link
each panel (#tables opens the Tables tab); load a panel's data on first open, not page load.
Text-only tabs that look like links; pill buttons doing tab duty; a selected state shown by color alone; re-fetching every panel on page load.
Top bar
The 60px navy bar with the gold hairline is chrome — the shell renders it, never the page. Active section = gold-washed pill. Search sits in the bar, right side, pill-shaped.
var(--navy,#172E55), 3px gold underline. Active pill
rgba(220,202,175,.16) + gold text. Idle links #dce4f2, hover white on
rgba(255,255,255,.09). Search: pill, translucent white fill, gold hairline border. A page never
renders its own brand mark, Home link or identity chip (design.one_chrome_rule).Tables
Semantic <table> always — a grid of divs is invisible to
assistive tech and to pure-table.js. Filter row above; sorted column carries the gold inset rule.
| Family | Tier | Cockpit | Status |
|---|---|---|---|
| deal | Spine | /pure-deal-cockpit | Healthy |
| comms | Spine | /pure-command-center | Healthy |
| nav | Console | /navigation-management | Governs |
| voice | Spine | /pure-voice | Repointed 8/20 |
inset 0 -3px 0 var(--gold,#DCCAAF) and navy text. Rows: 1px #ece7da hairlines, warm hover wash.
Status is a chip, never a colored row. Fields: white, 5px radius, green border only when focused.KPI & BI reports
KPI tiles carry the 3px gold top bar and the Down-Right shadow. Deltas are colored text, not arrows-in-circles. Bar lists beat pie charts at every size; navy is the series color, gold is the comparison.
$23.8M, counts flat, one decimal max.Controls
Filled = pills. Verbs, sentence case. A button that cannot safely act is absent, not greyed.
Green = on. A toggle writes a ROW and re-reads it — it never just repaints. If the write is refused, it says so and snaps back.
White, 5px radius, var(--line,#BFBFBF) border,
green when focused. Placeholder is an example, never a label.
Status lives in chips and 3px bars — never large colored fields, never a colored row.
The console pattern — how spines and hubs are driven
A console edits rows other surfaces read. One family row carries label, tier, cockpit, hats, org and owner lane; the hub computes its index from the registries; the guard writes staleness back onto the row. Nothing stores what it can compute, so an edit here IS the edit everywhere.
mcp_family_reference refuses a
caller with no hat), so a page that forgets to check cannot leak.Live consoles: Site Control (scores · consolidation · switchboard · crons · instruments · lessons) · Navigation Management · Palette & Tokens · Users & Hats.
States — loading, empty, broken
The rule this week kept proving: an empty surface and a broken read must never look
alike. Loading is the brand wait state; empty says empty in words; broken announces itself with
role="alert" and says what to do.
data-pure-loading="Reading the fleet" on the waiting container
— the loader renders the brand mark + label and clears itself when content lands. Only on pages that ship
pure-boot.js, or you have replaced visible text with permanent blankness.role="alert", confirmations green with role="status". A cron proves work with a declared
metric, or its success only proves it returned. A watcher that cannot say what it is unable to see is not an
instrument.Search results, property cards & maps
4 bd · 3 ba · 2,410 sqft · Pottsboro ISD
Active
Photo left (5px radius), facts one muted line, price right in navy, status chip under it. The WHOLE row is the link; Enter opens it (type-and-enter always works — search never requires picking from a dropdown).
Navy pin = listing; green = the SELECTED one; gold circle = a cluster with its count. Map and list are one surface: hover a row, its pin lifts; click a pin, its row scrolls into view and the pin turns green. Geo data comes from the geocode queue — a page never geocodes ad hoc.
The two shells — and the gate between them
PURE is two shells reading one set of rows. The pure shell serves people doing
the work; the admin shell governs the rows those surfaces read. A page belongs to exactly one shell
(mcp_page_shell decides from the registry), and the SAME family row drives both sides.
Cockpits and hubs. Deal Cockpit, Command Center, Poppy Desk, property, search. State moves here.
Sign-in gate: anon sees public pages only; everything else routes through the sign-in gate. Client pages are gated by per-deal token, never staff sign-in.
Consoles. Site Control, Navigation Management, Users & Hats, Palette. Rows are edited here.
Reachable only with a staff hat; admin reads refuse hatless callers in the function, so the gate holds even if a page forgets.
<a href>: state lives in rows, so nothing needs handing
over. A route resolving is not a page existing — tombstones answer 200 — so shell links are verified
ALIVE by the nightly crawl, not merely reachable.Identity — hats, users, organizations
Who you are is worn, not assumed. The top bar carries exactly two identity controls — the hat switcher and the account capsule — and Mike's screenshot of them is the standard rendered below. Wearing a hat re-renders the rail from the nav registry; it never grants anything.
rgba(255,255,255,.09), gold hairline
border, gold text, chevron drawn in border strokes (never an emoji or glyph font). Avatar: 36px gold circle, navy
initial. Both open shell-owned menus — a page never renders its own identity chip.Mobile, touch & haptics
Fluid to
min(1320px,94%), one column under 880px, sidebar becomes the drawer, brand pane collapses to a
band. Fit the viewport — internal scrollbars are a cop-out (the 8/13 sign-in rule). Safe-area padding via
env(safe-area-inset-*).
44px minimum, 48px for primary actions; type collapses per the mobile ladder (base 12/16) but never below 12px; hover-only affordances are forbidden — anything hover reveals must also appear on focus and on tap.
navigator.vibrate(10) on destructive confirms and scan/capture success; failure is a distinct pattern — vibrate([30,40,30]) (double-pulse) fired ONLY alongside the red role="alert" announcement, so a refused write is FELT as different from a success and never silent — a tap that changes
data may pulse; a tap that navigates never does. Silent no-op where unsupported; off with
prefers-reduced-motion.
Swipe only where a visible affordance shows it (drawer edge, image galleries). Never swipe-to-delete without an inline confirm. Pull-to-refresh only on feed surfaces; everywhere else the refresh is a button.
Sign-in — desktop, mobile & the turnstile
The live gate is /sign-in — it IS the specimen. Desktop: two full-height columns, brand pane left (navy gradient, gold rule, trust rows), credential pane right; fills the viewport edge to edge with NO page scroll — content compresses by viewport height and sheds brand copy first, the credential form never. Mobile: one column, brand pane collapses to a compact band.
| Rule | What it means |
|---|---|
| One gate | Every success path routes through PureMFA.gate. MFA (TOTP) appears automatically when the org flag flips — enrollment at sign-in, never a separate errand. |
| Fast paths first | Face/Touch ID (passkey) and the one-tap email link sit ABOVE the password form; duplicates below are hidden whenever the fast block shows — no device loses a route. |
| The turnstile | Anon → the page renders public content only. Signed-in → min_rank nav filtering + hat checks in every function. A gated page shows the sign-in card INLINE (never a dead end), carries ?next=, and returns you to the exact page after auth. |
| Sign-out | Confirms inline, clears the acting session first if one is live, lands on /signed-out with a one-click way back in. |
| QR sign-in | Desktop shows a QR; phone scans, approves with biometric; approval is a row (mcp_qr_login_approve, staff-gated) — the desktop polls the row, never trusts the client. |
Sign-in, the turnstile & the gate
One door, three widths, four proofs. The live page is the specimen: /sign-in — fills the viewport with NO page scroll (content compresses, sheds by height: trust copy → trust rows → MFA panel → headline; the credential form is never shed), collapses to one column under 880px with the brand pane as a compact band.
| Element | Standard |
|---|---|
| Fast sign-in | Face/Touch ID (passkey) + one-tap email link at the TOP; the duplicate lower buttons are hidden while the fast block shows and return wherever passkeys are unsupported — no device loses a route. |
| Turnstile | Cloudflare, config from mcp_turnstile_cfg — a ROW, not a page constant. Rendered inside the form above the button, never floating. A failed challenge announces (role="alert"); it never silently swallows the submit. |
| QR hand-off | Desktop shows a QR; the phone approves (mcp_qr_login_start/status/approve/deny). The desktop poller announces approved / denied / expired in words — all three end states, never a spinner that outlives its request. |
| MFA | TOTP appears at sign-in automatically when the org row flips mfa_required — enrolment and challenge are the same surface; the ladder step is a row, not a deploy. |
| After the gate | mcp_login_route decides the landing by hat — the page never guesses. ?next= survives the whole trip, MFA included. |
Email & comms
Email is Arial, table-layout, 600px column — the one place Open Sans does not go.
Sender: PURE <noreply@acquisto.biz>; marketing honors suppression + {{unsubscribe}}.
Templates are pre-generated and versioned rows — a send references a template id, never inline-invented HTML.
- One button per email — navy pill, a verb. Secondary actions are underlined navy links.
- Branded QR on anything a phone continues: navy-on-white, quiet zone, deep-links with the SAME token discipline as the deal-window — the QR is a signed URL, never a bare route.
- Every send is a row (template id, version, recipient, outcome) — comms history reads from rows, so the CRM timeline and the email are never two stories.
- SMS mirrors email: template rows, opt-in enforced, STOP honored, one link max.
The site dictionary — one meaning, everywhere
A dictionary DEFINES a word. A constraint ENFORCES a value. A term is not governed
until it has both. This is the rule whose absence produced every vocabulary problem on this site, and the
proof sits inside our own schema: pure_pages.protection, .shell, .status
and .strictness carry CHECK constraints and hold four or five clean values each, because the
database refuses anything else. pure_spine_pages.role carried none and grew to 55. Same
project, same week. One column was defended and the other was not.
So: any column whose values are a vocabulary is constrained, and the constraint is generated FROM the dictionary — never hand-typed beside it. Add a term and the constraint widens; retire one and the gate names every row still using it. One source of truth, and no way to write around it. A glossary can tell a person what atlas means; only a constraint can stop four people typing atlas, reference, registry and docs into one column over four months.
The real-estate terms come first — RESO and MLS field names, deal stage, closing, mortgage, tax. A wrong word there does not just read badly; it reaches a contract or a feed.
The five governed columns, and the gate that proves it
The rule above is only true if something checks it, so something does.
A dictionary DEFINES a word. A constraint ENFORCES a value. A term is not governed until it has both.
tools/governed-vocab-gate.mjs reads the LIVE constraint through
pg_get_constraintdef — never a copy, because a gate reading a copy of the rule cannot
detect the rule changing — and fails in both directions: a value the constraint allows that nobody
defined, and a definition pointing at a value the constraint no longer allows.
| Column | Values | Held by |
|---|---|---|
pure_spine_pages.role | the seven, and no eighth | CHECK — member rows only; an excluded page has no role, and inventing one to satisfy a constraint is inventing data |
pure_spine_pages.verdict | in · in-unwired · out | CHECK |
pure_spine_pages.family | a spine that exists | FOREIGN KEY — the creation door made physical: you cannot type a new spine into being from a page row |
pure_spines.status | fragmented → aligning → partial → aligned | CHECK — kept as a four-stage ladder rather than collapsed; what they lacked was definitions that do not overlap |
pure_pages.protection_category | five page classes; NULL stays legal | CHECK — filling the 803 nulls is the unclassified drain, not this rule |
All five were constrained on 8/22, and the gate immediately found that
protection_category had five allowed values and zero definitions — a rule with no
meaning behind it, which is the exact half-governance the sentence above warns about. Defined the same hour.
That is what the gate is for: the failure it caught first was ours.
Terms live in pure_dictionary rows (term, plain definition, field bindings,
hat visibility). Everything else READS them:
| Reader | How it consumes the row |
|---|---|
| Tooltips | Dotted-underline terms pull the definition at render — never retyped per page, so a correction lands everywhere at once. |
| Fields | A field''s label, help text and format rule bind by field_key — the same key the field-mapping and RETS/RESO layers use, so the label a human reads and the column a feed writes are one row apart, never two opinions. |
| Forms & documents | TREC form fields map to the same keys; the doc-vault extractor writes INTO them; a filled form and a property record disagree only if the DATA disagrees. |
| Calendars & deadlines | Deadline names (option period, earnest money due) are dictionary terms — the calendar chip, the contract clause and the reminder all say the same words. |
| Poppy | Answers "what does DOM mean" from the row — the phone and the tooltip can never drift apart. |
Onboarding — person → hat → workspace
| Step | What happens | Where it lives |
|---|---|---|
| 1 · Invite | Admin creates the person + intended hat; system sends the branded email with a signed accept link (and QR). | Users & Hats console |
| 2 · Accept | Accept-invite page: set credential, enroll passkey, TOTP if the org requires it — one screen, no separate errands. | /accept-invite |
| 3 · Hat lands | The hat row activates; nav renders from the registries for that hat; nothing is granted by the page. | pure_nav_modules + functions |
| 4 · First run | The home surface greets by role with its 3-step start (agent: first listing · client: your deal room · staff: your queue) — driven by rows, dismissable, never a tour overlay. | pure-home |
| 5 · Proof | Onboarding is DONE when the person has signed in, worn the hat, and touched their first real row — measured, not assumed. | activity rows |
PII & per-field control
PII is gated at the FIELD, not the page. A field row carries its sensitivity class and the hats that may read or write it; functions enforce it; the UI renders what survives.
Three states: readable · masked-with-reveal (reveal = an audited row + step-up when classed) · refused (chip says WHY, never a blank). A refused field never renders as empty — empty and forbidden must not look alike.
Field Admin holds the matrix: rows = field keys, columns = hats, cells = none/read/write, classes = public · business · personal · financial · identity. Financial + identity always mask by default and always audit reveals. The matrix is data — changing a cell changes every surface at next read, no deploy.
- No real client PII until the auth window closes (standing order).
- Exports honor the same matrix — a CSV is a surface.
- Poppy reads through the same functions — her clearance is the caller''s, never her own.
- Client-room pages show a client THEIR data only — the token scopes the deal, the matrix scopes the fields.
The management console — everything established, one roof
The admin shell is the management console. Everything standardized this month is already a row-driven surface inside it — adopting this suite adopts THEM:
| Governs | Surface | Drives |
|---|---|---|
| Pages & health | Site Control (scores · consolidation · switchboard · crons · instruments · lessons) | compliance scoring, fold queue, ui.globals, watcher fleet |
| Navigation | Navigation Management | both shells'' menus, hats per row, min_rank |
| Brand & theme | Palette & Tokens · Brand Kit · this suite | 13 palettes, token pairs, specimens |
| People & access | Users & Hats · Field Admin | hats, invites, the PII field matrix |
| Families | Spine console (Site Control → families) | spine/hub/console rows, cockpit per family, guard chips |
| Data & meaning | Dictionary · Field Mapping | terms, tooltips, field keys, feed bindings |
| Comms | Command Center · Machine | template rows, versions, suppression, send history |
| Boneyard | Boneyard | parked pages, ribbons, successors, cold review |
The back office — one management console, integrated
Everything established this month is already console-driven; the conversion carries it over INTACT rather than rebuilding it. One rule: every governed thing has exactly one console, and every console is reachable from /pure-home under its owner's hat.
| Governs | Console | What it drives |
|---|---|---|
| Pages, scores, crons, lessons | Site Control | kill-switch, compliance scores, consolidation queue, cron health, instruments, lessons |
| Navigation & hats | Navigation Management | both shells' menus, per-hat visibility, min_rank gates |
| Color & theme | Palette & Tokens | all 13 palettes, follow-the-time-of-day, every token pair |
| Fields & PII | Field Admin | the 5,112-row policy table, formats, groups, lineage |
| Email & comms | Command Center · Machine | 105 templates, 3 themes, QR lockstep, suppression, send ledger |
| Dictionary | Data Dictionary | 336 terms, tooltips, field labels, form + calendar vocabulary |
| People & identity | Users & Hats | hats, acting sessions, org seats, the security ladder |
| Spines & families | Family consoles (per spine) | cockpit choice, hats, enabled, guard chip — the lockstep pattern |
| Retirement | Boneyard | park/restore/ribbons — nothing is ever silently deleted |
The back end — one management console set
Everything above is DRIVEN from the admin shell — each standard has exactly one console, and every console edits rows the surfaces read:
| Governs | Console | Registry |
|---|---|---|
| Pages, scores, health, lessons | Site Control | pure_pages + score/instrument registries |
| Navigation, menus, hats-per-row | Navigation Management | pure_nav_modules |
| Palettes & tokens (13 themes) | Palette & Tokens | pure_themes |
| People, hats, orgs, acting | Users & Hats | hat/org registries + pure_act_sessions |
| Fields & PII policy | Field Admin | pure_field_policy (5,112 rows) |
| Vocabulary | Data Dictionary | pure_dictionary (336 terms) |
| Email templates & approvals | Email Admin | pure_email_templates (105) + approvals |
| Spines, hubs & families | Site Control · Consolidation tab | family registry + guards |
| Retirements | Boneyard | pure_boneyard |
pure_field_policy · terms come
from the dictionary · sends go through the email path · and its console can change it without a
deploy. That list IS the compliance scorer — the nightly score measures conversion progress page by page.Wayfinding — hamburger, pins, tooltips, mobile
Everything that opens, closes, points or explains. Try them — the specimens work.
aria-expanded tracks state. Appears under 1100px only; desktop keeps the rail.navigator.vibrate(10) on confirm-class actions (pin, toggle, approve) only, silent where unsupported,
and suppressed under prefers-reduced-motion. Type collapses per the mobile ladder; drawers replace
rails under 1100px; nothing horizontal-scrolls except tables, which scroll inside their card.Search, maps & property pages
Field CMS — add and edit database fields from the admin shell
The full content-management loop: a staff admin can ADD a field, EDIT its rules, WIRE it to surfaces and RETIRE it — without a deploy, because every step is a row.
| Step | What the admin does | What it writes |
|---|---|---|
| Add | “New field” in Field Admin: name, type, format rule, dictionary definition, PII class | a field-registry row + a dictionary row; the column lands via the gated DDL queue (reviewed, reversible) — never ad-hoc SQL |
| Edit | change label, format, help text, required-ness | the same row; every renderer, validator and tooltip follows on next load |
| Gate | per-hat visibility grid (view / edit / hidden per hat) | (field, hat) rows — the FUNCTION redacts, the UI merely reflects |
| Wire | attach to surfaces: intake forms, property page, CRM, exports | surface-binding rows; a surface renders the fields its rows name — no hardcoded field lists |
| Retire | park the field | row disabled; renderers drop it; data is retained, never deleted — the Boneyard rule applied to columns |
Devices, permissions & profiles
Ask at the moment of use with a one-line reason. DENIED gets a designed state: what broke, the manual fallback, and the re-enable path ("Settings → Safari → Camera") — never a dead button. UNAVAILABLE (no GPS on desktop) hides the control and offers the fallback outright.
Camera (scan/upload), GPS (showing check-in, security), biometrics (passkey), mic (voice), push. Each is a CAPABILITY ROW in the device console: per-org and per-hat enablement, so an org can switch off GPS stamping platform-wide with one toggle. Pages read the matrix — never re-detect, never re-ask.
One profile surface (My Profile): identity (name, photo, license), contact + comms preferences (which channels, quiet hours — suppression rows the senders honor), security (passkeys, sessions, step-up history), devices (this device''s grants, revoke any session). Staff edits about OTHERS happen in Users & Hats — self-service and governance never share a form.
Success = single 10ms pulse. Failure =
double-pulse [30,40,30], always paired with the red announcement — felt, seen, and read
by assistive tech together. Navigation never pulses either way; reduced-motion silences both.
Brand assets — editable from the admin shell
| Asset | Where it is edited | Who consumes it |
|---|---|---|
| Site icon / favicon + app icons | Brand Kit → upload once, all sizes cut server-side | every page head, PWA manifest, bookmark tiles |
| Logos (full color / reversed) | Brand Kit → versioned rows with clear-space rules | shells, emails, print kits, sign-in brand pane |
| Palette & tokens (13 themes) | Palette & Tokens console | every themed page + both shells |
| QR style (module color, center mark) | Brand Kit → QR block | curb kits, postcards, upload links, sign-in QR |
| Email frame (header, footer, from-name) | Email Admin → frame template | every template in the registry |
| Taglines & boilerplate | Brand Kit → copy rows | landing, emails, print — one row, many readers |
Ingest & vision — every upload read, versioned, filed
Anything, anywhere: the uploader accepts drag/camera/email-link. The file gets its stamped name (YYYY-MM-DD_HHMM_<artifact>) at birth.
Vision classifies (contract, disclosure, receipt, license, survey…) and extracts fields; extraction confidence is stored WITH the values.
Filed to its deal/person/property by the extracted keys; below-threshold extractions queue for a human, never silently guessed.
Re-uploads of the same document stack as versions with provenance (who, when, which engine version read it). Nothing is overwritten.
Comms suite — tickets, projects, notes, messages, one spine
Comms is ONE family (Mike''s 8/19 ruling): messages, tickets, projects, notes and email are views over the same spine, so a thing never has to be retyped to change kind.
| View | What it is | Cross-links |
|---|---|---|
| Messages | threads with people (SMS/email/portal), suppression-aware | escalate to ticket in one click, thread history rides along |
| Tickets | raw intake preserved verbatim + refined work order, effort-ranked | links its deal/page/person; board rows are the queue |
| Projects | ticket clusters with a goal and an owner lane | rolls up ticket status; done = every ticket closed with proof |
| Notes | attached observations on any row (deal, person, property) | a note naming an id links that id (the no-dead-mentions rule) |
| template-registry sends, versioned | every send visible on the thread it belongs to |
Calendars — subscribe to many, see one
1. Every calendar is a FEED computed from its spine (showings
from the deal spine, deadlines from critical dates) — never hand-entered twice.
2. Subscription is per-user rows; the merged view renders whatever is on.
3. Outbound ICS per feed, so phone calendars subscribe to the same truth.
4. Deadline chips carry dictionary tooltips (option period, financing deadline).
5. An event names an id → the id links to its spine (no dead mentions).
Crons, self-heal & site health
| Instrument | Lives in | The rule it enforces |
|---|---|---|
| Compliance scorer (nightly) | Site Control · Scores | every page scored on the standard; worst-first queue |
| Cron fleet + work-done metric | Site Control · Crons | a green run proves it RETURNED; rows-checked proves it worked; checked=0 alarms |
| Uptime engine + storm guard | Site Control · Instruments | 2-strike notify; simultaneous fleet failure = checker fault, never N outages |
| Link/orphan/leak guards | Site Control · Instruments | catalog crawled live; nav↔pages two-way; anon exposure vs allowlist |
| Self-heal queues | Site Control · Consolidation | one item per off-hours run; a healer that did nothing says why |
| Lessons registry | Site Control · Lessons | every error becomes a lesson.* row checked BEFORE similar work |
Wireframe — everything above, composed
One governed page assembled ONLY from the standards on this page — the proof the suite is complete enough to build from.
The spine standard — every page lives in one
A spine is a family: its tables, its functions, the ONE cockpit that governs it, the hats that may reach it, and the standards it is held to. Everything below is the rule; the live reading of the rule is at the bottom of this section, so the standard and its score never drift apart.
1. Every page is in exactly one spine
Not most pages. Every page. A page with no spine row is not "out" — it is invisible, and an absent row cannot be queued, assigned or counted. Two spines exist so that no page can fall outside:
| Spine | Holds | Its rule |
|---|---|---|
unclassified | Every enabled page not yet assigned to a family. | A page may not stay here. The queue is worked oldest-first, and this is the one spine whose
health is measured by shrinking. Pages wait at the safe floor — staff protection,
admin shell — because over-protected and visible beats unclassified and invisible. |
boneyard | Every parked page. Derived, not stored. | A park is a judgement made at a moment, so a parked page keeps a reason and earns a re-test:
any whose named functions all still exist is flagged for a second look. Parked is a state, not a deletion.
This spine holds no rows of its own — pure_boneyard already had 250 parked pages
with its own park, restore and reconcile tools, and copying them into pure_spine_pages would
have created a second store for one fact. The atlas reads them at read time. One store, two readers. |
Both spines exist as of 8/22, and the sweep is done. Of 841 registry pages:
194 sit in a real family, 434 wait in unclassified, and the rest are parked. Zero belong to
nothing — and tools/spine-homeless-gate.mjs was baselined at that zero the same hour,
because the one moment a ratchet is cheap to set is the moment the count reaches nothing.
The waiting room was not one queue but three, which the sweep recorded per page: 345 never examined by any spine, 73 examined and recorded out of every family that looked (out of a family means “not part of that family” — it never meant “belongs nowhere”), and 16 disabled with no park record at all, which is two open questions rather than one: where does it belong, and why is it off?
2. Seven role names, and no eighth
A role says what a page DOES inside its family. The list is closed. It is closed because
it was not: 176 governed pages once carried 55 different role names, 23 of them used exactly once, with
tool beside tools and signature beside signing. Nobody was
careless — the column was free text, so it grew.
| Role | Means |
|---|---|
cockpit | The one surface that governs the family. Exactly one per spine. |
writer | Creates or changes the family's records. |
console | Where staff do the day's work in this family. |
atlas | Reads and explains the family. Never writes. |
monitor | Health, audit and evidence for the family. |
governance | The rules the family is held to. |
door | How someone outside staff gets in. |
hub is not a role. A dozen pages are named -hub,
which is exactly the confusion a closed list prevents.
3. The gate is declared, and so is the shell
Every spine row carries gate + gate_why and
shell + shell_why — declared with a reason, never derived from a name. A role name
cannot tell you what a surface is: the STORE cockpit writes the catalogue and is admin-only; the DEAL cockpit is
where an agent works all day. Both are role=cockpit.
The shell test. A page belongs to the ADMIN shell if and only if a non-staff user seeing it would be a security incident. Everything else is the pure shell. No third category, and no page in both — a page that is genuinely both is two pages.
4. Placement belongs to nav; the registry belongs to pages
Shell and menu are one subject at two zoom levels: the shell decides which menus a page can appear in at all, the menu row decides where inside. So Navigation Management owns placement — moving a page between shells and between menus — and Site Control owns the registry, which is what a page IS. Both call the same functions; a second door is never a second implementation.
5. The creation door — new things are born here, by a person
This is the rule that stops the others from being needed again. A new page, function, spine or vocabulary term is created from the admin shell, by a human, through the cockpit that governs it. Everything after that — filling it in, wiring it, styling it — is ordinary work anyone may do.
It is the cockpit-writer law applied to the one act it never covered. A cockpit must perform or launch every governing act, and creating is the most consequential governing act there is, because every later problem inherits from it. Six listing writers exist because six people each created one. The cheapest moment to stop a duplicate is before it exists.
| Creating | Door | How it holds |
|---|---|---|
| A vocabulary value | the dictionary | Impossible otherwise. A CHECK constraint generated from pure_dictionary refuses the write. |
| A spine | Navigation Management | Impossible otherwise. A spine must name a cockpit that exists; the constraint refuses one that does not. |
| A page | Navigation Management | Cannot survive a push. A file can always be written; the gate fails on any page with no registry row born through the door. |
| A function | the family's cockpit | Cannot survive a push. Same shape: pure_evolve register verifies the rpc exists, and the gate fails on one nothing declared. |
Built 8/22 — mcp_page_create(), at
Navigation Management › Create a page.
One call takes the registry row, the spine row, the role, the gate and its reason, and the shell and its reason,
and hands back a stub whose head is already correct. It refuses a path that already exists, a path that is not
URL-safe, a placeholder title, a family that does not exist, a role outside the seven, a second cockpit for
a spine, a gate or shell with no reason — and shell=auto, because auto is a derivation and this
is the moment the decision is made.
What it honestly cannot do: PURE is a static site deployed from git, so no database function can write a file into the repo. The door owns identity and governance — the half that actually rots — and makes the file trivial. Saying it “creates the page” would be the kind of half-true this suite exists to catch.
Its companion finding, from the spine index built beside it: of 631 spine members,
seven declare shell=pure. 55 declare admin, four none, and 560 declare nothing. Twelve of the
fifteen real families have no declared pure-shell surface. They are not unreachable — auto
derives a shell from which menu a page is in and falls back to pure — so the separation is
real in behaviour and almost entirely undeclared in the registry. Every page born through the door
declares its shell at birth, which is how that number starts moving.
The door must be faster than the workaround, or it will be walked around. Creating a page from the cockpit has to do more for you than writing a file does — pre-fill the spine, the role, the shell, the protection, the boot scripts and the registry row in one action. A rule that costs more than the behaviour it replaces is a rule people route around, and then the drift returns wearing a compliant name.
Worth stating plainly: this constrains Claude more than anyone, as the highest-volume creator on the system. That is the point. The fastest-moving actor is where a naming mistake compounds quickest, and a stub whose identity a person already settled is a better brief than a blank page.
6. Every guard names what it guards
A healer with no declared subject is not a guard — it is a job that runs. The chain is spine → standard → gate → cron → ticket, and each link names the next, so a failure can say which family is unguarded rather than only which job failed. An instrument that cannot state its own population must report UNKNOWN, never a clean run.
Wired 8/22. When this rule was written, 417 active crons ran and not one of
them named a spine or a standard — so a cron failing told you a job had failed and nothing else, never
which family had just lost its cover. pure_cron_meta now carries the subject, on the table that
already had a row per job rather than a new one.
| Source | Count | How much to trust it |
|---|---|---|
declared | 6 | A person said so, through mcp_cron_guards_set. This is the fact. It always beats a derivation. |
derived | 134 | The function the job calls matches pure_<family> / mcp_<family> — the same prefix rule the spine map already uses. Cheap, and wrong the moment naming stops tracking ownership. A stand-in that says so, not an answer. |
| UNKNOWN | 280 | Nobody knows. Reported as unknown rather than guessed from a job name, because a guessed family produces a coverage number that looks like governance and is not. |
Two ratchets, moving opposite ways on purpose. The 280 unknowns may only shrink.
But a spine with no active healer must stay at zero — that is not a slow backlog, it is cover
disappearing, and it is silent by construction. Exactly one spine had none: unclassified, the only
spine whose health is measured by shrinking, which could have refilled in silence. It has one now, and it alerts
on growth rather than depth — 434 waiting is the known starting position, and a guard that mails
every morning trains everyone to skip its mail, and then reports the one real break to nobody.
Spine progress — live from the consolidation registry
This is not a specimen: it reads mcp_spine_progress() — the same
pure_spine_pages registry the family passes write — every time you open it. Numbers here are
never typed into markup (the eight-for-eight regex lesson). IN counts pages folded into a family surface;
OUT counts pages retired or parked through the Boneyard flow; a family''s cockpit is the surface that must
perform or launch every governing act (design.cockpit_writer_ruling).
Loading the registry…
Conversion findings — the defect log Code fixes from
Every off-standard page found during the offender sweeps is logged as a ROW in
pure_conversion_findings (finding + prescribed fix + class), not as chat. This list is that registry, live via
mcp_conversion_findings() — when Code fixes one during the conversion, the row flips to fixed with the commit
as receipt, and this page shows it with no edit. Classes matter: a transient, client-token or public
page failing a shell check is a SCORER gap, not a page defect.
Loading findings…
The spine atlas — every spine, every page, editable
The tab before this one states the rule and scores it. This one is the
register: all 15 spines, every page inside each, the role it holds, the gate and shell declared for it,
and every page that is declared in two spines at once. It reads mcp_spine_atlas() live, so it is
the rows themselves — not a copy of them.
It edits, too. Every row has an editor, because a register nobody can correct
becomes wrong and stays wrong. Saving calls mcp_spine_page_set(), which is staff-gated, refuses a
role outside the seven, refuses a second cockpit in a spine, and refuses a family that does not exist.
There is no delete. Removing a page from a spine means setting its verdict to out with a
reason — the row, the role it used to hold and the reason all survive. A spine that forgets it once
examined a page will examine it again and reach a different answer.
role is what the page does in this family (seven values, no
eighth). verdict is in (a member), in-unwired (a member that calls no
function yet — assigned so it cannot hide) or out (examined and excluded, with the reason kept).
gate and shell are declared with a reason, never derived from the page's name.
also in means the page is a member of another spine as well — two declared gates for one page, which
is the one thing the standard's single best property forbids.Loading the registry…
Debug overlay — URL-flag diagnostics from the user’s own session
Adopted 8/23/26 from the dead-account-pill case. When a user reports “X doesn’t work” in their signed-in browser, outside probes can’t see their session — test frames get redirected to sign-in, screenshots show symptoms, and guessing burns deploy cycles. The fix: instrument the live page itself, gated behind a URL flag, and let the user’s browser report the facts. Full spec: DEBUG-OVERLAY-STANDARD.md (repo root).
The pattern
- Ship an inert diagnostic file (e.g.
pure-pill-debug.js): error listeners for everyone, renders nothing without the flag (?pilldebug=1). - Enable via a
ui.globalsrow (ord ~100, defer) — a switchboard row, never page edits. Snapshot globals before the change. - Send the user one clickable link: the reported page + the flag.
- The overlay draws a bottom strip reporting from their session: JS errors, mount state of every layer in the failing chain, auth presence (never tokens), and — v2, for lost clicks — wrapped API invocations, the element each click lands on, and
elementFromPointat the target’s center (covered:true= an invisible overlay is eating the click). - The user screenshots the strip — the strip is the diagnosis; each line maps to one hypothesis.
- Remove the globals row when the case closes. The inert file may stay; the row must not.
Rules & what the reference case proved
- Never render without the flag; never collect PII — report token presence, not tokens.
- Escalate progressively: v1 state snapshot first (proves or rules out crashes), v2 interaction trace only after v1 facts are known. Each version’s header records what the previous one proved — the file reads as the case history.
- Reference case, one screenshot each: v1 proved the session healthy (zero errors, shell + capsule + panel + APIs all mounted) — eliminating every crash hypothesis at once; v2 proved the click path clear (
onclick:true,would_receive_click= the capsule itself,covered:false, panel painted) — narrowing the whole case to the handler’s own trace on the next click. - Works because it inverts the visibility problem: instead of us probing a session we can’t enter, the session reports itself — same shape as announce-the-outcome, applied to debugging.
Conversion findings — the defect log Code fixes during the conversion
Live from mcp_conversion_findings() over pure_conversion_findings —
every page triaged against this suite files what is actually wrong (and what turned out to be a scorer gap
instead), so fixes happen systematically inside the spine conversion rather than as one-off sweeps. OPEN rows
are Code’s conversion queue; FIXED rows keep their commit ref as the receipt. Anything found off-brand goes
in this table, not in a chat message.
Loading the findings registry…