PURE Brand Standards by Acquisto Dictionary

One look. One voice. Every surface.

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.

PaletteTypeLogoSpacingIconographyImageryBI & Reports PURE LoaderPickersTabsFieldsComponentsEvery componentDeep linksCollapseDocumentsDemosTablesScorecards ComplianceCalendarToastsVoiceWhat not to do PositioningWhere it is controlledPast workApp shellsProject setupGovernance

Foundations

Colour & tokens

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.

Navy is the brand, gold is the one accent

Gold carries emphasis, the 2px top hairline and active states — nothing else earns an accent. --gold-ink is for small gold-toned labels and section eyebrows on light backgrounds, not for body text.

The page background is warm, not white

--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.

Status colours mean one thing each

Green is success and active, red is error and destructive, amber is a warning or an ageing item, slate is neutral metadata. A status colour used decoratively stops meaning anything.

One default palette — PURE Signature

Daylight (navy · gold · cream) is the site-wide default, stored in 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.

Every theme carries the full 62-token set

Each 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.

Theme-exempt pages are data

The front door, sign-in and step-up opt out of theming via ui.theme_exempt (or data-notheme), managed from the Palette & Tokens editor — saved through mcp_theme_exempt_set, no redeploy.

No invented tokens

No new accent colour, radius or shadow. Radii 0 / 5 / 8 / 16 / 360, and there is one shadow. A near-miss hex is not a new token, it is a mistake — tools/palette-report.mjs finds them and the push gate holds the count at zero.
The document behind this standard: Colour tokens · generated from design.standards_index by tools/standards-docs.mjs.

Foundations

Typography

Two typefaces do all the work. Folded in from brand/type.html.

Montserrat — display and UI

Headings, buttons, labels, tile titles. Weights 600 / 700 / 800. Tight, confident, geometric.

Open Sans — body and long form

Paragraphs, descriptions, table data. Weights 400 / 600 / 700. Legible at the sizes product text actually runs at.

Cinzel — the wordmark, and nothing else

It exists to set the logotype apart. A heading in Cinzel makes the wordmark ordinary.
Page title
Montserrat 800 · 26–32px
Section heading
Montserrat 800 · 17–19px
Eyebrow / label
Montserrat 800 · 11px · uppercase · +.06em
Body text — the readable default for paragraphs and descriptions.
Open Sans 400 · 14–15px
Small / meta
Open Sans 600 · 11–12px

Never

No Inter, Roboto or Arial. No Cinzel outside the wordmark. Nothing below 12px in product or 24px on a slide. Headings take text-wrap:pretty.
The document behind this standard: Typography · generated from design.standards_index by tools/standards-docs.mjs.

Foundations

Spacing & layout

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.

Multiples of 4

The working rhythm is 6 / 8 / 12 / 14 / 18 / 22 / 24 / 34px. Card padding 16–22px, section gaps 18–24px, page margins 22px.

Grid and gap, never bare margins

Lay groups out with 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.

Fluid column, never a pinned one

The content column is centred with 22px side padding and capped fluidly — 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.

Density

One idea per card; whitespace separates sections; tap targets stay at 44px or more on phones. Do not cram, do not run text edge to edge, nothing under 12px, and no SEO-style link grids — that is the consumer site, not PURE.
The document behind this standard: Spacing & layout · generated from design.standards_index by tools/standards-docs.mjs.

Foundations

Iconography — line only, no emoji

One icon package

All packs are consolidated into a single 185-symbol superset served through the 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.

Stroke line icons, 1.8–2.2px

Single-weight outline icons, currentColor, rounded caps. Consistent 24px viewBox.

Never emoji in product chrome

The “no-clown” standard — emoji never stand in for UI icons, status, or buttons.

Live icon pack

Pack · consumed by Navigation Management and Hat Relationships
Every page renders icons from this pack via 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.
Admin — set pack site-wide:

Alias control pure_icon_alias · live on every rail, no deploy

Loading aliases…

Governance — one pack, one alias authority

Reading pure_icon_lib…

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.

The document behind this standard: Iconography · generated from design.standards_index by tools/standards-docs.mjs.

