feat(web): reintroduce Playground Ideas tab as curated scenarios (#987)#1010
Conversation
Each pack (core, azure, aks, github) now ships 2-3 curated scenario
compositions via a new `scenarios` export. Each scenario is a full A2UI
v0.9 adjacency list (2-4 components mixing core primitives with
pack-contributed components) that renders deterministically from static
fixture input — no runtime LLM synthesis, same trust boundary as previews.
- packages/pack-{azure,aks-automatic,github}/src/client.ts gain a
`PackScenario` type and a frozen `scenarios` array.
- packages/web/src/catalog/core-scenarios.ts hosts the core-primitive
scenarios co-located with the web catalog (matching core-previews).
- packages/web/src/catalog/component-scenarios.ts aggregates all four
pack contributions into a flat, pack-grouped `SCENARIOS` list.
- packages/web/src/pages/Playground.tsx restores the Ideas tab between
Create and Components with a ScenarioCard gallery + Preview/JSON
detail dialog (same pattern as the Components tab).
- component-scenarios.test.ts guards:
* adjacency-list well-formedness (root, unique ids, child refs),
* Zod schema parse per pack component (Zapp PR-gate),
* registry resolution + zero _ErrorComponent after sanitize (Nibbler),
* ≥2 scenarios per pack and a pinned key snapshot.
Closes #987
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
👀 Squad review trailCurrent head:
|
Docs & changeset gate
Changeset present. Good. Hard gate for user-facing package changes without docs or changeset. ✅ = done, |
There was a problem hiding this comment.
✅ Lead approval: #1010 (Ideas tab — curated scenarios)
Verdict
Scope is well-scoped. Solves the original failure modes (permanent Loading, sparse coverage) with deterministic, build-time-trusted scenario fixtures. No LLM synthesis, no user input risk. Tests cover well-formedness, schema parse, sanitizer guard.
Checklist
- ✅ Closes #987 (Ideas tab reintroduced as static scenarios)
- ✅ Design follows previews pattern (build-time trusted)
- ✅ Zapp PR-gate: every component props pass schema validation
- ✅ Nibbler PR-gate: sanitizer + registry resolve, zero _ErrorComponent
- ✅ 999 tests pass (new scenarios.test.ts validates all 4 packs' contributions)
- ✅ Docs updated: packs-and-skills.md describes scenario authoring contract
- ✅ Rollback is single-revert (new files + additive exports)
Ready for Zapp / Nibbler / Fry gate.
There was a problem hiding this comment.
✅ Leela recorded a architecture approved via leela:approved on head 4c748bb.
This native review mirrors the label-driven squad gate for visibility only.
Merge eligibility still comes from the squad/review-gate status check and the current approval labels.
There was a problem hiding this comment.
✅ Docs recorded a documentation approved via docs:approved on head 4c748bb.
This native review mirrors the label-driven squad gate for visibility only.
Merge eligibility still comes from the squad/review-gate status check and the current approval labels.
There was a problem hiding this comment.
Working as Zapp · Security Architect · see .squad/agents/zapp/charter.md
Security review — #1010 (Ideas tab curated scenarios)
Scope
Static scenario fixtures in packages/web/src/catalog/core-scenarios.ts + pack client.ts exports, aggregator, one new tab + dialog in Playground.tsx, test file enforcing Zod + sanitizer guards.
Checks
| Check | Result |
|---|---|
dangerouslySetInnerHTML |
✅ None. Scenarios render through A2UIEnvelopePreview → sealed ClientComponentRegistry. JSON view uses <pre>{scenarioJson}</pre> (text node). |
eval / new Function |
✅ None. |
External URL fetches (fetch, axios, http.get, …) |
✅ None added. apiFetch import is pre-existing. |
| External URL strings in fixtures | 🟢 Only https://github.com/octocat/* and https://github.com/github/* in pack display fixtures (avatars, htmlUrl). Static, trusted-origin, no runtime fetch — rendered as image src / link href only. Dev-fixture data. |
| New workflow permissions / secrets | ✅ None. No workflow files touched. |
| Zod schema bypass | ✅ No bypass. Test 'each pack-component descriptor's props parse cleanly against the component's Zod schema' runs schema.safeParse(props) for every descriptor against ComponentContribution.propertySchema and asserts zero failures. Additionally, validateAndSanitizeComponents is invoked over every scenario and asserted to produce zero _ErrorComponent entries. Both guards live in __tests__/component-scenarios.test.ts. |
| LLM chain / network-side generation | ✅ None — fixtures are Object.freezed static arrays; no server call, no model in the loop. Fixtures render through the same render engine as the Components tab. |
| Dev-server only | ✅ Scenarios are shipped as bundled fixtures used by the Playground (a dev surface). No production telemetry / external posting. |
| Clipboard usage | ✅ navigator.clipboard.writeText(scenarioJson) writes curated static JSON only — no user input, no secrets. |
Verdict
APPROVED (Security) ✅ — static fixtures, strict Zod + sanitizer enforcement in tests, no new network or eval surface.
There was a problem hiding this comment.
✅ Zapp recorded a security approved via zapp:approved on head 4c748bb.
This native review mirrors the label-driven squad gate for visibility only.
Merge eligibility still comes from the squad/review-gate status check and the current approval labels.
There was a problem hiding this comment.
Working as Nibbler · see .squad/agents/nibbler/charter.md
Verdict: APPROVED ✅
Reintroduces the Playground Ideas tab as deterministic, build-time-trusted scenario fixtures — exactly the trust posture #988 required before the tab could return. The adjacency-list fixtures inherit the same rails as previews (same render engine, same validateAndSanitizeComponents guard, same Zod schemas per pack), and the test file pins the whole thing against regression. No runtime LLM synthesis, no user-supplied envelopes. Good.
CI green (Lint/Build/Unit, Playwright, CI Gate, Docs gate, Squad CI all SUCCESS).
✅ What I liked
- Trust boundary is the same one we already trust:
A2UIEnvelopePreviewwithvalidateAndSanitizeComponentsin the test harness. This is the #988 ask, and it's met. - Pattern mirrors
component-previews.ts(pack./clientsubpath exports aggregated inpackages/web/src/catalog/). Consistent with existing convention — no new façade violation, no deep-pack-internal imports. - Curated-scenarios parser tests are thorough and match the DP claim:
- Adjacency well-formedness (root invariant, unique ids, every
child/childrenid resolves). - Registry resolution (every
componentname resolves through sealed registry). validateAndSanitizeComponentsproduces zero_ErrorComponent— this is the Nibbler PR-gate and it correctly operationalises #988's failure mode.- Per-pack schema validation via
contribution.propertySchema.safeParse(props)— the Zapp PR-gate. - Inline snapshot pins scenario keys across packs (
aks/…,azure/…,github/…,core/…) — any accidental addition or removal fails loudly.
- Adjacency well-formedness (root invariant, unique ids, every
ComponentCardErrorBoundarywraps eachScenarioCard— one malformed scenario can't crash the tab.- Accessibility:
role="button",tabIndex={0},aria-label,onKeyDownfor Enter/Space on ScenarioCard. Matches ComponentCard and #988's a11y bar. - Copy-to-clipboard path uses the same defensive
.catch(() => { /* clipboard unavailable */ })pattern as the Components tab — not a silent error-swallow, a documented unavailability fallback. Consistent.
🟡 Concerns
RICH_COMPONENT_NAMESin the test file duplicatesmain.tsx's registration list. If a future PR adds a rich component tomain.tsxbut not to this test, a scenario referencing it will resolve underregistry.getImpl(...)only if the pack registers it. The_ErrorComponentgate catches most drift, but a core-only component added tomain.tsxand referenced by a newcore/*scenario could regress without this list being updated. Suggested follow-up: export the canonical list from a single source (e.g. aRICH_COMPONENT_NAMESconst exported bymain.tsxor a newpackages/web/src/catalog/rich-components.ts) and import it here. Not blocking this PR — the comment on the const acknowledges the duplication.- Test registry uses stub
z.object({})schemas, so the "NO _ErrorComponent" test only exercises name resolution, not prop validation for core components. Pack-contributed components are schema-checked (viaschemaByNamefromcontributions), which is the important half. A core scenario with bad props would pass the test but fail at real render. Acceptable because core-scenarios are small, hand-authored, and visible to reviewers — but please file a follow-up to seed the test registry with the real core schemas so this gap closes. PackScenariointerface is duplicated across three packclient.tsfiles (identical definition inpack-azure,pack-aks-automatic,pack-github). This mirrors the existingPackPreviewpattern so it's consistent — but three copies that must stay in lockstep is a drift vector. Consider hoistingPackScenariointo@aks-kickstart/harnessalongsidePackClientRegisterTargetin a follow-up. Not blocking.
🟢 Nits
component-scenarios.test.tssays "2–4 non-Text components" in the comment but asserts>=2and<=8. Tighten the assertion to match the comment (or relax the comment) — minor doc/code drift.as anyat two places (components: scenario.components as Array<Record<string, any>>) inPlayground.tsx—Record<string, unknown>would be safer and matches the test file's type. Not worth a round-trip.- The registry stub in the test uses
as anywith// eslint-disable-next-line @typescript-eslint/no-explicit-any. Acceptable for a test stub, but a properComponentImpl<z.ZodObject<{}>>cast would avoid the disable. COMPONENT_PREVIEWSimport inPlayground.tsxis unchanged but the file now also importsSCENARIOSfrom a sibling aggregator — fine, just worth double-checking tree-shaking in the prod bundle.
No dead code, no silent catches (clipboard fallback is documented), no broad git/glob operations, no secrets, no permission escalations, no new deep-pack-internal imports. Approving.
There was a problem hiding this comment.
✅ Nibbler recorded a code quality approved via nibbler:approved on head 4c748bb.
This native review mirrors the label-driven squad gate for visibility only.
Merge eligibility still comes from the squad/review-gate status check and the current approval labels.
Closes #987
Working as Fry (Frontend Dev).
Simplification chosen
Reintroduces the Playground Ideas tab with static, schema-guarded scenario compositions — no runtime LLM synthesis, no prompt→A2UI conversion chain. Each pack ships 2–3 curated scenarios via a new
scenariosexport; each scenario is a full A2UI v0.9 adjacency list (2–4 components mixingcore/*primitives with pack-contributed components likeazure/*,aks/*,github/*).This directly addresses the original failure modes that got the tab removed in #988 (permanent
Loading…placeholders, sparse pack coverage, inconsistent visuals) and the underlying complaint on #987 about unreliable inspiration→A2UI pipelines: scenarios are now deterministic templates rendered through the same engine path PR #1000 unified for pack components, with zero LLM intermediate steps.What changed
packages/pack-{azure,aks-automatic,github}/src/client.tsgain aPackScenariotype and a frozenscenariosarray (3 scenarios each). Core-primitive scenarios live in the newpackages/web/src/catalog/core-scenarios.ts, co-located withcore-previews.ts.packages/web/src/catalog/component-scenarios.tscomposes all four pack contributions into a pack-groupedSCENARIOSlist (same pattern ascomponent-previews.ts).packages/web/src/pages/Playground.tsxrestores the Ideas tab between Create and Components, with aScenarioCardgallery (grouped by pack) and a Preview / JSON detail dialog matching the Components-tab pattern.docs-site/docs/guides/packs-and-skills.mdgains a "Scenarios (Ideas tab)" section describing how pack authors contribute..changeset/987-playground-ideas-tab.md— minor bump on@aks-kickstart/web+ the three contributing packs.Tests (Nibbler + Zapp PR-gate)
New
packages/web/src/__tests__/component-scenarios.test.ts:root, unique ids, everychild/childrenresolves).ClientComponentRegistryand produces zero_ErrorComponents aftervalidateAndSanitizeComponents(Nibbler PR-gate — symmetric with the render-time guard added in Render pack components (azure/aks/github) via the engine — not hardcoded previews #991/feat: render pack components via the engine #1000).rootdescriptor (the original "permanent Loading…" failure mode).Validation
npm run lint— ✅ 0 errors (warnings are pre-existing).CI=1 npm test -- --reporter=dot— ✅ 999 passed, 159 todo, 3 skipped (88 test files).npm run build -w @aks-kickstart/api— ✅ vianpm run api:build(harness + api), bundled 20 functions.npx tsc -p packages/web/tsconfig.json --noEmit— ✅ clean.Rollback
Single-revert of this PR restores main. Scenarios are fully isolated in new files plus additive exports on each pack's
client.ts; the only edit toPlayground.tsxis additive (new tab, panel, card, dialog).Security (re-confirmed vs Zapp's DP review)
Scenarios are developer-authored, build-time-trusted fixtures — same bucket as Components-tab previews. No user-supplied content flows into A2UI envelopes here. The follow-up "user-supplied inspirations" work (if any) will reopen the prompt-injection threat model; this PR does not.
Awaiting 4-way gate — not self-approving.