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 ( -
-
- -
- {kernelLabel(props.kernel)} - -