Commit Graph

9 Commits

Author SHA1 Message Date
Maxim acac2b7f27 docs(reskinnable-demo): correct the in-code comments the beat-parity wave slots could not reach
Comment-only, no behaviour change. Each of these sat OUTSIDE the boundary of the
slot whose work falsified it, so each described the tree as it was mid-migration.
All verified against the current tree before editing.

src/shell/skin-contract.ts
- `RuntimeProviders`: "airline needs neither, so omits it". Airline omits
  `RuntimeProviders` and DOES supply `useRuntimeProperties` — the two are
  SEPARABLE, and airline is now the worked example: one account holder, no
  switcher, so its hook reads no context and returns a frozen module constant. Do
  not mount an empty provider for symmetry.
- `useData`: "Omit when a skin has no shell-managed data (banking …)". Every skin
  omits it now; documented with the grep, plus WHY the field is kept (the shape is
  legitimate, it just has no worked example left).
- `CanvasSurface`: the "omit if the skin has no report canvas" branch is currently
  unexercised — every shipped skin has one.

src/shell/agent-registry.ts
- The logistics entry said it ships "neither `intelligence/seed-memories.ts` nor
  `intelligence/forget-memories.ts`" and is "identity plumbing only — do NOT read
  it as a durable-memory demo". It ships both (`ls src/skins/*/intelligence/`), and
  its `dev/reset` sweeps and re-seeds through them. Corrected, with the same
  properties-forwarding caveat the other five entries carry, and a note that
  "expensive half built, cheap half skipped" was its state for two releases.

src/skins/keel/data/types.ts
- The `THE REST SUBSTRATE` banner said the two substrates are "deliberately not
  merged yet", that `useKeelData` "holds runs in `useState` and ticks them on a
  900 ms interval", and that "the pages still read it through `useSkinData`". All
  three are false: one substrate, one clock, `useSkinData` returns undefined.
  Rewritten to name the server-settled read (`settle-runs.ts`, called by both
  `GET /ledger` and `GET /runs/[runId]`) and to record WHY the deleted client
  ticker was a defect rather than a design choice — it was a second clock that
  painted progress the server never heard of, which the next re-read after any
  write silently rewound.
- The `KeelData` interface header claimed to be "the interface every page,
  component, and tool codes against". It is not referenced by any code at all
  (`grep -rn KeelData src` returns only comments). Marked HISTORICAL, with a
  do-not-add-a-consumer note. NOT deleted: it is a doc pass, several comments
  across the skin describe the migration in terms of this shape, and removing an
  exported type is a code change for a separate commit. Flagged as a follow-up.

src/skins/keel/data/store.ts
- "it does not advance them on a timer, because the ticker lives in `useKeelData`
  on the client. Whichever slot migrates that hook has to decide where the ticker
  ends up" — decided: the server is the only clock now.

src/skins/keel/data/beat-map.md
- Header: "Keel today is `useKeelData`, an in-memory `useState` store, and it hits
  about one beat." Marked BUILT and reframed as the design record.
- Risk #2 (the two-substrates/ticker question, correctly called "the biggest single
  risk in the migration") marked RESOLVED, with the answer (move the clock, do not
  relocate the ticker) and the generalised lesson.

src/skins/airline/data/beat-map.md
- Header: the tools/prompt/pages/pills were "later slots". All landed.
- § "What this slot did NOT build": every row of the deferral table has shipped.
  Kept as the retrofit record — which is the most useful thing about it — with a
  third column saying where each landed, and the three flagged traps marked
  resolved (including "the reset route says memoryBeats: unarmed on purpose",
  which was removed in exactly the change that added the seed module, as
  instructed).

src/skins/airline/data/fare-waiver-codes.ts
- "⚠️ THE LINT GUARD DOES NOT COVER THIS SKIN YET." It does: both
  `src/skins/airline/tools.tsx` and `agent.ts` are in `withheldGateVocabulary`'s
  `files` glob. Also dropped its "COUNT the selectors" instruction (that count has
  rotted twice) in favour of the resolved-selector table in
  `skins-config.test.ts`, and spelled out that a green lint still leaves the three
  prose channels AND `waiverGround` — which matches no `*_CODES` pattern, so the
  rule cannot see it — as hand-review items.

