MEMORY
memory/project-C--Dev--GEM--robco-local-backup/museum.md
sha256 177bb8f4a4849225 · 7224 bytes ·
original held in the private archive
---
name: museum
description: "The archive museum — generator at museum/generate.mjs, regenerated by sync.ps1 as a non-blocking ritual; design constraints and conventions"
metadata:
node_type: memory
type: project
originSessionId: 798ebe8e-3057-4afc-a464-abd28677d015
modified: 2026-07-20T18:12:51.814Z
---
Built 2026-07-19. A generated, browsable view of THIS private archive lives at
`museum/`. One generator, `museum/generate.mjs` (Node built-ins only), walks the
archive once into a single manifest and renders every view from it.
**Hard constraints (settled by two external reviews, do not relitigate):**
- Standalone, NOT embedded in the app — the archive is private; anything the app
serves crosses the privacy boundary.
- Opened as a LOCAL FILE by double-click, so `fetch()` is unavailable. All DATA
(search corpus, manifest) is written INTO the page as inline JS, never fetched.
- No npm deps / framework / CDN / bundler / SW. Playwright (from the app repo's
node_modules) is used at GENERATION time ONLY, to screenshot the prototypes,
cached by content hash in `museum/.cache/thumbs` so unchanged ones never re-render.
**CSS interpretation (a deliberate, documented reading):** the app's real
stylesheets are concatenated in their canonical `<link>` order (the mobile sheet
LAST) into `museum/site/assets/robco.css` and referenced by a relative `<link>`, not
inlined per-page. A local sibling `<link>` loads fine under `file://` — the same
subresource mechanism the required in-place `<img src>` gallery depends on; inlining
the whole sheet into every generated page was rejected as pure waste. The ONE
museum-specific CSS block loads last and includes a **scroll reset**
(`body[data-museum]` overrides) because the app's `.glass-frame`/`body` are
`overflow:hidden` fixed-viewport — a museum page must scroll as a document or
everything below the fold is unreachable.
**Conventions worth keeping:**
- `graveyard/<slug>/record.md` — structural encoding of ruled-out ideas so the
generator can show "abandoned, and why" (a script can't infer it from prose).
Front-matter `original:` points at the real in-archive decision record to inline
verbatim — no later summaries; that's the failure mode.
- `museum/accounts/<version>.md` — the ONLY hand-written content; one per release,
currently unapproved (`draft: true`) pending owner-written accounts. Unapproved
renders the account plus ONE quiet provenance line at the foot — it does NOT shout
DRAFT. Missing → "ACCOUNT NOT FILED" (never invented). Owner-facing drafting notes
live in non-rendered front matter (`owner_note:`), never in the body. Rooms are
discovered by version-shaped folder names, never hardcoded.
- Stamp on every page = app commit + hash of the archive input tree; NO wall clock,
so it regenerates byte-identically when nothing changed (verified idempotent).
- Growth is plain SVG from real data, but **one series per panel, one axis per panel,
own units, from zero** — never two units on one frame. The earlier chart
normalised checks and code lines to finish at the same height against a checks-only
axis; that was numerically wrong, not just badly labelled. **Code lines is the hero
series, NOT check count** — count is inventory, not confidence, and the app retired
its test-count bookkeeping on 2026-07-20. The checks series is a CLOSED record that
visibly ends where the `Tests:` marker was stripped from `[Unreleased]`; never
re-promote it. Points are keyed off DATE, never off the presence of a Tests marker
(keying off the marker silently dropped the newest point when the marker went away).
- **The chart plots the IN-FLIGHT point, not just releases.** When nothing has shipped
for a while, a releases-only chart reads as a museum that stopped updating. The `dev`
working state is its own point: dashed segment, hollow ring, labelled IN FLIGHT,
amber row in the table. Never merged into the release line — that would fabricate a
release. ⚠ **This broke once already and is the canonical local example of an INERT
mechanism** (design doc §5): the Unreleased handling was written and correct, but
`if (!tm) continue` gated it on a `Tests:` marker, so when that marker was stripped
on 2026-07-20 the whole point vanished — date, files, lines included. It looked
healthy; the owner caught it by eye.
- **`growthCurrency()` is the recurrence guard.** It compares the newest charted point
against the pinned app ref's commit date; on disagreement the lobby AND growth page
render a generated NOT CURRENT notice. Test it by simulating the regression, not by
pinning MUSEUM_APP_REF to a tag (old refs have their own `[Unreleased]`, so that
passes for the wrong reason).
- Do NOT compute a fresh test count to "fix" the checks series ending where it does.
That would rebuild in the archive the bookkeeping obligation the app deliberately
deleted. The ending IS the record.
- Claims are LITERAL. The lobby states the things the build actually checks and
names what it does NOT check. Never "DATA INTEGRITY — clean". One of those checks is
a real internal-link check (`checkInternalLinks`) that resolves every generated
href/src on disk — it skips inline `<script>` bodies, or JS building markup as
strings scans as broken links.
- Keypad nav is `<a href>` + `aria-current`, never `<button onclick>`/`role=tablist` —
these are separate-page links. Zero JS in navigation.
- Search corpus is SHARDED into `assets/search-N.js`, loaded on first keystroke, weight
stated on the page, so search.html stays small instead of shipping the whole corpus.
`<script src>` works under `file://` where `fetch()` does not — that is the mechanism
that makes this possible. The shards are TRANSPORT, not a search partition: all load
before any query runs, so a result set is never silently partial.
- Sparse rooms (a version with only one artifact) render the artifact as a seated
module on an empty 6-slot bay + a coverage notice. ⚠ Wording is load-bearing: the
material "is present in the archive", NEVER "was never created".
**Wiring:** `sync.ps1` runs `node museum/generate.mjs` after `Sync-Memory` as a
RITUAL that can never fail the sync (mirrors `backup-nudge.js` fail-safe DNA; the
generator itself exits 0 on any error and leaves the previous museum intact via
atomic temp-build + swap). `museum/site/` IS COMMITTED as of 2026-07-20 — it was
gitignored as "regenerable output", but the owner reads this archive through GitHub
on a phone, so output living on one disk defeated the archive's whole purpose. The
museum step runs BEFORE sync.ps1's `git add -A`, so a refresh rides along in the same
sync commit and GitHub never goes stale. Only `.cache/` and `.build-tmp/` stay ignored.
Entry point is `museum/site/index.html` — NOT `museum/index.html`, which never existed
and is the path to correct if anyone reports the museum "missing". Note: `backup-nudge.js`
lives in the read-only app repo, so the pattern was extended into `sync.ps1`, not that
file. See [[planning-additive-only]] (the additive-only rename hazard is surfaced by
the generator's duplicate-content detector) and [[backup-memory-topology]].
STAMP · generated for RELEASE v2.8.5 commit 06e5180 (06e51801b38a) · archive input-tree hash c07fbfbdd2e1ddeb · 754 files · no wall-clock timestamp (regenerates identically when nothing changed).