Skip to content

Commit e567e25

Browse files
phodalQoder-AI
andcommitted
feat(studio): mount inspector workbench natively
Copies the Inspector workbench into Studio via /api/inspector-report and a Shadow DOM host while keeping /inspector as the sandboxed fallback. Implements docs/specs/2026-08-19-studio-native-inspector-workbench.md; validated with harness-studio build, unit, package, and browser tests. Co-authored-by: QoderAI <qoder_ai@qoder.com>
1 parent 72594cc commit e567e25

8 files changed

Lines changed: 510 additions & 2 deletions

File tree

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# Bring the Inspector workbench into Studio
2+
3+
## Traceability
4+
5+
- Spec ID: studio-native-inspector-workbench
6+
- Status: Implemented
7+
8+
## Intent
9+
10+
Move the Harness Inspector workbench experience into the Studio `/#/inspector` surface so reviewers can inspect retained delivery evidence without leaving the Studio shell. The migration keeps the existing self-contained Inspector HTML report as the authoritative offline artifact and compatibility fallback, while adding a native Studio mounting path that reads the report data through a server JSON boundary and runs a copied workbench asset inside the Studio page.
11+
12+
## Acceptance Scenarios
13+
14+
- AC-1: Starting Studio with `--inspector <report.html>` enables `/#/inspector`; when the report contains `HarnessInspectorReportV1` data, the page renders an in-Studio Inspector workbench without the iframe being the primary surface.
15+
- AC-2: The server exposes the privacy-filtered Inspector report data through a same-server JSON endpoint derived from the configured report only. Missing, unreadable, or non-workbench HTML does not expose arbitrary files and produces a bounded error.
16+
- AC-3: The native workbench uses a copied Inspector workbench asset in the Studio app bundle, keeps CSS isolated from Studio shell styles, and preserves the existing Capability/Date, Session, Trace/Replay, activity chart, and deep-link behavior as owned by the copied workbench.
17+
- AC-4: Existing `/inspector` HTML serving remains available and `/#/inspector` falls back to the legacy sandboxed iframe when the configured HTML is not a structured Inspector workbench report.
18+
- AC-5: Tests cover the JSON extraction contract, fallback behavior, and build integration. Browser smoke evidence verifies the native Inspector workbench loads in Studio without console or page errors.
19+
20+
## Non-goals
21+
22+
- Rewriting every Inspector workbench interaction as first-class React components in this change.
23+
- Changing the Harness Inspector report model, privacy filtering, correlation logic, or static HTML renderer output.
24+
- Adding write, resume, checkpoint mutation, or execution controls to the read-only Inspector surface.
25+
- Removing the existing `/inspector` compatibility route.
26+
27+
## Plan and Tasks
28+
29+
1. Add a server-side Inspector JSON query helper that extracts and validates the `inspector-data` JSON from the configured self-contained HTML report.
30+
2. Add `/api/inspector-report` to the Studio server with `no-store` JSON responses and bounded errors when no structured report is available.
31+
3. Copy the Inspector workbench JavaScript and CSS into the built Studio app assets during `harness-studio` build.
32+
4. Add a native `InspectorWorkbench` React component that fetches the report JSON plus copied assets, mounts the copied workbench into an isolated Shadow DOM, and scopes the workbench's document access to that mount.
33+
5. Replace the iframe-first `InspectorWorkspace` with the native component while retaining a sandboxed iframe fallback to `/inspector`.
34+
6. Add focused Vitest coverage for report JSON extraction and server route behavior, then run the Studio build/test gate and a browser smoke check against a real Inspector report.
35+
36+
## Test and Review Evidence
37+
38+
- AC-1/AC-3/AC-4: Browser smoke opened `http://127.0.0.1:3311/#/inspector` with `.qoder/native-studio-inspector.html`; the page rendered native `Harness Inspector`, `Capability`, and `Date` content, `/api/inspector-report` plus copied workbench assets returned 200, iframe count was 0, and no console or page errors were observed.
39+
- AC-2: `npm run harness-studio:test` passed 82 tests, including server coverage for valid structured Inspector JSON, 204 fallback for non-workbench Inspector HTML, missing-report errors, and unchanged `/inspector` HTML serving.
40+
- AC-5: `npm run harness-studio:build`, `npm test -w @qoder-ai/harness-studio -- server.test.ts`, `npm run harness-studio:test`, and `npm run harness-studio:test:browser` passed after the fallback response was changed to 204 to avoid browser console noise during legacy iframe fallback.

packages/harness-studio/scripts/build-app.mjs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { build } from "esbuild-wasm";
66

77
const packageRoot = join(dirname(fileURLToPath(import.meta.url)), "..");
88
const appDir = join(packageRoot, "dist", "app");
9+
const inspectorAssetRoot = join(packageRoot, "..", "..", "scripts", "harness-inspector", "ui");
910

1011
await mkdir(join(appDir, "assets"), { recursive: true });
1112
await build({
@@ -29,6 +30,8 @@ await Promise.all([
2930
...["tokens.css", "shell.css", "workbench.css"].map((file) =>
3031
copyFile(join(packageRoot, "src", "app", "styles", file), join(appDir, "assets", file)),
3132
),
33+
copyFile(join(inspectorAssetRoot, "workbench.css"), join(appDir, "assets", "inspector-workbench.css")),
34+
copyFile(join(inspectorAssetRoot, "workbench.js"), join(appDir, "assets", "inspector-workbench.js")),
3235
]);
3336
process.stdout.write(`Built studio app into ${appDir}\n`);
3437
process.exit(0);

packages/harness-studio/src/app/App.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { SquaresFour } from "@phosphor-icons/react/SquaresFour";
1111
import { TreeStructure } from "@phosphor-icons/react/TreeStructure";
1212
import { CompareView } from "./CompareView.js";
1313
import { ExperimentView } from "./ExperimentView.js";
14+
import { InspectorWorkbench } from "./InspectorWorkbench.js";
1415
import { RunView } from "./RunView.js";
1516
import { useRovingFocus } from "./roving-tablist.js";
1617
import {
@@ -247,7 +248,7 @@ function InspectorWorkspace(props: { config: StudioConfig }): React.JSX.Element
247248
if (props.config.inspectorEnabled) {
248249
return <section className="inspector-workspace" aria-label="Inspector workspace">
249250
<header><div><strong>Inspector Workbench</strong><span>Cross-delivery evidence · read-only</span></div><p>Capability / Date → Session → Commit / File</p></header>
250-
<iframe title="Harness Inspector Workbench" src="inspector" sandbox="allow-scripts" referrerPolicy="no-referrer" />
251+
<InspectorWorkbench fallback={<iframe title="Harness Inspector Workbench" src="inspector" sandbox="allow-scripts" referrerPolicy="no-referrer" />} />
251252
</section>;
252253
}
253254
return <EmptyWorkspace eyebrow="Observed delivery" title="Connect an Inspector report" detail="Inspector requires retained, privacy-filtered evidence. It never substitutes the recorded Session Debugger fixture for a real workspace." command="--inspector ./harness-inspector.html" />;

0 commit comments

Comments
 (0)