Skip to content

Commit e61beac

Browse files
phodalcodex
andcommitted
feat(studio): browse workspace artifacts
Derive a confined artifact catalog from retained workspace evidence and present it in a date/file-scoped three-pane workbench. Keep ordinary source read-only, reserve execution for explicit Canvas TSX, and preserve native plus provisioned renderer boundaries. Spec: docs/specs/2026-08-24-studio-artifact-workspace-browser.md Test: npm test (247); npm run typecheck; npm run test:browser (39); doc-link graph (8) Co-authored-by: Codex (GPT 5.6 Sol) <codex@openai.com>
1 parent f960910 commit e61beac

32 files changed

Lines changed: 1619 additions & 385 deletions
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
# Browse workspace artifacts by date and file
2+
3+
## Traceability
4+
5+
- Spec ID: studio-artifact-workspace-browser
6+
- Status: Ready for Review
7+
8+
## Intent
9+
10+
Make Artifacts a first-class view of the currently open project workspace. A
11+
reviewer should not have to select one Session before learning which outputs
12+
local coding agents changed or delivered. Studio should aggregate the existing,
13+
workspace-confined files named by retained `change` and `deliver` observations,
14+
let the reviewer navigate that set by Date or Files, show the matching Artifact
15+
set in an adjacent pane, and render the selected current revision through the
16+
existing Artifact View/Canvas boundary.
17+
18+
The open project remains the single workspace authority. Date is observation
19+
context, not retained file history: the Artifact renderer continues to show the
20+
current exact bytes and revision digest from the workspace.
21+
22+
## Acceptance Scenarios
23+
24+
- **AC-1:** Opening or replacing a project workspace derives one Artifact set
25+
from existing files referenced by retained Session `change` or `deliver`
26+
events. The set is available without selecting a Session, is confined to the
27+
canonical workspace root, and rejects symlinks, hard links, directories,
28+
missing files, path traversal, and absolute paths outside that root.
29+
- **AC-2:** `/api/artifacts` continues to satisfy the public
30+
`HarnessStudioArtifactCatalogV2` contract and, for a workspace-owned set,
31+
adds a Studio-owned navigation projection mapping Artifact ids to bounded
32+
Session/date observations. Revision-scoped content, snapshot, build, and
33+
hosted-view routes resolve only through the same confined set. A CLI-provided
34+
Artifact directory remains a compatibility authority when no project
35+
workspace is open.
36+
- **AC-3:** Artifacts opens directly into a docked three-pane workbench on wide
37+
screens: scope navigator on the left, matching Artifact rows in the middle,
38+
and the existing Artifact View host on the right. The newest available scope
39+
and its first Artifact are selected by default; there is no `Select a Session
40+
first` gate.
41+
- **AC-4:** Date mode groups observations by day and lists the Sessions that
42+
produced existing Artifacts. Files mode presents the same Artifact set as a
43+
hierarchical path tree. Selecting a day, Session, directory, or file updates
44+
only the adjacent scope; selecting an Artifact updates the renderer. Search
45+
filters the middle Artifact list without changing catalog authority.
46+
- **AC-5:** The UI labels the preview as the current workspace revision and
47+
exposes the observed Session/provider/time separately. It does not claim that
48+
prior file bytes, cross-Session Artifact identity, or a revision chain were
49+
retained.
50+
- **AC-6:** The title bar owns one project-wide **Change workspace** action.
51+
Replacing or disconnecting the workspace clears the previous Artifact
52+
authority and navigation selection. Sessions does not duplicate the change
53+
action in its local footer.
54+
- **AC-7:** At 1440x900, 1024x768, and 390x844, the primary Artifact question
55+
and selected preview remain reachable, document-level horizontal overflow is
56+
absent, keyboard focus is visible, list/tree controls have accessible names,
57+
and browser console/page errors are empty in meaningful populated states.
58+
- **AC-8:** Observing a source file does not make it executable. Ordinary
59+
`.tsx` and `.jsx` files use the read-only Source surface and have no build
60+
reference. Only the explicit `*.canvas.tsx` format enters Studio's React
61+
Preview or a provider-hosted Canvas lane. Studio-owned Mermaid Preview keeps
62+
its renderer dependency closure separate from workspace package imports and
63+
works when the open workspace itself contains `node_modules`.
64+
- **AC-9:** Recognized text source is syntax-highlighted and padded inside the
65+
editor boundary. Unrecognized extensions are sniffed from a bounded byte
66+
sample: valid text receives the plain Source surface while binary bytes stay
67+
unavailable. A compile or runtime failure automatically exposes the exact
68+
highlighted Source revision without hiding the failure status or Retry.
69+
70+
## Non-goals
71+
72+
- Scanning every file in the project and calling it an Artifact.
73+
- Retaining historical Artifact bytes, reconstructing old Session outputs, or
74+
adding cross-Session Artifact Thread identity.
75+
- Editing, writing back, annotating, or resuming a Session from Artifact View.
76+
- Changing adapter, renderer, Provider, Canvas, sandbox, or content-security
77+
selection policy beyond restricting executable TSX to the explicit Canvas
78+
format and completing the built-in Mermaid renderer dependency closure.
79+
- Adding a second directory chooser or an Artifact-specific workspace.
80+
81+
## Plan and Tasks
82+
83+
1. Derive normalized Artifact observations from workspace Session projections,
84+
bind them to the canonical workspace root, and retain only server-private
85+
paths plus privacy-safe Session metadata.
86+
2. Allow the Artifact catalog indexer and revision routes to resolve an explicit
87+
bounded list of nested workspace-relative files while preserving the legacy
88+
single-directory behavior.
89+
3. Add a Studio-owned navigation projection to the workspace catalog and expose
90+
the workspace Artifact count through configuration/navigation status.
91+
4. Replace the two-pane Explorer with Date/Files scope navigation, a matching
92+
Artifact list, and the existing Artifact renderer pane. Make selection and
93+
responsive pane switching keyboard-operable.
94+
5. Move **Change workspace** to the application title bar and remove the
95+
Sessions-local duplicate.
96+
6. Add focused server/model/component/browser coverage, then run package gates,
97+
local preview smoke checks, multi-viewport visual review, and Review
98+
Readiness Check.
99+
7. Keep ordinary TSX/JSX on the native Source surface, reserve compilation for
100+
`*.canvas.tsx`, and verify the Studio-owned Mermaid renderer against a real
101+
repository root whose `node_modules` is inside the Artifact authority.
102+
8. Make Source the useful failure surface, retain diagnostics in the status
103+
boundary, sniff unknown text without decoding binary artifacts, and verify
104+
source highlighting and editor padding on representative large files.
105+
106+
## Test and Review Evidence
107+
108+
- AC-1/AC-2: focused catalog and server tests with nested paths, duplicate
109+
observations, missing files, absolute/outside paths, links, and stale
110+
revision requests.
111+
- AC-3/AC-4/AC-5/AC-6: Studio model and browser flows against a workspace
112+
provider fixture with multiple days, Sessions, directories, and renderer
113+
formats.
114+
- AC-7: Playwright/browser screenshots at 1440x900, 1024x768, and 390x844;
115+
keyboard traversal, focus, horizontal overflow, console, and page-error
116+
assertions.
117+
- AC-8: registry/catalog assertions for ordinary TSX versus Canvas TSX, a
118+
browser Source-only flow, and a real-repository Mermaid compile check that
119+
exercises `beautiful-mermaid`, `entities`, `elkjs`, React DOM, and Scheduler.
120+
- AC-9: bounded unknown-text/binary catalog tests plus browser checks for
121+
highlighted source, failed-preview fallback, padding, and selection reset.
122+
- Package gates: `npm run typecheck`, focused Vitest, full
123+
`packages/harness-studio` tests, preview `/health` and `/canvas-module.js`,
124+
`git diff --check`, and the Markdown doc-link graph after regenerating it.
125+
126+
### Current receipts
127+
128+
- `packages/harness-studio`: `npm test` — 40 files, 247 tests passed.
129+
- Playwright: full Harness Studio browser suite — 39 tests passed, including
130+
Artifact Host native fallbacks, provider-hosted renderers, and all Artifact
131+
Workspace wide/compact/narrow cases.
132+
- Live browser: the 159 KB `workbench.css` reached highlighted state with
133+
padded source layout; the repository Mermaid graph with YAML front matter
134+
rendered successfully; no document-level overflow was observed at the three
135+
acceptance viewports.
136+
- Preview smoke: existing `127.0.0.1:58575` process returned `ok` from
137+
`/health` and JavaScript from `/canvas-module.js`.
138+
- Documentation routing: regenerated `docs/better-harness-doc-links.mmd`; all
139+
8 doc-link graph tests passed.
140+
141+
### Risks
142+
143+
- **Evidence paths are advisory:** retained Session resources may be stale or
144+
malformed. Re-resolve every candidate against the canonical workspace and
145+
index only its current regular-file bytes.
146+
- **Date can be misread as history:** keep observed time on the navigation row
147+
and current revision in the preview header; do not present a revision
148+
timeline without retained bytes.
149+
- **Large workspaces:** deduplicate observations before hashing, keep the
150+
existing Session bound, and index only changed/delivered paths rather than
151+
recursively walking the project.
152+
- **Concurrent dirty worktree:** the pre-existing Commit-view files and their
153+
overlapping stylesheet edits are outside this scope. Preserve those changes
154+
and edit only the Artifact-owned CSS region.
155+
- **Executable-source confusion:** file extension is not evidence that code is
156+
intended to run. Require the explicit Canvas suffix before attaching a build
157+
route; keep built-in renderer dependencies on a separate trusted closure.

