|
| 1 | +# Render live code artifacts in Studio |
| 2 | + |
| 3 | +## Traceability |
| 4 | + |
| 5 | +- Spec ID: studio-live-artifact-preview |
| 6 | +- Status: Implemented |
| 7 | + |
| 8 | +## Intent |
| 9 | + |
| 10 | +Turn the code-backed half of Artifact View into a working, Studio-owned runtime. |
| 11 | +An operator looking at a generated React artifact should see the rendered output, |
| 12 | +see compile or runtime diagnostics when it is invalid, and receive a newer |
| 13 | +revision without refreshing Studio after the artifact changes on disk. |
| 14 | + |
| 15 | +The lifecycle for this increment is: |
| 16 | + |
| 17 | +```text |
| 18 | +Artifact Revision |
| 19 | + -> confined source project |
| 20 | + -> ArtifactCompileRuntime |
| 21 | + -> immutable ArtifactBuildSnapshot |
| 22 | + -> sandboxed ArtifactPreviewRuntime |
| 23 | + -> ArtifactViewHost commit |
| 24 | +``` |
| 25 | + |
| 26 | +This is a Studio capability. It does not depend on Qoder Canvas and does not |
| 27 | +broaden the existing Qoder Canvas compatibility bridge. |
| 28 | + |
| 29 | +## Decisions |
| 30 | + |
| 31 | +### D-1: TSX and JSX use the code-backed lifecycle |
| 32 | + |
| 33 | +Studio resolves `.tsx` and `.jsx` artifacts to the `studio.react-preview` |
| 34 | +renderer with `backing: code`. Other code extensions remain inert source views |
| 35 | +until their executable contract is specified. The preview pane keeps an |
| 36 | +explicit Source view so rendered output never hides the revision that produced |
| 37 | +it. |
| 38 | + |
| 39 | +### D-2: The compiler owns a confined virtual project |
| 40 | + |
| 41 | +The compile runtime bundles the selected entry plus relative imports that |
| 42 | +resolve to regular files inside the configured artifact directory. Imports that |
| 43 | +escape that directory, symbolic or multiply-linked sources, extensionless |
| 44 | +ambiguous resolution, and arbitrary package dependencies fail closed. React's |
| 45 | +runtime modules are the only package imports supplied by Studio. |
| 46 | + |
| 47 | +Each build is bounded by source count, total source bytes, output bytes, and |
| 48 | +diagnostic length. Build identity covers the artifact revision and compile |
| 49 | +runtime version. Exact-revision build routes return immutable snapshots; a stale |
| 50 | +revision still answers `409`. |
| 51 | + |
| 52 | +### D-3: Preview execution is opaque-origin and message-driven |
| 53 | + |
| 54 | +The server hosts a revision-scoped preview document with a restrictive CSP. The |
| 55 | +iframe has `sandbox="allow-scripts"` without `allow-same-origin`; it receives no |
| 56 | +credentials, network access, top navigation, forms, or parent DOM access. |
| 57 | + |
| 58 | +The parent creates a `MessageChannel` after the frame loads and sends |
| 59 | +`runtime.init` with the expected artifact, revision, build, and runtime ids. The |
| 60 | +preview executes only after that handshake and reports `renderCompleted` or |
| 61 | +`renderFailed` over the transferred port. The host validates all ids and commits |
| 62 | +status only for the latest requested build, so a slow older build cannot replace |
| 63 | +a newer revision. |
| 64 | + |
| 65 | +### D-4: Catalog changes are streamed, builds stay revision-scoped |
| 66 | + |
| 67 | +`/api/artifacts/events` is a server-sent event stream. The server observes the |
| 68 | +currently active artifact directory and emits a coalesced invalidation when an |
| 69 | +entry or a nested project dependency changes. The browser then refetches |
| 70 | +`/api/artifacts` and re-resolves the active build; it does not accept descriptors |
| 71 | +or artifact bytes directly from the event. A dependency-only change may keep the |
| 72 | +entry revision stable while producing a new build id. |
| 73 | + |
| 74 | +The stream is advisory and reconnectable. The catalog and revision-scoped |
| 75 | +routes remain the authority, so a missed event cannot weaken correctness. |
| 76 | + |
| 77 | +## Acceptance Scenarios |
| 78 | + |
| 79 | +- **AC-1:** A `.tsx` or `.jsx` descriptor is code-backed, names the Studio |
| 80 | + sandboxed renderer, and advertises `execute` and `live-update`; `.ts`, `.js`, |
| 81 | + and non-code artifacts retain their current presentation behavior. |
| 82 | +- **AC-2:** The build endpoint returns a validated `ArtifactBuildSnapshotV1` |
| 83 | + bound to the descriptor revision. Repeating the same build reuses its build |
| 84 | + identity; changing source bytes produces a different revision and build id. |
| 85 | +- **AC-3:** A component with confined relative TSX/CSS imports renders inside an |
| 86 | + opaque-origin iframe. React runtime imports work, while filesystem escapes and |
| 87 | + unsupported package imports produce bounded diagnostics without crashing |
| 88 | + other Studio routes. |
| 89 | +- **AC-4:** Preview code cannot read the parent DOM or make network requests, |
| 90 | + and it starts only after a matching `runtime.init` handshake. The host exposes |
| 91 | + compiling, ready, compile-failed, and runtime-failed states with accessible |
| 92 | + text. |
| 93 | +- **AC-5:** Rewriting a selected component emits a changed catalog revision, |
| 94 | + refreshes the descriptor, compiles the new revision, and visibly commits the |
| 95 | + new render without a page reload. A stale build completion is ignored. |
| 96 | +- **AC-6:** Preview and Source are keyboard-reachable pane controls at wide, |
| 97 | + compact, and narrow layouts; the preview has bounded overflow, visible focus, |
| 98 | + no document-level horizontal overflow, and no unexpected console/page errors. |
| 99 | +- **AC-7:** Existing PPTX, SVG, image, text/diff, Qoder Canvas, immutable content, |
| 100 | + and stale-revision behavior remain covered and unchanged. |
| 101 | + |
| 102 | +## Non-goals |
| 103 | + |
| 104 | +- Changing, extending, or using Qoder Canvas as the Artifact View host. |
| 105 | +- Session Artifact manifests, event/tool-call trace links, or semantic |
| 106 | + selection back-links. |
| 107 | +- Revision retention, replay, or cross-revision comparison. |
| 108 | +- npm installation, arbitrary third-party package imports, Node APIs, server-side |
| 109 | + rendering, or executing `.ts`/`.js` files that do not declare a UI contract. |
| 110 | +- Additional native formats such as XLSX, DOCX, PDF, Mermaid, or Lottie. |
| 111 | +- Write-back from the preview into artifact source files. |
| 112 | + |
| 113 | +## Plan and Tasks |
| 114 | + |
| 115 | +1. Add build snapshot and preview protocol contracts with validators. |
| 116 | +2. Add the confined `ArtifactCompileRuntime` and focused behavior tests. |
| 117 | +3. Resolve TSX/JSX through the code-backed plugin provider and serve build plus |
| 118 | + sandboxed preview routes. |
| 119 | +4. Add the catalog SSE observer and browser refetch lifecycle. |
| 120 | +5. Extract a Studio `ArtifactPreviewHost` path for code-backed artifacts with |
| 121 | + Preview/Source controls, MessageChannel sequencing, and status UI. |
| 122 | +6. Run focused unit/server/browser verification, preview health and runtime |
| 123 | + smoke checks, visual review at 1440x900, 1024x768, and 390x844, and the |
| 124 | + required Markdown link graph update. |
| 125 | + |
| 126 | +## Test and Review Evidence |
| 127 | + |
| 128 | +- AC-1/AC-2: plugin resolution, catalog, build contract, cache, and |
| 129 | + stale-revision server tests. |
| 130 | +- AC-3/AC-4: compiler confinement and browser runtime protocol tests. |
| 131 | +- AC-5: browser test that rewrites the selected fixture and observes a new |
| 132 | + rendered revision without reloading the page. |
| 133 | +- AC-6: Playwright screenshots, keyboard interaction, overflow assertions, and |
| 134 | + console/page-error capture at all three layout widths. |
| 135 | +- AC-7: existing Artifact View focused tests plus package typecheck/build. |
| 136 | + |
| 137 | +Implementation evidence captured on 2026-08-22: |
| 138 | + |
| 139 | +- `npm run typecheck` and `npm run build` passed in |
| 140 | + `packages/harness-studio`. |
| 141 | +- Before concurrent Git History work appeared in the same worktree, |
| 142 | + `npm test -- --maxWorkers=1` passed 22 files and 143 tests in |
| 143 | + `packages/harness-studio`, and `npm run test:browser` passed all 20 then-current |
| 144 | + Playwright scenarios. The Artifact suite |
| 145 | + covers sandboxed TSX execution, handshake-only startup, compile diagnostics, |
| 146 | + source access, no-refresh rebuild, PPTX/SVG regressions, keyboard focus, |
| 147 | + console/page errors, and wide/compact/narrow overflow. |
| 148 | +- Live Preview screenshots were reviewed at |
| 149 | + `test-results/artifacts-live-wide.png`, |
| 150 | + `test-results/artifacts-live-compact.png`, and |
| 151 | + `test-results/artifacts-live-narrow.png`. |
| 152 | +- Root `npm test -- --maxWorkers=1` passed 100 files and 1,472 tests, with one |
| 153 | + existing skipped test before that concurrent work expanded. The Markdown link graph passed 8 tests and |
| 154 | + `git diff --check` passed. |
| 155 | +- A built Studio CLI smoke served the fixture artifact catalog and returned a |
| 156 | + ready `ArtifactBuildSnapshotV1` plus build-scoped Preview URI for |
| 157 | + `tool-mix.tsx`. |
| 158 | +- The repository-level optional Canvas preview smoke was not runnable because |
| 159 | + no Canvas SDK runtime is configured. This is an external prerequisite of the |
| 160 | + existing Qoder Canvas preview command; the Studio-owned React runtime does not |
| 161 | + load it. |
| 162 | +- After the concurrent Git History work was integrated into `HEAD`, the final |
| 163 | + full package rerun passed 24 files and 148 tests, and all 21 Playwright |
| 164 | + scenarios passed. This includes the final handshake and symlink-confinement |
| 165 | + hardening as well as the Git History regression surface. |
| 166 | + |
| 167 | +Risk review: |
| 168 | + |
| 169 | +- **Untrusted execution:** compilation does not make generated code trusted. |
| 170 | + Opaque-origin iframe sandboxing, CSP, denied dependencies, and an explicit |
| 171 | + handshake are required release gates. |
| 172 | +- **Resource exhaustion:** source and output budgets limit compilation, but a |
| 173 | + browser component can still consume CPU after mount. This increment does not |
| 174 | + claim process-level isolation; runtime failure/timeout handling is evidence |
| 175 | + for a future worker or process boundary. |
| 176 | +- **Watcher variance:** filesystem notifications differ across Windows, macOS, |
| 177 | + and Linux. The observer coalesces events and re-derives the catalog revision; |
| 178 | + tests assert the revision protocol rather than OS-specific event counts. |
0 commit comments