What shipped, when

Mirrors docs/CHANGELOG.md in the repository — the same list the maintainers keep.

0.6.0

2026-10-16, in flight
  • Rail: less crowded, cleaner — the server tree collapses to ONE quiet summary row (Server structure · 12 — the Server screen owns channel depth; a stored expand still reopens it, per guild); rows keep an even rhythm (9px padding, 1px gaps, quieted # markers).
  • Phone-friendly — below 768px every panel row is a real 44px tap target, the search field is 40px, the drawer fits min(84vw, 320px), and the topbar title trims to 15px. Battery SP-1..3. Appearance pass AP-1 - the depth language: cards and the hero rest at --shadow-rest (theme-re-tinted), interactive cards lift one pixel with a 120 ms state fade, primary buttons gain press feedback (quiet buttons tint instead of shrink), and the topbar title carries a static leaf hairline. No new keyframes, RM-frozen, battery AP-1 (5 legs). AP-2 the empty voice - the empty-states table now covers EVERY list screen: the four hardcoded empties (settings, setup, the overview's member search, tickets) and the three stray inline rows (suggestions, starboard, voice-hubs) fold into EMPTY_COPY with a reason + next action each; the copy-gate battery scans all 17 list screens and the probe's pins moved in the same commit. AP-3 the composition - a visual audit of the probe's own screenshots found the column left-stranded and the sidebar's rows wearing card chrome: main and the shell footer now center as one column, .split rests single-column and splits into master/detail only when a row is selected (tickets/appeals/members), the panel's nav rows are quiet rows (card chrome scopes to main li), empty cards compose (capped, centered, breathing), and the cases pager hides when a payload claims pages with zero rows. Battery AP-1 extended to 9 legs; probe green (LCP 604ms, CLS 0). CI caught the CLS gate on the composition slice: the rail rendered nothing while the structure query was in flight, and the loaded tree's arrival shifted the groups below (GRD-2.d). The rail now holds its shape in flight (a summary-silhouette skeleton, RM-frozen), and T-2.6's close legs wait for the real end state instead of a fixed 450ms settle (the flake's actual mechanism; green on three consecutive runs after). The drawer's reopen taps retry up to 3x — the opener tap can no-op right after a synthetic close (a browser race, not an app defect), which CI hit as a thrown .panel.open timeout; two more green harness runs after the retry fix. AP-4 the topbar cluster — one control shape: the live light, the palette and the theme toggles are 34px outlined circles (the status colour lives on the dot), and the pinned-server pill carries units ('42 members') — dropping the word below 480px so the pill can't push the cluster past a phone viewport (the SP-PHONE overflow leg caught it pre-publish). Battery AP-1 at 11 legs. AP-5 the quiet composition — the empty-state card claims a vertical band (min-height 38vh clamp, content centered in it) and the polka texture backs off (smaller dots, larger tiles, an earlier fade); the pinned pill truncates the NAME only (never '…members' mid-word; cap widened to 250px). Battery AP-1 at 13 legs. Shots verified: the pill reads in full, the card composes. DF-1 the status probe honest — dogfood sweep of the live site + app found the /status scheduler pill ignoring lease health and backlog (any queueDepth ≠ -1 read 'operational'): the probe now reads the fields its copy promises — stale leases → degraded-red, deep queue → busy-amber, uncomputed → no signal — and the state vocabulary is closed (operational/busy/degraded/no signal, no relabelling). Battery site-status-probe 3 legs; suite 1534. DF-2 AX-1 cleared — the dashboard's a11y backlog is fixed and the axe gate enforced — the control reset's color: inherit on buttons had been repainting every submit in page-text ink on moss (2.33:1); the light theme's leaf text green measured 3.87:1 on cards (now a same-hue AA green, 5.64:1); the logs expander's accessible name now leads with its own visible verb per state; and standalone empty states ride real lists (ul wrappers in ai/budget/settings). The axe batch measured 0 violations × 18 scans and CI now runs it --enforce (regressions block, no more unread backlog). Batteries: dashboard-ax1 4 legs; suite 1538. WT-1 the site's dead theme toggle became live — components/theme-toggle.jsx was built, matched the storage key and the stale-name mapping, but rendered nowhere; the site nav now carries it (per-theme palettes already existed, canopy is the default, canopy → grove → dawn cycle, icon follows the theme: moon/leaf/sun). Verified with eyes on all three themes + persistence across reload. Battery site-theme-toggle 3 legs. The site keeps its own lucide-react dep (the dashboard's dep-diet does not cross the site boundary). WT-2 the changelog page's mirror is now real — dogfood caught the site changelog frozen at the 10-16 snapshot while fifteen merged slices shipped after it. The page renders a COMMITTED JSON generated from docs/CHANGELOG.md (gen-changelog.mjs verbatim, minus emphasis markers), the stale hand-kept lib/changelog.js is deleted, and a new CI step (check-changelog-parity.mjs) regenerates + diffs — future doc moves go red instead of drifting silently. Gate paths proven (green/drift-fail-restore/--fix). Battery site-changelog-mirror 3 legs; suite 1544. DF-2b the parity gate's clock-hardened — CI's pg ties three same-second seed rows to the random UUID tie-break, shuffling the expected order (locally the clock advances). The T-5.2 seed stamps distinct milliseconds; the transport claim never depended on the writer's clock. Test-only, no deploy. Category rename - each category row gains Rename (inline edit pre-filled, unchanged names refuse to stage, changed-only { name } posts through the registry's own edit_channel) - the last verb of "create/rename/delete categories". The probe's category legs target data-op hooks (and prove the rename flow browser-real) — CI caught the first .quiet click grabbing Rename instead of Delete. A SP-PHONE harness leg proves it browser-real at 390×844: no horizontal overflow, the collapsed summary with its live count, every rail row a ≥44px tap target. WT-2b: the mirror gate caught its own slice (the WT-2 note moved the doc after the snapshot) — regenerated, red-as-designed; the CHECK job's separate failure was the CI-only lease-renewal race (an in-flight tick lands one write after stop(); the test now waits out that window) — fixed in the same follow-up. DF-3 dogfood of the dashboard's public surfaces (login door, OAuth intent, forged cookie, /app signed-out gate): the gate itself proved correct — the only catch being that a HIDDEN tab defers the 401 redirect until surfaced (third daemon truth). One real fix: the /status 'Sweep leadership' pill called ANY boolean value operational, so a live lease gap (observed false→true in prod within minutes) read all-clear against the card's own 'exactly one process' promise — now false = amber 'between leaders', true-only = operational, missing = 'no signal'. site-status-probe battery 4 legs; suite 1545. WR-3 close-out: the WR survey found DV3's code fully built across the unified-surface arc (WR-0..4: policy table, /api/action, confirm card, hold/appeal/discord mirror, pressable staged confirms) with ONE missing Part-11 gate — the Discord-mirror battery. test/dashboard-mirror.test.js adds it (exactly-one post with dashboard provenance; a failed post never fails the action; no channel = zero sends); suite 1549. DV3's remaining gate is the owner's scheduling call, not code. DF-4 copy-truth audit of /permissions against the REGISTRY itself: two ladder drifts fixed — 'Digests' moved from the moderator row to admin (every digest tool is admin-rank) and 'Propose server plans via staff AI (confirm-gated)' added to the support row (plan_server_changes + record_plan_steps are support-rank); the moderator row keeps its analytics tiles (the /api/analytics read is staff-scoped). New battery test/site-permissions-truth.test.js pins page-vs-registry both directions (3 legs); suite 1552. DEP-7 close-out — the 10-21 depth survey found the name-resolution hook pattern (DEP-7) landed everywhere except the claim lines that still printed RAW SNOWFLAKES: the tickets detail's opener/claimed-by, starboard's source/board channels, and voice hubs' room/hub ids. New MemberName consumption on tickets and a sibling ChannelName component (voice-hubs + starboard) resolve them the hook-clean way — the channel hook reads the rail's OWN /api/server?detail=1 cache key (no second fetch, no new endpoint), misses stay honest ids, and the stub's tickets row gained the real projection's openerId. Battery test/dashboard-depth-names.test.js 4 legs; suite 1556; probe green (LCP 1148ms, CLS 0, the starboard pin asserts the resolved 'general'). RA-1 the re-audit: same rubric, same method (code-reading + live probes + CI logs at be075f0c1) — weighted 8.9 vs the audit's 5.9 (bar 8.5) across nine dimensions, all 29 findings closed (28 fixed / 1 accepted by the recorded DW-LOGIN decision), strengths carried. The programme's remaining gates are the owner's calendar (MR3 write window, SSE-5 flip proof, sign-off), not code. Ledger archived to docs/ops/. The appendix programme executed (Part 15 §7's decision 6): generated reference tables — endpoint tables from the GRD-0 contract, the copy decks (EMPTY_COPY + the byte-pinned strings), the test matrix (battery → declared legs, 172 batteries), URL formats from the NAV-0 contract, plus the hand-kept DOM walk-throughs for every write surface and the rollback runbook transcript. scripts/gen-appendix.mjs regenerates; --check runs in CI (the freshness pattern) — the tables cannot drift the contract. The blog shipped (/blog: honest posts as committed data — post 1 'Confirm stays in Discord'; sitemap 9; the footer links it; battery site-blog 3 legs) and its gate catch banked the law: a slice adding a battery regenerates the appendix test matrix in the same commit. The dashboard mirror's EMBED (surface plan §11 decision 4 — the divergence the WR-3 survey noted, now settled): every successful dashboard action posts the standard log embed with the dashboard badge (title = the action · dashboard; T1 the flat case green / T2 the warn amber; fields: pressed-by + gateway kind; timestamp) — the provenance text line rides along for content-only reads; the GW-3 battery pins the embed's shape beside the exactly-one/message legs; the GRD-1 contract regen'd same commit. The audit's fix-list item 3 executed: scripts/coverage-map.mjs — one script that lists the untested files (265/307 battery-referenced, 86%; the 42 listed carry an honest why column — thin dispatchers and shell furniture annotated, real logic like security/panic + staff-form-interactions marked bare '—' = naming-true debt, no thresholds, never a gate). The map's highest-risk row closed in the same lane: test/security-panic.test.js — 3 legs (setPanic locks the server scope and audits both directions; the write-block refusal touches no channel; grantVerification's setup + bot-reach guards) — the map reads 267/307 referenced (87%). Tranche 2 of the map's real-logic rows: test/security-watch-wiring.test.js — 5 legs (screenJoin's kick + audit-reason wiring, the raid trip's staff log with its count, the write-block withholding the kick while the audit still lands; the staff-form button ladder's ephemeral-quiet refusals and the modal handler's defer-first + unknown-form path) — the map reads 269/307 referenced (88%). Tranche 3 — the dispatch table: test/tools-expanded-dispatch.test.js — 3 legs (the toggle wrapper persists the module flag and reports {applied, enabled}; the module-flag read arm mirrors the stored flag honestly per module; the hint arms count real rows, the feature groups resolve the FEATURE_TOOLS union, and an unknown view name answers a null VALUE, not an error) — the map reads 270/307 referenced (88%), its real-logic rows now empty. The GUI+sidepanel pass (Tibs' 'Improve gui and sidepanel'): the rail's quiet-default inversion — bare buttons stop painting primary moss (globals.css's bare button rule now rests transparent + hairline; the moss accent is .btn/.primary only), the nav-tree category rows read as quiet carets over their sub lists, and the server-tree channel rows replace the faint # markers with the DEP-5 kind glyphs at honest token ink (voice = the speaker glyph), the opacity stack gone; the two bare submit buttons (logs Filter, AI Ask) carry the primary accent explicitly; every destructive verb on a member card (Warn/Kick/Ban/…, the danger flag the policy table always carried) now wears the danger ink on its quiet row — destructive at a glance, still staged through the confirm card; the overview hero composes as two zones (identity + the four stat counts left, the 7-day sparkline right-anchored in its own zone, the title on one line, no stat orphan) and the servers block reads as one grouped zone; empty states gain an optional next-action link (settings → the Setup screen). Contrast re-verified (quiet-danger 4.7–5.6 across themes; tokens untouched), axe 0 × 18 scans enforced, batteries +6 (dashboard-appearance 16, the empty-shape, the appearance destructive-verb leg, the RS-2 'danger' local-constant drop) — suite 1575; probe green (LCP 804ms, CLS 0). The deferred bundle re-pin discharged in its own breath: the #298 bundle measured (js 1,205,040 / css 53,676) now conscious-typed into the MO-1 baseline (both inside the 6,144 cap; no cap change). W06 computed contrast pass — a dedicated compute of the site + dashboard token pairs found one real axe-invisible miss: dawn's button text on its moss fill measured 3.57:1 (axe's scan set only ran grove × canopy). Dawn's fill darkened to #55772b (hover #4a6626) in BOTH apps — 4.95:1, the same pin the other themes already hold, every other moss consumer only gains contrast — dawn joined the axe scan set (26 scans, 0 enforced) and a computed battery now pins every theme's on-moss@moss ≥4.5 from tokens.css.
  • GUI pass v2 (the html layer + the toolkit): the pass reached the surfaces #298 deliberately deferred — the login door (the server-rendered html OUTSIDE React) ships a ":focus-visible" ring on its CTA (the door had ZERO focus rules), a theme-color meta from the token pin, and its BROKEN mascot svg is fixed (the open tag never closed — eyes-on found the live door mascotless; output-level battery T-HTML.5 certifies well-formed tags per mood); every human timestamp in the app renders a real <time dateTime> element (the When component, 16 render sites, string-embedded sites documented); a print pass hides chrome and flattens cards (the dashboard prints like a report); and the toolkit gains check-all.mjs (all 7 gates one invocation, per-gate verdict+duration, package.json lint now calls it), a shots manifest.json (route/theme/viewport/bytes per png) and shot-diff.mjs (pure-node pixel diff over two runs, scanline ranges, no new dep). Battery dashboard-html-sweep + dashboard-toolkit (8 legs). check-html.mjs — a structural parser gate (html-validate): it renders every door state and validates WELL-FORMEDNESS — the class of bug that shipped the mascotless door (an svg open tag with no '>') is now a red build, not an eyes-only catch; wired as the 8th gate in check-all.mjs with a negative-control battery leg proving the validator rejects the broken bytes.
  • Site dep diet (motion + gsap removed): the site's two animation dependencies shipped their whole libraries on every page for three small effects — motion/react ran ONE IntersectionObserver reveal (FadeIn) and gsap a 5-element hero entrance + a 3 px scroll drift. All three are now dependency-free with the same feel: FadeIn = a 40-line IntersectionObserver + two-line CSS transition (same API, same stagger delays, RM-frozen), the hero = two register-amended CSS keyframes (hero-word-in/hero-rise-in, RM-frozen, the W08 §2 register amended in the same commit), and the canopy drift = a passive scroll listener + one rAF-throttled transform write (same magnitudes: 3 px / 1.5%, RM early-return + the CSS belt). Shipped JS: 884 → 618 KB (266 KB less), the biggest cost gone from phones; lighthouse's real-chromium site audit now reads LCP 1.2 s / TBT 0.88 s on this box. Batteries: site-dep-diet (4 legs) + the canopy battery re-pinned to the new mechanism (same laws, plus no-dep/passive/rAF legs).
  • Palette command deck (GUI-v3 G3-a) + Next security bump: the ⌘K palette learned its third result class — commands, built from the server's own ACTION_POLICY via a generated manifest (dashboard/scripts/generate-commands.mjs → palette-commands.json, 17 launch verbs routed to the screens where each flow lives; the palette never posts — the screen's ConfirmCard stays the one WR-2 gate). A drift gate (check-commands.mjs, wired as the 9th check-all gate) re-derives the manifest from the registry on every lint and goes red if the server policy changes — the same pattern as nav-aliases. Pages still outrank commands; commands outrank servers; every row keeps the listbox/arrow/Enter contract. Next.js 16.4.0 → 16.4.2 in BOTH apps (a known-vulnerability advisory halted dashboard builds until patched; builds green, in-caps). shot-diff.mjs swept of its dead PNG-writer path (oxlint 0 warnings). Batteries: T-G3A.1-.4 (manifest truth + scope law, scorer, wiring w/o write path, drift gate + tamper control).
  • Bot hardening: the link-egress bound — the digest privacy review's "no action now" note is actioned now that digests run hourly under durable scheduling: both public metadata fetches (links.js oembed + page) carry an explicit 10 s AbortSignal.timeout, and the body read races the same bound so a slow-drip server cannot hold a worker bucket open. Caps unchanged (≤3 links, ≤50k HTML, title/description only). Battery test/ai-link-egress.test.js (4).
  • Design: weather on the strip — mist — the expanded-weather park retires half: mist ships as a real span layer on the topbar (the pseudo slots are DW5's frozen fireflies + glow), canopy-dark-only, one mist-arrive 900 ms arrival and NO loop (the strip keeps its two ambient loops as the whole budget), tinted by the theme-scoped --atmo-mist, RM-frozen. Rain stays deliberately declined (budget + legibility). Battery WV-1–3; +197 B measured.
  • Design: the per-screen transition — the parked 150 ms swap is retired: one keyed wrapper replays the catalogue's own slide-down-180 on page swap (the vocabulary stays closed — no near-duplicate keyframe row), RM-frozen, zero-CLS, and guild switches deliberately stay quiet (a data swap is not a page swap). Battery PT-1–3; +66 B measured.
  • Plan hygiene: the W13 zero-orphan audit started — the parks list is dispositioned honestly: the ⌘K command palette park row retired (NAV-1 shipped long ago: components/palette.jsx + lib/palette-score.js + recents, wired in the topbar with the battery); the remaining five parks re-verified as genuine parks (per-screen transitions, expanded weather, Vitest class, site blog, visual regression).
  • Design pass: the Grove Lane — the rail is now a lantern-lit path: the active item's honey dot grows a halo (new lantern-glow settle), a moss vine line grows down its flank, glyphs rustle ≤8° on hover and dip on press, count chips pulse once on arrival, rows stagger in via arrive. Interaction-only motion (no loops; RM-frozen; tokens-only) — rail-lane.css (+2.6 KB) with the lantern-glow catalogue row and the GL-1..4 battery.
  • Dashboard server management: create + role settings — the owner's directive, built on the EXISTING registry tools (zero bot changes): the Channels tab gains a create form (text/voice/announcement/forum + category + topic + slowmode + NSFW via create_channel), the Roles tab gains a create form (name/colour/hoist/mentionable via create_role), and role rows gain a Settings editor (colour/hoist/mentionable via edit_role) reading the ?detail=1 role mirror. The facade's create_channel policy now maps the tool's slowmode/nsfw (the settings editor's own fields, one rule set for both doors). Every change stays a staged confirm (#mod-log, T2). Battery SM-1–3 + the RS-2 CONSUMED row for detail.roles; bundle re-pinned (measured +6,144 B).
  • W11 §2 (site): the prerendered-count assertion — site/scripts/check-atmosphere-counts.mjs chains into the site build: every prerendered page's atmosphere spans are counted against the Part-W08 §3 caps and logged in the build output (today's truth: the hero carries exactly the plan's record — 30 fireflies / 36 cap, 12 leaves / 14, 8 mist at its 8 cap; inner pages calm at 0); a cap breach fails the build. Battery pins the wiring + today's numbers.
  • A11y: the site's focus ring — the worksheet's queued informational finding closed in its own slice: button/a/input/select/textarea:focus-visible on the site carries a 2px --leaf outline with a moss colour-mix shadow (the dashboard's ring law, the site's palette; each theme's tokens shade it). The worksheet row flipped in the same PR.
  • W10 §4: the shared a11y worksheet — docs/A11Y-WORKSHEET.md (WP7's operational table): per-surface AA/reduced-motion/keyboard/labels state with evidence pointers, measured from the shipped tokens, batteries and greps; one informational finding queued (site :focus-visible), blocker rule written. Updates ride the same PR as any slice touching a surface row.
  • W11 §2: the login failure vocabulary — every OAuth/login failure branch logs its plan key (loginEvent: login_not_configured / oauth_denied / oauth_state_rejected / not_staff) with no tokens and no parameter values (presence booleans only) — the Part-W11 triage runbook's "login issues → server logs by vocabulary key" step is now literal.
  • W10 §3: the atmosphere lint reaches the site build — site/scripts/check-motion.mjs rides the site prebuild: any site @keyframes outside the Part-W08 §2 register fails the build by name (duplicates too), the mirror of the dashboard's MO-0 catalogue law. The lint's register derives from the plan's table and asserts in the battery; a vocabulary change is a plan amendment landing in the same slice.
  • W08: the motion & atmosphere contract battery — the plan's "whisper, never shout" law is now enforced: the closed keyframe vocabulary per surface (dashboard names live in the motion catalogue's register; the site and the login template match the plan's Part-W08 table exactly), the numeric budgets hold (brand strip 2,034 B ≤ 2 KB; the site's prerendered caps fireflies ≤36 / leaves ≤14 / mist ≤8 measured from the live pages), the timing law (ambient loops ≥3 s — wander 26 s, breathe 9 s; feedback ≤400 ms — confirm-press 240 ms), and reduced-motion coverage asserted per family (site / dashboard / strip / login door).
  • DW5: canopy brand-strip atmosphere (W07 closes) — the dashboard's topbar (the brand strip) gains a whisper-quiet canopy scene: two drifting firefly layers + a breathing moss wash along the strip's bottom edge, pure CSS (2,034 B, inside the ≤2 KB budget), pointer-transparent, reduced-motion frozen at rest opacities, and grove/dawn carry no selector (they stay calm; WP8 one-atmosphere-per-region honored — the strip never stacks with the hero or login scenes). The firefly colours are real tokens (--brand-ff-1/2 declared in the canopy block); both keyframes registered in the motion catalogue.
  • F-W30: one token source (W07) — site/scripts/check-token-parity.mjs rides the site prebuild: every var(--name) the site references must resolve to a site-defined block or the shared dashboard/src/tokens.css name set (missing names fail the build with the list). The login door's gradient is now token-only — the last two raw hexes replaced by color-mix over the glow/line/card tokens. Battery: presence scan + generator-path pin + the deliberate-overlap vocabulary check.
  • F-W31: one Sloth source — src/web-shared/sloth.svg is the single mark definition. The door template reads it at render (ink-tinted through tokens), the site's Sloth re-exports a GENERATED module (site/components/sloth-mark.gen.jsx via scripts/generate-web-shared.mjs), and the dashboard favicon is the mark at favicon weight (the legacy purple bolt retired). Battery: geometry identity across the three surfaces + generation integrity.
  • F-W32: sprite consolidation — the committed dashboard/public/icons.svg is the shared glyph set: T-W5.6 ids unique, T-W5.6b every PAGE_ICONS target resolves, T-W5.7 the <use href="/app/icons.svg#name"> pattern + the channel leaf survive, T-W5.7b every CHANNEL_ICONS glyph exists in the sprite.
  • F-W33: empty states & copy system — dashboard/src/lib/copy/empty-states.js is the ONE EN table (13 rows); all 17 Empty render sites across 10 screens consume EMPTY_COPY[key] (no inline copy left); the Sloth-mood component shape is pinned; "No lines yet. The sloth is waiting." stays as the register example.
  • F-W24: route & cookie parity (login surface; W06 closes) — the door's route table is snapshot-tested (T-W4.11): all six entryPage render sites (the 503 not-configured door, ?bye=expired, the plain door with the logged-out banner, the OAuth error page, the state-gate page, the 403 non-staff page) plus both /login 302s (logout with the cookie-clear, the staff-check bounce) and the signed-in grant are enumerated — any change to the door's routing must update the table in the same PR. The cookie parity is pinned (T-W4.12): the clear keeps HttpOnly; Secure; SameSite=Lax; Max-Age=0, the grant mirrors the attributes minus Max-Age (a session cookie). The logout surface rides the styled door (/login?bye=1 renders the F-W22 treatment with the "signed out" banner). With this, the W06 login phase is complete (F-W21–F-W24).
  • F-W23: the door's micro-moments (login surface) — the redirect morph: the continue pill announces itself on click (going class + the "Off to Discord…" label) while the navigation proceeds untouched — never delayed, and an instant redirect simply clips the morph (T-W4.7). Error pages shake once, gently (a single 400 ms shake-gentle pass; no red-flash bangs; frozen under reduced motion) (T-W4.8). Time-of-day art rides the server clock: artVariantFor() picks dawn (05:00–08:59 UTC), night (≥22:00) or canopy — rendered as an SSR body class only, zero client JS; unknown variants resolve to canopy (T-W4.9/T-W4.10); dawn warms the card, night deepens the fireflies — pure CSS keyed off the body class. Byte pins moved with the slice (the anti-drift history carries every prior measurement).
  • W06 opens: the login surface's anti-drift pins + design-system depth (dashboard+door) — the entry template (src/http/templates/entry.js) gains its output-byte snapshots per state (plain / expired / not-configured / logged-out-settle — T-W4.1: any token or styling touch must update the pins in the same PR), the XSS fixture proves every interpolated field escapes (T-W4.3), and the AA contrast pairs are computed against the real token values (T-W4.5: text 11.95, muted 5.24, on-moss 4.95 — measured, not asserted from memory). The plan's F-W22 design depth lands with it: the card rounds to 28px, and a firefly lantern hangs above the title — one 4 s lantern-sway loop, decorative (aria-hidden), frozen at rest under reduced motion (T-W4.6).
  • F-W14: Server tabs depth (dashboard) — the Members tab's list windows its rendering (the F-W11 pattern: content-visibility rows + honest scrollbar sizing, browser virtualisation, zero dependencies — role-chip filter walks stay paint-flat on cache-heavy servers), and the Roles tab now orders by member count (descending, name-ascending ties, sorted over a copy so the query cache stays pristine). The pairing rule holds as pinned: both server-page reads (/api/server, /api/members) sit in the read registry. With this, W05's page work (F-W11–F-W14) is complete.
  • F-W13: AI surface receipt chips (dashboard) — the Results panel now renders a chip per outcome kind over the engine's real status vocabulary (the nine statuses of src/services/ai/outcomes.js — succeeded lights live, warn tones fail/unverified/denied, unknown statuses fall back to neutral and never throw), and the chat's turn receipt became four count chips (not done / changed / already there / failed). Also fixed a latent runtime landmine: the screen rendered ConfirmCard without importing it — the first staged press would have thrown a ReferenceError; the import now rides this slice. The screen invents no budget field client-side (F14's only-what's-exposed rule).
  • F-W12: Tickets & Appeals master-detail (dashboard + one honest server leg) — the detail pane gains a status timeline (open → claimed → closed chips DERIVED from the detail's fields: claimed lights iff claimedById, closed iff closedAt — never guessed), the close affordance now mirrors the engine's rank leg through the byte-parity client mirror (rankAtLeast(rank, 'moderator'); an unknown rank fails closed), and claimed tickets ride the same close affordance as open ones. The master list keeps work in progress visible: the where clause widens to open + claimed (a claimed ticket vanishing from the master was a real defect; closed history stays off the master). Mobile stacks with a sticky back; the transcript stays inert text (no inline-HTML sink, bubbles render content as text nodes).
  • F-W11: Logs deepened (dashboard) — the flagship read page becomes the windowed viewer: a bounded scroll pane whose rows window their rendering (content-visibility: auto + honest scrollbar sizing — browser virtualisation, zero new dependencies), the sticky filter bar stays put while rows stream, a live arrival SLIDES from the top (180 ms) with one leaf-tinted inset pulse (600 ms) distinct from the archive's generic arrive, and every row expands to its full meta + content through a real disclosure button (aria-expanded, keyboard parity). Motion-safe by rule; both new animations joined the motion catalogue (MO-0).
  • F-W07: the cache & mount matrix (dashboard) — the serving contract becomes assertions over the booted server: index no-store, _next/ chunks immutable, SPA fallbacks no-store, /healthz /readyz always JSON, and a missing hashed chunk answers a strict text 404 (the "all black shell" class can never return). Redirects pinned exactly: /, /dashboard, /dashboard-classic → /app, and the legacy /dashboard/ deep-link map holds.
  • F-W08: the session shape pins (dashboard) — /api/me (ok,userId,clientId,csrfToken,username,avatar,liveTransport), /api/guilds (envelope + rows id,name,icon,memberCount,rank,override) and /api/status (aggregate, zero cross-guild content) have their key sets pinned; a shape change must update the pin and both clients in the same push.
  • F-W09: the live-feed behaviour battery (dashboard) — binds T-W2.10/T-W2.11 to the existing scoping and client-machine batteries and pins T-W2.12: the "n fresh" pill counts unique accepted rows (replay duplicates never inflate it) and the resync recovery coalesces through the guard.
  • F-W06: the tenancy registry battery (dashboard) — the endpoint registries are now the enforced contract: GUILD_SCOPED_ENDPOINTS + READ_ENDPOINTS export from src/http/api.js, and a CI battery pins registry parity (no orphans, counts upward), the set-driven gate (no per-route tenancy chains can grow — admitted POSTs stay exactly the three deliberate ones), and a verdict table mapping every tenancy-pressed route to the battery that presses it (docs/TENANCY-VERDICTS.md). Routes not individually pressed are covered by construction: registry membership runs the same gate before each handler.
  • F-W05: docs & pricing parity + polish (site) — /docs gains "What ships": an 11-group catalog of everything live today, each group naming the slash commands that ship it, generated against the repo and compared in the site build step (a landed command with no site mention turns the build red — prebuild runs the parity check, 54/54 modules covered). /pricing sources its offer from site/lib/pricing.js constants (no page drift) and the hosted card carries the staff-dashboard sign-in link.
  • F-W04: rain variant (site, demo flag — off by default) — ?weather=rain adds slow rain streaks behind the content on the canopy palette only (a ≥3.4 s loop, input-transparent, behind z-index:-1); grove and dawn ignore the attribute; reduced-motion freezes the streaks transparent. There is no UI toggle on purpose.
  • F-W03: dawn interlude (site) — toggling themes re-lights the hero art: canopy keeps the dark forest untouched, grove warms it slightly, dawn pours a sepia/hue sunrise cast — a 300 ms crossfade on the art image's filter, and nothing else on the page is ever filtered (text always sits on token surfaces). Reduced-motion swaps instantly.
  • F-W02: feature-card hover rustle (site) — the homepage feature cards' leaf icon tilts up to 8° (token-driven, hover AND focus-within keyboard parity) while a moss underline grows under the title (350 ms, from the left); the card's own lift is untouched. Touch devices skip the tilt; reduced-motion shows the underline instantly with no transforms.
  • PUB-1: deep links survive sign-in — a 401 while you sat on /app?page=logs&user=42 lands on the door with a signed next; after the Discord round-trip the callback 302s you straight back there. The destination rides the state blob (nonce.next.expiry.sig — tamper-proof), safeNext allowlists same-origin /app paths only (absolute URLs, protocol-relative hrefs, backslashes and foreign paths refused at signing AND at reading), and the legacy 3-segment blob still verifies.
  • DEP-5 landed complete: channel type icons (fixed: the registry lives in a LEAF module — src/icons/channel-kinds.jsx — because a screen importing icons/index.jsx closes nav-config → screens → icons → nav-config and the Docker build dies on the TDZ at [...PAGE_KEYS]; a battery pins the cycle law across every screen) — server.jsx's channel rows render the kind glyph from the committed sprite (7 new symbols: text/voice/announcement/stage/forum/category/directory + the honest compass for unknown types), via a channel-scoped CHANNEL_ICONS registry with the DS-2 throw law (a kind without a row throws at load); the forced "#" that called every channel a text channel is gone — the icon carries the type.
  • DEP-7, first depth slice: the community screens resolve names — invites (codes + top inviters), voice-hubs (room owners) and the levels ladder render the member's name through one shared useMemberName hook (one query per unique id over the existing /api/members find — zero new server surface); unresolved ids stay ids. Levels gains the share-of-leader xp meter; closed polls mark their winner. The dead pre-split community.jsx is deleted (the DEP-1 split screens were the live code all along).
  • DEP-2: Overview's live feed — the reference screen gains the last-100 activity module riding the shell's one SSE stream through the module bus (no second connection); rows arrive from now on with an honest empty state (the archive holds the past); display-scoped to the pinned guild, deleted rows keep their chip, guild-labelled on the bot-wide surface if ever mounted there.
  • WR-4: staged AI actions pressable — the Confirms list on the AI page presses yours in-browser through the same ConfirmCard every screen uses; /api/pending (GET the viewer's staged rows, POST settle) routes the press through the SAME settlePending service Discord's confirm buttons call, so ownership, expiry-claim atomicity, guild pin, rank + memberPermissions and the outcome ledger are Discord-identical by construction; foreign rows read "Confirm in Discord (not yours)"; contract surface +1 route, T-4.3 shape pin + press batteries added.
  • The log walks its whole history (DEP-3): /api/logs?before=<epoch> pages strictly
  • The OAuth handshake is CSRF-hardened (LOG-2): every sign-in renders a
  • The front door was rebuilt as designed states (LOG-0/1/4): one template
  • One icon registry with no default (DS-2): src/icons/index.jsx owns
  • The mascot has one source and a proper mood set (DS-3 + MO-2's
  • The dashboard's design system completes its scales (DS-1): the spacing
  • The member drawer completes DEP-5's Server set: a member row's name is
  • The budget screen tells the whole spend story (DEP-6): per-kind rows get
  • Rolling deploys can no longer leave the scheduler leaderless (fix):
  • Node 26 is the production runtime: the image base moved
  • Every screen's read shape is asserted (closes #187): the audit's five
  • A failed probe run now triages itself (closes #190): npm run doctor
  • Categories reached the dashboard (closes #189): the Server screen's
  • The channel editor widened (part of #189, audit Slice C): every text
  • Overview became the reference screen (audit Slice D): the concept's §4
  • States are proven per screen (closes #188): the dashboard's four states
  • Site lint is clean (CI hygiene): four unused imports removed from the
  • The probe waits on a state, not a selector (closes #186): every boot wait is now
  • The probe's profile dir broke the build: PR #185 put each run's Chrome profile at
  • The probe runs twice in a row again: each run gets its own userDataDir under
  • PR #182 broke the whole dashboard, and this fixes it: the deep-link effect referenced
  • Rail rows now actually land somewhere: the tree links every channel as
  • The probe now asserts structure, not just survival: the stub finally answers
  • Settings channel pickers were emptied by the kind fix: settings.jsx filtered
  • The rail drew categories as channels: /api/server?detail=1 sent discord.js enum
  • The two empty states that already rendered are now asserted: Tickets and Appeals receive
  • The rail designs its empty and broken states too: with no channels it says so and names
  • The tree marks drift, not volume: a channel whose permission overwrites differ from its
  • The tree remembers each server separately: collapse state persists per guild in
  • The rail leads with the server, not the menu: a collapsible tree of categories and
  • A written design concept for the dashboard: voice, type and spacing tokens, motion rules
  • No screen writes just because you opened it: the probe now counts POST /api/action
  • The probe harness visits every screen, and asks each one if it crashed: it sampled six
  • The SSE resume test stops racing its own fixture: four rows written in one
  • A comment about a bug tripped the design-token gate: the note explaining React error #310
  • The 404 map assertion caught up with the app (GRD-2 T-3.8): the harness required 18
  • The Tickets page no longer crashes on its own loading state: useState, useStaff and
  • The probe harness waits as long as its own peers: the post-swap guild check used a
  • The probe harness stands aside when there is no browser: on a hosted runner with no
  • CI's first real run found a test that assumed a build: POST /dashboard/ai was pinned to
  • The ledger states what it is (issue #158): the case drawer now says plainly that it
  • Case immutability decided (issue #152): the rule the ledger already followed is now
  • Bulk channel access (issue #150): the Channels tab selects up to twelve channels and
  • Member drawer completes (issue #151): XP adjust and nickname change join the
  • Channel access editing (MG-2b): the Server screen's channel rows gain
  • Snowflake ids are no longer coerced to Numbers (bug): the gateway turned
  • Plans reach the dashboard (P4 gap): plans were prose — the AI wrote
  • Health suggestions reach a surface (wiring): stageHealthSuggestionCard
  • Invite-spike detection has a caller (wiring): the B4 detector had tests
  • Tier-law guards (P4): test/action-tier-law.test.js iterates the policy
  • Community queue actions (MG-5, P3): Giveaways, Polls and Suggestions
  • Member actions (MG-4, P3): the Server screen's Members tab gains the
  • Case ledger (MG-3, P3): a new Cases page under Moderation, reading
  • Server manage mode (MG-2, P3): the Channels and Roles tabs are no longer
  • Settings screen (MG-1, P3): the first management screen. It renders the
  • Read surfaces (P2 of the unified surface): the dashboard can now see what
  • One write path (MG-6 + GW-5): the last two screens that still left for a
  • The action gateway (P1 of the unified surface): /api/action is now an
  • Write-path hardening (P0 of the unified surface): the dashboard's write
  • Dashboard writes, slice 1 (WR-2): closing a ticket no longer leaves the
  • Owner office, finished (issue #66): bulk edits — edit_channels_bulk,
  • Server-health reports (B3): one deterministic report engine — member
  • Wizard v2 (B3): saved plans render a multi-phase grouped preview with
  • Replica safety (B4): scheduled sweeps are fenced by a Postgres
  • Status page (B4): /status on the website reads the bot's enriched
  • Quota UX (B4): quota refusals say "resets in 2 h", and invite-spike
  • Website: FAQ, security model, and permissions matrix grounded in

0.5.0

2026-10-14
  • Knowledge store (B1): staff-curated entries ground member AI answers
  • Durable scheduling (B2): one Postgres-backed engine for delayed work —
  • Live tail (D2): per-guild SSE stream with ring buffer and Last-Event-ID
  • Dashboard prod swap (D1): the Next rebuild serves /app; the legacy SPA
  • Domain live (W1): slothlee.xyz with pricing and docs.

0.4.0

2026-10-06
  • Phase 6 community suite: giveaways, polls, suggestions, invite tracking,
  • AI memory: member profiles, conversation distillation, retention windows.

0.3.0

2026-09-23
  • Phase 5: welcome, private-thread tickets, levels with role rewards,
  • Phase 3.5/4: blacklist, mod cases, encrypted imaging backup/restore.

0.2.0

2026-09-22
  • Security spine: quarantine, lockdown, heat scores, URL checks, antinuke,

0.1.0

2026-09-22
  • Foundations: command/event routers, AI agent with tool registry and