1902 Commits

Author SHA1 Message Date
Ran Shemtov 03ea9c86d2 Merge branch 'main' into claude/xenodochial-khayyam-873a16 2026-09-14 09:40:13 +02:00
Ran Shem Tov 115f0c1029 Merge remote-tracking branch 'origin/main' into claude/xenodochial-khayyam-873a16
# Conflicts:
#	showcase/shell-docs/src/content/docs/integrations/mastra/doctest.json
2026-09-12 13:23:28 +02:00
Swapnoneel Saha 23a4cc28c1 fix(examples): keep the Pages Router demo working on Next 15 2026-09-11 22:41:06 +05:30
Swapnoneel Saha 2ac751ad03 fix(examples): scope Next.js overrides to supported majors 2026-09-11 21:55:24 +05:30
David McKay a3a5258e7d fix(reskinnable-demo): regenerate the lockfile for 1.71.0
CI installs with --frozen-lockfile and refused the previous commit: the
lockfile still carried the `overrides` block that package.json no longer
declares (ERR_PNPM_LOCKFILE_CONFIG_MISMATCH).

Regenerated against the committed manifest, so the lockfile now declares
neither `overrides` nor `patchedDependencies`. The patch entry is the reason
the lockfile was held back in the first place — it points at an untracked
patch file — and it is absent here because the committed package.json does not
declare it. Local checkouts that want the patch keep it the way they always
have: their own package.json and lockfile, both on skip-worktree.

Verified by reproducing the CI step: `pnpm install --frozen-lockfile` succeeds,
and lint, typecheck, 2494 unit tests and build all pass on the resulting tree.
2026-09-10 10:20:38 -07:00
David McKay 8a328e34c4 feat(reskinnable-demo): accept spreadsheet attachments, and move to CopilotKit 1.71.0
Dropping an .xlsx into the composer did nothing: the accept filter silently
discarded it, and widening that filter alone would have killed the run, because
the pinned @ai-sdk/openai@3 converter accepts no document media type but
application/pdf and throws before a request is made.

Spreadsheets now work end to end:

- A dependency-free .xlsx / .csv reader (ZIP central directory + OOXML through
  DecompressionStream and DOMParser). It decodes date serials via styles.xml so
  a date does not render as 45231, and places cells by column reference so a
  gap does not shift every later value under the wrong header.
- The sheet is rendered to a column-aligned table document. Comparisons of
  table encodings put a markdown-style aligned table well ahead of CSV for
  model comprehension, and the same document is what a human sees.
- buildPdf paginates. It emitted a single page and drew overflow past the
  bottom margin, so anything after ~48 rows was silently absent from a
  perfectly valid PDF. Single-page output is byte-identical, so the existing
  golden assertions stand.
- A per-run bridge around the agent swaps the media type to application/pdf on
  the model leg only, and restores the original type and metadata on the way
  back. Both the composer chip and the transcript therefore read XLS with the
  real filename. The bridge keys on the exact payload, so a genuine PDF
  attachment is never mislabelled.
- onUploadFailed is wired. A rejected or unreadable file now says so instead of
  vanishing with no trace.

The demo workbook is committed as a generator rather than a binary blob, so the
fixture exercises the real reader: shared strings, date-styled serials, a
second sheet, deflate.

