Repository navigation
v0.5.0
[0.5.0] - 2026-10-10
"Spend in the office": per-agent usage badges and a per-call detail panel on the canvas, fed only by host props (#76, #77); the portal reading those same figures from the real usage ledger for canvas badges, the agent inspector and the call-detail modal (#78); cost per meeting and per tool computed on the server and shown in the Meetings panel and tool tooltips (#80, #81); Model Ops's Matrix, Agents and Feed tabs reconciling call by call against the ledger instead of local counters (#79); and the library guide's four privacy rules written out in one place for integrators: the host computes, the component only displays; usage UI stays hidden until the host opts in; a missing figure is always "unknown", never 0; and the call detail panel is metadata only, never prompt, completion or tool-argument text. Full reference: the usage chapter of the library guide (español).
Added
-
Crew: its own audio engine, with music in the Lounge room (#150, epic #114): Crew gets a real audio engine (
src/crew/crewAudioEngine.ts), independent of the Cartoon mode sound effects (src/engine/soundEffects.ts); separate code, separate state, separate preference. Every sound is synthesized at runtime with Web Audio API oscillators, so there is no recorded audio file to license: origin and license (CC0, original code, no third-party IP) for each sound are documented insrc/crew/crewAudioAssets.ts(CREW_AUDIO_MANIFEST, the canonical source) and indocs/crew/AUDIO_LICENSES.md(human-readable table and rationale). Music is off by default and never starts on its own:CrewStage's accessibility-and-audio panel gets a Play/Pause button that only a click or keyboard activation can trigger; selecting the Lounge room and pressing Play starts its ambient loop, moving to any other room (Development, QA, the CEO office, etc.) fades it out and requires a fresh Play press to resume, so Lounge music can never play by accident elsewhere.CREW_ROOM_MUSICmaps every room explicitly (null= silence; onlyloungehas a track today), so adding a sound to another room is a one-line, reviewable change. The engine fades in/out on room changes and on manual pause, pauses on window/tab blur andvisibilitychangeand resumes only if it was already authorized to play (never a new autoplay), anddispose()s fully on unmount (every oscillator stopped, every node disconnected, theAudioContextclosed, no listener left behind): switching to Cartoon mode unmountsCrewStageand so cleans up Crew's audio without touching Cartoon's own sound preference.useCrewAudio(src/crew/useCrewAudio.ts) wires the engine to React's lifecycle and already-existing mute/volume preferences (crewPreferences.ts, wired but inert since #157); a browser blocking autoplay (AudioContext.resume()rejecting) is caught and surfaced as its ownblockedstate with a caption, never thrown. New subtitles for every audio state change (playing/paused/blocked) reuse the existing room/camera caption mechanism (subtitlespreference, #157), satisfying the same textual-equivalent requirement for this sound. Newtests/lib/crewAudioEngine.test.ts,tests/lib/useCrewAudio.test.tsx(mocked Web Audio API, autoplay-blocked handling, focus/visibility pause-resume, StrictMode-safe cleanup) andtests/lib/crewAudioAssets.test.ts(license manifest completeness, room isolation). All new English/Spanish UI text followsCrewStage's existing inline-ternary pattern. Deliberately out of this change's scope, per the issue's own split into sub-topics: the phone ring (#145) and the coffee sound (#146) are separate issues and are not wired to any room; the engine already exposes the primitive (playCue) and the per-room mapping those issues need, with no further change expected here. A per-room volume slider was not added:CrewPreferences.volume/muted(#157) stay global, and room isolation is achieved architecturally (each room's track is independent and fades out when left), matching the issue's "no sala de Lounge por accidente" acceptance criterion without inventing a second, undocumented preference axis. -
Example: library-host, a worked host app reading the rollup/calls API (#260, v0.5.0 milestone): new
examples/library-host/, the runnable example#82's guide points to for "getting figures from the server".usageFromRollup.tsdeclares the server's rollup/calls response shapes and two pure mappers,rollupToUsageandcallsToDetail, that only copy or null a figure, never compute one (locked by a token-scanner AST-style check intests/lib/libraryHost.test.tsx, since the whole point of this example is showing the one right way to feed the library real spend).HostOffice.tsxfetches both endpoints on mount and every 15 seconds (abortable, never overlapping) through a smallnode:httpproxy (proxy.ts, about 70 lines) that injectsAuthorization: Bearer $AGENT_VIEWER_API_TOKENserver side; the token variable is referenced only there and in the example's ownREADME.md(tests/library-host-proxy.test.mjschecks this statically, plus that the proxy forwards only allowlisted GET paths, strips a client-sentAuthorization, never echoes the token, and refuses to start empty).main.tsx/index.html/vite.config.tsgive it its own dev server, unrelated to the root build.seed.jsonl/seed.shseed five agents covering a known cost, mixed currencies, an explicitly unknown cost and a failed call (agent-viewer sendcannot sendllm.usage, soseed.shposts the batch endpoint directly, taking the server token as an argument rather than by the same environment variable name, so it stays outside that "only two files" rule too).
Reconciled against the real shipped#66, not the issue's illustrative sketch: the shipped rollup response has no singletotalTokensfield, only a per-kind breakdown already summed server-side (tokens.input.sum, ...); computing one frominput.sum + output.sumhere would be exactly the client-side arithmetic this mapper must not do, sototalTokensreads "unknown" on the compact badge in this example until the rollup API grows a combined field, while the per-kind breakdown, cost and the call detail panel all show real figures. Likewise, an agent absent from the rollup gets nobyAgententry, which the already-shipped#76badge and accessible list render as no usage text at all (not a literal "unknown" badge); this example's tests check that real, shipped behavior rather than the issue's pre-#76wording. Seeexamples/library-host/README.mdand the module doc comment inusageFromRollup.tsfor the full reasoning on both. -
Docs: reproducible, script-verified role palette measurement for the Crew visual style contract (#116 partial, epic #114): re-audited #116 and its own blocking foundations (CREW-001..008: #172/#173/#166/#168/#171 closed, #167/#170 still open) and confirmed the issue's core remains genuinely blocked, as it requires final art and real walk animation for all six roles that only exists, as a prototype, for the CEO; that is art direction, not something this change can decide. Found one real, bounded gap inside the already-merged contract itself: section 6 of
docs/crew/visual-style-contract.md/.es.mddescribed the five non-CEO roles' outerwear swatches as "measured automatically... 16-level color quantization, alpha >= 200..." but no script in the repository ever implemented that measurement; the documented values (~#005060and similar) were hand-eyeballed approximations, not reproducible ones. Newscripts/crew-palette-measure.mjs(a from-scratch PNG decoder built only onnode:zlib, no new dependency, since the project has no PNG-decoding library) measures each role'sidle-frontprototype PNG with that exact method, explicitly excluding hair/outline and light-skin pixels by hue and lightness so they cannot be mistaken for the outerwear color. The five swatches are now exact and script-verified (#085868,#1848C8,#283838,#482888,#C85838), replacing the old~-prefixed guesses in both documents, and locked by newtests/crew-palette-measure.test.mjs(npm run crew:palette:measurereproduces them against the real bank PNGs) so the table cannot silently drift from the art it describes. This does not ratify any palette: every swatch still readsunratified, and section 13's open follow-up ("art-director ratification... ") is explicitly left standing, since that is a human art-direction call this change does not make. No PNG/SVG/manifest asset was touched. See the#116row indocs/crew/IMPLEMENTATION-STATUS.mdfor the full accounting. -
Crew: real desk and plant artwork from the asset bank in the CEO office (#115 partial, epic #114): closes the gap the issue itself flagged as pending after #172/#194 ("this rescue does not yet integrate art into the rooms") for a small, honest slice. The executive desk and the floor plant (
furniture.desk-executive,furniture.plant-floor, both stillstatus: prototype, neverapproved) now render as the real original SVG in the independent Crew renderer, not the usual flat-shaded 2.5D box, only for theceo-desk/ceo-plantfurniture of the CEO office; the other 51 bank assets and the other ten rooms are untouched. Newsrc/crew/crewPropImages.ts(load/dispose, mirroring the existing CEO sprite loader) andsrc/crew/useCrewPropImages.ts(per-room load/cleanup, same lifecycle asuseCrewSprite), withcrewPropsLayer.ts/renderCrewRoom.tsdrawing the resolved image as a flat billboard instead of the polygon box when one is available, falling back to the box (with its own English/Spanish status text,data-testid="crew-prop-art-status") if the image fails to load. Two known, documented limits: the bank has no per-angle verified views for any furniture piece yet, so the same single image is reused across all four cameras rather than four distinct views; and because both SVGs are under Vite's 4 KB inline threshold, they ship as adata:image/svg+xmlURI inside their own lazy chunk instead of a hashed file (unlike the CEO PNG pilot), with no change to their original bytes. Newtests/lib/crewPropImages.test.tsplus cases added tocrewPropsLayer.test.ts/renderCrewRoom.test.ts, and a newtests/e2e/crew-prop-images.spec.tsproving the real image renders from all four cameras in the CEO office, is absent in every other room, falls back correctly, and that Cartoon mode has no regression. Not closing the issue: 9 of 11 rooms and 51 of 53 bank assets still render with no image, and per-angle art for these two pieces is still unverified; see the#115row indocs/crew/IMPLEMENTATION-STATUS.mdfor the full accounting. -
Docs: audit and close #173, Crew camera ADR (v0.3.0 milestone, epic #114): re-read the issue and the real code in
src/crew/on main against its own five literal acceptance criteria, instead of trusting this file's stale historical note or the ADR's self-imposed "requires G1" status (that G1 bar belongs to the full epic #114, not to this child issue). All five already hold today: the ADR (merged by #174), the per-room camera contract with explicitfront/right/back/leftpresets and continuous zoom (src/crew/crewCamera.ts), the dedicatedtests/e2e/crew-facing.spec.tsproving that rotating the camera never leaves characters facing forward by mistake, documentation that never conflates 2D scene rotation with real 3D perspective, and isolated single-office selection with a fluid desktop/mobile experience, all re-verified today (npm run lint, 15 Vitest, 5 Chromium E2E, all green). No product code changed; seedocs/crew/IMPLEMENTATION-STATUS.md(#173 row) anddocs/adr/ADR-001-crew-camera.md. The epic's own G1 certification (final illustrated art, physical-device QA) remains open and is owned by other epic issues, not #173. -
Portal: meeting and tool spend in the office (#81, v0.5.0 milestone): the Meetings panel and the tool chip tooltips show real cost and token figures from the usage ledger instead of the fake
0/$0.000a live meeting always showed before. Both are thin, read-only views overGET /api/v1/usage/rollup?groupBy=meeting|tool(#80), through asrc/integrations/ledgerClient.tsextension (meeting/tooldimensions,attribution,sessionIds/sessionCount) and new portal-only modules undersrc/components/usage/(useMeetingUsage,useToolUsage,ToolSpendTooltip,spendFormat), neither reachable fromsrc/lib(tests/lib/libraryIsolation.test.ts). Reconciled against the real shipped server, not the issue's pre-implementation sketch: there is no dedicated/usage/meetings//usage/toolsendpoint, tokens come per kind (input/output/cacheRead/cacheWrite/reasoning, each already summed server-side) rather than onetotal, and "unattributed" is the rollup's ownmeeting-dimension group withkey.meetingId: null(no per-meeting time-overlap heuristic exists on the server, by design). The Meetings panel never reads the deprecatedMeeting.tokensAccumulated/costAccumulatedin live mode, shows "unknown" for a meeting with no ledger rows yet, and adds a "Spend per meeting" table listing every portal meeting plus any server-only row plus the unattributed bucket, with no totals row. Demo mode (no server connected) keeps the old simulated figures, now tagged "Simulated", and makes no request. Tool chips inAgentInspectorandAgentDetailModal(current tool and per-task tools) get an accessible tooltip (hover and keyboard focus to open, blur andEscapeto close, hoverable per WCAG 1.4.13,aria-describedby) showing that tool's attributed calls, tokens and cost, or "no figures from the server" for a chip with no matching row. Library:AgentOfficegains the optionalmeetingUsageprop (MeetingUsage/MeetingUsageFigures, extendingUsageFigureswithcalls,callsWithoutCostandunattributedCalls, newformatMeetingUsage), shown next to the active meeting (or the latest concluded one with an entry) only whileshowUsageis also on, in a newdl.av-meeting-usagepanel and a second accessible "Meetings" list; nothing is summed across meetings, and an unmatched meeting id renders nothing.Meeting.tokensAccumulated/costAccumulatedare now@deprecated(removal deferred to 1.0, a breaking change). Five new portalmeetings.spend.*andtool.spend.*message groups and five new libraryusage.*keys, in English and Spanish. -
Portal: ledger-backed spend badges, per-call detail and display preferences (#78, v0.5.0 milestone): in live mode with a ledger-capable server, each agent on the office canvas shows a compact spend badge (
agentBadges, fed by thesrc/integrations/ledgerToUsage.tsmapper overGET /api/v1/usage/rollup?groupBy=agent, #66), and the top-bar chip readsrollup.totalsinstead of summing the SSE stream it happened to receive. A Last hour/Today/7 days/All selector next to the chip (src/integrations/usageWindow.ts, persisted asagent-viewer:usage-window) controls the window for both; figures survive a reload and never contradict a badge: once the ledger has answered, the canvas's own locally-summed telemetry (usageTelemetry) turns off. The mapper reads only, never invents: a token or cost figure the server did not fully report for every call stays "unknown", never0; an agent with no row in the rollup gets no badge at all.useLedgerAgentUsagerefetches on mount, on a window change, on a debounced newllm.usage/llm.failedevent and on a 60 s clock tick for a rolling window; demo mode and a server without the ledger endpoints make no request.
This item completes the rest of the issue on top of that foundation.AgentInspector,LiveTimelineSidebarandAgentDetailModal's metrics tab now read the agent's own rollup row (usageFigures/ledgerReadyprops,formatTokens/formatCostfromsrc/lib/usage.ts, never a hardcoded$) instead of the agent's local accumulators whenever the ledger has answered, falling back to the local counters exactly as before otherwise; the metrics tab also gains cache-read and cache-write token cards. The inspector's new "View calls" button opensAgentDetailModaldirectly on its metrics tab (onOpenDetailModalnow takes an optional tab,App.tsx'sdetailModalInitialTab), which lists that agent's calls newest first through a new cursor-paginatedsrc/components/usage/useAgentCalls.tshook (GET /api/v1/usage/calls, mirroring Model Ops' #79useModelOpsLedgerfeed pattern: 50 rows per page, dedup byeventId, a debounced refetch on a new usage/failed event for that agent, invalid-cursor recovery, a hard 500-row display cap with a cap hint) and a new presentation-onlysrc/components/usage/AgentCallsTable.tsx(time withreceivedAtin its tooltip, model, status witherrorCode, input/output/cache-read/cache-write/reasoning tokens, latency, cost with an "estimated"/"source unknown" tag, andrequestId).CallsQueryParams/fetchCalls(src/integrations/ledgerClient.ts) gainedfrom/to, the same pass-through conventionRollupQueryParamsalready had. Two new persisted display preferences (src/integrations/displayPreferences.ts,agent-viewer:mask-secretsandagent-viewer:show-usage-badges, both default on, lifted out ofSettingsModal's own state into controlled props): masking shortens a call'srequestIdwith a copy-to-clipboard button (full value still copyable); hiding usage badges clears the canvas badge map while the top-bar total keeps showing. ArequestIdmatching the server's[REDACTED:<rule>]marker (new exportedREDACTION_MARKER_PATTERNinsrc/integrations/redaction.ts, purely additive) always renders as a distinct "redacted" chip regardless of the mask preference. A failed refetch that keeps the last good rollup now shows as a distinct dimmed "stale" state, with a clock marker and an accessible note, in the top-bar chip, the inspector and the detail modal, instead of looking identical to a confirmed-fresh reading. NewagentDetail.calls.*,inspector.viewCalls/noCallsInWindow/stale,settings.usageDisplayTitle/showUsageBadges*andusage.stalemessage keys, in English and Spanish.
Deferred, out of this issue's scope on purpose: the warning for "server reports redaction is disabled" is not implemented. No endpoint in this codebase (GET /api/v1/usage/calls, the rollup, or any other) reports whether redaction is active; wiring that signal would mean adding a new field to the server's usage contract, which issue #78 itself lists as out of scope ("redaction rules ... #68"). The[REDACTED:...]chip above still renders correctly whenever the server does apply its marker; only the "redaction is off" notice itself needs that future contract change. -
Library: per-call detail panel from host props (#77, v0.5.0 milestone): an agent's usage total answers "who spent what"; clicking that agent now answers "which calls made up that number".
showCallDetails(AgentOfficeProps, off by default, independent ofshowUsage/showUsageBadges) opens a read-only<section class="av-call-details">panel next to the selected agent, fed only by the newagentCallDetailsprop (AgentCallDetails = Record<string, readonly AgentCallDetail[]>, newsrc/lib/callDetails.ts, also exportingAgentCallTokens,AgentCallStatusandAgentCallCostSource). The component never fetches, sums, prices, sorts or deduplicates: every call renders in the host's order with 12 fixed rows (provider, model, status, latency, request id, input/output tokens and cost source/cost always shown; cache read, cache write and reasoning tokens only when that key is present intokens, reusing theusage.cacheReadTokens/usage.cacheWriteTokens/usage.reasoningTokens/usage.costSourcecatalog keys andformatCostSourcefrom #76).AgentCallDetailis closed on purpose: no field can carry a prompt, a completion or any other free text,provider/model/requestIdare capped at 128 Unicode code points (cut with an ellipsis, in the text and intitle), unrecognizedstatus/costSourcevalues and non-string/empty/whitespace-only text read as "unknown" rather than the raw value, and the renderer (src/lib/CallDetailsPanel.tsx) picks each field explicitly instead of spreading the row, so an extra host key is never rendered. The panel opens for a click on the canvas, a choice in the keyboard agent list, or a controlledselectedAgentId, reusing the existingonSelectAgentcallback with no new prop added for it; it renders nothing for a selected id outside the current snapshot. Close it with its close button orEscape(focus inside the panel), both routed throughonSelectAgent(null). An agent with no entry, or an empty array, shows "No call details provided", never "0 calls". New:where-scoped CSS classes with no transition or animation (av-call-detailsand its-header/-close/-empty/-list/-item/-row/-request-idchildren), positioned bottom-right of the stage so it does not collide with the usage panel (top-right) or the toolbar (left). Not drawn intorecordReplay's video export, which has no option for it. Thirteen newcalls.*message keys in English and Spanish. Out of scope here, left for later issues: real ledger data from the calls API (#67) and the portal wiring that feeds it from the server (#78). -
Library: per-agent usage badges on the office canvas, fed only by host props (#76, v0.5.0 milestone):
showUsageBadges(AgentOfficeProps, off by default, independent ofshowUsage) draws a compact spend badge on every agent card fromusage.byAgent, display only: the embedded component still never invents, sums or prices usage. An agent with nousage.byAgententry gets no badge; an entry with missing ornullfields shows "unknown" for those.UsageFigures(src/lib/usage.ts) gainscacheReadTokens,cacheWriteTokens,reasoningTokens,costSource('provider-reported' | 'estimated' | 'unknown') andfailedCalls, all optional and backward compatible;formatUsageadds the matching rows only when the host sends those keys. NewformatUsageBadge(figures, locale, translate)(exported along withUsageBadgeandUsageCostSource) formats the compact card text: tokens under 10,000 exact and compact from there (12.3K), a reported zero cost as$0.00, a positive cost under half a cent as<$0.01(never$0.00), anest.mark for an estimated cost source, and aN failedchip.summarizeUsagefills the new fields for hosts with no usage service of their own: the three breakdown token fields are omitted when no counted event reports them, summed when every one does,nullwhen only some do (and read the deprecatedcachedTokensascacheReadTokens);costSourceis omitted until some event sends it, then the common value ornullon disagreement;failedCallscountsllm.failedevents independently ofllm.usage, giving an agent with only failed calls its own entry. The canvas (drawAgentOverlaysinsrc/engine/canvasRenderer.ts) receives only pre-formatted strings through the newagentBadgesprop (OfficeCanvas,RenderContext), never a number, and the badge path imports nothing frommodelOps: the card grows to fit the badge on one row up to the existing 190px cap, then wraps (52/66/80px for 1/2/3 rows), staying clear of heads, plaques and other cards via the existingplaceOverlaylayout. The accessible agent list is now fed byshowUsage || showUsageBadges, so the screen-reader text always carries the same exact figures as a visible badge. Ten newusage.*message keys in English and Spanish. Out of scope here, left for later issues: the per-call detail panel (#77), server/portal wiring of real ledger figures (#78) and budget/limit comparison (#87). -
Cost per meeting and per tool on the server (#80, v0.5.0 milestone):
GET /api/v1/usage/rollupgains two newgroupBydimensions,meetingandtool, so an auditor can answer "how much did this meeting cost" and "which tool is the expensive one" from honestly attributed figures, never from time overlap. Attribution is explicit only: a usage row isattributedto a meeting only through its ownmeetingId, and to a tool only through its owntoolCallIdmatched against atool.startedevent in the exact same(sessionId, agentId, toolCallId)scope (attributed/unresolved/ambiguous/unattributed, seeserver/usage/attribution.ts). New row-level filtersmeetingId,toolCallId,tool,meetingAttribution,toolAttribution;meetinggroups also carrysessionIds/sessionCount. Non-attributed groups are never dropped bylimit, and groups sort by attribution rank (attributed first) before anything else. New SQLite migration0011-rollup-attributionadds the derivedtool_calls/meeting_labelstables and thetool_call_resolutionview (backfilled fromevents.payload, rebuilt automatically if either table is ever dropped); memory mode keeps the equivalent index inServerState(server/serverState.ts). The Python and TypeScript SDKs already supportedtoolCallIdonusage()/tool*()calls (issue #64); this item only adds the server-side aggregation. Library and portal are unchanged (#81 builds the panels on top of this). Seetests/usage-rollup-attribution.test.mjsfor the full hand-computed fixture. -
Crew accessibility preferences (#157):
CrewPreferences(src/crew/crewPreferences.ts, re-exported from@warlockcode/agent-viewer) gainsmuted,volume,highContrast,subtitles,hudSizeandaccessibilityPreset, additive and backward compatible with the#171shape (onlyreducedMotion). NewapplyCrewAccessibilityPreset()sets several fields at once for a "screen reader" or "low vision" preset.CrewStagewires checkboxes/select/range controls for these preferences, a collapsed-by-default<details>disclosure to keep the toolbar usable on narrow viewports, visible camera/room-change captions gated bysubtitles(screen-reader-only otherwise), and a high-contrast canvas palette (src/crew/crewContrast.ts, with a WCAG contrast-ratio helper and unit tests). Mute/volume are wiring only: Crew has no audio engine yet (#150 depends on this issue), so they are a documented no-op until real audio exists. -
Model Ops on real ledger data (#79): the demo app's Model Ops console gets a
ledgermode, used whenever the portal resolves an API base. The Matrix, Agents and Feed tabs become a thin, read-only view over the server's usage-ledger rollup and calls read APIs (#66, #67): per-model and per-agent figures now reconcile call by call instead of being rebuilt from agent-level counters (aggregateModelUsage, now@deprecated demo-only, stays forsimulatedmode and the office plaque only), a metric the ledger never reported renders "n/a" (never0), a partially reported one carries a "partial" marker, cost is shown per currency and cost source as separate chips (never summed across either), cache read and cache write are separate columns, and failed calls are counted and filterable. The Feed tab lists real calls, paginated by cursor, with no fabricated entry, no prompt text and no invented "200 OK"/"CACHED" status. The Simulator tab keeps its permanent "Simulation" banner, offers only the demo price catalog's own models (never a model only observed on the ledger, whose price it does not know), and never makes a network request. Seven empty/error states (loading, server too old, unauthorized, network error, ledger empty with copy-ready snippets, filters match nothing, rollup truncated) cover every gap, in English and Spanish. New, portal-only modules:src/integrations/ledgerClient.tsandsrc/components/modelOps/*; neither is reachable fromsrc/lib(enforced bytests/lib/libraryIsolation.test.ts).simulatedmode (no API base, for example a plainnpm run dev) keeps the pre-ledger behavior behind a permanent "Simulated demo data" banner. No server, contract or SDK change. -
Docs: library guide rewrite for usage badges, call detail and the four privacy rules (#259, v0.5.0 milestone):
docs/library.md/docs/library.es.mdnow cover version 0.5.0 (header and install URL). A new "The four rules" box states, together and in both languages: the host computes and the component only displays; usage UI stays hidden until the host opts in (showUsage/showUsageBadges/showCallDetails); a missing figure is always "unknown", never0; and the call detail panel is metadata only, with no field for prompt, completion or tool-argument text. Added the previously undocumentedcalls.*(12 keys, from #77) andcrew.*(3 keys, demo/Crew-renderer-only) message-key tables, matchingOFFICE_MESSAGESexactly, and corrected the stale "116 keys" count to the real 156. New accessibility bullets describe the call details panel's actual focus andaria-livebehavior (no focus trap on open, no announcement on open or update); two new isolation-guarantees bullets state that no usage figure is ever persisted and that the usage UI renders nothing while its opt-in prop is off. Newtests/docs-parity.test.mjs(heading sequence, line count, code-block count and em-dash guard between the two guides) andtests/docs-usage-keys.test.mjs(theusage.*/calls.*tables cross-checked againstOFFICE_MESSAGES, in both languages). Split out of #82 as its own sub-issue, per.claude/agent-policy.md; the example host (#260) and the version bump/CHANGELOG/release prep (#261) are separate, still-open sub-issues.