From d7c3a95368e3eb4813a1da41bc098e8072e2e186 Mon Sep 17 00:00:00 2001 From: Luke Parker <10430890+Hona@users.noreply.github.com> Date: Sun, 23 Aug 2026 21:31:40 +1000 Subject: [PATCH] feat(app): refine expanded shell output (#44368) --- bun.lock | 1 + .../app/e2e/smoke/session-timeline.spec.ts | 3 +- packages/app/package.json | 1 + packages/app/src/index.css | 8 ++++ packages/app/vite.config.ts | 1 + .../src/components/message-part.css | 37 ++++++++++++++++++- .../src/storybook/current-session-fixtures.ts | 23 ++++++++++++ .../src/timeline/terminal-work.stories.tsx | 13 +++++++ .../session-ui/src/tools/tool-renderer.tsx | 22 ++++------- packages/ui/src/styles/tokens/theme.css | 1 + 10 files changed, 92 insertions(+), 18 deletions(-) diff --git a/bun.lock b/bun.lock index 850ca79e8c6..08319ccb7d8 100644 --- a/bun.lock +++ b/bun.lock @@ -54,6 +54,7 @@ "name": "@opencode-ai/app", "version": "1.18.15", "dependencies": { + "@ibm/plex": "6.4.1", "@corvu/drawer": "catalog:", "@dnd-kit/abstract": "0.5.0", "@dnd-kit/dom": "0.5.0", diff --git a/packages/app/e2e/smoke/session-timeline.spec.ts b/packages/app/e2e/smoke/session-timeline.spec.ts index 9fd2be7b744..b69307357b5 100644 --- a/packages/app/e2e/smoke/session-timeline.spec.ts +++ b/packages/app/e2e/smoke/session-timeline.spec.ts @@ -358,7 +358,8 @@ test.describe("smoke: session timeline", () => { const shellTrigger = shell.locator('[data-slot="collapsible-trigger"]') const shellSubtitle = shell.locator('[data-slot="basic-tool-tool-subtitle"]') await expect(shellSubtitle).toHaveCount(0) - await expect(shell.locator('[data-slot="bash-pre"]')).toContainText("$ bun typecheck") + await expect(shell.locator('[data-slot="bash-command"]')).toHaveText("bun typecheck") + await expect(shell.locator('[data-slot="bash-result"]')).not.toContainText("bun typecheck") await shellTrigger.click() await expect(shellTrigger).toHaveAttribute("aria-expanded", "false") await expect(shellSubtitle).toHaveText("bun typecheck") diff --git a/packages/app/package.json b/packages/app/package.json index 7749279f6e3..2e91c6d043e 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -52,6 +52,7 @@ "vite-plugin-solid": "2.11.14" }, "dependencies": { + "@ibm/plex": "6.4.1", "@corvu/drawer": "catalog:", "@dnd-kit/abstract": "0.5.0", "@dnd-kit/dom": "0.5.0", diff --git a/packages/app/src/index.css b/packages/app/src/index.css index cfade3c868a..e0751bfb389 100644 --- a/packages/app/src/index.css +++ b/packages/app/src/index.css @@ -17,6 +17,14 @@ font-style: normal; } +@font-face { + font-family: "IBM Plex Mono"; + src: url("@ibm/plex/IBM-Plex-Mono/fonts/complete/woff2/IBMPlexMono-Text.woff2") format("woff2"); + font-weight: 440; + font-style: normal; + font-display: swap; +} + @media (display-mode: standalone) { /* WebKit excludes safe-area insets from dvh in installed apps. */ #root { diff --git a/packages/app/vite.config.ts b/packages/app/vite.config.ts index 78200f591c6..11cbdd704de 100644 --- a/packages/app/vite.config.ts +++ b/packages/app/vite.config.ts @@ -43,6 +43,7 @@ export default defineConfig({ "web-app-manifest*", "assets/index-*.{js,css}", "assets/session-*.js", + "assets/IBMPlexMono-Text-*.woff2", "assets/Inter.ttf", "assets/JetBrainsMonoNerdFontMono-Regular.woff2", ], diff --git a/packages/session-ui/src/components/message-part.css b/packages/session-ui/src/components/message-part.css index dc14f3ea855..15e2830d284 100644 --- a/packages/session-ui/src/components/message-part.css +++ b/packages/session-ui/src/components/message-part.css @@ -419,11 +419,44 @@ overflow-wrap: anywhere; } - [data-slot="bash-prompt"] { - user-select: none; + &[data-variant="shell"] { + background: var(--v2-background-bg-base); + + [data-slot="bash-pre"] { + padding: 0; + } + + [data-slot="bash-pre"] code { + font-family: var(--v2-font-family-code); + font-weight: 440; + line-height: var(--line-height-base); + letter-spacing: -0.04px; + } + + [data-slot="bash-command"], + [data-slot="bash-result"] { + display: block; + padding: 12px; + } + + [data-slot="bash-result"] { + border-block-start: 0.5px solid var(--v2-border-border-base); + background: var(--v2-background-bg-layer-01); + color: var(--v2-text-text-muted); + } + + [data-slot="bash-command"] { + color: var(--v2-text-text-base); + } } } +[data-component="collapsible"].tool-collapsible:has( + > [data-slot="collapsible-content"] [data-component="bash-output"][data-variant="shell"] + ) { + --tool-content-gap: 6px; +} + [data-component="edit-trigger"], [data-component="write-trigger"] { display: flex; diff --git a/packages/session-ui/src/storybook/current-session-fixtures.ts b/packages/session-ui/src/storybook/current-session-fixtures.ts index f4c42b3ea8c..04ee7a33282 100644 --- a/packages/session-ui/src/storybook/current-session-fixtures.ts +++ b/packages/session-ui/src/storybook/current-session-fixtures.ts @@ -507,6 +507,29 @@ export const terminalPassedDocument = document([ }), ] satisfies SessionMessageInfo[]) +export const expandedShellDocument = document([ + user("msg_user_shell_expanded", "Inspect the active service and its available feature surface.", 52_100), + assistant({ + id: "msg_assistant_shell_expanded", + offset: 52_200, + completed: 52_900, + content: [ + completedTool({ + id: "tool_shell_expanded", + name: "shell", + offset: 52_300, + args: { + command: + "opencode2 api get /openapi.json > /private/var/folders/j/gd69b2|16y91666jzf3p9g22asdasc0000gn/T/opencode/temp/tes", + }, + output: + '{"location":{"directory":"/Users/usrnk1","project":{"id":"global","directory":"/","canonical":"/"}},"data":[{"id":"opencode","name":"OpenCode","description":"Use this skill for any question about OpenCode itself, including how OpenCode works, using or configuring it, migrating from V1 to V2, troubleshooting it, developing plugins or integrations, using the OpenCode SDK, clients, server, or API, and contributing to the OpenCode codebase."}]}', + metadata: { exit: 0 }, + }), + ], + }), +] satisfies SessionMessageInfo[]) + export const terminalFailedDocument = document([ user("msg_user_terminal_failed", "Run the focused Session UI tests.", 53_000), assistant({ diff --git a/packages/session-ui/src/timeline/terminal-work.stories.tsx b/packages/session-ui/src/timeline/terminal-work.stories.tsx index d444a76dbd1..d5c4565df7b 100644 --- a/packages/session-ui/src/timeline/terminal-work.stories.tsx +++ b/packages/session-ui/src/timeline/terminal-work.stories.tsx @@ -1,5 +1,6 @@ import { CurrentSessionTimelineStory } from "../storybook/current-session-story" import { + expandedShellDocument, recoveryDocument, standaloneShellCompletedDocument, standaloneShellRunningDocument, @@ -72,6 +73,18 @@ export const TestsPassed = { ), } +export const ExpandedShell = { + render: () => ( + + ), +} + export const TestFailed = { render: () => ( +
{ - const out = stripAnsi(props.output ?? "").replace(/\r\n?/g, "\n") - return `${command()}${out ? "\n\n" + out : ""}` - }) + const output = createMemo(() => stripAnsi(props.output ?? "").replace(/\r\n?/g, "\n")) return (
- + )} > - - - {text()} + + {command()} + {(value) => {value()}} ) diff --git a/packages/ui/src/styles/tokens/theme.css b/packages/ui/src/styles/tokens/theme.css index 6f17dcb8076..ba414282346 100644 --- a/packages/ui/src/styles/tokens/theme.css +++ b/packages/ui/src/styles/tokens/theme.css @@ -131,6 +131,7 @@ --font-family-text: "Inter", sans-serif; --v2-font-family-sans: "Inter", sans-serif; + --v2-font-family-code: "IBM Plex Mono", var(--font-family-mono); --line-height-tight: 12px; --line-height-compact: 16px; --line-height-base: 20px;