Skip to content

feat(composer): Build — chat-driven creation from approved components (Phase 3 slice) - #40

Merged
ryandmonk merged 1 commit into
mainfrom
feat/build-chat
Aug 5, 2026
Merged

feat(composer): Build — chat-driven creation from approved components (Phase 3 slice)#40
ryandmonk merged 1 commit into
mainfrom
feat/build-chat

Conversation

@ryandmonk

Copy link
Copy Markdown
Contributor

The ratified Phase 3 vertical slice. Build is now the primary product experience: select a governed project, describe an interface in chat, and get a surface generated from the approved design-system vocabulary — validated in front of you, rendered through the trusted registry, refinable conversationally, and savable as a governed worked example. Consumes published @aestheticfunction/dspack-gen@0.2.0 (dspack-gen#49, released via OIDC before this PR — no override).

The interaction flow

Each turn shows: the ask → streamed pipeline (attempt N → S1/S2/S3 per attempt → the verbatim repair message when sent → emit) → outcome → the surface rendered through the project registry → Accept / Refine. Refinement seeds the immediately prior surface + the new instruction (RunOptions.conversation), regenerates a complete surface through every gate, and keeps prior turns in the thread for comparison and audit. Accept is server-side fail-closed (/project/save-example: S1–S3 re-linted on the agent, unknown intents refused, harness + ledger preserved) and lands the result as a contract worked example — immediately part of that intent's few-shot corpus, so accepted results compound generation. Builds never write intents, rules, rationales, mappings, or casualty declarations.

Honest failures: findings verbatim; an ask needing components the owner hasn't approved is named as a vocabulary gap (evidenced structurally from S2, never from message text), recorded in the turn as the future Component Workshop hook — never silently invented.

Build-first IA: Build is the first nav item and the default view for a ready connected project; otherwise visibly disabled with the exact reason (no intents authored — author one in Governance, 2 component(s) unmapped — finish the Mapper, …). The project home checklist reads as the runway and gains Start building when ready.

Providers and privacy

scripted is the always-available zero-model twin: a fresh run plays a contract-derived S2 violation then the intent's latest worked example — every scripted run demonstrates the governed fail→repair→pass loop deterministically, and accepting visibly changes what scripted plays next. A refinement returns the prior surface with a deterministic textual change only when the seed is present (the ratified non-vacuous proof, pinned at three layers). Local Ollama is the first real provider; Anthropic keeps working through the agent's env (no new UI, no claims). No browser keys, no hosted AI, no new bindings. The Build view states the provider and privacy boundary; project source never leaves the machine.

Real-model evidence (ollama:gemma4:e4b, the scoped demo contract)

  • Fresh ask ("a compact deployment status card…"): S1/S2/S3 PASS on the first attempt, outcome passed, root info-card — the same 8-GB-class model that scored 0/72 on the full shadcn vocabulary.
  • Seeded refinement ("change the health pill to 'Degraded' with an amber hue and add a region pill 'us-east'"): passed first attempt, surface differs from the prior, and contains exactly Degraded, amber, and us-east; the two-turn seed is recorded verbatim in the audit report.

Evidence

  • Fail-first at every layer: dspack-gen 5/5 (released as 0.2.0); agent 7 new tests (conversation position + malformed-400, scripted non-vacuity, save-example refusals incl. lint-invalid 422 with nothing written, accept→few-shot round-trip against the real saved file); composer-core 6 new (readiness reasons, fold, vocabulary-gap vs formatting, acknowledged casualties don't block); 13 new DOM specs written before the UI settled (first run: 6 real failures, including two product-behavior corrections).
  • DOM suite 34/34 (21 ledger + 13 Build) in ~46s — real static export + real agent + real project files, zero model calls. Covers every required item: disabled-with-exact-reason, unlock + default view + CTA, streamed fail→repair→pass, refine-differs + prior-persists, accept → disk → reload → scenario list → few-shot round-trip observable via scripted playback, vocabulary gap, direct-route 422 (nothing written), double-submit and double-accept locks, role="status" live announcements, unambiguous accessible names, keyboard + focus (Accept unmount focuses the confirmation), axe over pending/failed/success/refinement states.
  • Full workspace: all unit suites green (agent 40/40, composer-core 40/40), typecheck clean, exactly one dspack-gen version (0.2.0), exhibit e2e 106 passed / 4 pre-existing skips, hosted composer production smoke 13/13 (untouched by this PR).

Out of scope, per ratification

Hosted AI/bindings · persisted multi-thread history · accounts/collaboration · intent inference · Component Workshop (hook only) · React component generation · Vue/Vuetify · per-node visual editing · patch-based refinement · a second surface artifact · grammar-budget UI · dspack-emit#26 · unrelated redesign.

🤖 Generated with Claude Code

The Phase 3 vertical slice, exactly as ratified: select a governed
project, describe an interface in chat, generate it from the approved
design-system vocabulary (dspack-gen 0.2.0 under the project-scoped
contract), watch S1-S3 + bounded repair + emit stream per turn (AG-UI
SSE), see it render through the trusted registry, refine it
conversationally (the prior surface + the new instruction, regenerated
COMPLETELY through every gate, prior turns kept for audit), and accept
the result as a contract worked example — which immediately joins that
intent's few-shot corpus, so accepted results compound generation.

Agent: /project/run gains HttpAgent-shaped bodies + the conversation
seed (validated, 400 on malformed) and deterministic scripted twins — a
fresh run scripts a contract-derived S2 violation then the intent's
LATEST worked example (the governed fail->repair->pass loop with zero
models; accepting visibly changes what scripted plays next), a
refinement returns the prior surface with a deterministic textual change
ONLY when the seed is present (the ratified non-vacuous proof). New
/project/save-example: server-side fail-closed acceptance — S1-S3
re-linted on the agent, unknown intents refused, harness + ledger
preserved; a disabled client button is a courtesy, this gate is the
contract. Governance is never written by builds.

Composer: composer-core buildReadiness (exact remaining-setup reasons) +
foldBuildEvents + vocabularyGap (S2-evidence only — a gap is named,
never invented; the future Component Workshop hook); the Build view
(thread, pipeline strip, trusted-registry canvas via the in-browser emit
twin, provider/privacy statement, Accept/Refine with locks and focus
management); Build-first IA (first nav item, default view when ready,
disabled with the exact reason otherwise, Start-building CTA,
"Set up your design system, then build with it").

Evidence: agent 40/40 (7 new, fail-first); composer-core 40/40 (6 new,
fail-first); DOM suite 34/34 incl. 13 new Build specs (disabled-with-
reason, unlock+default, streamed fail->repair->pass, refine differs and
prior persists, accept->reload->few-shot round-trip via scripted
playback, vocabulary gap, direct-route 422, double-submit/accept locks,
live-region announcements, accessible names, keyboard+focus, axe over
pending/failed/success/refinement). Real-model evidence (gemma4:e4b over
the scoped demo contract): fresh ask passed S1-S3 first attempt; seeded
refinement passed and applied exactly the requested changes (Degraded/
amber/us-east), conversation recorded in the audit report. Exhibit e2e
106 passed/4 skips; hosted composer smoke 13/13; typecheck clean; one
dspack-gen version (0.2.0) across the tree.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Copilot AI lite review requested due to automatic review settings August 4, 2026 21:18

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR delivers the Phase 3 “Build” vertical-slice experience in the composer: chat-driven surface generation from a project’s approved component vocabulary, streamed gate visibility (S1–S3 + repairs + emit), refinement seeded by the prior surface, and server-side fail-closed acceptance into the contract’s worked-example corpus.

Changes:

  • Bump @aestheticfunction/dspack-gen to 0.2.0 and wire conversation-seeded refinement through /project/run.
  • Add composer-core “Build” logic (readiness gating + streamed event folding + vocabulary-gap detection) and a new Build UI view with Build-first IA defaults.
  • Add agent-side /project/save-example (fail-closed) plus new E2E + unit coverage for Build flow and accessibility states.

Reviewed changes

Copilot reviewed 20 out of 21 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
pnpm-lock.yaml Locks the workspace to @aestheticfunction/dspack-gen@0.2.0 and adds new workspace deps.
playwright.config.ts Excludes Build specs from the default Playwright config.
playwright.composer-agent.config.ts Includes Build specs in the composer-agent Playwright project.
packages/composer-core/src/index.ts Re-exports Build readiness/folding/gap utilities from composer-core.
packages/composer-core/src/composer-core.test.ts Adds unit tests for Build readiness, streaming fold, and vocabulary-gap classification.
packages/composer-core/src/build.ts Introduces the pure Build domain logic (readiness, fold, vocabularyGap).
e2e/support/agent-project.ts Updates helpers for Build-first navigation and adds a “connectReady” helper.
e2e/composer-build.spec.ts Adds end-to-end Build flow coverage (disabled reasons, streaming, refine, accept, vocabulary gap, fail-closed save).
e2e/composer-build-a11y.spec.ts Adds accessibility tests for live announcements, control names, keyboard flow, and axe scans across states.
e2e/composer-agent.spec.ts Adjusts existing agent E2E tests for Build-first navigation behavior.
docs/COMPOSER.md Documents the Phase 3 Build model, readiness requirements, providers/privacy, and acceptance semantics.
apps/web/package.json Bumps @aestheticfunction/dspack-gen dependency to ^0.2.0.
apps/composer/package.json Bumps @aestheticfunction/dspack-gen and adds @dspack-studio/agui-bridge dependency.
apps/composer/app/views/project-view.tsx Adds Build-first runway text and “Start building” CTA when ready.
apps/composer/app/views/build-view.tsx Adds the Build UI (prompt, model/intent selectors, streamed pipeline, gap panel, accept/refine controls, rendering).
apps/composer/app/state.tsx Adds Build state, readiness computation, streaming run orchestration, and acceptance integration.
apps/composer/app/composer.tsx Adds Build as first nav item, auto-opens Build when ready, and disables Build with exact readiness reason.
apps/composer/app/agent-client.ts Adds model listing, AG-UI streaming client for /project/run, and /project/save-example client call.
apps/agent/src/project.ts Adds conversation parsing + scripted refinement behavior, selects latest example for few-shot/scripted, and implements /project/save-example.
apps/agent/src/project.test.ts Adds tests for /project/run streaming/refinement and /project/save-example fail-closed acceptance/refusals.
apps/agent/package.json Bumps @aestheticfunction/dspack-gen to ^0.2.0 and updates dependency ordering.
Files not reviewed (1)
  • pnpm-lock.yaml: Generated file

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +142 to +145
export async function connectReady(page: Page, root: string): Promise<void> {
await connect(page, root);
await page.getByTestId("nav-build").isEnabled();
}
Comment on lines +590 to +603
const result = await agentSaveExample(projectPath, {
id: exampleId,
intent: turn.intent,
name: `Chat: ${turn.prompt.slice(0, 60)}`,
prompt: turn.prompt,
surface: turn.progress.surface,
});
if (!result.ok) {
setNotice(`Accept failed: ${result.error}`);
return;
}
if (!result.value.ok) {
setNotice(`Accept refused by the gates: ${result.value.findings.map((f) => f.message).join("; ").slice(0, 300)}`);
return;
Comment on lines +497 to +502
const clearBuildThread = useCallback(() => {
buildStream.current?.cancel();
buildStream.current = null;
setBuildTurns([]);
setBuildBusy(false);
}, []);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants