diff --git a/docs/notes/claude-science-ui-behavior-audit.md b/docs/notes/claude-science-ui-behavior-audit.md
index 89c44113..a9e828b2 100644
--- a/docs/notes/claude-science-ui-behavior-audit.md
+++ b/docs/notes/claude-science-ui-behavior-audit.md
@@ -42,7 +42,7 @@ This is a behavior index, not a request to reproduce Claude branding. It records
| Surface | Claude Science behavior | OpenScience contract |
| -------------------- | ---------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
-| Default presentation | A step opens into a language/environment header, source, and separate output control. | Notebook and R cards auto-open so code is never hidden behind Show steps. |
+| Default presentation | A step opens into a language/environment header, source, and separate output control. | Notebook, artifact, and remote-compute cards start collapsed so completed work stays compact and expands on demand. |
| Source height | Long source remains contained inside the operation instead of dominating the transcript. | Show exactly five code lines in a vertically and horizontally scrollable source window; retain the complete source in that window. |
| Output | Text output is visually separated from source. | Text output is open by default and independently scrollable. |
| Figures | Figures appear immediately after the cell that produced them. | Inline notebook images stay visible even when text output is collapsed. |
@@ -51,15 +51,15 @@ This is a behavior index, not a request to reproduce Claude branding. It records
## 5. Compute
-| Surface | Claude Science behavior | OpenScience contract |
-| --------------- | ------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------- |
-| Host strip | Memory, CPU, live-kernel count, and running count are always visible. | Host totals combine local kernels, shell commands, and remote jobs. Unknown metrics render as unavailable, never fabricated zeroes. |
-| Project ledger | Work is grouped by owning session with a current-session marker. | Compute aggregates every session in the project and does not reset when the selected session changes. |
-| Kernel row | Language, state, age/cell count, activity label, RSS, CPU, and stop action form one dense row. | Live rows show named kernel, state/recovery text, uptime, RSS, cores, and Stop. |
-| Job row | Long-running/background work stays visible independently of chat scroll. | Shell commands and Modal/GPU jobs are first-class rows with command/target, resources, duration, status, output, artifacts, cleanup, and cancel. |
-| Completed work | Claude commonly leaves idle kernels visible. | OpenScience intentionally stops finished kernels, then keeps up to five recent local completion rows so the trail remains without wasting compute. |
-| Manual creation | Claude exposes environment setup as part of agent work, not a user kernel launcher in the completed session. | Do not expose manual kernel creation. Kernels start only when an agent executes work. |
-| Cleanup | Claude exposes stop/kill per kernel but may leave kernels idle. | The research agent must stop every named kernel after outputs and artifacts are verified. Remote cleanup warnings remain visible. |
+| Surface | Claude Science behavior | OpenScience contract |
+| --------------- | ------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------ |
+| Host strip | Memory, CPU, live-kernel count, and running count are always visible. | Host totals combine local kernels, shell commands, and remote jobs. Unknown metrics render as unavailable, never fabricated zeroes. |
+| Project ledger | Work is grouped by owning session with a current-session marker. | Compute aggregates every session in the project and does not reset when the selected session changes. |
+| Kernel row | Language, state, age/cell count, activity label, RSS, CPU, and stop action form one dense row. | Live rows show named kernel, state/recovery text, uptime, RSS, cores, and Stop. |
+| Job row | Long-running/background work stays visible independently of chat scroll. | Shell commands and Modal/GPU jobs are first-class rows with command/target, resources, duration, status, output, artifacts, cleanup, and cancel. |
+| Completed work | Claude commonly leaves idle kernels visible. | OpenScience removes completed, stopped, and killed local kernels from Compute; their source, results, and artifacts remain in chat and Files. |
+| Manual creation | Claude exposes environment setup as part of agent work, not a user kernel launcher in the completed session. | Do not expose manual kernel creation. Kernels start only when an agent executes work. |
+| Cleanup | Claude exposes stop/kill per kernel but may leave kernels idle. | The research agent must stop every named kernel after outputs and artifacts are verified. Remote cleanup warnings remain visible. |
## 6. Files and artifacts
@@ -98,7 +98,7 @@ OpenScience's research prompt now requires at least two decision-useful figures
## 9. Intentional OpenScience differences
-- Finished kernels are stopped automatically rather than left idle. Recent completion rows preserve visibility without retaining memory.
+- Finished kernels are stopped automatically rather than left idle, and disappear from Compute once they are no longer live.
- Manual kernel creation is removed. The execution ledger describes real work; it is not a launcher.
- Compute also includes shell subprocesses and Modal/GPU jobs, which the reference surface did not expose in this exact local run.
- Project-wide Files and Compute remain stable while sessions switch, matching the requested cross-session workspace model.
@@ -108,13 +108,13 @@ OpenScience's research prompt now requires at least two decision-useful figures
- [x] Exact prompt starts exactly four named managed kernels.
- [x] Four kernel rows are visible in Compute during execution.
- [x] Python source and output remain visible in chat while working and after completion.
-- [x] Source is auto-open but capped to a five-line scroll window.
+- [x] Computed cards start collapsed; expanded source is capped to a five-line scroll window.
- [x] Figures display inline beside their producing cells.
- [x] Saved report, tables, and figures auto-appear in Files.
- [x] Artifact titles are meaningful and previews open beside chat.
- [x] Failed analysis is visible and can be retried without losing history.
- [x] Every named kernel stops after result verification.
-- [x] Recent completed local work remains visible in Compute.
+- [x] Completed, stopped, and killed local kernels disappear from Compute.
- [x] Modal/GPU jobs have live and recent-result rows with resources, logs, artifacts, cancel, and cleanup state.
- [x] The right workspace remains project-scoped across session changes.
- [x] Compute and artifact cards adapt at narrow container widths.
diff --git a/frontend/ui/src/components/message-part-artifact.test.ts b/frontend/ui/src/components/message-part-artifact.test.ts
index f006cc19..045fa94b 100644
--- a/frontend/ui/src/components/message-part-artifact.test.ts
+++ b/frontend/ui/src/components/message-part-artifact.test.ts
@@ -6,6 +6,10 @@ const source = () => readFileSync(fileURLToPath(new URL("./message-part.tsx", im
test("saved workspace artifacts render previewable, openable results", () => {
const part = source()
+ const artifact = part.slice(
+ part.indexOf("function SavedArtifactTool"),
+ part.indexOf('ToolRegistry.register({\n name: "artifact"'),
+ )
expect(part).toContain('name: "artifact"')
expect(part).toContain('data-component="saved-artifact-tool"')
@@ -16,12 +20,18 @@ test("saved workspace artifacts render previewable, openable results", () => {
expect(part).toContain("data.openFile?.(artifact().path)")
expect(part).toContain("Open beside chat")
expect(part).toContain("Show save receipt")
+ expect(artifact).not.toContain("defaultOpen")
})
-test("Modal and compute job results use a dedicated visible renderer", () => {
+test("Modal and compute job results use a dedicated compact renderer", () => {
const part = source()
+ const remote = part.slice(
+ part.indexOf("function RemoteComputeTool"),
+ part.indexOf('ToolRegistry.register({ name: "modal"'),
+ )
expect(part).toContain('name: "modal"')
expect(part).toContain('name: "compute_job"')
expect(part).toContain('title: props.tool === "modal" ? "Modal compute" : "Remote compute result"')
+ expect(remote).not.toContain("defaultOpen")
})
diff --git a/frontend/ui/src/components/message-part-notebook.test.ts b/frontend/ui/src/components/message-part-notebook.test.ts
index e0849ae5..ae8f6218 100644
--- a/frontend/ui/src/components/message-part-notebook.test.ts
+++ b/frontend/ui/src/components/message-part-notebook.test.ts
@@ -5,8 +5,12 @@ import { fileURLToPath } from "node:url"
const source = () => readFileSync(fileURLToPath(new URL("./message-part.tsx", import.meta.url)), "utf8")
const styles = () => readFileSync(fileURLToPath(new URL("./message-part.css", import.meta.url)), "utf8")
-test("notebook tools open source, text output, and figures by default", () => {
+test("notebook tools keep complete source, output, and figures behind a compact summary", () => {
const part = source()
+ const kernel = part.slice(
+ part.indexOf("function KernelTool"),
+ part.indexOf('ToolRegistry.register({\n name: "notebook"'),
+ )
expect(part).toContain('name: "notebook"')
expect(part).toContain('name: "rkernel"')
@@ -20,6 +24,7 @@ test("notebook tools open source, text output, and figures by default", () => {
expect(part).toContain('props.input.action === "stop"')
expect(part).toContain('trigger={{ title: "Kernel stopped"')
expect(part).toContain('title: props.status === "completed" ? "Computed" : "Computing"')
+ expect(kernel).not.toContain("defaultOpen")
expect(styles()).toContain("max-height: calc(5 * 1.55em + 20px)")
expect(styles()).toContain("overflow: auto")
})
diff --git a/frontend/ui/src/components/message-part.tsx b/frontend/ui/src/components/message-part.tsx
index 1abecd2d..3b655eeb 100644
--- a/frontend/ui/src/components/message-part.tsx
+++ b/frontend/ui/src/components/message-part.tsx
@@ -830,7 +830,6 @@ function KernelTool(props: ToolProps & { language: "python" | "r"; label: "Pytho
return (
{
- if (!value) return "—"
- const seconds = Math.max(0, Math.floor((Date.now() - value) / 1_000))
- if (seconds < 60) return `${seconds}s ago`
- const minutes = Math.floor(seconds / 60)
- if (minutes < 60) return `${minutes}m ago`
- const hours = Math.floor(minutes / 60)
- if (hours < 24) return `${hours}h ago`
- return `${Math.floor(hours / 24)}d ago`
-}
-
-export function KernelResultCard(props: { kernel: KernelStatus }): JSX.Element {
- return (
-
-