Also moves @copilotkit/* to 1.71.0 and @ag-ui/* to 0.0.59, and deletes the
@ag-ui canary overrides from both pnpm-workspace.yaml and package.json. Those
existed only because a released @ag-ui/core rejected SUBAGENT_* events and
killed banking's harness console; stable 0.0.59 ships SUBAGENT_STARTED,
SUBAGENT_FINISHED and SUBAGENT_ERROR, and 1.71.0 depends on exactly 0.0.59, so
the overrides would now hold the tree behind its own dependency.

Verified on the running demo, not just in tests: gen UI renders, the subagent
harness completes the offsite run (14 rows, 6 merchants, $2,377.15, 1m 4s),
memory recall returns seeded memories, and a spreadsheet answers with a table
built from the real file. Lint, typecheck, 2494 unit tests and build all pass.

Checked the reskin skill for staleness: buildPdf was documented as single-page
and two passages said onUploadFailed is wired by nobody. Both corrected here.
2026-09-10 10:20:37 -07:00
Tyler Slaton bba4113b8e fix(react-core): improve Inspector message shortcuts 2026-09-09 11:49:42 -07:00
copilotkit-qa-bot[bot] 737c2f82d8 Merge remote-tracking branch 'origin/main' into codex/fac-196-a2ui-contract 2026-09-09 10:41:05 -07:00
copilotkit-qa-bot[bot] 9d70137b04 fix: pin starter React smoke dependencies 2026-09-09 10:22:54 -07:00
copilotkit-qa-bot[bot] c058c0d31a fix: pin starter React smoke dependencies 2026-09-09 10:22:54 -07:00
copilotkit-qa-bot[bot] 5426185ded Merge remote-tracking branch 'origin/main' into codex/fac-156-crewai-flows 2026-09-09 10:01:16 -07:00
copilotkit-qa-bot[bot] 28989e4452 Merge remote-tracking branch 'origin/main' into codex/fac-196-a2ui-contract 2026-09-09 10:01:15 -07:00
copilotkit-qa-bot[bot] e20e1c7922 fix: stabilize CrewAI flows smoke coverage 2026-09-09 08:38:50 -07:00
Benjamin Taylor 2c05ed6885 chore(release): keep release notes in one CHANGELOG.md per release lane
The notes now land in a source-controlled changelog instead of a scratch file
that rides the release branch. One file per lane, because the lanes version
independently: a shared file would interleave `1.70.0`, `angular/0.5.0` and
`channels/0.9.0` into one unreadable sequence.

  monorepo  ->  CHANGELOG.md
  angular   ->  packages/angular/CHANGELOG.md
  channels  ->  packages/channels/CHANGELOG.md

`write-changelog.ts` prepends this release's section on the release branch,
create-pull-request commits it (a tracked file, always staged), and
`extract-release-notes.ts` reads the section back in the publish job as the
GitHub Release body. The changelog is therefore both the durable record and the
review surface: editing a section on the release PR changes what ships.
release-notes.md goes back to being ignored, so the same notes never exist as
two editable copies.

Also deletes 29 changesets-era changelogs that no tooling had written since
April. They stopped at 1.55.2 while the lane shipped 1.69.3, and
packages/angular/CHANGELOG.md still claimed 1.54.3 from before that lane split
onto its own 0.x line. Their content stays recoverable from git history. A test
pins the tracked changelog set to the lanes so they cannot creep back and
contradict the real versions.

Extraction never fails the publish job: it runs after npm publish, so a miss
annotates loudly and falls through to the existing bodyless-release fallback
rather than stranding the tag.

Committed with --no-verify: the pre-commit nx lane cannot run in this worktree
(packages/core and packages/channels-ui have no node_modules, and
`nx run @copilotkit/core:build` fails identically with the tree clean). The only
change under packages/** is deleting orphan markdown that no build or test
reads.
2026-09-09 08:31:01 -05:00
copilotkit-qa-bot[bot] 88f14db913 fix: complete CrewAI flows starter prompts 2026-09-08 17:27:45 -07:00
Mike Ryan 9f57bced70 docs(examples): align Angular starter Node prerequisites 2026-09-08 13:49:08 -07:00
github-actions[bot] c2dd6cf3b8 style: auto-fix formatting 2026-09-08 20:47:09 +00:00
Mike Ryan d6ad0f8d31 fix(examples): align LangGraph starter AG-UI dependency trees 2026-09-08 13:44:34 -07:00
Mike Ryan e3b249f0f7 chore(examples): update CLI starters to current SDK releases 2026-09-08 13:43:10 -07:00
Guido Vizoso 7f6d6008b4 fix(showcase): say "Company-wide", not "all", in the publish refusal
The demo's climactic refusal listed its breaches with the raw department
id, two inches below an exception block spelling the same scope out:

    Publish refused: …
    Burn Rate · all · 2026-08          <- the receipt
    Burn Rate · Company-wide           <- the block above it

`publishRefusalPayload` resolved `metricId` through `metricLabel` and
forwarded `department` untouched, and every breach the seeded demo
refuses on is company-wide — so the one id that reached the room was the
bare word "all", which reads like a bug rather than a scope.

The label map already existed in THREE identical copies (the catalog
renderers, the CEO dashboard, the Metrics Explorer) and the receipt had
none, so a fourth copy would have fixed this line and left the next
surface to make the same mistake. All four now share one module, which
also gives the two pages that indexed the map directly the `Object.hasOwn`
guard only the renderers had.

Nothing round-trips the field back into a query — file_variance_narrative
takes a metric and a period, no department — so the label is safe as the
only form that leaves the payload.

The new test pins "all" specifically, plus an unmapped key still falling
through as itself, since that path exists to report a query the ledger
could not answer and the raw key is what names it. Verified live: the
receipt now reads "Burn Rate · Company-wide · 2026-08".
2026-09-08 15:39:41 -03:00
Guido Vizoso 6d24c89ee7 fix(showcase): drop the note from the exec initiative card, one line per row
The note was the last thing making this block taller than its
neighbours. Printed on every row it was 757px of content beside a 112px
metric tile; restricted to off-track rows it was still 311px against
243px for the two blocks below it. A pinned dashboard card answers "what
is the state of things" — five names, five owners, five statuses — and
the prose behind any one of them is a follow-up question, not something
to read five of at a glance.

Every row is now a single 27px line and the card is 269px, level with
the rest of the grid.

THE NOTE IS UNPRINTED, NOT DELETED. It stays on the cell's `title`, and
the page readable still publishes all five verbatim off the ledger, so
"what am I looking at?" reads out every explanation exactly as before.
The test asserts the `title` survives precisely so a later cleanup
cannot drop the data path while looking like a no-op.

Verified against three mutants: reprinting the note, stripping the owner
along with it, and dropping the `title` each fail.
2026-09-08 15:39:41 -03:00
Guido Vizoso 1ea6cc58a4 fix(showcase): put the initiative owner on the name line so green rows cost one line
The previous pass moved Owner out of its own COLUMN, which fixed the
name wrapping, but it landed on a sub-line of its own — so all five
initiatives still cost two lines each, including the three green ones
whose entire entry was then two lines to say a name and "on track".

Owner now rides the name line. The sub-line is the note alone, which
only a non-green row has, so a green row is a single 27px line and the
block stops being the outlier on the dashboard.

Measured on the CEO dashboard at a 533px card: 371 → 311 tall, against
243 for the two blocks below it. It was 757 before this sequence began.
2026-09-08 15:39:40 -03:00
Guido Vizoso 6e27380718 fix(showcase): stop the exec initiative block dwarfing every card beside it
Measured on the CEO dashboard: the initiative table was 667px of content
next to a 112px metric tile, and since the grid stretches a row to its
tallest card it padded Revenue vs Plan out to 757px — ~600px of dead
space beside a six-line block.

Two causes, both width rather than data. The table split three ways by
content, so with the assistant open — the demo's normal state, leaving
the card 286px — the Initiative column got 120px while Owner and Status
took the other half for a person's name and a three-letter code, and
every initiative name wrapped. Owner now sits on the sub-line where it
costs no width, and `table-fixed` at 78% stops the widest owner name
renegotiating the split.

The note then earns its lines only when the status is not green: a green
row's prose only repeats what its pill already says, and the rows that
keep an explanation are the ones a reader has to act on. Nothing is lost
— the full note stays on the cell's `title`, and the page readable
publishes all five verbatim off the ledger. It is also clamped to two
lines, since note length is ledger data and nothing upstream bounds it.

667 → 347 of content; the row goes 757 → 436.

The renderer test that asserted every note rendered is replaced by two
that pin the rule in both directions — printed for off-track, withheld
for green, and reachable on `title` either way. Verified against three
mutants: printing every note, printing none, and dropping the `title`
each fail exactly one of them.
2026-09-08 15:39:40 -03:00
Guido Vizoso 1f6973c423 feat(showcase): leave the CEO dashboard with nothing on it but composed blocks
Two fixed strips sat above the pinned-block grid: an exception feed and
an initiative RYG strip, both read straight off the ledger and neither
agent-configurable. The seeded dashboard ALSO pins an `exceptionList`
and an `initiativeTable` block, so the page opened showing each set of
rows twice — once in chrome the assistant cannot touch, once in a block
it can. For a demo whose whole claim is "the assistant composes this
page", static duplicates of the composable blocks were the strongest
argument against it.

Both strips come off. A page header replaces them, matching the CFO
dashboard's, so the two screens still read as the same screen with a
different set of blocks on it.

The page readable is now derived per-kind from what is actually pinned:
unpin the exception block and the agent stops claiming exception rows
are on screen, rather than reading out a feed the CEO is not looking at.
`visibleExceptions` and `ceoReadableExceptions` are unchanged and keep
their tests — they were always the rows the block shows.

Two consequences worth naming. The strip's click-through into the
Metrics Explorer goes with it: the block's rows are not links, so an
exception can now only be drilled into by asking Vantage to navigate.
And three contrast pairs cited `pages/ceo-dashboard.tsx` for the
initiative pills; the pills now render only in the catalog's
InitiativeTable, so the dead sites are dropped rather than the pairs
weakened.
2026-09-08 15:39:40 -03:00
Guido Vizoso bdec0dc513 fix(showcase): line up the bottoms of two exec blocks sharing a grid row
The grid was told `items-start`, which stopped a short card stretching
but left its neighbour's extra height as a ragged gap. Dropping it
returns the row to `items-stretch`; `h-full` on the card and `flex-1` on
its body make the card actually use that height instead of leaving the
slack outside its own border. Two cards side by side now line up top and
bottom.
2026-09-08 15:39:40 -03:00
Guido Vizoso 6609d460c3 fix(showcase): let an exec initiative card carry status once, not three times
The RYG strip stacked a 40%-opacity status wash, a 4px coloured left
edge and the status pill — three signals saying one word. On the dark
theme the washes muddied the surface enough that the tinted cards read
as a different material from every other card on the page.

The pill stays as the carrier and the card keeps a hairline tinted to
match, which is still enough to scan a row by colour. Cards also widen
to 15rem and let the initiative name wrap to two lines instead of
truncating, since several names lost their last word at the old width.

theme.test.ts's contrast guard caught this immediately: three composited
pairs it checks describe text over those washes, and the washes no
longer render anywhere in exec. They are retired with a note rather than
weakened, so the guard keeps failing if a wash comes back untested.
2026-09-08 15:39:39 -03:00
Guido Vizoso fa92193883 fix(showcase): draw one frame and one title around a pinned exec block
Every block renders inside a host that already draws card chrome — the
chat bubble in inline-block-surface.tsx and the dashboard card in
dashboard-grid.tsx — and both the shared Tile and buildBlockOps were
drawing their own on top of it. A pinned block came out with two rings,
two backgrounds and two shadows, and with its title printed twice: once
in the card header beside the move/remove controls, once as the Heading
component the op builder emitted.

Tile drops the chrome and keeps only its label; buildBlockOps emits the
Heading only when the block is NOT pinned, so a block in the chat still
names itself. The initiative table's label was a bare "Initiatives",
which under a card titled "Key Initiatives" was the double title in a
second form — it now carries the count and how much of it is off track,
the way its sibling labels carry metric, department and period.

The grid gains items-start so a short card no longer stretches to the
height of the tallest one in its row.
2026-09-08 15:39:39 -03:00
Guido Vizoso 15eb3f95a1 fix(showcase): render the block and disclose the setting a kind ignores 2026-09-08 15:39:39 -03:00
Guido Vizoso 201cfcacb3 fix(showcase): send the model somewhere real when a block prop is refused 2026-09-08 15:39:39 -03:00
Guido Vizoso f5d515c98c fix(showcase): give the department label lookup own-key semantics 2026-09-08 15:39:39 -03:00
Guido Vizoso 69ebf39723 chore(showcase): lint plain-object registry lookups guarded only by truthiness
The shape behind three 500-class defects: an object literal inherits
Object.prototype, so REGISTRY["constructor"] returns a truthy inherited member
and the `?? null` guard never fires. A new no-restricted-syntax selector flags a
computed lookup on a Caps-named object whose result is guarded by ??, ||, !, an
if test or a ternary test — the guard is the discriminator, so the couple of
dozen type-closed lookups in the exec skin stay quiet.

Scoped to the exec skin and the shell only; every sibling skin still carries the
shape and a wider glob would turn the tree red. src/shell/documents is exempt
(pdf.ts indexes by a character its own regex class closed) and that exemption is
itself asserted. skins-config.test.ts gains the resolved-selector rows and
red/green fixtures for the selector.
2026-09-08 15:39:38 -03:00
Guido Vizoso 5361041117 test(showcase): bind the exec withheld-code sweep to its union and to schema branches
Both sweeps hand-copied the four VAR-* codes, so a fifth code added to
NarrativeCode would simply never be looked for. Both now derive the list through
`as const satisfies Record<NarrativeCode, true>`, which fails the typecheck
instead.

The schema walker also read only enum values and describe() text: a
`z.literal("VAR-TIMING")` (member on _def.value) or a union of literals (branches
on _def.options) published the whole catalogue into a tool's JSON schema and the
sweep stayed green — proven by mutation. It now walks literals, union and tuple
branches, record key/value types and intersection halves, with a synthetic
fixture pinning that coverage so the arms cannot regress silently.
2026-09-08 15:39:38 -03:00
Guido Vizoso 4fc1c00372 test(showcase): pin the exec metric vocabulary across all four of its restatements
MetricId and Department are restated by hand in four files. Only two carried
`as const satisfies Record<MetricId, true>`; catalog/definitions.ts and agent.ts
kept plain z.enum literal lists, so a metric added to the union stayed
unbindable and unaskable with every typecheck green. Both now derive their enum
from a compiler-checked record, and a new parity suite reads the four SHIPPED
schemas (plus the narratives route, through its handler) and the seed, asserting
they are set-equal — which satisfies alone cannot do, since it checks each copy
against the union and never against the ledger that has to hold rows for it.
2026-09-08 15:39:38 -03:00
Guido Vizoso 641128921d fix(showcase): stop the exec block surface and the budget memo dying on reachable inputs
Three crash paths in the exec skin, each reachable from an ordinary value:

- `Heading`/`Text` read `props.text.path` for a non-string label, but the
  A2UI binder resolves dynamic props before render and passes `undefined`
  for an unresolved binding — it never leaves a standing `{ path }` object.
  Reading into it threw a TypeError out of the renderer, taking the whole
  surface down instead of the one label that failed to bind. The guard now
  reports whatever shape arrives, and the comment that described the old
  standing-ref theory is corrected.

- `DEPARTMENT_LABEL[dept].toLowerCase()` threw for a department key outside
  the four seeded ones — a key the agent can send, and one that lands on the
  failure path by construction. The label now falls back to the raw key, so
  the path that exists to REPORT a bad query no longer throws on one.

- The budget memo's fixed 0.62 driver split does not survive whole dollars:
  overruns of $2 and $4 round to a 1/1 and a 2/2 TIE, which the builder's
  `DriverSplitError` refuses and the route turned into a bare HTTP 500.
  The timing share is now ceiled off the rounded overrun, which keeps it
  strictly the larger driver for every overrun, and the route maps the
  builder's own refusals to their codes (NOT_AN_OVERRUN 404, DRIVER_SPLIT
  409) rather than reporting a ledger fact as a server fault.
2026-09-08 15:39:38 -03:00
Guido Vizoso 639b171a90 fix(showcase): stop one unrenderable block from 500ing the whole ledger GET
Ops are rebuilt from the stored spec on every read, and buildBlockOps throws for a spec it cannot render (seeds construct blocks directly, bypassing assertValidBlockSpec). That throw escaped the handler and 500'd the entire snapshot, which the provider's first-load gate turns into an error panel above the chat and the grid - taking out the very grid needed to unpin the bad block. The failure is now scoped to its own block: empty ops plus an opsError, block kept so its card chrome still offers unpin, logged loudly.
2026-09-08 15:39:37 -03:00
Guido Vizoso 69d63765a9 fix(showcase): treat an unreadable 2xx filing receipt as a filed narrative
The narratives route appends the narrative before composing its response, so a 201 whose body will not parse is a lost receipt, not a failed filing. A bare res.json() rejected with a SyntaxError, skipping refresh() and printing a parser error on beat 6's filing form over a narrative that IS in the ledger. Mirrors publishPack's existing lost-receipt arm: reconstruct the row from what was sent (the fields the form and the teach chain read), refresh either way.
2026-09-08 15:39:37 -03:00
Guido Vizoso 9989d2388c fix(showcase): resolve untrusted dashboard and agent ids by own key only
`state.dashboards` and `agentRegistry` are plain objects indexed with
caller-supplied ids — a URL path segment for the exec block routes and for
`publishPack`, and a URL-derived `agentId` for the shared CopilotKit route.
A plain-object index walks the prototype chain, so `constructor`,
`toString`, `valueOf`, `hasOwnProperty`, `__proto__` … each resolve to a
truthy INHERITED member.

In the exec store that sailed past the `!dashboard` guards and threw the
raw `undefined.blocks` TypeError those guards exist to prevent: a 500 with
no body where `store-errors.ts`' mapped 404 belongs. Worse in
`publishPack`, which THREW instead of returning, breaking the returned-not-
thrown refusal contract the packs route (no try/catch) relies on. Both now
go through one `lookupDashboard` helper using `Object.hasOwn`.

`agentRegistry` is latent today — the route's `?.` swallows the inherited
member — but it is the same class, one registry over from the `getSkin`
fix. It is now built on a null-prototype object rather than behind an
accessor, so the guarantee covers the index sites that already exist
(including the agent-map builder) instead of only callers that remember a
helper. Enumeration is unaffected, so `agentIds` is unchanged.

Both suites pin the behaviour per inherited key rather than pinning which
own-key primitive implements it.
2026-09-08 15:39:37 -03:00
Guido Vizoso 308c7a25fb fix(showcase): settle the exec skin's chips and keep its receipts operator-facing
Four defects in Vantage's tool surface, all of them things the room reads or
waits on:

- navigateTo's `period` carried a schema-level `.regex(...)` and `top` an
  `.int().min(0)`. The AI SDK rejects a bad VALUE at the parameter boundary and
  emits a `tool-error`, which @copilotkit/runtime has no arm for (verified
  against ai@6.0.259) — no TOOL_CALL_RESULT is emitted, so an off-format period
  left the chip spinning "Navigating…" forever. Same defect class agent.ts
  already documents for `months`. Both are relaxed to shape-only and checked in
  `navLeverRefusal` inside the handler, answered as a correctable result;
  `period` is validated through the Metrics Explorer's own
  `normalizePeriodLever`, so the tool cannot refuse a lever that page would
  honour. No other frontend schema carries a value constraint.
- get_metrics and list_exceptions had no exact renderer, so the shell's
  wildcard chip — which keys "done" off status alone — ticked "Reading metrics
  ✓" over a read that returned an error. Both now render through `ReadSettle`:
  a refusal reads as a refusal, a success keeps the settled label and never
  prints the series payload.
- A failed pin printed "Could not pin that block: ALREADY_PINNED: …". The strip
  removed only the ledger's prefix and leaked the store enum into the
  transcript; `pinFailedLine` strips the coded prefix too and keeps the store's
  remedy.
- `refusalLine` preferred the producer's `message`, and agent.ts writes those
  FOR THE MODEL — the room saw "Call render_metric_block again…" and "do not
  retry the same call". The message is now used only when it is already
  operator-safe, with a per-code operator phrase table behind it. The model's
  copy of the message is unchanged.
2026-09-08 15:39:37 -03:00
Guido Vizoso 65dd8e4f92 chore(showcase): reconcile cross-agent seams from the round-3 fix batch 2026-09-08 15:39:36 -03:00
Guido Vizoso 3f6a532c90 docs(showcase): correct the reskinnable-demo counts, greps and cross-references CR round 3 flagged 2026-09-08 15:39:36 -03:00
Guido Vizoso 830f9f49f7 fix(showcase): make the exec skin's readables say what its screens say
The Metrics Explorer's readable published every row: the unlevered page is
528 seeded points, so ~100KB of JSON went to the agent on every turn. It now
publishes a bounded prefix and says so (showingCount, rowsTruncated), so a
cap can never be mistaken for the whole view.

Alongside it, the readables and screens that disagreed:

- ceo-dashboard publishes varianceDisplay beside the raw fraction, and nulls
  a non-finite variancePct deliberately rather than letting JSON do it while
  the card reads "n/a"; visibleExceptions is memoized.
- The exception cards drill in with the metric they name, via a new metric
  lever on filterMetricRows and its own control on the explorer.
- board-packs emits each timestamp's display string beside the ISO instant,
  unifies the two divergent dashboard-title fallbacks, makes its newest-first
  comparators total, logs the submit it is actually performing, and hands
  FiledNarrativesList the list and label map the page already derived.
- cfo-dashboard's readable page id is "finance", the nav segment navigateTo
  takes; board-packs' is "packs" for the same reason.
- normalizeDepartmentLever trims, and a def-less metric is now reported once
  by id instead of dropped in silence.
- Explorer smalls: the unknown-unit diagnostic fires once per unit rather
  than twice per row per render, the breaches-only toggle carries
  aria-pressed, lever writes replace instead of flooding history, and the
  top select no longer claims an off-vocabulary value came "from chat".
2026-09-08 15:39:36 -03:00
Guido Vizoso 6002737bf5 fix(showcase): disable the exec reset button while a reset is in flight 2026-09-08 15:39:36 -03:00
Guido Vizoso e5cf59ce16 fix(showcase): resolve only known exec roles so every memory bucket is swept 2026-09-08 15:39:36 -03:00
Guido Vizoso 3daf0316c5 fix(showcase): log the backend's rejection body when an exec seed POST fails 2026-09-08 15:39:35 -03:00
Guido Vizoso 4ec50e7a6a fix(showcase): record failed exec memory deletes instead of abandoning the bucket 2026-09-08 15:39:35 -03:00
Guido Vizoso 9182627674 docs(reskinnable-demo): correct two false claims in exec's skin comments
`execToolLabels` was described as covering the "report" beats; exec
ships no a2ui report tool at all, which is the very reason the skin
supplies no `CanvasSurface`. And the `CanvasSurface` note cited the
shared canvas's "no renderer for this kind" branch as how exec is
handled, while report-canvas.tsx and skin-contract.ts both say that
branch is defensive, never runs for exec (its surfaces are all
`block:`-prefixed and skipped before the region is claimed) and must
not be cited as covered.

In the test, the comment calling catalog/designSkill OPTIONAL on the
`Skin` contract was wrong — both are required — so it misnamed what
these cases guard. They guard the EMPTY value the type cannot see, which
the catalog case now actually asserts rather than only checking the
field is defined.
2026-09-08 15:39:35 -03:00
Guido Vizoso f918d5b9d3 fix(reskinnable-demo): resync exec's OGUI design brief with the shipped theme.css
The brief inlines every colour as a literal because the sandbox iframe
ships a bare CSS reset and `var(--brand)` resolves to nothing in there —
but the copy was never resynced after the contrast fix. It shipped the
PRE-fix brand (43 55% 45%, the 2.71:1 pair), positive (152 50% 38%),
dark negative (0 65% 60%, 4.31:1) and muted ink (220 9% 44%), so
generated UI rendered the unreadable palette the app itself had already
replaced. Three of the four are the exact ratios theme.test.ts records
as the failures it exists to catch.

Every literal is now updated to the current token value and PINNED:
theme.test.ts parses the `hsl()` values out of the brief and asserts
each equals the theme.css token it names, failing on any literal it has
no mapping for, so the two files cannot drift apart again.

Also records why the dark guidance keys on `prefers-color-scheme` while
the app toggles a `.dark` class: the class cannot reach the iframe (the
renderer builds its document from the model's HTML plus a reset), so the
media query is the only dark signal the generated markup can observe.

Two ride-alongs in the same file: the per-pair site scan is now
per-SITE, because asserting the flattened match list was non-empty let a
pair citing five files pass on one survivor (gutting ceo-dashboard.tsx
kept all 46 green; it now fails 6). And the note claiming tools.tsx
"is being given" a `dark:text-brand-violet` counterpart is replaced by
listing tools.tsx among that pair's sites, so the claim is grepped.
2026-09-08 15:39:35 -03:00
Guido Vizoso f37edd6c4c fix(reskinnable-demo): window exec's sandbox metric series only on a positive months
The OGUI sandbox getter still used a plain `if (months)`, the shape
`store.ts`'s `periodWindow` was written to replace: 0 is falsy and
silently returned the full 24-month history, and a negative reached
`slice(-(-3))` === `slice(3)` — the OLDEST periods, an inverted window
answering a request for the newest three. `months` arrives from
LLM-authored JS inside the iframe, which is the least constrained caller
in the app, so both are reachable rather than hypothetical.

Reimplements the same normalization locally (the store's helper is
private) and names the contract in the comment. Also fails loud on an
unknown metricId/department, which previously resolved to an empty
series indistinguishable from a metric with no rows yet, and derives the
zod enums from `satisfies Record<MetricId, true>` records so the schema
cannot drift from the unions in `data/types.ts` — the pattern
`api/exec/v1/narratives/route.ts` already uses.

The fixture is unsorted so the getter's own sort is actually exercised;
pre-sorted, deleting the sort left every assertion green.
2026-09-08 15:39:34 -03:00
Guido Vizoso c4a76082fb test(reskinnable-demo): stop the layout suite timing out under a full run
Mounting the layout waits on vite transforming the whole shell tree behind
`layout.tsx` — about a second when the file runs alone, but over vitest's 5s
default whenever it competes for the transform pool with the other 60-odd
suites. `pnpm vitest run src/shell src/app` failed here on most runs while the
same file passed in isolation, and the failure named a timeout rather than
anything the tests assert. Pre-existing: the suite fails the same way at the
previous revision.

Load the module once at file scope instead of per mount, and give the describe
block a 30s budget.
2026-09-08 15:39:34 -03:00
Guido Vizoso b51c961da7 test(reskinnable-demo): require an identifyUser resolver for every skin agent
`identifyUser` is optional by type but mandatory in practice — drop one and the
skin still renders, still chats, and silently loses its durable-memory scope,
with nothing in tsc, lint or the existing suite to catch it (verified by
mutation: removing exec's resolver kept all four tests green).

Also replaces the `agentIds` assertion, which compared the export against
`Object.keys(agentRegistry)` — its own definition, so it held for every possible
implementation. It now checks `agentIds` against the client SkinRegistry keys,
which stays meaningful if the export is ever rewritten as a hand-kept literal.
2026-09-08 15:39:34 -03:00