|
| 1 | +# Unify artifact revisions, adapters, and renderers in Studio |
| 2 | + |
| 3 | +## Traceability |
| 4 | + |
| 5 | +- Spec ID: studio-artifact-view-model |
| 6 | +- Status: Implemented |
| 7 | + |
| 8 | +## Intent |
| 9 | + |
| 10 | +Refactor Harness Studio's flat preview descriptor into a data-backed Artifact |
| 11 | +View lifecycle with explicit content revisions, data adapters, immutable |
| 12 | +snapshots, renderer providers, and user-facing browsing. Use that lifecycle to |
| 13 | +render PPTX files in Studio without treating Canvas as the universal Artifact |
| 14 | +abstraction or executing presentation bytes as code. |
| 15 | + |
| 16 | +Canvas remains the name of the existing Qoder Canvas Viewer integration only. |
| 17 | +The Studio-owned abstraction is Artifact View: |
| 18 | + |
| 19 | +```text |
| 20 | +Artifact Revision -> Data Adapter -> Data Snapshot -> Renderer -> View Host |
| 21 | +``` |
| 22 | + |
| 23 | +This increment implements the data-backed half of that model. Code project |
| 24 | +compilation, build snapshots, preview execution, cross-revision comparison, and |
| 25 | +Session trace addressing remain future work. |
| 26 | + |
| 27 | +## Decisions |
| 28 | + |
| 29 | +### D-1: Catalog V2 owns file identity and presentation references |
| 30 | + |
| 31 | +`HarnessStudioArtifactCatalogV2` remains a read-only, browser-safe catalog. Each |
| 32 | +descriptor contains: |
| 33 | + |
| 34 | +- a stable catalog-local thread id; |
| 35 | +- an exact revision id and SHA-256 digest; |
| 36 | +- the bounded content reference; |
| 37 | +- a data-backed family and format; |
| 38 | +- the selected adapter id/version/schema and snapshot URI; |
| 39 | +- the selected renderer id/provider/type/status; |
| 40 | +- renderer capabilities and an optional unavailable reason. |
| 41 | + |
| 42 | +The catalog does not inline parsed Office payloads, absolute paths, runtime |
| 43 | +code, UI state, or Session claims. V1 is superseded rather than extended with a |
| 44 | +parallel optional-field vocabulary. |
| 45 | + |
| 46 | +### D-2: ArtifactDataSnapshotV1 is immutable and format-specific |
| 47 | + |
| 48 | +Every snapshot is bound to one artifact id and revision id. Its common envelope |
| 49 | +contains adapter provenance, summary, structure, semantic addresses, resources, |
| 50 | +diagnostics, and a discriminated payload. The first payloads are: |
| 51 | + |
| 52 | +- `artifact/raw-v1`: exact content reference for direct text/image/SVG/JSON |
| 53 | + renderers; |
| 54 | +- `pptx/v1`: deck size, ordered slides, shapes, text runs, images, notes, |
| 55 | + addresses, and unsupported-feature diagnostics. |
| 56 | + |
| 57 | +Snapshot generation is bounded by compressed input, expanded entry, entry |
| 58 | +count, text, media, and response limits. Artifact bytes never choose an adapter |
| 59 | +or local executable path. |
| 60 | + |
| 61 | +### D-3: The plugin registry chooses Adapter and Renderer separately |
| 62 | + |
| 63 | +The trusted registry resolves one data adapter and one renderer for each file. |
| 64 | +Resolution order is: |
| 65 | + |
| 66 | +1. an operator-provisioned Qoder Canvas viewer with `overrideBuiltIn`; |
| 67 | +2. a Studio-native data-backed plugin; |
| 68 | +3. a matching non-overriding Qoder Canvas viewer; |
| 69 | +4. the raw adapter plus an honest unavailable renderer. |
| 70 | + |
| 71 | +Qoder viewer `scripts/index.mjs` is wrapped as a |
| 72 | +`QoderViewerSidecarAdapter`; `index.canvas.tsx` is wrapped as a |
| 73 | +`QoderCanvasRenderer`. The existing opaque-origin iframe and runtime discovery |
| 74 | +remain the bridge implementation. |
| 75 | + |
| 76 | +The current `esbuild-wasm` transform is a `TrustedRendererCompiler`. It compiles |
| 77 | +only provisioned renderer code and is not an Artifact compiler. |
| 78 | + |
| 79 | +### D-4: PPTX has a cross-platform native baseline |
| 80 | + |
| 81 | +`PptxArtifactDataAdapter` parses untrusted OOXML with pure JavaScript libraries |
| 82 | +inside the Studio server. It reads slide order, slide dimensions, positioned |
| 83 | +shapes, text runs, fills, images, and speaker-note presence into `pptx/v1`. |
| 84 | + |
| 85 | +`NativePptxRenderer` renders the snapshot as a slide rail plus DOM stage. It |
| 86 | +supports navigation, zoom-to-fit, and semantic slide/shape addresses. Unsupported |
| 87 | +OOXML is reported in diagnostics; the baseline is observable coverage, not |
| 88 | +PowerPoint or Walnut pixel parity. |
| 89 | + |
| 90 | +An installed ChatGPT Walnut runtime remains an explicit, Studio-private, |
| 91 | +experimental provider receipt. `harness-studio walnut probe|install|verify|remove` |
| 92 | +may provision reviewed assets only after consent, but no private ChatGPT API is |
| 93 | +treated as a parser contract. The native PPTX adapter is therefore the default |
| 94 | +portable fallback. A future reviewed Walnut adapter can implement the same |
| 95 | +snapshot schema without changing the catalog or renderer host. |
| 96 | + |
| 97 | +### D-5: Artifact View uses a grouped Explorer |
| 98 | + |
| 99 | +The left pane defaults to collapsible format families: |
| 100 | + |
| 101 | +1. Documents; |
| 102 | +2. Images & diagrams; |
| 103 | +3. Data; |
| 104 | +4. Source & text; |
| 105 | +5. Other. |
| 106 | + |
| 107 | +It provides filename search and a `Grouped | Flat` presentation switch. This is |
| 108 | +not a filesystem tree: the current catalog has no relative paths or directory |
| 109 | +nodes. Folder Tree becomes eligible only after a future catalog owns confined |
| 110 | +path hierarchy and recursive indexing. |
| 111 | + |
| 112 | +Rows show a format icon, filename, human format, size, and an accessible |
| 113 | +unavailable indicator. The preview owns the selected filename, revision, |
| 114 | +adapter/renderer metadata, snapshot diagnostics, and document navigation. |
| 115 | + |
| 116 | +At narrow width, Explorer and Preview become two view tabs so a long list cannot |
| 117 | +push the selected document below the fold. |
| 118 | + |
| 119 | +## Acceptance Scenarios |
| 120 | + |
| 121 | +- **AC-1:** `/api/artifacts` returns only a valid |
| 122 | + `HarnessStudioArtifactCatalogV2`; each artifact has an exact revision and |
| 123 | + content digest, adapter reference, snapshot URI, renderer reference, family, |
| 124 | + and capabilities. Absolute filesystem paths never reach the browser. |
| 125 | +- **AC-2:** `/api/artifacts/:id/snapshot` returns a valid |
| 126 | + `ArtifactDataSnapshotV1` bound to the descriptor revision. A changed source |
| 127 | + digest cannot reuse the prior snapshot. |
| 128 | +- **AC-3:** Direct code, diff, JSON, text, image, and SVG behavior remains inert |
| 129 | + and readable through native renderers. Arbitrary artifact TSX/JSX is never |
| 130 | + executed. |
| 131 | +- **AC-4:** A real PPTX produces ordered slides, dimensions, positioned text |
| 132 | + shapes, embedded images, notes presence, semantic addresses, and explicit |
| 133 | + diagnostics. Corrupt, oversized, path-traversing, or expansion-heavy OOXML |
| 134 | + fails closed without stopping other Studio routes. |
| 135 | +- **AC-5:** The native PPTX renderer opens the repository's |
| 136 | + `Better-Harness-one-page.pptx` in Studio, visibly renders its first slide, |
| 137 | + exposes slide navigation and adapter/renderer identity, and has no page or |
| 138 | + console errors. |
| 139 | +- **AC-6:** An overriding Qoder viewer still wins. A non-overriding viewer is a |
| 140 | + fallback when no native plugin exists. Qoder sidecar adaptation and renderer |
| 141 | + compilation retain timeout, size, temp cleanup, path scrubbing, CORS, CSP, |
| 142 | + and opaque-origin iframe boundaries. |
| 143 | +- **AC-7:** Walnut bootstrap remains explicit, content-addressed, |
| 144 | + receipt-verified, Studio-private, and absent from normal startup, npm package |
| 145 | + bytes, source control, and release claims. Missing or unreviewed Walnut builds |
| 146 | + do not disable native PPTX rendering. |
| 147 | +- **AC-8:** Grouped and Flat explorer modes preserve the selected revision; |
| 148 | + search matches filenames only; empty groups disappear; PPTX belongs to |
| 149 | + Documents regardless of renderer provider. |
| 150 | +- **AC-9:** At 1440x900, 1024x768, and 390x844, the explorer and PPTX stage have |
| 151 | + no document-level horizontal overflow, keyboard focus is visible, the preview |
| 152 | + remains reachable, and dark/light contrast remains accessible. |
| 153 | +- **AC-10:** Debugger and other catalog consumers use V2 descriptors without |
| 154 | + inventing retained artifacts or reconstructing content/snapshot URLs from |
| 155 | + ids. |
| 156 | +- **AC-11:** Package typecheck/build/tests, root tests, package audit, Markdown |
| 157 | + link graph, and preview health/runtime smoke checks pass or report an exact |
| 158 | + external Canvas-runtime prerequisite separately. |
| 159 | + |
| 160 | +## Non-goals |
| 161 | + |
| 162 | +- ArtifactThread persistence across renamed files or independent Sessions. |
| 163 | +- Parent revision chains, replay storage, compare mode, annotations, mutation, |
| 164 | + save-back, or Office round-trip editing. |
| 165 | +- XLSX, DOCX, PDF, GLB, Lottie, or Mermaid native adapters in this increment; |
| 166 | + the registry and snapshot contract make them additive follow-ups. |
| 167 | +- ArtifactCompileRuntime, project bundling, virtual filesystems, incremental |
| 168 | + rebuilds, WebContainer, Sandpack, or executable React artifacts. |
| 169 | +- Copying or distributing Walnut, ChatGPT hashed chunks, private protobufs, |
| 170 | + application IPC, or full `ArtifactTabContent` UI. |
| 171 | +- Claiming PowerPoint, Walnut, or Qoder Canvas pixel parity. |
| 172 | + |
| 173 | +## Plan and Tasks |
| 174 | + |
| 175 | +1. Replace the V1 catalog contract with browser-safe V2 revision, adapter, |
| 176 | + renderer, family, capability, and snapshot contracts. |
| 177 | +2. Introduce an Artifact plugin registry; adapt existing direct renderers and |
| 178 | + Qoder Canvas discovery behind separate adapter/renderer references. |
| 179 | +3. Rename the trusted Canvas TSX transform boundary to |
| 180 | + `TrustedRendererCompiler` and the host boundary to |
| 181 | + `QoderCanvasViewerBridge`, retaining compatibility re-exports only where |
| 182 | + focused tests still need them. |
| 183 | +4. Implement bounded OOXML ZIP/XML parsing, PPTX snapshots, snapshot/resource |
| 184 | + routes, cache identity by source digest plus adapter version, and behavioral |
| 185 | + malformed-input tests. |
| 186 | +5. Implement the grouped Artifact Explorer and native PPTX renderer with shared |
| 187 | + Studio tokens, semantic slide addresses, diagnostics, and responsive tabs. |
| 188 | +6. Keep and integrate the explicit Walnut bootstrap commands as provider |
| 189 | + capability evidence; do not make Studio startup mutate the cache. |
| 190 | +7. Validate direct renderer regressions, Qoder override/fallback behavior, the |
| 191 | + real repository PPTX, browser layouts, package/root gates, package contents, |
| 192 | + and diff hygiene. |
| 193 | + |
| 194 | +## Test and Review Evidence |
| 195 | + |
| 196 | +- AC-1/AC-2/AC-4/AC-10: 137 Studio tests passed across 21 files. Focused PPTX |
| 197 | + tests cover exact revision and snapshot binding, leading-zero text, positioned |
| 198 | + shapes, embedded resources, notes path redaction, semantic addresses, corrupt |
| 199 | + archives, unsafe ZIP paths, oversized expanded entries, and cache invalidation. |
| 200 | +- AC-3/AC-6: the V2 catalog, direct renderers, Qoder override/fallback registry, |
| 201 | + sidecar freshness, trusted renderer compilation, active-content CSP, SVG |
| 202 | + sandbox, and malformed route behavior all remain covered in that package run. |
| 203 | +- AC-5/AC-8/AC-9: all 17 Playwright scenarios passed. Native PPTX rendering, |
| 204 | + Grouped Explorer, wide 1440x900, compact 1024x768, narrow 390x844, automatic |
| 205 | + Preview navigation, keyboard focus, horizontal overflow, theme contrast, and |
| 206 | + browser errors are verified with screenshots. The live in-app browser also |
| 207 | + rendered `Better-Harness-one-page.pptx`: 19 shapes, one complete 1280x950 |
| 208 | + image, preserved `01` text, no page overflow, and no console/page errors. |
| 209 | +- AC-7: eight focused Walnut bootstrap tests cover explicit consent, |
| 210 | + content-addressed install, verification, tamper detection, app-change |
| 211 | + invalidation, symlink rejection, removal, and cross-platform cache roots. A |
| 212 | + read-only real probe found ChatGPT 26.818.22352 and 34 reviewed assets for |
| 213 | + DOCX/PPTX/XLSX. Package dry-run contained 926 files and no ASAR/WASM or |
| 214 | + extracted ChatGPT asset. |
| 215 | +- AC-11: Studio typecheck/build/tests, full browser suite, 1,435 root tests, |
| 216 | + six Markdown link-graph tests, package dry-run, and `git diff --check` passed. |
| 217 | + The optional root Canvas preview smoke was not runnable because no Canvas SDK |
| 218 | + runtime was configured; native PPTX rendering does not use that runtime. |
| 219 | + |
| 220 | +### Risks |
| 221 | + |
| 222 | +- **Model overreach:** the attachment describes a larger future runtime. This |
| 223 | + increment freezes only data-backed contracts exercised by current consumers. |
| 224 | +- **PPTX fidelity:** fonts, themes, layout inheritance, charts, SmartArt, |
| 225 | + animation, cropping, and grouped transforms are complex. Diagnostics must |
| 226 | + preserve uncertainty rather than imply parity. |
| 227 | +- **Archive expansion:** OOXML is a ZIP container. Enforce compressed, |
| 228 | + expanded, entry-count, path, media, and snapshot limits independently. |
| 229 | +- **Plugin trust:** artifact bytes may select a format but never a plugin path. |
| 230 | + Only the operator-controlled registry and provisioned viewer roots execute. |
| 231 | +- **Cache staleness:** cache keys include artifact digest, adapter id/version, |
| 232 | + and schema id; mutable file metadata alone is not sufficient identity. |
| 233 | +- **UI density:** format groups improve findability only if search, selection, |
| 234 | + disclosure, and narrow Preview navigation remain distinct and keyboard-safe. |
0 commit comments