feat(runtime): connect Studio to a real runtime and generate real React from approved plans - #102
Merged
Conversation
Adds the implementation status summary covering completed phases 1-3 and the remaining phase 4 release-hardening milestones. Co-Authored-By: Claude <noreply@anthropic.com>
Removes the frontend/ ignore entry so the directory can be tracked. The directory itself remains untracked until its contents are added deliberately. Co-Authored-By: Claude <noreply@anthropic.com>
`pageMap` was declared `z.unknown()` in start_art_direction, submit_discovery_answers, and revise_creative_brief. Zod compiles that to an empty JSON Schema, and MCP hosts serialize untyped parameters as strings, so the value reached the engine as a string and `validatePageMap` rejected it on its `isRecord` check every time. The error surfaced as "Page map must declare single-page or multi-page kind", pointing at the page map contents rather than at the wire format, which reads as a malformed payload when the object never arrived as an object at all. Because `topicResolved` requires a valid `session.pageMap` for the page-map topic and has no fallback path, this made the topic unresolvable and blocked the Phase 2 workflow past discovery for every MCP host. Declaring the object shape keeps the payload structured over the wire. The design engine still owns full validation. `interpretations`, `answers`, and `decisions` were unaffected because `z.array()` emits a real type. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Local scratch directory for MCP workflow testing. Holds session state, per-step tool responses, authored source, and driver scripts that should not enter the repository. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… into agent/runtime-docs
Mirrors PR #100 and PR #101 onto this branch for local testing: - Studio, Preview, and demo-site now bind server.host to 127.0.0.1 instead of defaulting to IPv6-loopback-only, matching what the trusted local runtime's origin allowlist already expects. - apps/studio/public/dev-runtime.local.js and apps/preview/public/dev-runtime.local.js let window.__UNIVERSAL_RUNTIME__ be set before app code runs, so a real local runtime can be connected for manual testing instead of always falling back to the fixture client. Co-Authored-By: Claude <noreply@anthropic.com>
# Conflicts: # .gitignore # ROADMAP.md # apps/preview/vite.config.ts # apps/studio/vite.config.ts # docs/RUNTIME_CONTRIBUTOR_WORKFLOW.md
frontend/vite.config.ts had no server config, defaulting to port 5173 (colliding with Studio) and IPv6-loopback-only binding. Since a browser's localhost resolution tries IPv6 first, frontend silently won that race over Studio's IPv4 binding, serving the marketing site at localhost:5173 instead of Studio with no error from either side. Mirrors PR #100's follow-up commit: explicit 127.0.0.1:5176 with strictPort, and updates SETUP.md's port table and override examples. Co-Authored-By: Claude <noreply@anthropic.com>
Follow-up to 5d1ca55 -- the doc edit wasn't staged in time for that commit. Co-Authored-By: Claude <noreply@anthropic.com>
Records the design for wiring ArtDirectorBridge into the local runtime so Studio can produce a real Design Plan v2 and generate an actual project. The bridge is fully implemented and tested but has never been constructed outside tests, so "Generate React site" fails on the missing enginePlan. A second defect compounds it: Studio probes the bridge before bootstrap, so the probe 401s and Studio latches to the fixture client permanently. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
"Overwrites the first" read as a limit on prompts or projects. Neither is restricted: prompting is unlimited, and projects are unaffected once a plan is captured in enginePlan. The only lost case is an unfinished wizard, which Studio cannot currently reach with its single-project state. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Six tasks: probe for the design-mcp entry, spawn a real stdio session, construct the bridge in the runtime CLI, memoize Studio's single-use bootstrap, probe the bridge only after a session exists, and verify the whole path manually. Task 6 is manual by design. Every existing bridge test passes against a FakeSession, which is why a bridge that was never constructed in production went unnoticed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The main-ordering test never imported main.tsx, so it asserted the contract by hand and would pass even if the fix were reverted. Task 5 now extracts initializeStudio() into its own module and tests that. Task 1's probe test also returned early instead of skipping, which reads as a pass when nothing was asserted. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
local-runtime declared @7shep/universal-mcp as a workspace dependency while design-mcp devDepends on @universal/local-runtime. Turbo builds its task graph from dependencies and devDependencies alike, so the pair closed a cycle and `turbo run build|typecheck|test` all aborted at graph construction. design-mcp's edge is load-bearing and correctly placed: runtime-build-mcp.ts imports RuntimeService/RuntimeFailure from production source, and bundle.mjs derives esbuild `external` from `dependencies` only, so promoting it out of devDependencies would break the published bundle. So local-runtime drops its manifest edge instead and resolves design-mcp's entry by relative path. resolveArtDirectorEntry now separates the two failures the old single existsSync conflated: a missing package directory means the monorepo layout moved and is logged loudly, because a silent undefined would start the CLI without an art director and skip the integration tests; a missing dist/ is just an unbuilt package and stays quiet. The build ordering returns as a task-level edge in turbo.json, which does not reintroduce a manifest cycle, so local-runtime's integration tests still get design-mcp's dist/. Also corrects the isTransportFailure comment, which claimed a contract keyed on INVALID_SESSION/ILLEGAL_TRANSITION/IDEMPOTENCY. No design-mcp code contains "IDEMPOTENCY"; the bridge regex matches only 2 of the 8 real codes and the rest, including REQUEST_ID_CONFLICT, are retried as transport failures. The regex itself is tracked as separate follow-up. Finally, Studio now warns when the bridge probe returns false. hostBridgeAvailable swallows every error and returns false, so the headline failure mode previously produced no devtools output at all before latching to the fixture client. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ed on Windows Project and revision ids derived from a user prompt are long and appear in both the project and revision directory names. The revision directory then carries a deep pnpm tree (node_modules/.pnpm/<pkg>@<version>_<peers>/node_modules/<pkg>/... adds 200+ characters), pushing resolution past Windows MAX_PATH. The generated project's production build failed with ERR_PACKAGE_IMPORT_NOT_DEFINED naming vite's "#module-sync-enabled", which pointed at the package rather than the path. Verified: same sources and lockfile, installed offline, build fails at the 306-character path and succeeds at a short one. For the observed ids the full resolution path drops from 326 to 237 characters. Segments are truncated with a digest of the full id appended, so the mapping stays deterministic and collision-free. retention.ts recomputes revision paths and compares them to the stored workspacePath, so it now shares the same helper; diverging would have made retention silently skip every revision as unsafe-path. Ids already under the bound are unchanged, so existing revisions stay reachable. Also logs a warning when a revision path is long enough to be at risk, since the build error names a package rather than the path that actually broke. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The deterministic provider returns eleven fixture files and never reads the plan's routes, so a membership-site prompt produces a keyboard sample site and the implementation review correctly rejects it. The LiveProviderFactory seam exists but cli.ts passes no factory. Design: one streamed schema-constrained call generates the whole project, then a self-check runs the review's six predicates and, on a gap, makes exactly one repair call. The provider never claims success the review would refuse. The provider lives in its own package because design-mcp bundles local-runtime into its published artifact; a re-export from local-runtime's index.ts would inline the entire Anthropic SDK into an npm package. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The table listed a `provider-error` fallback that does not exist. The union is authentication | rate-limit | timeout | cancelled | malformed-output | unavailable | internal, so 5xx maps to `unavailable` and everything else to `internal`. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Revision 1 assumed a metered UNIVERSAL_PROVIDER_API_KEY, which contradicts a hard project constraint: generation must run on an existing subscription and must not add per-token billing. Both agent CLIs are installed and authenticated locally (Claude Code 2.1.220, codex-cli 0.146.0), and both support schema-constrained headless runs. The provider now spawns one of them behind a small adapter seam, reusing the subprocess pattern already proven by the art director bridge. Dropping the SDK also drops revision 1's package-boundary problem, so the modules live in local-runtime beside the other subprocess code. Corrects two claims revision 1 got wrong: provider-config.ts does need to change (its live path requires an API key), and there is no generic provider-error failure code. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…iption CLI DeterministicReactProvider returned eleven fixture files regardless of the plan, so every generation produced a mechanical-keyboard sample site that the review gate correctly rejected. The live seam in provider-config.ts had no factory. This fills it by spawning an already-authenticated agent CLI -- claude or codex -- as a subprocess. Generation runs on the operator's existing subscription: no API key, no per-token billing, no SDK dependency. The deterministic provider stays the default, so `pnpm dev` still needs no credentials. The self-check calls reviewGeneratedImplementation directly rather than reimplementing its predicates. An independent copy would drift, and the checks that actually reject generated work are the ARCH_* findings from the TypeScript compiler analysis, which a regex reimplementation would miss entirely. Running the real gate inside the provider turns a thirty-second install-and-build round trip into a millisecond check, and feeds its exact messages into one bounded repair. At most two CLI invocations, ever: an unbounded repair loop against a metered subscription is the expensive failure mode. Both passes share one ten-minute budget. A repair that still fails returns its flawed output rather than suppressing it -- the review gate stays the authority on correctness. Supporting changes: - ProviderError lets a provider name its failure. Without it every throw collapsed to `internal`, leaving authentication, rate-limit, timeout and unavailable unreachable anywhere in the codebase, and every failure marked retryable. - runSupervisedCommand gains stdin piping, because a design plan is far larger than Windows' 32767-character command line, and a configurable output cap, because the 64 KB default would truncate a project payload into malformed JSON. - cli.ts probes the selected CLI at startup, so a missing tool fails immediately with a message naming it instead of ten minutes into a generation. Verified end to end against both CLIs. Each passed the gate on the first pass with no repair: claude-code 429s/18 files, codex 126s/11 files. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Takes the local runtime from "Studio talks to a fixture" to "a prompt produces a real, buildable site you can open in Preview."
What was broken
DeterministicReactProviderreturned the same eleven fixture files regardless of the design plan, so a prompt for a membership site produced a mechanical-keyboard sample site. The review gate correctly rejected it, the build was quarantined, and Preview had nothing to serve. The live provider seam existed inprovider-config.tsbut nothing filled it.The live generation provider
Generation now spawns an already-authenticated agent CLI —
claudeorcodex— as a subprocess. No API key, no per-token billing, no SDK dependency: it runs on the operator's existing subscription. The deterministic provider stays the default, sopnpm devstill needs no credentials and every existing test runs with no network and no CLI.Activate with
UNIVERSAL_GENERATION_PROVIDER=claude-code(orcodex).Three decisions worth calling out:
The self-check runs the real gate, not a copy of it. The provider calls
reviewGeneratedImplementationdirectly rather than reimplementing its predicates. An independent copy would drift, and the checks that actually reject generated work are theARCH_*findings from the TypeScript compiler analysis, which a regex reimplementation would miss entirely. This turns a thirty-second install-and-build round trip into a millisecond check, and feeds its exact messages into the repair prompt.At most two CLI invocations, ever. An unbounded repair loop against a metered subscription is the expensive failure mode, so the invocation ceiling is asserted in tests. Both passes share one ten-minute budget.
A failing repair returns its flawed output. The review gate stays the authority on correctness; a provider that suppressed bad output would only hide the diagnostics the operator needs.
Supporting changes
ProviderErrorlets a provider name its failure. Without it every throw collapsed tointernal, leavingauthentication,rate-limit,timeout, andunavailableunreachable anywhere in the codebase and every failure marked retryable.runSupervisedCommandgains stdin piping (a design plan far exceeds Windows' 32767-character command line) and a configurable output cap (the 64 KB default would truncate a project payload into malformed JSON).cli.tsprobes the selected CLI at startup, so a missing tool fails immediately with a message naming it instead of ten minutes into a generation.Also in this branch
MAX_PATHfix — prompt-derived project ids produced a 225-character revision directory; the generated project's pnpm tree added ~101 more, pushing past 260 and failing the build with an ESM resolver error that named vite rather than the path. Segments are now bounded to 40 characters with a digest suffix.retention.tsshares the same helper, since it recomputes these paths and would otherwise silently skip every revision asunsafe-path.Verification
pnpm -w test23/23 packages green,pnpm -w buildclean.Verified end to end against both real CLIs. Each passed the deterministic review gate on the first pass with no repair:
claude-codecodexReproduce with
pnpm --filter @universal/local-runtime smoke:live [codex]. It is opt-in and consumes real subscription usage, so it is not part ofpnpm test.Known limitation
There is no progress signal during generation.
runSupervisedCommandbuffers the child's output rather than streaming it, so Studio showsgeneratingfor several minutes with nothing behind it. Claude Code supports--output-format stream-json --include-partial-messages, so a follow-up could stream progress into the existing/api/v1/events.🤖 Generated with Claude Code