Foundations

Identity seal — lives in the icon package

The trust badge is part of the icon system so every sign-in stays consistent. Drop <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.
Click it for the security explainer.

Live admin control

Grow the package — on standard, on a cron

Need an icon that isn’t here? Queue it. The 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).

    Fix a live page to standard

    See a page with off-standard or missing icons? Paste its URL — 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

      Imagery

      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.

      Listing photography is honest

      Natural light, straight verticals, rooms as a buyer will actually find them. Retouching stays honest — misleading edits are an MLS and TREC advertising problem before they are a taste problem.

      Persona portraits are cast, not repaired

      Each persona has a canonical portrait and talk loop in the asset library. Reusing one render across desks is a deliberate casting decision — never “fixed” back to one file per desk. Pages fall back to the still portrait until a loop ships.

      Every public page declares its share card

      og-default.png at 1200×630, so a link pasted into mail or chat renders the brand and not a random screenshot.

      Never a face without a release, never PII in a screenshot

      Keep faces out of marketing imagery unless the person has signed a release. Client documents and client data never appear in a screenshot — not in a doc, not in a ticket, not in a deck.

      Prefer the real thing to an illustration, and an illustration to nothing.

      The document behind this standard: Imagery · generated from design.standards_index by tools/standards-docs.mjs.

      Live admin control

      Business intelligence & reports

      Reports on this surface

      Governed by BI Reports admin · same numbers on every surface
      Every report reads its definition and its numbers live from the ecosystem database — nothing here is hardcoded — rendered with the shared PURE chart standard. Placements are governed centrally, so a change here shows up everywhere the report is embedded.

      Governance

      reports · placements
      Add, edit, and place reports on any surface from the BI Reports admin. Any page drops a live, governed report with three lines — no code change on the target page:
      <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>
      Open BI Reports admin

      System

      Components & conventions

      Buttons

      Navy primary with cream text; 10px radius; 600 weight.

      PureStamp

      Every timestamp renders via PureStamp.format() (CT), dual local on hover. Filenames lead with YYYY-MM-DD_HHMM so Downloads auto-sort.

      Naming standard

      Stamp-first unique names on every output. Read the standard

      Theme

      Light / Dark / Bright via pure-theme.js; the mode follows the user across every page.

      Pure Dock & Upload

      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.

      Cards

      White surface, 1px hairline border, 13–16px radius, the one shadow. Optional 3px gold left-accent for module tiles. Hover lifts 3px and deepens the shadow — that is the whole interaction.

      Buttons and pills

      Navy primary with cream text, a ghost secondary, and status pills at the 360px radius. Green is the one primary call to action in email.

      One loading indicator, everywhere

      The animated PURE mark is the only waiting state — page and section loads, requests in flight, AI thinking, uploads, parsing, saves. No spinners, no bouncing dots, no emoji. See PURE Loader; a hand-typed “Loading…” also fails compliance point 9.

      The raised position means a reference

      A superscript mark points at something you can open, the way a footnote number does — the th in “10th” is typography; a footnote mark is a promise there is somewhere to go. Copy, info and tooltip affordances sit on the baseline. Seven components had reached for the raised position for things that take you nowhere, which turned the convention into noise on every tile. Guard: tools/superscript-gate.mjs.
      Full write-up: The raised position.

      One selector, declared once

      A shared component that declares the same selector twice — where the later block contradicts the earlier one and leaves the properties coupled to it stranded — renders a hybrid nobody designed, and only on the hosts narrow enough to show it. The theme chips read Daylight · Golden Hour · Midnight on one page and D.. · G.. · M.. on another from identical code. Guard: tools/css-double-rule-gate.mjs.

      Skeletons, empty states and mobile helpers ship with it

      They are in 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

      PURE Loader

      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.

      Headless — mark the element

      Put 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.

      When you need to call it

      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.

      A wait state is not an empty state

      Show the loader only while something is genuinely in flight. When a call finishes with nothing, say so in words — an indicator that spins forever reads as broken, and a blank panel reads as an empty result that may not be true.

      A wait has a deadline

      Reported 8/18: Navigation Management showed the mark spinning under “Available pages 0” and “Menu structure 0”. The read had finished and returned nothing, so the page never overwrote the element and the loader span forever — the exact failure the rule above warns about, committed by the component that publishes it. After twelve seconds a loader now replaces itself with a sentence: “Still nothing for the menu — it either came back empty or never answered.” Both of those are facts a person can act on; a spinner is not. Tune with data-pure-loading-timeout per element or window.PURE_LOADER_TIMEOUT per page; 0 disables it for a genuinely long job.

      Why it also felt slow

      The scanner watched every DOM change on the page and ran a subtree query on each added element. A table drawing 500 rows therefore ran 500 queries while the browser was already busy painting them. It now coalesces to one document-wide pass per frame — identical work, done once instead of five hundred times.

      Kill switches

      Site-wide: set the 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.

      Where it is used

      Reading the register…

      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.

      The document behind this standard: Wait states · generated from design.standards_index by tools/standards-docs.mjs.

      Standard

      Tabs

      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.

      When a tab is the right answer

      Peer views of one subject that a person switches between and rarely needs side by side — a page’s Hats and its Fields. If the two are read together, they are sections, not tabs. If one is a step before the other, they are a flow, not tabs. Two to five; past that it is a menu.

      Never hide the primary action

      Save, Accept, Sign — the thing the page exists for must not live behind a tab. A hidden action is a missed action.

      Label with nouns, and carry the number

      “Fields”, not “View fields”. Where a count decides whether the panel is worth opening, put it in the tab — a tab that hides how much is behind it makes people click to find out.

      Linkable, or it is a place you cannot send anyone

      #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.

      Keyboard and screen readers, by construction

      Real 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.

      Fails visible, not blank

      Without the script every panel simply shows. Nothing is hidden until something can show it again — a tab strip that fails to load must never take the content with it.
      The document behind this standard: Tabs · generated from design.standards_index by tools/standards-docs.mjs.

      Standard

      Field-level controls

      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.

      The verdict is the server’s

      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.

      What masking honestly is

      Hiding and masking in the page defeat shoulder-surfing, screenshots and casual copying — the everyday leak. They do not defeat devtools: if the value reached the page, it reached the person. For anything that must never reach them, mask at source with 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.

      The set

      masked shape only, staff Reveal is recorded · login_required blank to signed-out · staff_only internal hats · hats an explicit hat list · upgrade_required a product lock, never a security one · redact_export on screen, stripped from CSV/PDF/print · audit_read records who looked · hidden off for everyone, field and data untouched.

      Reveals are recorded first

      The audit row is written before the value is shown. An audit that depends on the render succeeding is an audit with a hole in it.
      The document behind this standard: Field · generated from design.standards_index by tools/standards-docs.mjs.

      Enforced standard

      Tables & data display

      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.

      1

      Search past 3×3

      More columns and more rows than that and an inline search box appears on its own. Small tables stay clean — a search box over four rows is furniture.
      2

      Sort past 2×2

      Click-to-sort headers, three-state: ascending, descending, off. aria-sort is set so the state is announced, not just drawn.
      3

      Select, then act

      Single and multi select with a header select-all (including the indeterminate middle state) and shift-click for a range. The bulk bar appears only once something is selected — Copy, Export, Share, Tag, Archive.
      4

      Status as pills, numbers as values

      A status column renders as a tone pill, never as bare text the eye has to parse. Numbers that summarise the table pair with a pure-statcard.js tile above it — value, trend, sparkline.
      5

      Columns are gated, not merely hidden

      Who sees a column is a field control, not a table option: a column named in 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.
      6

      An empty table is a sentence

      “No rows yet”, “Nothing matches that search” and “Could not read this — nothing is missing, we could not ask” are three different facts and must read as three different things. A failed load that renders as an empty table is the most expensive bug on this site, because it looks like an answer.
      8

      A header says what it is a header for

      Every <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.
      7

      Export carries the redactions

      A column marked 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.

      Data Grid — dense, every module on

      Search · sort · multi-select · bulk bar · pagination · density · column show/hide. For registries and admin lists: Site Control, Boneyard, Security Posture, Audit Log, products, rate limits.

      Simple List — light, read-only

      Sort only, compact, zebra, no selection. For summaries inside dashboards, drawers and embeds, where a bulk bar would be noise around four rows.

      Live-bound, fail-soft

      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.

      Accessible and responsive by construction

      Real <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.

      Type a surface to load the row it resolves to today.

      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

      Scorecard pattern

      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 .

      Three states, color-coded

      Have (green) · Partial (amber) · Gap (red). A coverage % + progress bar sits on top.

      Grounded in evidence

      Every “have” cites the real table/function/page behind it — never an unbacked claim.

      Advance, don't just track

      Each card advances gap partial have; changes are logged and reversible.

      Human steps are scoped

      Anything needing an account owner or a paid toggle is clearly labelled “Needs a human” with the exact step.

      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).

      The document behind this standard: Scorecard pattern · generated from design.standards_index by tools/standards-docs.mjs.

      Enforced standard

      Page compliance — the measured rule

      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.

      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

      Calendar & date input

      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.

      The document behind this standard: Calendar & date input · generated from design.standards_index by tools/standards-docs.mjs.

      Language

      Voice & terminology

      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.

      Clear over clever

      Plain language, short sentences, and jargon defined — the Dictionary exists for exactly that.

      Calm and confident

      No exclamation marks, no urgency tricks. State what is true and what happens next.

      Respectful

      Fair-housing aware, inclusive, never pushy. The client’s interest comes first, including when that costs us the faster close.

      Do

      “Your offer was accepted. Next: schedule the inspection by Jun 22.” Real numbers, real dates, the next action.

      Don’t

      “AMAZING NEWS!! You’re going to LOVE this!!!” No hype, no emoji as a shout, no vague enthusiasm. Nothing in that sentence tells the reader what happened or what to do.

      Names are precise

      ARE is Acquisto Real Estate, the brokerage brand — never the English verb. PURE is PURE MLS Technologies, the platform the lanes build. Product names are exact: Pure Chat.ai, PureSign, TrustSeal, DealSpine, Pure Dock, Pure Uploader, People, Activity & Time — title case, “Pure” prefix on shared utilities. When a name might collide, ask before using it.

      A role is a hat

      Mike’s ruling, 8/17: hat survives. In text a person reads, PURE says hat — role= in markup and access_role in the schema are code, and stay. tools/vocab-gate.mjs holds the canon surfaces at zero.
      The document behind this standard: Voice & terminology · generated from design.standards_index by tools/standards-docs.mjs.

      Enforced standard

      What not to do

      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.

      No emoji as UI

      Never as an icon, in a tile, a button, a nav row or a status. Use the line icons. Emoji are fine inside a message a human typed — that is content, not chrome.

      No AI-slop visuals

      No aggressive gradients, no rainbow colour, no scattered drop-shadows (there is one shadow), no rounded-corner-plus-left-accent on everything, no decorative SVG illustration.

      No off-brand type

      No Inter, Roboto or Arial. No Cinzel outside the wordmark. Nothing under 12px in product text.

      No invented tokens

      No new accent colour, radius or shadow. Navy and gold plus status; radii 0 / 5 / 8 / 16 / 360; the one shadow. A near-miss colour is not a new token, it is a mistake — the palette report finds them and the push gate holds the count at zero.

      No pure-white pages

      Backgrounds use gold-50 #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.

      The document behind this standard: What not to do · generated from design.standards_index by tools/standards-docs.mjs.

      System

      Where each standard is controlled

      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

      Confirmations & toasts

      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.

      1

      A confirmation that cannot say no is not a confirmation

      Success and failure must not look the same. This is the empty-table bug in another costume: a refusal drawn like a save looks like an answer, and the person walks away believing the write happened. If you take one rule from this section, take this one.
      2

      Say what happened, then what to do

      “Refused: not authorized — nothing was written” beats “Error”. Name the outcome, name the consequence, and if there is a next step give it. Voice applies to a toast exactly as it applies to a page.
      3

      Failures are announced, successes are not

      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.
      4

      Failures wait longer

      A success can leave after about four seconds. A failure holds for six and a half, and a sticky toast (timeout:0) is right when the person must act. Hovering pauses the countdown, because reading is not the same as dismissing.
      5

      Never a native dialog

      No 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.

      The document behind this standard: Confirmations & toasts · generated from design.standards_index by tools/standards-docs.mjs.

      Felt feedback (pure-haptics.js)

      Every announced outcome is also felt

      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

      Component standards — every one, in one place

      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.

      Enforced standard

      Collapsible sections

      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.

      Open is the default, and that is deliberate

      A page whose content is hidden until someone clicks cannot be searched with the browser’s own find, cannot be printed, and reads as empty to a crawler. Collapse is a convenience the reader chooses, never a default the page imposes. The only thing that starts closed is a section a person closed themselves.

      The address wins over the memory

      Arriving at #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.

      Print restores everything

      A printout of a page somebody collapsed is not the page. Every section prints open, whatever is on screen.

      Headless

      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.
      The document behind this standard: Collapsible sections · generated from design.standards_index by tools/standards-docs.mjs.

      System

      The documents

      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.

      Every document in PURE

      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.

      The document behind this standard: The documents · generated from design.standards_index by tools/standards-docs.mjs.

      System

      Live component demos

      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.

      Overlays — pure-modal.js

      Focus-trapped, ESC and backdrop close, focus restored on exit. Four shapes, one component.

      KPI tiles — pure-statcard.js

      Value, trend direction and a sparkline. A number without a trend is a number nobody can act on — 179 pages still draw their own.

      Copy — pure-copy.js

      Mark the value, not the handler: data-copy on any element makes it click-to-copy with the standard confirmation. Try it — MLS# 20581234.

      Export — pure-export.js

      CSV and print-PDF with PURE-standard stamped filenames (YYYY-MM-DD_HHMM_<artifact>), and columns marked redact_export are stripped from the file.

      Upload — pure-uploader.js

      One capture surface for files, photos, camera and paste, on every page from the dock. PureUploader.open() raises it from anywhere.

      Date — pure-datepicker.js

      The brand calendar replaces the OS control on every input[type=date], and the native input is preserved so existing form logic keeps working.
      The document behind this standard: Live component demos · generated from design.standards_index by tools/standards-docs.mjs.

      Foundations

      Positioning — PURE and ARE

      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.

      DimensionPUREAcquisto Real Estate
      JobB2B platform — the software agents and brokers run the business inB2C site — find homes, book a consult, meet the team
      VisitorAgents, brokers, transaction partiesBuyers and sellers in North Texas
      Domainacquisto.bizacquistorealestate.com
      ColourNavy with a gold accentSky blue with white
      Display typeMontserrat, plus Cinzel for the wordmarkPoppins
      Body typeOpen SansOpen Sans — the shared thread
      ImageryIcon and data led; almost no photographyPhoto and video first: homes, team, neighbourhoods
      IconsLine icons, never emojiEmoji appear in consumer marketing
      ToneCalm, composed, credibleWarm, local, inviting

      The visitor’s context decides the skin

      When the properties merge, consumer routes wear ARE and authenticated platform routes wear PURE. Shared infrastructure — auth, data, components, tokens — lives underneath both. Neither brand is diluted to make the merge easier.

      Calm authority

      Navy carries every header and primary surface. PURE never shouts; hierarchy does the work.

      One clear action

      Every screen and every email has ONE primary call to action. Secondary paths are quiet links, never competing buttons.

      Say what happened and what happens next

      “Your disclosure was filed to the deal” beats “Success!”. Completions carry a Central Time stamp and an operator, and artifacts follow the naming standard so downloads sort themselves.

      No native browser dialogs

      No confirm(), no alert(). Branded, dismissible in-app modals replace them — a browser dialog is chrome PURE does not control and cannot theme.
      The document behind this standard: Positioning · generated from design.standards_index by tools/standards-docs.mjs.

      Provenance

      Past work reviewed

      The catalog Ana submitted — every prior design referenced while setting these standards.

      System

      Tooltips — one source, and it is the dictionary

      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.

      Never title= for a tooltip

      Not in markup, not from JavaScript. A tooltip written by a script is exactly as unthemeable as one written in HTML.

      If the control needs a NAME, use aria-label

      An icon-only button with no visible text needs an accessible name, and that is what aria-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.

      If the reader needs a DEFINITION, use the glossary

      A term the business defines — hat, CDA, clearing, effort — belongs in the dictionary, and the tooltip comes from the row. Two people typing two definitions into two title= attributes is how a vocabulary drifts.

      If it is an explanation, put it on the page

      Guidance a reader needs in order to act is not tooltip material at any size. A hover that hides the instruction is an instruction most people never read.

      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.

      The document behind this standard: Tooltips · generated from design.standards_index by tools/standards-docs.mjs.

      Foundations

      Section headers

      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.

      1

      The controls sit on the baseline of the heading, never below it

      The collapse control belongs at the head of the line and the link control at the end of the heading text. A control that wraps to its own line reads as content.
      2

      The header spans the section

      A rule under the header, full width. Without it the header is the width of its own text and the section has no visible top edge — which is exactly what a heading plus two floating chips looks like.
      3

      The kicker is a category, not a sentence

      One or two words — Foundations, System, Governance — so a reader can tell what KIND of section this is before reading the heading.
      4

      Controls are quiet until wanted

      Present in the DOM and reachable by keyboard at all times; prominent on hover and on focus. A control that only exists on hover cannot be tabbed to; a control that always shouts competes with the heading it serves.

      No native tooltip on any of them

      See Tooltips. The collapse control carried one until 8/18 and it is the reason this standard exists.

      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.

      The document behind this standard: Section headers · generated from design.standards_index by tools/standards-docs.mjs.

      System

      Consoles — a live index over one table

      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.

      Every console, and whether it is wired to anything

      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”.

      The document behind this standard: Consoles · generated from design.standards_index by tools/standards-docs.mjs.

      System

      Spines — one cockpit per family, and the hats named once

      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.

      Every spine, counted live

      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.

      The document behind this standard: Spines · generated from design.standards_index by tools/standards-docs.mjs.

      System

      Site behaviors — what actually loads on every page

      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.

      The document behind this standard: Site behaviors · generated from design.standards_index by tools/standards-docs.mjs.

      System

      Acting as someone else — View as and Act for

      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.

      The document behind this standard: Acting as someone else · generated from design.standards_index by tools/standards-docs.mjs.

      System

      Email & send — what may leave, and to whom

      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

      The document behind this standard: Email & send · generated from design.standards_index by tools/standards-docs.mjs.

      Visual

      Themes — one palette table, every look

      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.

      The document behind this standard: Themes · generated from design.standards_index by tools/standards-docs.mjs.

      Governance

      Accessibility — armed where the rules are written

      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.

      The document behind this standard: Accessibility · generated from design.standards_index by tools/standards-docs.mjs.

      Governance

      Freshness — convert on touch, nothing older than a day

      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.

      The document behind this standard: Freshness · generated from design.standards_index by tools/standards-docs.mjs.

      System

      Overlays — and the two ways they stop closing

      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.

      The document behind this standard: Overlays · generated from design.standards_index by tools/standards-docs.mjs.

      Enforced standard

      What counts as proof

      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

      Time — one clock, and it says which one

      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

      The shapes we keep failing in

      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.

      The document behind this standard: Failure shapes · generated from design.standards_index by tools/standards-docs.mjs.

      Enforced standard

      QR codes

      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.

      Make one

      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.

      The document behind this standard: QR codes · generated from design.standards_index by tools/standards-docs.mjs.

      Enforced standard

      What every PURE email looks like

      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

      Time zones — and what happens when you travel

      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.

      Your setting

      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.

      Sign in to set your own zone — signed out, everything renders on the company clock.

      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.

      The document behind this standard: Time zones & travel · generated from design.standards_index by tools/standards-docs.mjs.

      Enforced standard

      Every control says what it does

      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.

      The document behind this standard: Accessible names · generated from design.standards_index by tools/standards-docs.mjs.

      Enforced standard

      One shell decision, taken from the row

      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

      A revoke must name PUBLIC, and be verified at the anon path

      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

      Live data, or an alert that says so

      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

      Automation closes a card only on a passing done-check

      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

      Every name is a link to its one home

      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

      The BI band is rows, not markup

      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

      One shape per kind of value

      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

      A card is a condition, not an event

      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

      Fix the writer before you clear the pile

      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

      Every menu entry earns live data, or says why it cannot

      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

      What a stranger may see

      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

      One source of truth, and a link to it

      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.

      Worked example — the menu tree

      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.

      SurfaceWhat it showsWhere the link sits
      Site Map — registryHow many menus each page is inOn the number itself, per row — #tab=<menu>&page=<path>. A 0 reads “place it”.
      Site ControlWhat a page is — it owns pure_pages, not placementIn the App shells card, next to the fact it does not own
      Hat RelationshipsWhich hats can see a menu itemAlready 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.

      The document behind this standard: One source of truth, and a link to it · generated from design.standards_index by tools/standards-docs.mjs.

      Governance

      Retiring a surface

      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.

      Why two stores, and why not one

      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.

      The guard, right now

      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.

      The document behind this standard: Retiring a surface · generated from design.standards_index by tools/standards-docs.mjs.

      Governance

      Who keeps each standard true

      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.

      What each standard is measured by

      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.

      The registry

      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.

      The document behind this standard: Orchestration · generated from design.standards_index by tools/standards-docs.mjs.

      Governance

      Two shells, and when each one is right

      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.

      1

      Three answers, not two

      A page carries the PURE shell, the admin shell, or none. 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.
      2

      Audience decides, not mechanism

      A page that hardcodes a shell wins over the registry at run time — which is why “the file wins, so record the file” is the wrong rule for a 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.
      3

      One line, not a bundle

      The way a page gets a shell is <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.
      4

      Headless, per the mandate

      Shared shell behaviour belongs in 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.
      5

      A label follows its state, it is not told

      If two or more places can change a piece of UI state, the thing that DESCRIBES that state must observe it, not be repainted by each writer. The rail proved it on 8/24: four paths toggled 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.
      Scope, honestly: a census found only four other places that set a label from a class, and in each one the same handler owns both — so this is a rule to apply when a second writer appears, not a framework to build now.

      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.

      Canon: design.appshell_two_shells_final · enforced by tools/shell-gate.mjs, tools/shell-agreement-gate.mjs and tools/shell-size-gate.mjs.

      Governance

      Running PURE as a project

      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 generatedmcp_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.

      Paste this into the project’s Instructions

      Short on purpose. Everything it does not say, the live brief says — and it tells the reader that the live brief outranks it.

      Or select the box above and copy it by hand.

      What to attach as project context

      1

      Nothing that changes weekly

      No page lists, no cron lists, no counts. Those live in 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.
      2

      Do attach the things that are genuinely stable

      The brand marks, a logo lockup, and any real contract or proposal PDF. Those are documents, not state.
      3

      Let the connectors carry the rest

      Supabase reads the live database, GitHub reads the live branch, and Poppy dispatches any skill through pure_tool. A file copied out of any of them is stale the moment it is attached.

      The regular cadence

      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.

      Generated live by mcp_project_brief() · staff-gated, so it is readable signed in and returns nothing to the anon key.

      Governance

      How the standard is enforced

      Rules & memory

      Standing project rules + pure_memory keys carry the canon between sessions.

      Crons

      Scheduled audits keep the standards true between pushes. This used to name three crons out of the roughly three hundred that run and point at a markdown file; it now points at the live join — who keeps each standard true — which reads the scheduler itself, so a cron that is renamed or disabled shows up as missing instead of staying green in a document nobody regenerates.

      Brand Admin review

      Cecile approves design-audit, emoji and token findings in Brand Workspace. Standards here (incl. Tables & Scorecards) are brand-manager editable from that workspace.

      Scored, not asserted

      Every enabled page carries a compliance score against the measured rule, re-scored by cron and readable per page in Site Control. The push gate refuses a change that regrows a metric this project already paid to shrink.
      The document behind this standard: How the standard is enforced · generated from design.standards_index by tools/standards-docs.mjs.