packages/harness-studio/scripts/inspector-workspace-provider.mjs

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,19 @@ function debuggerProjection(summary, identity) {
152152
}
153153
for (const [index, call] of calls.entries()) {
154154
const kind = debuggerKind(call.family);
155+
const resources = [...new Set([
156+
...(Array.isArray(call.filePaths) ? call.filePaths : []),
157+
call.filePath,
158+
].filter((value) => typeof value === "string" && value.trim() !== ""))];
159+
const projectedCalls = (resources.length === 0 ? [undefined] : resources).map((resource, resourceIndex) => ({
160+
id: resourceIndex === 0 ? call.id || `tool_${index + 1}` : `${call.id || `tool_${index + 1}`}_${resourceIndex + 1}`,
161+
name: call.toolName || "Unknown tool",
162+
summary: call.actionLabel || "Observed tool call",
163+
input: call.detail || "Input not retained in the privacy-safe Inspector projection.",
164+
output: call.status === "failed" ? "Inspector observed a failed call." : "Result payload not retained in the summary projection.",
165+
duration: Number.isFinite(call.durationMs) ? `${call.durationMs} ms` : "not retained",
166+
...(resource === undefined ? {} : { resource }),
167+
}));
155168
events.push(debuggerEvent({
156169
id: `tool_${index + 1}`,
157170
kind,
@@ -163,15 +176,7 @@ function debuggerProjection(summary, identity) {
163176
rpcId: `t${index + 1}`,
164177
direction: "Agent → Client",
165178
method: "session/tool-call",
166-
toolCalls: [{
167-
id: call.id || `tool_${index + 1}`,
168-
name: call.toolName || "Unknown tool",
169-
summary: call.actionLabel || "Observed tool call",
170-
input: call.detail || "Input not retained in the privacy-safe Inspector projection.",
171-
output: call.status === "failed" ? "Inspector observed a failed call." : "Result payload not retained in the summary projection.",
172-
duration: Number.isFinite(call.durationMs) ? `${call.durationMs} ms` : "not retained",
173-
...(call.filePath ? { resource: call.filePath } : {}),
174-
}],
179+
toolCalls: projectedCalls,
175180
}));
176181
}
177182
for (const [index, turn] of responses.entries()) {

0 commit comments

Comments
 (0)