The living standard every PURE page is built and audited against. Tokens, type, iconography, components and language — plus the past work we reviewed to get here. Enforced by rules, memory and crons, not by memory alone.
Foundations
PURE is navy and gold — a private-bank palette. Deep navy carries the brand, warm gold is the single accent, and everything else is a small, purposeful set of status colours. Use the token, never a hand-picked colour: when a token changes the whole product follows, and anything hardcoded stays behind.
ARE vs PURE: “ARE” always means Acquisto Real Estate, never the English verb. ARE Blue is reserved for ARE, lane and brokerage moments — it never touches the PURE mark. PURE MLS Technologies is the platform, “by Acquisto”.
A hardcoded page cannot print the time the database answered. Press it twice a minute apart — if answered_at_ct moves, these values are being read, not typed.
--gold-ink is for small gold-toned labels and section eyebrows on light backgrounds, not for body text.--gold-50 is the default page background. A stark white page is off brand, and it is the fastest way to spot a surface that never adopted the tokens.pure_theme_auto.default_theme and honored by pure-theme.js. Follow-the-time-of-day is OFF by default and strictly opt-in per person; Sunrise, Golden Hour, Sunset, Midnight plus the season and scene palettes are selectable options under the default, never the default. Set or change the default from Palette & Tokens.pure_themes row now holds the complete semantic roles — surfaces, text, brand, plus --nav-* (incl. --nav-here-*), --topbar-*, --pill-*, --icon, --link, --btn-*, --field-*, radius and shadow — and a light base :root ships the same variables in every mode. Nothing hardcodes navy or gold; shells and pages consume the variables.ui.theme_exempt (or data-notheme), managed from the Palette & Tokens editor — saved through mcp_theme_exempt_set, no redeploy.tools/palette-report.mjs finds them and the push gate holds the count at zero.design.standards_index by tools/standards-docs.mjs.Foundations
Two typefaces do all the work. Folded in from brand/type.html.
text-wrap:pretty.design.standards_index by tools/standards-docs.mjs.Foundations
Folded in from brand/logo.html and brand/logo-usage.html — two pages nothing linked to, so the rules that keep the mark consistent were reachable only by knowing the URL.
design.standards_index by tools/standards-docs.mjs.Foundations
PURE breathes. Consistent, generous spacing is most of what makes it read as a premium tool rather than a crowded dashboard. Folded in from brand/spacing.html.
display:grid or flex plus gap. Cards use repeat(auto-fill, minmax(220px, 1fr)) so they reflow when items are added or reordered — margins between inline siblings do not survive either.min(1320px, 94vw), not a fixed pixel width. This is point 7 of the measured rule, and it is measured now rather than merely written.design.standards_index by tools/standards-docs.mjs.Foundations
ui.icon_pack control (one swap point, no per-page packs). The canonical reference is Brand & Icons in the admin shell; the old Icon Library page is parked in the Boneyard.currentColor, rounded caps. Consistent 24px viewBox.PureIcons.loadPack() — swap it below and the whole site updates, no redeploy. Icons are monochrome and inherit currentColor. This section is the one icon surface; /icons, /icon-picker and /icons-ext3-preview are parked and redirect here.
The rules this page proves: drawings live only in the pack (the emoji-to-sprite converter emits
<use> references and is canon-forbidden from carrying path data);
aliases live only in pure_icon_alias (the seed in pure-icons.js is generated
by icon-alias-sync --write, never hand-edited); and no alias may point at a symbol that does
not exist — enforced at every push by ci-gate, re-proven live in this band on every load.
New symbols enter through the intake queue below; repoint an alias from Brand
Standards icons and every rail follows without a deploy.
design.standards_index by tools/standards-docs.mjs.Foundations
<span data-pure-seal> on any auth surface; edit once via PureIcons.setSeal({…}).
One definition in pure-icons.js — label, points and link are data, swappable from the admin
backend. Used on reset-password, sign-in and the security centre.Live admin control
icon_autobuild
cron draws new icons to the same 1.5px / 24-grid standard, appends them to the merged pack and stages a preview
for review — nothing ships unseen. One batch per off-hours run (8pm–8am CT).icon_autobuild scans it for emoji-as-icon, non-pack glyphs and off-grid strokes, swaps in
pack symbols, and stages the diff for review. Nothing overwrites a live page unseen.Foundations
Images in PURE earn their place: honest listing photography, consistent persona portraits, and a branded card on every shared link. Folded in from brand/imagery.html.
og-default.png at 1200×630, so a link pasted into mail or chat renders the brand and not a random screenshot.Prefer the real thing to an illustration, and an illustration to nothing.
design.standards_index by tools/standards-docs.mjs.Live admin control
<div data-bi-surface="home"></div>
<script src="pure-charts.js"></script>
<script src="pure-livebind.js?v=fix-observer-813"></script>
<script src="pure-bi-embed.js"></script>
System
Navy primary with cream text; 10px radius; 600 weight.
Every timestamp renders via PureStamp.format() (CT), dual local on hover. Filenames lead with YYYY-MM-DD_HHMM so Downloads auto-sort.
Stamp-first unique names on every output. Read the standard
Light / Dark / Bright via pure-theme.js; the mode follows the user across every page.
One tidy bottom-right corner; a universal Upload & file entry (PureUploader.open()) on every page; floating actions never cover content.
The building blocks all live in pure-ui.css (canon design.system.pure_ui_css). Reuse them — do not reinvent spacing, radius or shadow, because a one-off card is a card the theme cannot re-skin.
tools/superscript-gate.mjs.tools/css-double-rule-gate.mjs.pure-ui.css already. A page that writes its own is a page that will write its own empty state too, and an empty state is where this site has most often lied to a reader.Standard
One wait indicator for the whole product: the animated PURE logo, served from /pure-loader.gif by pure-loader.js. It loads on every page from the ui.globals switchboard, so nothing needs a script tag. A hand-written “Loading…” is not the standard — it is a surface that has not adopted it yet.
data-pure-loading="Loading the menu" on the container that is waiting. It fills itself, with the attribute value as the label, and clears the moment real content is written into it. No page code, no script tag, and it works for sections rendered after boot.PureLoader.inline(el, msg) fills a region · PureLoader.overlay(msg) veils the page for a blocking action · PureLoader.dots(el) is the small thinking row for chat · PureLoader.done(el) or .clear(el) removes it.data-pure-loading-timeout per element or window.PURE_LOADER_TIMEOUT per page; 0 disables it for a genuinely long job.pure-loader.js globals row to on:false. One page: window.PURE_LOADER_AUTO_OFF = true keeps the API and turns off the automatic scan.Read from pure_component_usage, which tools/component-usage.mjs checks against the repo on every push — a surface that adopts or drops the loader shows up here, and a list that drifts from the code fails the gate. Every entry opens the live page.
design.standards_index by tools/standards-docs.mjs.Standard
A list of 180 organizations is a scroll, not a choice. pure-select.js upgrades any <select> with 8 or more options into a searchable picker, from the ui.globals switchboard — a surface gets it by having a select, not by writing code. The native select stays in the page and stays the value, so forms, .value reads and change listeners are untouched, and if the file never loads every picker still works exactly as before.
Type sup, mls, or board. This is the same catalog Hat Admin reads — not a copy pasted into this page.
acq and Acquisto shows you why it is in the list. PureMark.html(text, query) (pure-mark.js, site-wide) escapes the text first and marks it second, so the only tags in the output are its own; a search box that builds its own highlight is how one becomes an injection. Multi-word queries mark each word. Highlighting is presentation only — it never changes which rows matched.optgroup label and an optional data-keywords attribute are all matched, so an org can be found by its id or its parent group as well as its display name.role="combobox" with aria-activedescendant, and the match count is announced politely as you type.data-pure-search forces the picker under 8 options; data-no-enhance leaves a select alone; window.PURE_SELECT_OFF = true turns it off for one page; the globals row on:false turns it off everywhere.design.standards_index by tools/standards-docs.mjs.Standard
One implementation, pure-tabs.js, site-wide from the ui.globals switchboard. A surface adopts it by marking its markup — no script tag, no per-page code:
<div data-pure-tabs="access">
<button data-tab="hats">Hats</button>
<button data-tab="fields">Fields</button>
</div>
<div data-tab-panel="hats">…</div>
<div data-tab-panel="fields">…</div>
This is the live component — arrow keys move between tabs, Home and End jump, and the hash follows your choice so this panel can be linked to.
A count belongs in the tab when it tells you whether the panel is worth opening. Use <span class="cnt">.
Panels load their data on pure:tabchange — a tab nobody opened should not have cost a query.
#tab=fields for the first group on a page, #tab-<group>=<id> for any other. The hash follows the click with replaceState, so Back leaves the page rather than walking your own tab history.role="tablist" / "tab" / "tabpanel", aria-selected, aria-controls, and a roving tabindex so the strip is one stop and the arrows move within it. Three pages on this site had that before today; every tab that adopts the standard has it now.design.standards_index by tools/standards-docs.mjs.Standard
Page access decides who opens a page. These decide what they see once inside. Set per field on Hat Admin a page Fields; stored in pure_field_policy, applied everywhere by pure-field-guard.js. Default is inherit: if you can open the page, you see the field.
mcp_field_controls reads the caller’s own JWT and returns show / hide / mask / reveal / lock. The browser applies it and decides nothing — a browser that decides who sees what can be told to decide differently.pure_field_mask(value, kind) inside the RPC that returns it. A control that implies more protection than it gives is worse than no control.design.standards_index by tools/standards-docs.mjs.Enforced standard
One table everywhere — pure-table.js. PureTable.create builds one from data (including straight from an RPC), PureTable.scan upgrades a plain <table> that is already on the page. Nothing to import: the component ships from the ui.globals switchboard on every page of both shells, so a table is covered by existing rather than by adopting.
Eight rules. The first two carry live thresholds — the numbers below are read from the * row of pure_table_configs, so changing the default in the config tab changes this sentence too. A standard that quotes a number it does not read is a standard that goes stale quietly.
aria-sort is set so the state is announced, not just drawn.pure-statcard.js tile above it — value, trend, sparkline.pure_field_policy is hidden by pure-field-guard.js, which removes the cells under the heading rather than the heading alone. Hiding the header and leaving the column shifts every value one place left, under the wrong name.<th> carries scope. Without it a screen reader can read the value in a cell and cannot say what the value is of — a twelve-column deal table becomes a wall of unlabelled numbers. The rule is inferred, never guessed at twice: a cell in <thead>/<tfoot> (or in a first row that is all headers) is scope="col"; the first cell of a body row is scope="row"; a spanning header is colgroup/rowgroup. A scope the author wrote is never overwritten, and a table marked data-no-scope is left alone. Fixed in two places because one is never enough: tools/th-scope-gate.mjs --fix corrects the markup in the repository and ratchets it so the class cannot regrow, and pure-table.js re-scopes the live DOM on every scan — load, shell mount, and whenever a <table>, <tr> or <th> appears. That second half is the one that matters most here: most PURE tables are drawn from an RPC after load, so their headers exist in no file a rewriter could ever reach. Full write-up: A table header has to say what it is a header for. redact_export stays on screen and is stripped from CSV, PDF and print. A control that the download quietly undoes is not a control.Enforced two ways, so neither can be the only line of defence. At runtime: pure-table.js upgrades every qualifying table and scopes every header, on load and on every later arrival. At push: tools/table-audit.mjs fails on a page that carries a table but never loads the component, or on a grid built from <div> the scanner cannot reach, and tools/th-scope-gate.mjs fails when unscoped headers grow. The division is deliberate — a gate can only see files, and most of these tables do not exist until an RPC answers. Canon: design.data_table_standard.
The question was whether PURE needs two kinds of table. It does not — it needs one component whose capabilities are switches, and two saved combinations of those switches. One thing to maintain, one set of bugs, one accessibility pass.
source:{rpc, params, map} binds a table straight to a definer RPC — no per-page fetch, no per-page render loop. If the read fails the table says so; it does not draw an empty body.<table> semantics, keyboard selection, and a collapse to stacked cards on phones — in the component, so no page has to remember.The real component over sample rows — sort a header, search, check rows to raise the bulk bar, page through. The switches below drive this demo only, and say so: a panel that looks like a control desk and operates nothing is how the retired page came to claim for a month that the standard had not shipped.
This writes to the database. A row keyed by surface (a page path, or * for the site default) sets what the standard does on that surface; mcp_table_config_get resolves exact path, then last path segment, then *. The write is admin-gated and audited, and a refusal is reported as a refusal — never as a toast that says saved.
Two different questions, kept apart because they fail differently. Is the component on? is a live read of the switchboard and true this second. Is any page escaping it? is a fact about the repository, so it is published from the repository — docs/table-coverage.json, fetched by this panel — and never typed here or copied into a row nobody re-checks. The push gate re-runs the sweep and fails when that file has fallen behind, so the number below cannot be older than the push that changed it.
Where a table should be next: the adoption list names every surface still rendering rows by hand where PureTable.create with a source:{rpc} would do it headlessly — each one is a page of loop code that becomes a config object.
New standard
The reusable shape for any tracked level-up program — security posture, design-system build-out, standards sweeps. Each item is a card with a have / partial / gap status, an owner, an effort, evidence, and a one-click Advance. Reference implementation: Security Posture .
Admin-editable — add a row, change a status, reassign an owner, rewrite a level-up note. Backed by a registry table + mcp_*_next/_set/_advance RPCs (see the security-posture & design-system specs).
design.standards_index by tools/standards-docs.mjs.Enforced standard
Adopted by Mike 8/16/26 from the exemplar trio — Deal Cockpit (workflow), Navigation Management (admin console), Property Search (data-browse). Not a memo: an engine. Every enabled page is scored on script-checkable points, crons re-score the catalog continuously, results land in pure_design_scores and surface in Site Control. Nothing that a script cannot decide belongs in the rule — which is why each point below carries how it is measured, in the same row that defines it.
Full document: Page compliance — the ten-point standard in the docs library (raw: docs/PAGE-COMPLIANCE.md). Generated from the same row, never hand-edited.
Showing the written copy, not the live rule — the canon could not be read just now, so what follows may be behind the row the scorer actually uses.
mcp_page_shell.<table> markup. See Tables.assets/icons/**. Never emoji.min(1320px,94vw).data-pure-loading, never a typed “Loading…”.data-pure-tabs, so a panel can be linked to.Stub pages are compliant by construction: a small page whose only scripts are pure-boot/pure-shell renders everything at runtime from one source — its surface is the shell config, and it is scored as such rather than as a static page. New pages start from _template-page.html, which scores full marks on every point the day it is copied.
New standard
Every date field uses the PURE calendar — navy header, cream accents, line-icon navigation, theme-aware — replacing the off-brand OS control. Drop in any <input type="date"> and pure-datepicker.js upgrades it automatically, site-wide.
Standard ID t-pure-datepicker · loaded by pure-shell.js. The native input is preserved (same value/change events) so existing forms keep working.
design.standards_index by tools/standards-docs.mjs.Language
PURE writes like a trusted advisor: clear, brief, and respectful of the reader’s time. We explain; we do not hype. We say what a thing does, then its name. Canonical terms live in the PURE Dictionary and are enforced in copy. Folded in from brand/voice.html.
role= in markup and access_role in the schema are code, and stay. tools/vocab-gate.mjs holds the canon surfaces at zero.design.standards_index by tools/standards-docs.mjs.Enforced standard
The “no clown stuff” rules, folded in from brand/donts.html. Canon: brand.no_clown_standard. The first of these is a ratcheted gate, not an opinion — emoji in product chrome fails the push and the count may only ever go down.
#F8F4EF — warmer, and on brand.The test: would a private bank ship this screen? If it looks like a flashy startup or a clip-art deck, it is off brand.
design.standards_index by tools/standards-docs.mjs.System
A standards page that only describes things is a poster. Each row below names the surface that changes the rule, the row it is stored in, and the guard that catches it drifting — so “who do I ask to change this?” has an address instead of an answer.
Enforced standard
One confirmation component, pure-toast.js, shipped on every page of both shells from the ui.globals switchboard. Nothing to import. PureToast.ok() is green with role="status"; .err() is red with role="alert", which a screen reader interrupts for; .warn() and .info() sit between them.
role="alert" interrupts a screen reader; role="status" waits its turn. That asymmetry is the point — announcing every save is noise, and staying quiet about a refusal is a trap.timeout:0) is right when the person must act. Hovering pauses the countdown, because reading is not the same as dismissing.alert(), no confirm(). Those are chrome PURE does not control, cannot theme and cannot place. See Positioning.PureToast.auto(msg) reads the message and picks the kind — “Refused”, “could not” and “nothing was written” go red; “Saved”, “Sent” and “Added” go green. It is deliberately conservative: anything it cannot classify stays exactly where it was, so the worst case is the behaviour a page already had. Prefer .ok() and .err() where you know the outcome — the code should not have to read English to know whether it succeeded.
design.standards_index by tools/standards-docs.mjs.Felt feedback (pure-haptics.js)
Since 9/3/26 the toast carries its own felt half, site-wide and with no page edits: a success toast vibrates the success pattern on a phone and pulses green on a desktop; an alert toast vibrates the fail pattern and pulses red; a warn toast gives a single tap. The same feedback fires when a page cannot show its live data (PureLiveRequired.fail), when a gated form refuses to submit (pure-field-validate), and on a sign-in result. Reduced-motion users get the vibration and the toast, never the screen tint.
Rule for authors: do not call PureHaptics for an outcome a toast already announces — it would fire twice. Reach for PureHaptics.capture() / .celebrate() only for moments that have no toast (a photo captured, a deal closed). Configuration is one row: ui.haptics {on, flash, strength, patterns, colors}, controlled from the admin Security console.
System
Ten shared components, all shipped from the ui.globals switchboard on every page of both shells. Nothing to import and nothing to install: a page is covered by existing. Switching one off is a row in pure_ui_settings — one location, no deploy, and the page below reads that row so it shows what is actually on rather than what was intended.
Hand-rolls is the number that matters. The component already ships, so a hand-rolled copy is not a missing feature — it is a second answer to a settled question, and the second answer is almost always the worse one. Both is a page mid-migration: the cheapest tranche, because finishing it means deleting code rather than writing any.
Register: tools/component-standards.json · sweep: tools/component-standards.mjs · full list: component adoption. Adding the next component is a row in the register plus a globals row — not another tool, section and gate.
Opt-in standard
A standard nobody can link to is a standard nobody can cite. Inside a container marked data-deeplink — this page is one — every heading with an id carries a small ⟉ mark on the baseline: click it to copy that section’s absolute address. Since 9/3/26 the mark is opt-in, not everywhere (Mike: it had been overdone across both app shells and read as clutter). A page earns the mark only where a link-to-section genuinely helps — standards, runbooks, long reference pages — by marking that scope. Nothing else on the site shows one.
The mark is the whole control. It used to say COPY LINK as well, and the copy component then added its own ⟉ to the same element — so the affordance said the same thing twice and grew wide enough to compete with the heading it belonged to. One mark, in the position a reference mark belongs, is the standard. The name a screen reader announces lives in aria-label, which is the only accessible name it has now that there are no words — so it may never be blank.
pure-deeplink.js ships from the ui.globals switchboard but does nothing until a page marks a scope: add data-deeplink to the main or section that wants links. Inside it, any h2/h3 with an id, and any in-page anchor, gets the control. No page code, no script tag, and sections rendered after boot get it too.https://acquisto.biz/brand-standards#tables, not #tables. A fragment pasted into a message goes nowhere; the whole point is that the person on the other end lands on the rule you meant.PureCopy, so the confirmation is the one confirmation. This component does not invent a second toast, and it never invents a second clipboard path.data-nodeeplink on the element or an ancestor.Every section on this page also has its own document, which carries the same deep link — see The documents.
design.standards_index by tools/standards-docs.mjs.Enforced standard
This page is twenty-eight sections long. Every heading now carries a hamburger: close the ones you are not reading and the page becomes the two or three rules you came for. Your choice is remembered for this surface.
#tables opens Tables even if you had closed it, and following a link on the page opens its target. A link must land on the thing it names — a deep link into a collapsed section is a link that appears to go nowhere.pure-collapse.js from the switchboard adopts every <section id> that has a heading. Opt out per section with data-nocollapse, per page with window.PURE_COLLAPSE_OFF, site-wide with the globals row.design.standards_index by tools/standards-docs.mjs.System
Every standard on this page has its own markdown document, generated from the same row the section renders and never hand-edited — a hand-edited copy of a standard is a second standard, and the day it drifts nobody can tell which one is being enforced. Each carries the section’s deep link, the surface that changes the rule, the row it lives in and the guard on it.
Adding a standard to design.standards_index generates its document; removing one deletes it, and the push fails if a document is missing, drifted or orphaned. Those documents are a category of the library below, not a separate place.
The repo says what exists; this table says what we think of it. Bodies load from the live site, so what you read is what shipped — not a stored copy that drifted.
Manifest: docs/docs-manifest.json (published by tools/docs-manifest.mjs, and the push fails if a document exists that the library would never show) · review state: mcp_docs_review_state · saved by mcp_doc_review.
design.standards_index by tools/standards-docs.mjs.System
Working examples of the components that do not have a demo inside their own section. Everything here is the real component, loaded from the ui.globals switchboard on this page exactly as it is on every other — press the buttons. Adoption numbers for all of them are in Every component.
pure-modal.jspure-statcard.jspure-copy.jsdata-copy on any element makes it click-to-copy with the standard confirmation. Try it — MLS# 20581234.pure-export.jsYYYY-MM-DD_HHMM_<artifact>), and columns marked redact_export are stripped from the file.pure-uploader.jsPureUploader.open() raises it from anywhere.pure-datepicker.jsinput[type=date], and the native input is preserved so existing form logic keeps working.design.standards_index by tools/standards-docs.mjs.Foundations
One company, two faces. Acquisto Real Estate is the consumer brokerage — how the public finds homes and meets the team. PURE is the platform — how professionals run the business. They are endorsed brands: distinct identities, visibly related, sharing one DNA strand (ARE Blue) and one body typeface. ARE is the storefront; PURE is the workshop behind it.
| Dimension | PURE | Acquisto Real Estate |
|---|---|---|
| Job | B2B platform — the software agents and brokers run the business in | B2C site — find homes, book a consult, meet the team |
| Visitor | Agents, brokers, transaction parties | Buyers and sellers in North Texas |
| Domain | acquisto.biz | acquistorealestate.com |
| Colour | Navy with a gold accent | Sky blue with white |
| Display type | Montserrat, plus Cinzel for the wordmark | Poppins |
| Body type | Open Sans | Open Sans — the shared thread |
| Imagery | Icon and data led; almost no photography | Photo and video first: homes, team, neighbourhoods |
| Icons | Line icons, never emoji | Emoji appear in consumer marketing |
| Tone | Calm, composed, credible | Warm, local, inviting |
confirm(), no alert(). Branded, dismissible in-app modals replace them — a browser dialog is chrome PURE does not control and cannot theme.design.standards_index by tools/standards-docs.mjs.Provenance
The catalog Ana submitted — every prior design referenced while setting these standards.
System
PURE has exactly one tooltip: pure-glossary.js, sourced live from pure_dictionary. Mark a term and the definition arrives from the row — edit the row and every tooltip in the product updates. A native title= is the operating system’s tooltip: unthemeable, delayed by a value PURE does not set, invisible on touch entirely, and carrying whatever sentence somebody typed that day instead of the definition the business agreed.
title= for a tooltiparia-labelaria-label is for. It gives a screen reader the same words with no OS chrome attached. Most title= in PURE was reaching for this and grabbing the wrong attribute.title= attributes is how a vocabulary drifts.Three exemptions, each because the browser is doing something other than a tooltip: <svg><title> is a graphic’s accessible name (an element, not the attribute), <iframe title> is required by HTML and renders nothing, and <abbr> is the one element where the browser tooltip is the semantic — PURE does not use it for UI. Measured by tools/tooltip-audit.mjs, ratcheted in ci-gate: the count may only go down.
design.standards_index by tools/standards-docs.mjs.System
A native <select> never shrinks below its own label. The reason is not tidiness. A select in a flex row inherits flex-shrink:1, so the row squeezes it until the closed control is narrower than the text inside it — All modules renders as All mod. The browser then draws the option popup at the width of the closed control, so the list arrives as a narrow column with every option wrapped onto two lines.
width or min-width is the thing this standard replaces. It fixes one page and leaves the next one narrow.--select-min on the control or any ancestor, e.g. .deal-filters{--select-min:20ch}. Below 560px the floor drops and the control goes full width, because a phone row cannot afford a 12ch minimum beside anything else.pure-boot.js, the admin shell or the pure shell, and a rule that only lands when a shell mounts is missing on exactly the pages that fail first. All three carry the same declaration; tools/select-standard.mjs fails the build when they drift, because a drifted copy makes the control behave differently depending on which loader won the race.Scope is deliberate: select:not([multiple]):not([size]). Multi-selects and list boxes are sized by their row count and are left alone, and custom dropdown components draw their own list inside the page, so the operating-system limitation does not apply to them.
tools/select-standard.mjsFoundations
A section header is one object with four parts, in one order: the collapse control, the kicker, the heading, and the link control. It spans the full width of the section and sits on a rule, so a long page reads as a stack of sections rather than a stream of text with buttons in it.
Shipped by pure-collapse.js (the control) and pure-deeplink.js (the link), assembled by the shared header rule in the shell stylesheet. The header on this very section is the reference implementation — if it looks wrong here, the standard is wrong, not the page.
design.standards_index by tools/standards-docs.mjs.System
A dashboard reports. A console changes something. If a surface only shows numbers it is a dashboard and belongs in Scorecards. The moment it writes to the table it displays, it is a console — and PURE has nine of them, previously built five different ways because there was no rule to build to.
A console is not a new component. It is an assembly of standards that already exist: the grid is Tables, the counts are Scorecards, the filters are Searchable pickers, the rows are addressable through Deep links, and the empty state is Wait states. What the standard adds is the assembly — and one rule none of those parts could enforce alone: a console must name the single table it indexes. A console over two tables is two consoles sharing a scrollbar.
The one point that can be checked from inside the database: does the RPC each console claims to call actually exist. This is the check that would have caught pure-config-tables blaming authentication for a function that had never been built.
Standard: design.console_standard · registry: design.console_registry · read: mcp_console_standard(). Adding a console is a row in the registry — and the day its RPC is renamed, this table says so instead of the page saying “sign in”.
design.standards_index by tools/standards-docs.mjs.System
A spine is a table family + a function family + ONE cockpit that governs it. A spine without a cockpit is governed by whoever last edited a page. A cockpit without a spine is a page pretending to be a system.
The word means two different things here, and one of them was already stamped COMPLETE. This is the governance spine — pure_spines / pure_spine_pages. The data spine is mcp_pure_spine, one call that gives any surface the signed-in person, their desk, clearance, location and open work; that one was finished 7/31 and two status rows say so truthfully. Nobody had to notice this table was empty, because searching the word found a COMPLETE first. Name which spine before reporting either one done.
Pieces are counted from the catalogue, not asserted: tables matching the family prefix plus functions matching it. The column that matters is pages assigned — a spine with rules and no pages governs nothing.
Standard: design.spine_standard · registry: pure_spines + pure_spine_pages · read: mcp_spine_standard() · guard: tools/spine-gate.mjs. Adding a spine is a row; assigning a page is a row. Neither is a deploy.
design.standards_index by tools/standards-docs.mjs.System
A standard describes what a component should do. This list decides whether it runs at all. pure-boot.js reads ui.globals on every page load, so turning a behavior on or off is a database write and not a deploy — and eleven of the standards on this page name “ui.globals” as the surface that controls them without ever offering a way to open it. This is that surface, and it lives here because a switch belongs beside the rule it switches.
The link between a script and its standard is derived, never typed: each src is matched against the controlled_by and stored_in columns of the standards index. Add a standard and its global links itself; rename one and it reads as unlinked rather than pointing confidently at the wrong rule.
One table: pure_ui_settings (org_id='*', settings->globals) · read mcp_globals_console() · write mcp_ui_settings_set('globals', …), admin-gated and audited. Off means gone site-wide on the next load — the emergency kill for the whole plane is window.PURE_BOOT_OFF=true.
design.standards_index by tools/standards-docs.mjs.System
Two modes, one list. They already existed and were named two different ways in the same panel — the buttons said act as while the footer said Viewing as. Named once here, with what each actually enforces.
Standard: design.actas_standard · sessions: pure_act_sessions · gate: mcp_act_start · selector: the account panel, four live lists from mcp_actas_options. Choosing a name grants nothing — the server decides.
design.standards_index by tools/standards-docs.mjs.System
One scale, both channels. Poppy has refused to read a closing figure to an unverified caller since the day the voice gate shipped — pure_voice_auth_policy classifies every action L1–L4 and holds it against the caller’s assurance level. An email carrying the same figure went out with no classification at all. The levels below are now the single scale for both, and L0 — the public tier everyone referred to and nobody had written down — is a row like the rest.
Standard: design.email_standard · scale: pure_pii_levels · read: mcp_email_standard() · library: pure_email_templates · console: Machine → Templates
design.standards_index by tools/standards-docs.mjs.Visual
A theme is a row, not a stylesheet. pure_themes holds one row per palette with 64 tokens each, and pure-theme.js injects them as CSS custom properties at runtime — so adding a palette, retiring one, or changing a single colour is a database write with no deploy. The changer people actually use is the account panel, under Appearance & theme; it reads the same rows.
Why some chips looked alike. Mike, 8/18: “some of them don’t render well.” They rendered exactly as told. Each chip drew three dots from navy, gold and gold-50 — and navy and gold are identical on eight of the thirteen themes, so two of the three dots matched and the only thing separating Desert from Spring from Winter was a third dot that is a near-white in all of them. Exact duplicates were zero, which is why nothing ever flagged it. The honest measure is NEAR duplicates, and the swatch now samples bg, ink and gold, which differ on every theme.
One table: pure_themes · read mcp_themes_console() · write mcp_theme_upsert · applied by pure-theme.js (ui.globals ord 15). A swatch must sample tokens that vary. The pair distances below are computed on every read, so a palette added tomorrow is measured the same way rather than trusted.
design.standards_index by tools/standards-docs.mjs.Governance
PURE shipped aria-label="" on every heading of every page for weeks, and a tool that fails on exactly that had been sitting in the repo the whole time. tools/a11y-audit.mjs loads pages in headless Chromium at 320/360/390 px and fails on the defect classes that keep biting: controls with no accessible name, touch targets under 24×24, body horizontal scroll on a phone, <th> without scope, and text inputs under 16 px (iOS zooms the page on focus). It was never wired into the gate. That is why the blank label survived.
Armed on the canon set, not on everything. Turning it on across 766 pages would fail every push on a backlog nobody has triaged, which teaches people to bypass the gate. It runs on this page and every console in the registry — twelve pages, three viewports, about nineteen seconds — and it is a ratchet: the count may only go down. A rule enforced on the surfaces that define the rules is a rule; the rest is a burn-down.
Baseline 46. That is what the canon set scores today, and it is stated rather than hidden. pure-tech-report.html scores 0 and is the shape to copy. The ratchet is proven in both directions: one nameless 10 px button took the count to 48 and failed the push; removing it passed.
Instrument: tools/a11y-audit.mjs · baseline: tools/ratchet-baseline.json (a11y_canon_findings) · the page set is the AUDIT list in the tool, which tracks the console registry.
design.standards_index by tools/standards-docs.mjs.Governance
A page edited for any reason adopts the current standards before it is pushed. Not a sweep — the page is already open. Sweeping 330 hand-rolling pages is a project nobody schedules and a risk nobody should take: six pages broke in one afternoon on 8/17 from exactly that. Converting the page you already have open costs nothing, and it is the only adoption mechanism here that has never stalled.
Standard: design.freshness_standard · cron: page-freshness, daily 11:20 am CT, calling mcp_page_freshness_tick(). Regressions are compared only within the same scoring scale — comparing a 6-point score to a 10-point one would report the scale change as a mass regression, which is a trap the nightly drafts already fell into once.
design.standards_index by tools/standards-docs.mjs.System
An overlay that will not close is not a cosmetic bug. It is a page the reader cannot leave. Both failures on 8/18 rendered perfectly and threw nothing anybody could see — which is why they need a rule and a guard rather than care.
Component: pure-close.js — one X, sized and coloured from brand tokens so the Brand Manager reskins every overlay at once. Canon: design.overlay_standard · guard: tools/dead-binding-gate.mjs, hard fail at zero.
design.standards_index by tools/standards-docs.mjs.Enforced standard
Evidence for the claim you are making, at the altitude you are making it. On the Tech Report was reported healthy because mcp_tech_report() returned real rows in SQL. The next morning every section of it read HTTP 404: function public.[object Object]. The function was fine — the page could not call it. The evidence was real, and it supported a different claim than the one made.
Guard: tools/page-proof.mjs — every other gate here reads files; this one loads each page in a browser with every read stubbed empty and watches what it does. Baseline 21 across 8 canon pages, and it was checked by reintroducing the original defect. Full write-up: What counts as proof.
The one-line version. If the proof and the promise are on different rows of the ladder, you have not verified it — you have rehearsed it.
Enforced standard
PURE runs on Central. A closing deadline shown in the reader's own zone with nothing saying so is not a smaller bug than a wrong date — it is the same bug, harder to notice. PureStamp has been canon since 6/12 and shipped on 31 of 780 pages while 243 rendered dates by hand; it is a ui.globals row as of 8/19, so every page has it and marked elements format themselves.
Component: pure-stamp.js · canon design.timestamp_standard · ratchet tools/adoption-ratchet.mjs (dates). Full write-up: One standard, four classes.
Governance
pure_memory holds 907 pieces of hard-won knowledge — 277 lessons, 144 bugs, 417 canon rows, 69 routines. Read back, they are not 907 separate mistakes. They are seven shapes, over and over.
The column that matters is the last one. A shape with a detector cannot recur silently. A shape without one relies on somebody remembering — and this week proved that does not happen: a second time-zone system was built on 8/19 without anyone finding the first, while lesson.search_before_building had been sitting in this database since 8/16. The lesson existed. Nothing made anyone read it.
Registry: pure_failure_shapes · read mcp_failure_shapes() · canon design.failure_shapes. Adding a shape is a row; adding its detector is what closes it.
design.standards_index by tools/standards-docs.mjs.Enforced standard
PURE has had a real QR standard for months — eleven functions, a self-healing host guard and a lockstep checker — and not one line of it was written down. A standard nobody can point at gets reinvented by the next person who needs a QR code, which is how the estate ends up with four of them.
The generator that used to live at /pure-qr, folded in here so the tool and the rule are the same page. It runs the shipped component — pure-qr.js, the same one every sign, rider and postcard uses — so what you download here is what prints there.
Mint or resolve: mcp_email_qr_url(target, deal, kind, size) · render: pure_email_qr_block_labeled() · place in an email: pure_email_qr_inject() · heal the host: mcp_qr_host_guard(dry_run) · prove QR and link agree: mcp_email_qr_lockstep(). Sign-in QRs are mcp_qr_login_start/_status/_approve/_deny — single-use, short-lived, approved on a second device.
design.standards_index by tools/standards-docs.mjs.Enforced standard
Four emails from noreply@acquisto.biz arrived on one morning with four different chromes: a bare <div> from the connector watchdog, an older navy header on License Watch and the digest, and a third look on the Daily Brief. In the seven days after the email standard shipped, zero of 161 outbound emails used it.
Applied by trg_comms_wrap on pure_comms_log — on the table, because 161 sends came from fourteen principals and a rule written at the callers is wrong the first time somebody adds the fifteenth. Themes: the email standard · pure_email_themes.
Enforced standard
PURE is a Central-time company in McKinney, Texas. America/Chicago is the record and the default: it is what a stamp means when nothing else is said. A person can differ from it — by choosing a zone, or by travelling — and the difference is always visible rather than guessed from whichever laptop they opened.
Three modes, and travel is the interesting one: it follows your device while you are away and ends by itself the moment your device says you are home again. It never overwrites your home zone, because forgetting to switch back is the normal case.
Read: mcp_tz_resolve() · write: mcp_tz_set() · stored on pure_user_prefs · canon design.timezone_standard · rendered by pure-stamp.js, which also measures the server clock so a device with the wrong time cannot stamp a filing with a time that never happened.
design.standards_index by tools/standards-docs.mjs.Enforced standard
An icon-only button with no text and no aria-label is announced as “button” and nothing else. The reader is told there is a control and not what it is for — the same defect as a table header with no scope, in a different place.
Component: pure-a11y.js · canon design.accessible_names · ratchet tools/adoption-ratchet.mjs (unnamed) · also gated by tools/a11y-audit.mjs on the canon set.
design.standards_index by tools/standards-docs.mjs.Enforced standard
A page wears chrome it did not choose for itself. Until 9/1, 43 pages hardcoded a <script src="/pure-appshell.js"> tag — which always wins, because pure-shell.js correctly no-ops when a shell is already mounted. Their pure_pages.shell row was inert decoration. Three of them were public or client_token with shell='none' in the row, and mounted the internal staff app shell anyway.
Registry: pure_pages.shell (read mcp_page_shell, one line per page: <script src="/pure-shell.js" defer></script>) · guard mcp_nav_shell_audit() — shell_unset, boneyard_outside_admin and header_echo must stay empty · canon design.chrome.
Enforced standard
Postgres grants EXECUTE to PUBLIC by default on every new function, and information_schema reports the effective privilege — so a grant inherited through PUBLIC reads exactly like a direct grant to anon. On 8/31 a revoke of an anon mass-texting endpoint ran clean and changed nothing for exactly this reason. Only pg_proc.proacl shows the difference: a leading =X entry is the PUBLIC grant.
Source of truth: pg_proc.proacl, never information_schema · guard: mcp_upgrade_register() derived row D-public, target 0, not closable by hand · canon design.grants.
Enforced standard
A surface either shows live rows or says out loud that it cannot. It never shows a stored copy that looks like data. The storefront served a 46-item seed while the live catalog held 71; upgrades.html declared its fallback honestly on screen and still served it to every visitor, because the RPC it named did not exist. A failed read must never wear the data’s clothes.
Registry: pure_data_surfaces.fallback_rows · pure_live_outages · guards mcp_fallback_drift_tick, mcp_live_outage_tick and mcp_upgrade_register() derived row D-fallback, target 0 · canon design.livedata.
Enforced standard
On 9/2 five review cards carried “auto-processed by Autopilot”; two had no work behind them for eight weeks, and one named a function that did not exist. The phrase recorded a decision. Nothing recorded work. So the rule moved from the writer to the table: when an Autopilot-dispatched card’s status moves to done, the database runs the card’s done_check at that moment and refuses the transition if it fails or is absent. A person may still close it — and the card says who did, and that the check had not passed.
Guard: trg_board_done_check, BEFORE UPDATE on pure_board — it refuses, it does not report · evaluator pure_done_check_run(jsonb), four kinds (fn, row, count, human), identifiers through %I, values bound · canon design.done_check.
Enforced standard
A noun a person can read but not follow is a dead noun. Each entity kind — deal, listing, person, organisation, vendor, ticket, document, meeting, report — has exactly one home route, held in pure_link_homes, and the entity’s name on any page is the anchor to it, carrying the key and never the label. Briefed by Design 9/2; written to canon by Code the same day.
Registry: pure_link_homes (entity kind → home route) · guard mcp_link_homes_check() — every home is an enabled page and every kind has one home · doc linking.md
Enforced standard
A page family carries one Pure BI strip. Which reports fill it, in what order and for which hats is a row in pure_bi_placements edited in the BI admin; the page declares only a slot and pure-bi-embed.js draws it from a live bi_reports() read with the states kit. The Tech Control Panel showed “76 tables” for two months because its band was markup; live was 1,054.
Registry: pure_bi_placements · pure_bi_reports · read bi_placements(p_surface, p_hat) · guard mcp_bi_band_coverage() — page families with no placement · doc bi-band.md
Enforced standard
A number, a date, money, a status, a person, an address, a table, a count: each has one named component and a page uses it or is wrong. Time is PureStamp, fields are PureFormat by pure_field_formats, a status is a dictionary pill, a person is a linked name behind the PII gate, rows are PureTable, a count is a stat tile that drills. Component names, not prose.
Registry: pure_components · pure_field_formats · pure_dictionary · guards: the adoption ratchet (dates, clipboard, tooltips), the th-scope gate, component-standards REGROWTH · doc datashape.md
Enforced standard
Three floods hit the board in one week — 155,000 bug reports from one form, 1,750 upload notices, 654 stale snapshots of recurring checks — and all were the same missing idea: the writer minted a card per run. A condition writer keeps one card per condition, keyed by what and where, updated in place and closed by the run that finds the condition gone. An event writer keeps one card per event with a signature, a window and a ceiling. Every writer says which it is.
Writer: pure_condition_card(id, active, title, notes, lane) · event pattern: pure_error_reports.signature · guard mcp_board_writer_lint() (condition writers whose id carries run time or randomness; card BOARD-WRITERS-RUNTIME-ID) · doc condition-card.md
Process standard
Deleting 154,733 rows without fixing the form would have been a cleanup done twice. The order is fixed: readers repointed, writer guarded, rows snapshotted, then the clear — with the operator’s go-ahead recorded. Noise is archived, never marked done; the pile is counted from the table, never from the brief that reported it.
Registry: dated snapshot tables (RLS on) and undo.* rows · guards: the never-delete hook, mcp_board_writer_lint, lesson.backup_tables_need_rls · doc writer-first.md
Enforced standard
On 9/2/26 the primary nav held 76 routed pages. Twenty-seven of them reached the database for nothing — invented KPI tiles and fictional rows on pages a person opens from the menu — and thirty-eight more read live without a failure state, so a broken read could wear the data’s clothes. Seven were converted the same night; this standard makes both numbers ratchets that only fall.
Registry: tools/nav-primary-routes.json (snapshot of pure_nav_tree primary, refreshed by tools/nav-routes-refresh.mjs) · guard: tools/shell-live-gate.mjs in gate:all, ratchet tools/shell-live-baseline.json (the reader self-tests every known spelling of a read on each run and fails loudly if it loses one) · renderer pure-page-live.js · playbook playbook.make_page_dynamic · doc shell-live.md
Enforced standard
Until 8/19 PURE had six answers to “is this page public?” — four hand-copied arrays in JavaScript, one regex, and a column that meant something else. Thirty-four paths between them and ten agreed. The disagreements were live: a signed-out visitor asking for the privacy policy, the terms, Do Not Sell, the accessibility statement, the SMS policy or a one-tap email link was redirected to sign-in.
Registry: pure_pages.protection (read mcp_public_paths, write mcp_page_protection_set) · exemption view pure_public_exemptions · canon design.page_protection · standard PUBLIC-EXEMPTION-REGISTRY.
Governance
Two surfaces that both write a subject drift, and the drift stays invisible until somebody compares them. Two surfaces where one writes and the other reads and links back cannot drift at all. That is the whole pattern.
pure_nav_tree has exactly one writer: Navigation Management. Two surfaces that show placement now read it and link back, deep-linked to the row rather than the top of the page — because a link that lands somebody at the top of a 311-page list is the same as no link.
| Surface | What it shows | Where the link sits |
|---|---|---|
| Site Map — registry | How many menus each page is in | On the number itself, per row — #tab=<menu>&page=<path>. A 0 reads “place it”. |
| Site Control | What a page is — it owns pure_pages, not placement | In the App shells card, next to the fact it does not own |
| Hat Relationships | Which hats can see a menu item | Already linked — the hats chip is the deep link |
The link belongs in the page, not the menu. A menu entry is for going somewhere. This is for the moment the reader is already looking at the fact and wants to change it. Canon: design.source_of_truth_standard · the owner of each subject is a row in design.console_registry, rendered at Consoles.
design.standards_index by tools/standards-docs.mjs.Governance
Seven steps, in order. Nothing is deleted before the thing replacing it is real and complete. The run this process from is Navigation Management → Combine, which saves the plan first and parks separately — boneyarding a page before its features live elsewhere loses working features.
The question that produced this section: “I don’t know why we would have different stores in the same app shell — probably something we should reconcile into one.” Right instinct, and the cardinality is the answer.
Step 7 is not advice — it is a read. Any live menu row pointing at a parked or disabled target appears here and is filed as a Poppy-owned ticket by the nav-retired-guard cron.
Standard: design.retirement_standard · stores: design.pages_vs_nav · read: mcp_retirement_standard() · guard: mcp_nav_retired_audit(), cron nav-retired-guard 11:40 am CT.
design.standards_index by tools/standards-docs.mjs.Governance
A gate runs when somebody pushes. A cron runs when nobody does. Every standard on this page needs both: the gate stops a change that breaks the rule, the cron notices when the rule quietly stopped being true. This table is the join — read live, so a cron that is renamed, disabled or deleted shows up as missing here on the next load instead of lingering as a green claim.
Poppy runs all of it; a human is the backup, not the operator. Every row this files opens as a Poppy-owned ticket in the one ticket system with no approval in the path. A human is asked only when Poppy has worked a section twice and it is still not covered — a failed work pass, never merely an old ticket. So Runner is a measurement, and driving that column to all-Poppy is what “remove the human” means in practice.
Counts are facts about the repository and are published from it (docs/standards-metrics.json); health and cadence are facts about the database and are read live. Neither pretends to own the other. A bar is the backlog in the files — not what a visitor experiences, because each of these standards also applies itself at runtime.
Four fields, all required except the control surface. stored_in is what powers it and guard is what checks it — a row missing either is how a registry starts lying, so they are enforced at the write, not here.
Map: design.standards_orchestration · read: mcp_standards_orchestration() · the one cron: standards-orchestrate, daily 10:35 am CT, calling mcp_standards_orchestrate_tick(). Adding a cron to a standard is a row in the map, not a deploy — and the moment it is added, this table and that standard’s document both change.
design.standards_index by tools/standards-docs.mjs.Governance
The rule was canon before this page said it.
design.appshell_two_shells_final has been the decision since 7/13/26 and every
shell argument since has been settled against it — but it was never written down here,
so the standard the Site Control rows are judged by lived only in the database. Rendered live
below rather than retyped, because a copy is the thing that goes stale.
auto is
not a fourth answer — it is the registry's word for “nobody decided”, and
mcp_page_shell() resolves it by MENU MEMBERSHIP, not by the JS heuristic. The
heuristic is the offline fallback only, so a page can resolve one way online and another way off.
public or
client_token surface. A signed-out visitor must never be handed the staff frame.
There the FILE is the defect; adopting a registry value cannot fix it.
<script src="/pure-shell.js" defer></script>.
The loader asks the registry on every load, so the decision stays data. Hardcoding
pure-appshell.js or admin-appshell.js freezes it into the file and
takes it away from the people who govern it.
pure-shell-core.js, and anything every page needs
belongs in pure-boot.js — which already guarantees the canon fonts, the
favicon and PureTip on all 683 booting pages with zero per-page work. A shell file
that grows is a job that has not been moved yet; tools/shell-size-gate.mjs holds
the ceilings.
mini and only one repainted, so a 58px collapsed rail carried the expanded label,
clipped to “« collaps”. Fixed with one
MutationObserver in PureShellCore.railToggle, which covers every
writer including the two that never repainted at all.
Where the rows are: Site Control → App shells lists every page with what its file does, what the registry says, a verdict and a one-click adopt. This section is the rule; that tab is the decision.
design.appshell_two_shells_final · enforced by tools/shell-gate.mjs, tools/shell-agreement-gate.mjs and tools/shell-size-gate.mjs.Governance
The problem this solves: a pasted brief is a snapshot, and snapshots lie. Project instructions typed once go stale the first time a count moves, and nobody notices because the paste still reads confidently. So the brief here is generated — mcp_project_brief() reads the registry, the spines, the scheduler and the canon keys at call time. The short block below is the only thing anyone pastes anywhere, and its first instruction is to go read the live one.
Short on purpose. Everything it does not say, the live brief says — and it tells the reader that the live brief outranks it.
mcp_project_brief() and in the gates. A context file that repeats them creates a second store for one fact, which is the drift this whole consolidation exists to prevent.pure_tool. A file copied out of any of them is stale the moment it is attached.What a collaborator is asked to do on a repeating basis, and what they are asked never to do. This is the same list that ships in the handover file.
mcp_project_brief() · staff-gated, so it is readable signed in and returns nothing to the anon key.Governance
pure_memory keys carry the canon between sessions.design.standards_index by tools/standards-docs.mjs.