src/skins/airline/tools.test.ts
- Header said `statusKeyedTerminalRender` "covers logistics only; airline's glob
  entry is a later slot's, so until it lands this file is the whole guard" and that
  `withheldGateVocabulary`'s glob "does not list airline yet either". Both globs
  list airline now. Also fixed "Three defect classes" over a list of five.

src/skins/keel/skin.tsx
- "exactly as it does for the four other REST-backed skins" → every skin; nothing
  sets `useData`.

src/proxy.ts
- "matching how the other three skins behave" → numeral-free. This was one of the
  two known-stale instances named in `skin-roster-docs.test.ts`'s header; that
  header is updated in the app-docs commit, and the remaining one
  (`e2e/inset-layout.spec.ts`'s hardcoded four-skin loop) is deliberately left —
  fixing it means adding assertions against skins the spec has never visited, which
  is a coverage change rather than a prose fix.

docs/teach-mode/README.md
- It correctly refuses to write the roster into prose, but its verified-by-role
  paragraph named only banking/commerce/logistics/people and its "so copy commerce
  or logistics" line named the only two skins with pinned replay behaviour. The
  `offerWorkflowRecording` grep now returns every registered skin, and
  `ls src/skins/*/teach-mode-directives.ts` — added as the mechanical
  discriminator for role #3 — returns four. Also: "logistics and commerce both"
  skip project-scoped rows in `forget-memories.ts` is now every skin but banking,
  replaced with the grep that proves it.

Does this make anything in .claude/skills/reskin/ stale? No — the reverse. The
preceding commit updated the skill for exactly these facts, and these comments were
brought into line with it. Checked: `grep -rn "useKeelData\|use-data\|in-memory"
.claude/skills/reskin/` names no path or symbol that no longer exists.

Verified: pnpm lint, pnpm exec tsc --noEmit, pnpm test:unit (197 files / 2227
tests), pnpm build — all green.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-12 17:51:18 +02:00
Maxim b5800c35b6 docs(reskinnable-demo): fold logistics into the teach-mode roster
The standing rule at the top of CLAUDE.md, answered out loud: YES, this change
made things in `.claude/skills/reskin/` and its neighbours wrong. Logistics is
now a FOURTH teach-mode skin, and four separate places were written against
three.

The DERIVATIONS the previous commit introduced all still read correctly —
`grep -l offerWorkflowRecording src/skins/*/tools.tsx`,
`grep -rln useAgentContext src/skins/*/layout.tsx` and
`ls src/skins/*/intelligence/seed-memories.ts` each pick logistics up
automatically, which is exactly what they were converted for. What rotted was
the PROSE around them:

- `docs/teach-mode/README.md` enumerated a role-by-role verdict for every skin
  the roster grep returns. The grep now returns four and the paragraph named
  three, so a reader would have found logistics uncertified with no way to tell
  whether that was an omission or a verdict. Logistics satisfies all five roles;
  it is also a second worked example of the pinned replay directives, so "so
  copy commerce" now names both.
- That file's memory-SCOPE divergence note gave only half the argument for
  `user` over `project`. The half it was missing is the decisive one: a skin
  whose `forget-memories.ts` skips project-scoped rows — logistics and commerce
  both do — has a presenter reset that physically cannot un-teach a
  project-scoped memory, so the second run of the demo opens already taught and
  the beat proves nothing while looking perfect.
- `failure-modes.md` § 10 described withholding the vocabulary from the agent
  and never said to BUILD the surface the operator demonstrates on. Withholding
  perfectly with no filing form is an unlearnable gate — which is the state
  logistics was actually in. Points at the worked example and the two
  load-bearing properties of it.
- `demo-beats.md` said the pre-bar skins "ship four or five" pills. Logistics
  ships ten. Fixed, and the sentence now says so as the reason to run the count
  rather than trust the prose.

`CLAUDE.md`'s beat matrix gains logistics' teach-a-procedure row, and its
logistics bullet is corrected: it claimed the skin omits `chatHeaderActions`
and `onSuggestionSelect`, which beat 3d set two commits ago.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-12 04:30:31 +02:00
Maxim da69dea5c5 fix(reskinnable-demo): stop the beat-6 rule block disabling the LOCK_SKIN guards
Flat-config `rules` options are REPLACED, not merged. The beat-6 block added in
f39a6fb0b4 was the last one matching src/skins/logistics/tools.tsx and listed
only withheldGateVocabulary, so it silently switched OFF the three LOCK_SKIN
URL-contract selectors for that file — one no-runtime-symptom guard traded for
another, and nothing surfaced it: that file has no nav shape to violate today,
lint was green and all 1163 tests held. Verified with --print-config, which
reported 1 selector for logistics/tools.tsx against 3 for banking/tools.tsx.

The block now restates all three LOCK_SKIN selectors alongside the beat-6 one
(4 for the covered files, 3 elsewhere, confirmed by --print-config), and carries
a warning that every future widening must do the same and must be checked with
--print-config rather than by lint passing. Functionally re-proven: a hardcoded
"/logistics/lanes" in tools.tsx is caught again.

The glob also now covers src/skins/logistics/agent.ts — the file where the prompt
leak actually shipped, and where a server-side defineTool could carry the same
enum. Green from birth, and proven to bite by a throwaway ESCALATION_CODES import.

Accuracy fixes where a future author reads first: the rule's message no longer
claims to cover a prompt or a 422 body (it matches identifiers and cannot see
prose); its header says FOUR leak channels, not three; failure-modes.md § 10 now
states out loud that the rule covers two of five channels, that the prose ones
are a hand-review grep, and that widening the glob must restate the LOCK_SKIN
selectors; escalation-codes.ts no longer asserts a filing form that does not
exist.

verify-logistics-gate.sh: bare `mktemp` (GNU coreutils rejects the BSD `-t
<prefix>` form, so the script died on Linux/CI); step 1 now asserts the refusal
PRESENTS the symptom as well as omitting the fix (an empty message passed
before); step 4 re-reads the shipment and asserts appliedMitigation landed at the
over-authority cost, since a route answering 200 without committing passed
before. Deviation: the review asked for "the exception cleared" — mitigate does
not clear `exception` (see PATCHABLE_FIELDS in shipments/[id]/route.ts), so the
assertion is on appliedMitigation, which is the field that endpoint alone can
write.

Skill staleness: this commit is itself a skill update — the flat-config trap, the
rule's real coverage, and the two vacuous-script shapes are all recorded in
failure-modes.md § 10, with the SKILL.md and demo-beats.md pointers widened to
say "tools.tsx AND agent.ts".

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-11 22:45:30 +02:00
Maxim f39a6fb0b4 fix(reskinnable-demo): withhold logistics' escalation vocabulary from the agent
Beat 6 claims the agent learns an unknown procedure by watching once. An
agent holding the unlock vocabulary already knows it, clears the gate
unaided, and there is nothing to teach.

Logistics published the catalogue FOUR ways, not the three the plan named:
a useAgentContext readable described as "valid escalation codes", a
z.enum(ESCALATION_CODES) on fileEscalation's schema, that tool's own
description pointing at "the catalogue in your context", and an agent.ts
RULES line listing the codes among what is "provided". Closing three of
four moves the leak rather than closing it, so all four are closed: the
code parameter is now a free z.string() whose describe() states the
withholding, and the prompt names escalation codes as the one thing NOT in
context. The route already refused an uncatalogued code without
enumerating the valid set (audited, unchanged).

The justifying/decoy split and checkAuthority are unchanged, and
escalation-codes.test.ts already covered the predicates, so no test was
added. The guard is instead a new AST no-restricted-syntax rule,
withheldGateVocabulary in eslint.config.mjs, scoped to
src/skins/logistics/tools.tsx: every symptom here is invisible (the app
compiles, type-checks, lints and demos with the readable restored), and the
schema leak was line-wrapped, so a source-text guard would silently never
have matched. It was verified RED (6 errors, including the wrapped :268
enum) before the removals and green after.

docs/teach-mode/verify-logistics-gate.sh proves the gate over pure REST
with no agent: refusal names the symptom and no code, a decoy records and
approves without unlocking, an uncatalogued code is refused without
enumerating, a justifying code lifts the gate. It diverges from the plan's
draft on four audited facts — mitigate requires plannerId and answers 403
OVER_AUTHORITY (not 422 ABOVE_APPROVAL_AUTHORITY); "absorb" always costs
$0 and can never be over authority, so the script discovers the bounded
planner, the exception shipment and the over-authority KIND (expedite)
from the live API; and the refusal legitimately names the generic recovery
path ("file an escalation"), so the leak assertion is that it names no
CODE.

Skill staleness (CLAUDE.md standing rule): YES, the reskin skill was wrong
here and is updated in this commit. SKILL.md and templates.md told every
author to enumerate closed-set parameters with z.enum "so the vocabulary
reaches the model" with no carve-out — the exact advice that produced this
defect; all three sites now carry the beat-6 exception. failure-modes.md
gains § 10 (the five leak channels, why the guard is a lint rule, and the
requirement to append your skin to its files glob), renumbering the old
§ 10 to § 11. demo-beats.md's beat-6 part 3 and SKILL.md's beat-6 walk step
now name the channels and the REST proof.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-11 22:29:55 +02:00
Maxim 190be901d5 refactor(reskinnable-demo): migrate people and commerce onto the shell teach module
Both gain the visible-duration floor, the feed de-dupe and the automatic
fresh-window reset they were missing; both lose their manual reset() and
their per-skin @keyframes, which the shell's token-driven
.recording-vignette replaces.

Six copies of the ref-counted state machine are now one.

Skill impact: checked. `.claude/skills/reskin/demo-beats.md` item 4 already
points at `@/shell/teach` (Task 2), and neither SKILL.md nor templates.md
teaches a per-skin recording context or per-skin vignette keyframes, so
nothing there went stale. One live doc DID: `docs/teach-mode/README.md`'s
role table still named the banking copy Task 2 deleted — repointed at
`src/shell/teach/`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-11 20:01:32 +02:00
Maxim d9dcf69893 docs(reskinnable-demo): update the app docs for a six-skin roster
`CLAUDE.md`, `README.md`, `.env.example` and `docs/teach-mode/README.md`.

The roster went from four skins to six, and almost every count derived from it was
wrong. Corrected here:

- The demo-beat matrix, which now lists `commerce` and reports gen-UI counts that
  match the registrations actually made.
- The `useData` contract row, which names BOTH in-memory skins rather than one.
- The list of skins that identify their user, and the list that ship
  `intelligence/seed-memories.ts` -- `commerce` was missing from both.
- `.env.example`, which capped `LOCK_SKIN` at four legal values.
- The claim that `people` re-scopes memory per operator, stated more strongly in
  CLAUDE.md than the code supports.

Two decisions about HOW the docs were fixed, because they cost the most time:

- CROSS-SKIN CLAIMS ARE SCOPED TO THE SKINS ACTUALLY CHECKED. Several sentences
  asserted a property of "every skin" on the evidence of one or two. They now name
  the skins verified.
- EACH FIX WAS CHECKED AGAINST THE SENTENCE BESIDE IT. Correcting one count
  repeatedly left its neighbour false, because the counts are stated redundantly in
  adjacent prose. That pattern is what motivated the roster test in the shell.

Subsumes: 7f5813d7ea 1600591918 8071d05812 f78f6f7cd4 ca2145228d ad76437e1c
4308c02135 3fab172fb1 0ca1564406 f0afa4acce

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-11 05:42:45 +02:00
Maxim 3d4de1cedd feat(reskinnable-demo): add the people skin (Rowan), a demo-complete People Ops desk
Rowan is a People Operations command center and the second skin built against
the full nine-beat bar in `.claude/skills/reskin/demo-beats.md` (banking was the
first). Pages: Roster (index), Compensation, Requests, Onboarding.

REST-backed like banking and logistics: `/api/people/v1/*` serves one `ledger`
snapshot read plus the write paths, a generated `offer-letter` PDF, and a
presenter-gated `dev/reset`. Components read the ledger through the skin's own
`usePeopleLedger()` context, so `useData` is omitted. That context is mounted in
`RuntimeProviders` rather than `Providers`, which lets the single fetch also feed
`useRuntimeProperties`.

The signature element is the band ladder: one rail per level, each normalized to
its OWN band, so "halfway up L3" and "halfway up L7" line up at the same height
and become comparable. Anyone outside their band is drawn outside the rail, in
the negative colour, always labelled.

Beats, all walked in a browser against a live Intelligence stack:

  1  face          showCompBands renders the ladder + a two-sentence answer
  2  rich thread   gen-UI replays intact on reopen after a hard reload
  3a drive the app setBaseSalary — the figure is typed into a chat card and
                   goes straight to REST; it appears nowhere in the transcript
  3b sees screen   route readable + per-page on-screen readables on all four
                   pages; Roster and Requests give different, correct answers
  3c levers        HITL confirm naming the levers, then
                   ?status=pending&sort=aging_desc&top=10 with the Status, Sort
                   and Show controls visibly tinted, "TOP 10 OF 11"
  3d multimodal    an offer-letter PDF rides the pill, and the filed packet
                   survives deleting the thread and reloading
  4  memory        seeded preference recalled AND named in the component's
                   `note` slot
  5  stored skill  one vague sentence fires three visible writes in order, no
                   confirmations, amid four distractor tools
  6  teach a skill 422 OUT_OF_BAND (symptom only) -> decline -> record the
                   demonstration -> save -> apply unaided to a DIFFERENT person
                   in a fresh thread

Notes for reviewers:

- The beat-6 gate is deliberately discriminating. Decoy exception codes file and
  finalize successfully and still do not lift it, and an unknown code is refused
  without enumerating the catalogue — so "the agent filed an exception" is not
  the same as "the agent cleared the gate". Two out-of-band comp requests are
  seeded so the case taught on stage and the unaided replay are different people.
- Seed dates are relative offsets materialized at store init, not absolute ISO
  strings, so request aging and the generated offer letter stay coherent years
  from now and a Reset genuinely re-freshens the queue.
- Memories are seeded and saved at `user` scope, not `project`. Verified against
  the running stack: a project-scoped row is returned for EVERY user id in the
  instance, so with several skins sharing one backend it is not a per-skin
  boundary. For the same reason this skin's `forgetAllMemories` skips
  project-scoped rows rather than deleting data it does not own, and `dev/reset`
  reports the skipped count.
- `temperature` is not set. gpt-5.4 rejects it and the value is discarded, so
  carrying it alongside a comment claiming determinism would be misleading.
- Beat 2 additionally requires the thread-list identity fix sent separately; the
  skin merges and runs fine without it, it just cannot demo thread reopen.

Docs updated for the fifth skin per the app's standing skill-staleness rule:
CLAUDE.md (skin list, substrate split, beat matrix), README.md,
docs/teach-mode/README.md (teach-mode is now per-skin, not banking-only), and
`.claude/skills/reskin/{SKILL,demo-beats,templates}.md` — including six
"only banking does this" claims that are no longer true.

Verified: pnpm build, pnpm lint, pnpm test:unit (335/335) on this base.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-07 18:43:44 +02:00
Guido Vizoso 7aab1bead2 docs(reskinnable-demo): document the inset frame and retire the old chrome
The reskin skill was the urgent part: it is the sanctioned path for adding a skin,
and it still taught two patterns the frame retired. It told authors to publish
--nw-nav-inset-left/right from a useEffect so the floating selector could dodge their
nav -- both the variables and that selector are gone -- and to root the layout at
h-screen overflow-hidden, which overflows the app card by the frame's padding.
Following it produced a skin with dead code and a broken root height. The nav-inset
step is removed with a short "Retired" note so it is not reintroduced by copying an
older skin, and the root guidance now explains that the CARD is the bound.

Adds a "The inset frame" section to CLAUDE.md covering what a reader needs before
touching src/shell/layout: the two-panel model and its bounds, why the thread rail is
a fixed element rather than a nested panel, the v4 API rename and the
id-becomes-data-testid behaviour, where the shell controls live, the fixed 12px card
radius, and the h-full requirement for skin layouts.

Also corrects the composition chain, the shell's ownership list, and the
four-skins-not-two count across CLAUDE.md, README.md and DESIGN.md, plus the skill's
verification step, which told authors to look for their skin in a bottom-left
floating pill.
2026-08-05 09:29:46 -03:00
Maxim 1387f32dee docs(reskinnable-demo): document the architecture and add the reskin skill
Describes the app that exists: shell versus skins, the Skin contract field by
field, the client/server boundary that keeps a skin's agent out of the browser
bundle, per-skin server identity, and the theming split. Adds a repo-local
authoring skill that walks someone through creating a skin and registering it
in both registries.

The inherited docs described a single-skin banking demo, which was actively
misleading once a second skin shipped, and carried claims that were false here
— that OGUI can only render inline in the chat (a published-SDK limitation
that does not apply on workspace packages) and that the airline skin omits
every optional contract slot when it ships nine tool labels.
2026-07-30 21:49:02 +02:00