diff --git "a/piweb/hf-design.patch" "b/piweb/hf-design.patch" --- "a/piweb/hf-design.patch" +++ "b/piweb/hf-design.patch" @@ -1,8 +1,16 @@ diff --git a/AGENTS.md b/AGENTS.md -index a5c8983..e9a1459 100644 +index a5c8983..0f7f118 100644 --- a/AGENTS.md +++ b/AGENTS.md -@@ -174,7 +174,8 @@ lib/ +@@ -138,6 +138,7 @@ lib/ + extension-ui-queue.ts FIFO queues for extension dialogs and custom panels, by request id; isBlockingExtensionUiRequest() (which method a run waits on) + markdown.ts shared markdown helpers + markdown-blocks.ts splitMarkdownBlocks(): where a streaming reply may be cut into blocks parsed apart ++ step-summary.ts a turn's steps counted by kind (ran, read, edited, …) for their one summary line above the answer + highlight-queue.ts code fences past a task's highlight budget get their colors in idle slices + prism-languages.ts loadPrismLanguages(): every Prism language in a chunk of its own, loaded when a highlighter first needs it + gfm-autolink-email-loader.cjs bundler loader: remark-gfm's email regex without a lookbehind literal +@@ -174,7 +175,8 @@ lib/ components/ AppShell.tsx layout, URL state, tab management SessionSidebar.tsx toolbar row (Sessions | Files tabs, or the Pi Web brand with the files below them; new, search): tree, archive, menus, toast; files section (resizable, foldable; one header row with the file keys and changed-files count, the picker's row) or tab (picker boxes + file buttons), FileExplorer @@ -12,7 +20,7 @@ index a5c8983..e9a1459 100644 SessionTree.tsx virtualized rows of the session tree and the archive view SidebarMenu.tsx sidebar popup menu (bottom sheet on phones), portaled to body: filter field, item secondary actions, the files tab's classic look ProjectWorktreePicker.tsx project and worktree boxes + menus: the files' (one row below the sessions, stacked in the tab) and the new-session bar's -@@ -274,3 +275,14 @@ Location: `~/.pi/agent/sessions//_.jsonl` +@@ -274,3 +276,20 @@ Location: `~/.pi/agent/sessions//_.jsonl` --accent --user-bg --tool-bg --font-mono ``` @@ -27,6 +35,12 @@ index a5c8983..e9a1459 100644 + +Its light and dark values are written by the design system's `design/build.py` between +`tokens:begin` and `tokens:end`; `--border` is transparent (fills tell surfaces apart). ++The same block defines the scales components use instead of literals: `--radius-xs` … `--radius-xl` ++(4, 6, 8, 10, 12px), `--size-control-lg` / `-control` / `-control-sm` / `-control-xs` (36, 32, 28, ++24px, Claude Desktop's button heights), `--size-row`, `--size-bar` and `--space-*`. Buttons are the ++`.ui-btn` classes in `app/globals.css` (`--sm`, `--xs`, `--lg`, `--icon`, `--primary`, …); the ++transcript's pieces (`.turn-user`, `.turn-reply`, `.steps-summary`, `.tool-call-row`, `.composer`) ++are styled there too. diff --git a/app/favicon.ico b/app/favicon.ico index 2da6b7dc00c64c59ef23cff153a6051478ad9315..b407f41443e3e94f4204a1862227e0d457f7d823 100644 GIT binary patch @@ -3066,10 +3080,10 @@ index 0000000..8d003fe +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/app/globals.css b/app/globals.css -index d6b5535..401412e 100644 +index d6b5535..69eff6c 100644 --- a/app/globals.css +++ b/app/globals.css -@@ -24,46 +24,80 @@ +@@ -24,46 +24,107 @@ --font-mono-font: var(--font-mono); } @@ -3170,15 +3184,42 @@ index d6b5535..401412e 100644 + --border: transparent; + --shadow-pop: 0 12px 36px rgba(0, 0, 0, 0.6), 0 2px 8px rgba(0, 0, 0, 0.4); +} ++ ++:root { ++ --space-0\.5: 2px; ++ --space-1: 4px; ++ --space-1\.5: 6px; ++ --space-2: 8px; ++ --space-3: 12px; ++ --space-4: 16px; ++ --space-5: 20px; ++ --space-6: 24px; ++ --space-8: 32px; ++ --space-12: 48px; ++ --radius-xs: 4px; ++ --radius-sm: 6px; ++ --radius-md: 8px; ++ --radius-lg: 10px; ++ --radius-xl: 12px; ++ --radius-full: 999px; ++ --size-bar: 36px; ++ --size-row: 28px; ++ --size-control-lg: 36px; ++ --size-control: 32px; ++ --size-control-sm: 28px; ++ --size-control-xs: 24px; ++ --size-sidebar: 260px; ++ --size-content: 720px; ++} +/* tokens:end */ + +:root { -+ --chat-content-max-width: 820px; ++ --chat-content-max-width: 720px; + --chat-content-font-size: 14px; } [data-theme="mist"] { -@@ -72,7 +106,7 @@ html.dark, +@@ -72,7 +133,7 @@ html.dark, --bg-panel: #e9f0ee; --bg-hover: #e0eae7; --bg-selected: #d7e4df; @@ -3187,7 +3228,7 @@ index d6b5535..401412e 100644 --text: #202e2b; --text-muted: #455f56; --text-dim: #52685f; -@@ -83,6 +117,18 @@ html.dark, +@@ -83,6 +144,18 @@ html.dark, --assistant-bg: #f4f8f7; --tool-bg: #ecf3f0; --bg-subtle: rgba(24,65,50,0.04); @@ -3206,7 +3247,7 @@ index d6b5535..401412e 100644 } [data-theme="rose"] { -@@ -91,7 +137,7 @@ html.dark, +@@ -91,7 +164,7 @@ html.dark, --bg-panel: #f3edef; --bg-hover: #eee3e7; --bg-selected: #e8dce1; @@ -3215,7 +3256,7 @@ index d6b5535..401412e 100644 --text: #34282e; --text-muted: #65505a; --text-dim: #705b65; -@@ -102,6 +148,18 @@ html.dark, +@@ -102,6 +175,18 @@ html.dark, --assistant-bg: #fcf7f8; --tool-bg: #f7eef2; --bg-subtle: rgba(89,39,62,0.04); @@ -3234,7 +3275,7 @@ index d6b5535..401412e 100644 } html[data-theme="pine"], -@@ -111,7 +169,7 @@ html[data-theme="pine"], +@@ -111,7 +196,7 @@ html[data-theme="pine"], --bg-panel: #212b28; --bg-hover: #2b3632; --bg-selected: #35433c; @@ -3243,7 +3284,7 @@ index d6b5535..401412e 100644 --text: #e6ede8; --text-muted: #c3d0c6; --text-dim: #afc2b5; -@@ -122,6 +180,18 @@ html[data-theme="pine"], +@@ -122,6 +207,18 @@ html[data-theme="pine"], --assistant-bg: #19201f; --tool-bg: #222d29; --bg-subtle: rgba(210,236,219,0.04); @@ -3262,7 +3303,7 @@ index d6b5535..401412e 100644 } /* WebKit reports dvh/innerHeight without the status-bar strip for installed -@@ -135,28 +205,12 @@ html[data-theme="pine"], +@@ -135,28 +232,12 @@ html[data-theme="pine"], } } @@ -3295,7 +3336,7 @@ index d6b5535..401412e 100644 } /* Theme toggle: circular wipe via View Transitions API. -@@ -218,9 +272,9 @@ body { +@@ -218,9 +299,9 @@ body { margin-bottom: 22px; } @@ -3307,7 +3348,52 @@ index d6b5535..401412e 100644 } .web-login-brand h1 { -@@ -703,9 +757,9 @@ button.extension-widget-trigger:focus-visible { +@@ -247,7 +328,7 @@ body { + min-width: 0; + padding: 10px 10px 10px 14px; + border: 1px solid color-mix(in srgb, var(--border) 70%, transparent); +- border-radius: 14px; ++ border-radius: var(--radius-xl); + background: var(--bg); + box-shadow: 0 1px 2px rgba(15, 23, 42, 0.04), 0 8px 24px -12px rgba(15, 23, 42, 0.10); + transition: border-color 0.15s, box-shadow 0.15s; +@@ -307,7 +388,7 @@ html.dark .web-login-error { + min-height: 32px; + padding: 7px 14px; + border: none; +- border-radius: 8px; ++ border-radius: var(--radius-md); + background: var(--accent); + color: var(--accent-contrast); + cursor: pointer; +@@ -388,7 +469,7 @@ html.dark .web-login-error { + flex-shrink: 0; + padding: 0 8px; + border: 1px solid #343a46; +- border-radius: 5px; ++ border-radius: var(--radius-sm); + background: transparent; + color: #9ca3af; + cursor: pointer; +@@ -502,7 +583,7 @@ html[data-keyboard-open] .terminal-extra-keys { + background-color: color-mix(in srgb, var(--text-dim) 45%, transparent); + background-clip: padding-box; + border: 2px solid transparent; +- border-radius: 5px; ++ border-radius: var(--radius-sm); + } + /* Only ever set background-color here: the `background` shorthand resets + background-clip and would snap the thumb to the full 10px on hover. */ +@@ -538,7 +619,7 @@ html[data-keyboard-open] .terminal-extra-keys { + } + ::-webkit-scrollbar-thumb { + border-width: 1px; +- border-radius: 3px; ++ border-radius: var(--radius-xs); + } + } + +@@ -703,9 +784,9 @@ button.extension-widget-trigger:focus-visible { .extension-widget-placement-icon { display: block; @@ -3320,7 +3406,7 @@ index d6b5535..401412e 100644 fill: currentColor; } -@@ -826,8 +880,8 @@ button.extension-widget-trigger:focus-visible { +@@ -826,8 +907,8 @@ button.extension-widget-trigger:focus-visible { } :root { @@ -3331,12 +3417,32 @@ index d6b5535..401412e 100644 --font-ui: var(--font-ui-default); --font-mono: var(--font-mono-default); --font-ui-weight: 400; -@@ -908,12 +962,11 @@ pre, code { +@@ -883,11 +964,11 @@ pre, code { + .markdown-body ol > li { list-style-type: decimal; } + .markdown-body li > p { margin: 0; } + .markdown-body li::marker { +- color: color-mix(in srgb, var(--accent) 72%, var(--text-muted)); +- font-weight: 600; ++ color: var(--text-dim); ++ font-weight: 500; + } + .markdown-body strong { +- color: color-mix(in srgb, var(--text) 88%, var(--accent)); ++ color: var(--text-strong); + font-weight: 700; + } + .markdown-body em { color: var(--text-muted); } +@@ -904,16 +985,15 @@ pre, code { + max-width: 100%; + height: auto; + margin: 8px 0; +- border-radius: 6px; ++ border-radius: var(--radius-sm); } .markdown-body .markdown-inline-code { background: var(--bg-subtle); - border-radius: 5px; -+ border-radius: 4px; ++ border-radius: var(--radius-xs); padding: 1px 5px; color: var(--text); font-family: var(--font-mono); @@ -3345,24 +3451,42 @@ index d6b5535..401412e 100644 } .markdown-body .contains-task-list { padding-left: 0; -@@ -967,8 +1020,8 @@ pre, code { +@@ -935,7 +1015,7 @@ pre, code { + height: 14px; + margin: 0; + border: 1px solid var(--border); +- border-radius: 4px; ++ border-radius: var(--radius-xs); + background: var(--bg); + display: inline-grid; + place-content: center; +@@ -967,8 +1047,8 @@ pre, code { margin: 8px 0; overflow-x: auto; overflow-y: hidden; - border: 1px solid var(--border); - border-radius: 7px; -+ border-radius: 10px; ++ border-radius: var(--radius-lg); + background: var(--bg-surface); } .markdown-body table { border-collapse: separate; -@@ -1108,16 +1161,13 @@ pre, code { +@@ -1088,7 +1168,7 @@ pre, code { + padding: 7px 8px; + overflow: auto; + border: 1px solid var(--border); +- border-radius: 6px; ++ border-radius: var(--radius-sm); + background: var(--bg-panel); + font-family: var(--font-mono); + font-size: 11px; +@@ -1108,16 +1188,13 @@ pre, code { min-width: 0; max-width: 100%; margin: 6px 0; - border: 1px solid var(--border); - border-radius: 7px; -+ border-radius: 10px; ++ border-radius: var(--radius-lg); overflow: hidden; - background: var(--bg); - box-shadow: 0 1px 0 color-mix(in srgb, var(--border) 42%, transparent); @@ -3376,7 +3500,70 @@ index d6b5535..401412e 100644 color: var(--text-dim); font-size: 11px; display: flex; -@@ -1503,12 +1553,11 @@ pre, code { +@@ -1137,7 +1214,7 @@ pre, code { + } + .markdown-code-action { + border: 1px solid var(--border); +- border-radius: 5px; ++ border-radius: var(--radius-sm); + padding: 2px 7px; + background: var(--bg); + color: var(--text-muted); +@@ -1228,7 +1305,7 @@ pre, code { + max-width: 100%; + max-height: 100%; + object-fit: contain; +- border-radius: 8px; ++ border-radius: var(--radius-md); + box-shadow: 0 20px 60px rgba(0, 0, 0, 0.4); + } + +@@ -1244,7 +1321,7 @@ pre, code { + height: 36px; + padding: 0; + border: 1px solid var(--border); +- border-radius: 6px; ++ border-radius: var(--radius-sm); + background: var(--bg-panel); + color: var(--text-muted); + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.28); +@@ -1320,7 +1397,7 @@ pre, code { + height: 24px; + overflow: hidden; + border: 1px solid var(--border); +- border-radius: 5px; ++ border-radius: var(--radius-sm); + background: var(--bg); + } + .mermaid-zoom-stepper button, +@@ -1347,7 +1424,7 @@ pre, code { + .mermaid-zoom-icon-button { + flex: 0 0 24px; + border: 1px solid var(--border); +- border-radius: 5px; ++ border-radius: var(--radius-sm); + } + .mermaid-zoom-stepper button:hover:not(:disabled), + .mermaid-zoom-icon-button:hover { +@@ -1384,7 +1461,7 @@ pre, code { + .mermaid-zoom-canvas { + padding: 12px; + border: 1px solid var(--border); +- border-radius: 7px; ++ border-radius: var(--radius-sm); + background: var(--bg-panel); + margin: 0 auto; + transition: width 0.15s ease; +@@ -1460,7 +1537,7 @@ pre, code { + height: 24px; + overflow: hidden; + border: 1px solid var(--border); +- border-radius: 5px; ++ border-radius: var(--radius-sm); + } + + .file-viewer-mode-button { +@@ -1503,12 +1580,11 @@ pre, code { height: 32px; padding: 0; box-sizing: border-box; @@ -3391,7 +3578,7 @@ index d6b5535..401412e 100644 opacity: 0; visibility: hidden; pointer-events: none; -@@ -1584,22 +1633,21 @@ pre, code { +@@ -1584,22 +1660,21 @@ pre, code { .new-session-logo { flex: none; @@ -3420,7 +3607,7 @@ index d6b5535..401412e 100644 line-height: 1.15; white-space: nowrap; } -@@ -1613,7 +1661,8 @@ pre, code { +@@ -1613,7 +1688,8 @@ pre, code { gap: 6px; min-width: 0; color: var(--text-dim); @@ -3430,7 +3617,16 @@ index d6b5535..401412e 100644 line-height: 1.4; white-space: nowrap; } -@@ -1652,7 +1701,7 @@ pre, code { +@@ -1638,7 +1714,7 @@ pre, code { + gap: 3px; + margin: -3px -4px; + padding: 3px 4px; +- border-radius: 5px; ++ border-radius: var(--radius-sm); + color: var(--accent); + font-weight: 600; + text-decoration: none; +@@ -1652,7 +1728,7 @@ pre, code { /* The ring inside the box, clear of the "→" and "·" beside it. */ .new-session-update:focus-visible { @@ -3439,7 +3635,7 @@ index d6b5535..401412e 100644 outline-offset: -2px; } -@@ -1678,12 +1727,12 @@ pre, code { +@@ -1678,12 +1754,12 @@ pre, code { } .new-session-logo { @@ -3455,13 +3651,14 @@ index d6b5535..401412e 100644 } } -@@ -1722,14 +1771,14 @@ pre, code { +@@ -1722,14 +1798,14 @@ pre, code { min-width: 0; height: 29px; padding: 0 10px; - border: 1px solid var(--border); - border-radius: 7px; +- border-radius: 7px; - background: var(--bg-hover); ++ border-radius: var(--radius-sm); + background: var(--bg-subtle); color: var(--text-muted); font: inherit; @@ -3472,7 +3669,16 @@ index d6b5535..401412e 100644 } /* Above a fresh composer (inline): no box until hovered or open, 12px, the -@@ -1781,8 +1830,9 @@ pre, code { +@@ -1752,7 +1828,7 @@ pre, code { + height: 26px; + padding: 0 6px; + border: 0; +- border-radius: 6px; ++ border-radius: var(--radius-sm); + background: transparent; + } + +@@ -1781,8 +1857,9 @@ pre, code { max-width: 28ch; overflow: hidden; color: var(--text-dim); @@ -3483,7 +3689,7 @@ index d6b5535..401412e 100644 } .project-picker-parent { -@@ -1851,7 +1901,8 @@ pre, code { +@@ -1851,7 +1928,8 @@ pre, code { path's color and code type, cut at its end. */ .project-picker-label.is-alias { color: var(--text); @@ -3493,7 +3699,7 @@ index d6b5535..401412e 100644 } /* A path or a branch, cut at its left (rtl), the text itself kept left to -@@ -1901,7 +1952,8 @@ pre, code { +@@ -1901,7 +1979,8 @@ pre, code { border-color: color-mix(in srgb, var(--accent) 40%, transparent); background: color-mix(in srgb, var(--accent) 6%, transparent); color: var(--text-dim); @@ -3503,30 +3709,391 @@ index d6b5535..401412e 100644 } /* No worktree list here (the files tab): a box that says why, and does nothing. */ -@@ -2474,3 +2526,103 @@ span.token.table { +@@ -1978,7 +2057,7 @@ pre, code { + box-sizing: border-box; + flex: 0 0 24px; + border: 1px solid var(--border); +- border-radius: 5px; ++ border-radius: var(--radius-sm); + background: transparent; + color: var(--text-muted); + cursor: pointer; +@@ -2037,7 +2116,7 @@ pre, code { + .markdown-file-preview pre { + background: var(--bg-panel); + border: 1px solid var(--border); +- border-radius: 6px; ++ border-radius: var(--radius-sm); + padding: 12px 16px; + overflow-x: auto; + font-family: var(--font-mono); +@@ -2049,7 +2128,7 @@ pre, code { + font-size: 0.9em; + background: var(--bg-panel); + padding: 1px 5px; +- border-radius: 3px; ++ border-radius: var(--radius-xs); + } + .markdown-file-preview pre code { + background: none; +@@ -2061,7 +2140,7 @@ pre, code { + margin-bottom: 20px; + padding: 14px 16px 12px; + border: 1px solid var(--border); +- border-radius: 8px; ++ border-radius: var(--radius-md); + background: var(--bg-panel); + } + .markdown-frontmatter-title { +@@ -2474,3 +2553,428 @@ span.token.table { display: none !important; } } + -+/* A tool call in the transcript (MessageView ToolCallView): one 24px line -+ with no outline until it is opened, as Traces and HuggingChat show them; a -+ fill once open, a red tint when the call failed. */ -+.tool-call { -+ border-radius: 8px; -+ transition: background 0.12s; ++/* ---------------------------------------------------------------- buttons ++ Measured on Claude Desktop's: a 32px button with 12px sides and 6px corners ++ (.ui-btn), 28px in toolbars and the composer (--sm), 24px under messages and ++ inside rows (--xs), 36px with 8px corners for a page's one main action ++ (--lg). Ghost by default: no fill until hovered; pressed, it gives a little. */ ++.ui-btn { ++ display: inline-flex; ++ flex-shrink: 0; ++ align-items: center; ++ justify-content: center; ++ gap: 6px; ++ height: var(--size-control); ++ min-width: 0; ++ padding: 0 12px; ++ border: 0; ++ border-radius: var(--radius-sm); ++ background: transparent; ++ color: var(--text-muted); ++ font: inherit; ++ font-size: 13px; ++ font-weight: 500; ++ line-height: 1; ++ white-space: nowrap; ++ cursor: pointer; ++ transition: background-color 0.12s, color 0.12s, opacity 0.12s, transform 0.1s; +} -+ -+.tool-call:not(.is-open):not(.is-error):hover { ++.ui-btn:not(:disabled):hover, ++.ui-btn[aria-expanded="true"] { + background: var(--bg-hover); ++ color: var(--text-strong); +} ++.ui-btn:not(:disabled):active { transform: scale(0.97); } ++.ui-btn:disabled { cursor: not-allowed; opacity: 0.45; } ++.ui-btn > svg { flex: none; } ++.ui-btn .ui-btn-chevron { margin-left: -2px; color: var(--text-dim); } ++.ui-btn--lg { height: var(--size-control-lg); padding: 0 16px; border-radius: var(--radius-md); font-size: 14px; } ++.ui-btn--sm { height: var(--size-control-sm); padding: 0 8px; gap: 5px; } ++.ui-btn--xs { height: var(--size-control-xs); padding: 0 6px; gap: 4px; font-size: 12px; } ++.ui-btn--icon { width: var(--size-control); padding: 0; } ++.ui-btn--icon.ui-btn--sm { width: var(--size-control-sm); } ++.ui-btn--icon.ui-btn--xs { width: var(--size-control-xs); } ++.ui-btn--primary { background: var(--primary); color: var(--primary-contrast); } ++.ui-btn--primary:not(:disabled):hover { background: var(--primary); color: var(--primary-contrast); opacity: 0.86; } ++.ui-btn--secondary { background: var(--bg-subtle); color: var(--text-strong); } ++.ui-btn--secondary:not(:disabled):hover { background: var(--bg-selected); } ++.ui-btn--danger { background: var(--danger-soft); color: var(--danger); } ++.ui-btn--danger:not(:disabled):hover { background: color-mix(in srgb, var(--danger) 22%, transparent); color: var(--danger); } ++.ui-btn[data-active] { color: var(--accent); } ++.ui-btn[aria-pressed="true"] { background: var(--bg-selected); color: var(--text-strong); } ++.ui-btn[data-off] { color: var(--text-dim); } ++/* Send: a quiet square until there is something to send, then ink. */ ++.ui-btn--send { background: var(--bg-selected); color: var(--text-dim); opacity: 1; } ++.ui-btn--send:disabled { opacity: 1; } ++.ui-btn--send[data-ready] { background: var(--primary); color: var(--primary-contrast); } ++.ui-btn--send[data-ready]:not(:disabled):hover { background: var(--primary); color: var(--primary-contrast); opacity: 0.86; } ++/* Steer and Follow-up while the agent runs: a tint says where the message goes. */ ++.ui-btn--tint[data-tint="steer"] { background: color-mix(in srgb, var(--warning) 16%, transparent); color: var(--warning); } ++.ui-btn--tint[data-tint="follow-up"] { background: color-mix(in srgb, var(--accent) 16%, transparent); color: var(--accent); } ++.ui-btn--tint:not(:disabled):hover { filter: brightness(1.12); } ++.ui-btn--tint:disabled { background: var(--bg-subtle); color: var(--text-dim); } + -+.tool-call.is-open { ++/* ---------------------------------------------------------------- session header ++ The conversation's top bar leads with the session's title (Claude Desktop's ++ header); its actions are 28px icon buttons on the right. */ ++.session-header-title { ++ flex: 1 1 auto; ++ min-width: 0; ++ padding: 0 10px 0 12px; ++ overflow: hidden; ++ color: var(--text-strong); ++ font-size: 13px; ++ font-weight: 500; ++ white-space: nowrap; ++ -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 24px), transparent); ++ mask-image: linear-gradient(to right, #000 calc(100% - 24px), transparent); ++} ++.session-header-title.is-new { color: var(--text-muted); font-weight: 400; } ++ ++/* ---------------------------------------------------------------- composer ++ One filled box, as in Claude: the draft on top, the toolbar inside it (attach ++ and model on the left; reasoning, tools, compact, sound, then Send). No ++ outline; 12px corners. */ ++.composer { ++ display: flex; ++ flex-direction: column; ++ gap: 6px; ++ min-width: 0; ++ padding: 10px 8px 8px 14px; ++ border-radius: var(--radius-xl); + background: var(--bg-surface); ++ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.06); ++ transition: background 0.15s; ++} ++.composer .chat-input-textarea { padding: 0 6px 0 0; } ++.composer-toolbar { ++ display: flex; ++ align-items: center; ++ gap: 4px; ++ min-width: 0; ++ margin-left: -6px; ++} ++.composer-toolbar > .chat-input-controls { ++ flex: 1 1 auto; ++ min-width: 0; ++ align-items: center; ++ gap: 2px; ++} ++.composer-send { ++ display: flex; ++ flex: none; ++ align-items: center; ++ gap: 4px; ++} ++@media (max-width: 640px) { ++ .composer { padding: 8px 6px 6px 12px; } +} + -+.tool-call.is-error { -+ background: var(--danger-soft); ++/* ---------------------------------------------------------------- transcript ++ Claude Desktop's hierarchy: the person's prompt in a small filled bubble on ++ the right; the agent's reply as plain text with no label or container; what ++ the agent did on the way (its steps) folded into one summary line above the ++ reply; the reply's actions in one quiet row under it. */ ++.turn-user { ++ display: flex; ++ flex-direction: column; ++ align-items: flex-end; ++ gap: 2px; ++ margin: 0 0 6px; ++} ++.user-bubble { ++ min-width: 0; ++ max-width: min(85%, 640px); ++ padding: 6px 12px; ++ border-radius: var(--radius-md); ++ background: var(--user-bg); ++ color: var(--text-strong); ++ font-size: calc(14px + var(--chat-font-size-offset, 0px)); ++ line-height: 1.6; ++ overflow-wrap: anywhere; ++ overflow-y: auto; ++} ++.user-bubble.is-editing { ++ background: color-mix(in srgb, var(--accent) 14%, var(--user-bg)); ++ box-shadow: 0 0 0 2px var(--focus-ring); ++} ++/* The actions under a message: icon buttons that show on hover or focus. */ ++.message-actions { ++ display: flex; ++ align-items: center; ++ gap: 0; ++ min-height: var(--size-control-xs); ++} ++.message-actions .ui-btn { color: var(--text-dim); } ++.message-actions .ui-btn:not(:disabled):hover { color: var(--text-strong); } ++.message-actions .ui-btn[data-done] { color: var(--success); } ++.message-actions .ui-btn[data-busy] { color: var(--accent); } ++.message-actions-reveal { ++ display: flex; ++ align-items: center; ++ opacity: 0; ++ transition: opacity 0.12s; +} ++:is(.turn-user, .turn-reply):is(:hover, :focus-within) .message-actions-reveal, ++.message-actions-reveal.is-pinned { opacity: 1; } ++.message-meta { ++ padding: 0 6px; ++ color: var(--text-dim); ++ font-size: 11px; ++ font-variant-numeric: tabular-nums; ++ white-space: nowrap; ++} ++.turn-reply { margin: 0 0 22px; } ++.turn-reply > .message-actions { margin: 4px 0 0 -6px; } ++.turn-reply.is-step { margin: 0; } ++.reply-blocks { display: flex; flex-direction: column; gap: 6px; min-width: 0; } ++ ++/* The steps of a turn: one summary line, "Ran 2 commands, read a file", that ++ opens onto the steps themselves along a thin rail. */ ++.turn-steps { margin: 0 0 8px; } ++.steps-summary { ++ display: inline-flex; ++ align-items: center; ++ gap: 6px; ++ max-width: 100%; ++ min-height: 26px; ++ margin-left: -6px; ++ padding: 0 8px 0 6px; ++ border: 0; ++ border-radius: var(--radius-sm); ++ background: transparent; ++ color: var(--text-muted); ++ font: inherit; ++ font-size: 13px; ++ text-align: left; ++ cursor: pointer; ++ transition: background-color 0.12s, color 0.12s; ++} ++.steps-summary:hover { background: var(--bg-hover); color: var(--text-strong); } ++.steps-summary > span { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } ++.steps-summary .steps-count { flex: none; color: var(--text-dim); font-variant-numeric: tabular-nums; } ++.disclosure-chevron { flex: none; color: var(--text-dim); transition: transform 0.15s; } ++[aria-expanded="true"] > .disclosure-chevron { transform: rotate(90deg); } ++.steps-list { ++ position: relative; ++ display: flex; ++ flex-direction: column; ++ gap: 4px; ++ margin: 4px 0 4px 1px; ++ padding-left: 16px; ++} ++/* A drawing, not a border: the rail the steps hang from. */ ++.steps-list::before { ++ content: ""; ++ position: absolute; ++ top: 4px; ++ bottom: 4px; ++ left: 0; ++ width: 1px; ++ background: var(--line); ++} ++.steps-list .markdown-body { color: var(--text-muted); } ++ ++/* A tool call or a thought: one 26px line until opened ("[icon] bash npm test 2.4s"); ++ opened, its detail sits below in a filled box. A failed call names itself in red. */ ++.tool-call { min-width: 0; margin-left: -6px; } ++.tool-call-row { ++ display: flex; ++ align-items: center; ++ gap: 4px; ++ min-width: 0; ++ min-height: 26px; ++ padding-right: 6px; ++ border-radius: var(--radius-sm); ++ transition: background-color 0.12s; ++} ++.tool-call-row:hover { background: var(--bg-hover); } ++.tool-call-head { ++ display: flex; ++ flex: 1; ++ align-items: center; ++ gap: 8px; ++ min-width: 0; ++ min-height: 26px; ++ padding: 0 0 0 6px; ++ border: 0; ++ border-radius: var(--radius-sm); ++ background: transparent; ++ color: var(--text-muted); ++ font: inherit; ++ font-size: 13px; ++ text-align: left; ++ cursor: pointer; ++} ++.tool-call-icon { flex: none; color: var(--text-dim); } ++.tool-call-name { flex: none; color: var(--text-muted); font-weight: 500; } ++.tool-call-row:hover .tool-call-name, ++.tool-call.is-open .tool-call-name { color: var(--text-strong); } ++.tool-call.is-error .tool-call-name, ++.tool-call.is-error .tool-call-icon { color: var(--danger); } ++.tool-call-arg { ++ flex: 1; ++ min-width: 0; ++ overflow: hidden; ++ color: var(--text-dim); ++ font-family: var(--font-mono); ++ font-size: 12px; ++ text-overflow: ellipsis; ++ white-space: nowrap; ++} ++.tool-call-arg.is-prose { color: var(--text-muted); font-family: inherit; font-size: 13px; } ++.tool-call-arg.is-prose > p { display: inline; margin: 0; } ++.tool-call-meta { flex: none; color: var(--text-dim); font-size: 11px; font-variant-numeric: tabular-nums; } ++.tool-call-body { ++ margin: 2px 0 6px 6px; ++ overflow: hidden; ++ border-radius: var(--radius-md); ++ background: var(--bg-surface); ++ font-size: 12px; ++} ++.tool-call.is-error .tool-call-body { background: var(--danger-soft); } ++.thinking-body { ++ margin: 2px 0 6px 28px; ++ color: var(--text-muted); ++ font-size: calc(13px + var(--chat-font-size-offset, 0px)); ++ line-height: 1.6; ++ white-space: pre-wrap; ++ overflow-wrap: anywhere; ++} ++.tool-call-side { flex: none; } ++ ++/* A diff in a tool call (MessageView UnifiedFilesView): line number, mark, code; ++ added and removed lines tinted, no rules between them. */ ++.unified-diff { ++ max-height: 560px; ++ overflow: auto; ++ padding: 6px 0; ++ font-family: var(--font-mono); ++ font-weight: var(--font-mono-weight); ++ font-size: calc(12px + var(--chat-font-size-offset, 0px)); ++ line-height: 1.6; ++} ++.unified-diff-file + .unified-diff-file { margin-top: 8px; } ++.unified-diff-path { ++ padding: 2px 12px 4px; ++ overflow: hidden; ++ color: var(--text-muted); ++ text-overflow: ellipsis; ++ white-space: nowrap; ++} ++.unified-diff-line { display: flex; min-width: 0; } ++.unified-diff-line.is-added { background: color-mix(in srgb, var(--success) 14%, transparent); } ++.unified-diff-line.is-removed { background: color-mix(in srgb, var(--danger) 14%, transparent); } ++.unified-diff-no { ++ flex: none; ++ width: 44px; ++ padding-right: 8px; ++ color: var(--text-dim); ++ text-align: right; ++ user-select: none; ++} ++.unified-diff-mark { flex: none; width: 16px; color: var(--text-dim); user-select: none; } ++.unified-diff-line.is-added .unified-diff-mark { color: var(--success); } ++.unified-diff-line.is-removed .unified-diff-mark { color: var(--danger); } ++.unified-diff-line.is-empty .unified-diff-text { color: var(--text-dim); } ++.unified-diff-text { ++ flex: 1; ++ min-width: 0; ++ padding-right: 12px; ++ color: var(--text); ++ white-space: pre-wrap; ++ overflow-wrap: anywhere; ++} ++ ++/* An error or a cut-off reply under the agent's text: a tinted note, no outline. */ ++.reply-notice { ++ padding: 8px 12px; ++ border-radius: var(--radius-md); ++ font-size: 13px; ++ line-height: 1.5; ++ white-space: pre-wrap; ++ overflow-wrap: anywhere; ++} ++.reply-notice.is-error { background: var(--danger-soft); color: var(--danger); } ++.reply-notice.is-warning { background: color-mix(in srgb, var(--warning) 12%, transparent); color: var(--warning); } + +/* The sidebar's bottom row (AppShell): Models, Skills, Settings; under it, + the row a host may fill ([data-host-slot="sidebar-footer"]). */ @@ -3535,7 +4102,7 @@ index d6b5535..401412e 100644 + flex-shrink: 0; + justify-content: space-between; + gap: 2px; -+ padding: 6px; ++ padding: 4px 6px; +} + +.sidebar-host-slot { @@ -3554,10 +4121,10 @@ index d6b5535..401412e 100644 + justify-content: center; + gap: 5px; + min-width: 0; -+ height: 30px; ++ height: var(--size-control-sm); + padding: 0 6px; + border: 0; -+ border-radius: 8px; ++ border-radius: var(--radius-sm); + background: none; + color: var(--text-muted); + font: inherit; @@ -3725,7 +4292,7 @@ index 79c8282..97731f1 100644 lang: "en", icons: [ diff --git a/app/settings.css b/app/settings.css -index 0bf21eb..0dbe4c5 100644 +index 0bf21eb..4938ff2 100644 --- a/app/settings.css +++ b/app/settings.css @@ -40,9 +40,9 @@ @@ -3735,12 +4302,30 @@ index 0bf21eb..0dbe4c5 100644 - border: 1px solid var(--border); - border-radius: 8px; - box-shadow: 0 8px 32px rgba(0, 0, 0, 0.18); -+ border-radius: 14px; ++ border-radius: var(--radius-xl); + box-shadow: var(--shadow-pop); + background: var(--bg-elevated); } .config-panel-header { +@@ -51,7 +51,7 @@ + align-items: center; + gap: 10px; + height: 50px; +- padding: 0 14px 0 18px; ++ padding: 0 12px 0 16px; + border-bottom: 1px solid var(--border); + } + +@@ -78,7 +78,7 @@ + margin-left: auto; + padding: 0; + border: 0; +- border-radius: 5px; ++ border-radius: var(--radius-sm); + background: transparent; + color: var(--text-muted); + cursor: pointer; @@ -153,8 +153,9 @@ } @@ -3752,6 +4337,15 @@ index 0bf21eb..0dbe4c5 100644 } /* Three lines, then scroll: the phone layout gives the whole sidebar 190px. */ +@@ -188,7 +189,7 @@ + height: 30px; + padding: 0 8px; + border: 0; +- border-radius: 5px; ++ border-radius: var(--radius-sm); + outline: none; + background: transparent; + color: var(--text); @@ -236,8 +237,9 @@ .models-sidebar-badge { flex-shrink: 0; @@ -3768,12 +4362,26 @@ index 0bf21eb..0dbe4c5 100644 min-width: 0; padding: 6px 9px; - border: 1px solid var(--border); - border-radius: 5px; +- border-radius: 5px; - background: var(--bg); ++ border-radius: var(--radius-sm); + background: var(--bg-surface); color: var(--text); font-family: var(--font-mono); font-size: 12px; +@@ -290,9 +291,9 @@ + flex-shrink: 0; + padding: 6px 12px; + border: none; +- border-radius: 5px; +- background: var(--accent); +- color: var(--accent-contrast); ++ border-radius: var(--radius-sm); ++ background: var(--primary); ++ color: var(--primary-contrast); + cursor: pointer; + font-size: 12px; + font-weight: 600; @@ -370,8 +371,9 @@ .enabled-models-count { margin-right: auto; @@ -3785,43 +4393,214 @@ index 0bf21eb..0dbe4c5 100644 } .enabled-models-note { +@@ -383,7 +385,7 @@ + .enabled-models-error { + padding: 7px 9px; + border: 1px solid rgba(239, 68, 68, 0.3); +- border-radius: 5px; ++ border-radius: var(--radius-sm); + color: #ef4444; + font-size: 11px; + line-height: 1.4; @@ -401,10 +403,9 @@ box-sizing: border-box; height: 30px; padding: 0 9px; - border: 1px solid var(--border); - border-radius: 5px; +- border-radius: 5px; ++ border-radius: var(--radius-sm); outline: none; - background: var(--bg); + background: var(--bg-surface); color: var(--text); font-family: inherit; font-size: 12px; +@@ -413,7 +414,7 @@ + .enabled-models-list { + max-height: 360px; + border: 1px solid var(--border); +- border-radius: 6px; ++ border-radius: var(--radius-sm); + background: var(--bg-panel); + overflow-y: auto; + } +@@ -457,7 +458,7 @@ + .enabled-models-pin { + flex-shrink: 0; + padding: 1px 4px; +- border-radius: 3px; ++ border-radius: var(--radius-xs); + background: rgba(99, 102, 241, 0.12); + color: rgba(99, 102, 241, 0.8); + font-size: 9px; +@@ -497,7 +498,7 @@ + justify-content: space-between; + gap: 18px; + min-height: 68px; +- padding: 10px 18px; ++ padding: 8px 16px; + border-bottom: 1px solid var(--border); + background: var(--bg-panel); + } +@@ -541,7 +542,7 @@ + height: 28px; + padding: 0 6px; + border: 1px solid var(--border); +- border-radius: 4px; ++ border-radius: var(--radius-xs); + background: var(--bg); + color: var(--text); + font: inherit; +@@ -566,7 +567,7 @@ + flex: 1; + min-width: 0; + min-height: 0; +- padding: 20px; ++ padding: 16px; + overflow-y: auto; + font-family: inherit; + font-size: 12px; +@@ -662,7 +663,7 @@ + background: var(--border); + border: 5px solid transparent; + background-clip: padding-box; +- border-radius: 7px; ++ border-radius: var(--radius-sm); + } + + .config-field-label { +@@ -697,7 +698,7 @@ + .config-scope-tag { + flex-shrink: 0; + padding: 1px 5px; +- border-radius: 3px; ++ border-radius: var(--radius-xs); + background: rgba(120, 120, 120, 0.12); + color: var(--text-dim); + font-size: 10px; +@@ -730,7 +731,7 @@ + height: 30px; + overflow: hidden; + border: 1px solid var(--border); +- border-radius: 7px; ++ border-radius: var(--radius-sm); + } + + .config-scope-switch-option { +@@ -760,7 +761,7 @@ + .config-scope-switch.is-small { + display: flex; + height: auto; +- border-radius: 5px; ++ border-radius: var(--radius-sm); + font-size: 12px; + } + @@ -891,9 +892,8 @@ width: 100%; height: 36px; padding: 0 11px; - border: 1px solid var(--border); - border-radius: 6px; +- border-radius: 6px; - background: var(--bg-panel); ++ border-radius: var(--radius-sm); + background: var(--bg-surface); color: var(--text); font-family: var(--font-mono); font-size: 12px; -@@ -1181,9 +1181,9 @@ +@@ -935,7 +935,7 @@ + min-height: 30px; + padding: 6px 9px; + border: 1px solid var(--border); +- border-radius: 6px; ++ border-radius: var(--radius-sm); + background: var(--bg-panel); + color: var(--text-dim); + cursor: pointer; +@@ -976,7 +976,7 @@ + } + + .config-notice { +- padding: 8px 18px; ++ padding: 6px 16px; + border-bottom: 1px solid var(--border); + background: var(--bg-panel); + color: var(--text-muted); +@@ -1104,7 +1104,7 @@ + align-items: center; + gap: 8px; + min-height: 52px; +- padding: 9px 14px; ++ padding: 8px 12px; + border-top: 1px solid var(--border); + } + +@@ -1159,7 +1159,7 @@ + gap: 6px; + border-width: 1px; + border-style: solid; +- border-radius: 5px; ++ border-radius: var(--radius-sm); + cursor: pointer; + font-family: inherit; + letter-spacing: 0; +@@ -1168,35 +1168,39 @@ + transition: background-color 0.14s ease, border-color 0.14s ease, color 0.14s ease, box-shadow 0.14s ease; + } + ++/* Claude Desktop's button metrics, as .ui-btn (app/globals.css): 32px with 12px ++ sides, 28px with 8px sides, 6px corners, 13px medium labels. */ + .config-button-default { +- height: 32px; +- padding: 0 14px; +- font-size: 12px; ++ height: var(--size-control); ++ padding: 0 12px; ++ font-size: 13px; ++ font-weight: 500; + } + + .config-button-small { +- height: 28px; +- padding: 0 10px; +- font-size: 11px; ++ height: var(--size-control-sm); ++ padding: 0 8px; ++ font-size: 12px; ++ font-weight: 500; } .config-button-primary { - border-color: var(--accent); - background: var(--accent); - color: var(--accent-contrast); +- font-weight: 600; + border-color: transparent; + background: var(--primary); + color: var(--primary-contrast); - font-weight: 600; ++ font-weight: 500; } -@@ -1206,8 +1206,9 @@ + .config-button-secondary { +- border-color: var(--border); +- background: transparent; +- color: var(--text-muted); ++ border-color: transparent; ++ background: var(--bg-subtle); ++ color: var(--text-strong); + } + + .config-button-danger { +- border-color: rgba(239, 68, 68, 0.35); +- background: rgba(239, 68, 68, 0.06); +- color: #ef4444; ++ border-color: transparent; ++ background: var(--danger-soft); ++ color: var(--danger); + } + + .config-button-ghost { +@@ -1206,20 +1210,27 @@ } .config-button-primary:not(:disabled):hover { @@ -3830,10 +4609,33 @@ index 0bf21eb..0dbe4c5 100644 + border-color: transparent; + background: var(--primary); + opacity: 0.86; ++} ++ ++.config-button-secondary:not(:disabled):hover { ++ background: var(--bg-selected); ++ color: var(--text-strong); } - .config-button-secondary:not(:disabled):hover, -@@ -1254,9 +1255,9 @@ +-.config-button-secondary:not(:disabled):hover, + .config-button-ghost:not(:disabled):hover { +- border-color: var(--border); + background: var(--bg-hover); +- color: var(--text); ++ color: var(--text-strong); + } + + .config-button-danger:not(:disabled):hover { +- border-color: rgba(239, 68, 68, 0.5); +- background: rgba(239, 68, 68, 0.12); ++ background: color-mix(in srgb, var(--danger) 22%, transparent); ++} ++ ++.config-button:not(:disabled):active { ++ transform: scale(0.97); + } + + .config-button:focus-visible { +@@ -1254,9 +1265,9 @@ width: 32px; height: 18px; padding: 0; @@ -3845,7 +4647,25 @@ index 0bf21eb..0dbe4c5 100644 cursor: pointer; transition: background 0.15s, border-color 0.15s; } -@@ -1405,10 +1406,9 @@ +@@ -1342,7 +1353,7 @@ + min-height: 30px; + padding: 0 8px; + border: 0; +- border-radius: 5px; ++ border-radius: var(--radius-sm); + background: transparent; + color: var(--text-dim); + cursor: pointer; +@@ -1358,7 +1369,7 @@ + + .config-list-action-button[aria-current="page"] { + background: var(--bg-selected); +- color: var(--accent); ++ color: var(--text-strong); + } + + .config-list-action-button:disabled { +@@ -1405,10 +1416,9 @@ height: 84vh; max-height: calc(100dvh - 16px); overflow: hidden; @@ -3853,24 +4673,88 @@ index 0bf21eb..0dbe4c5 100644 - border-radius: 8px; - background: var(--bg); - box-shadow: 0 16px 48px rgba(0, 0, 0, 0.22); -+ border-radius: 14px; ++ border-radius: var(--radius-xl); + background: var(--bg-elevated); + box-shadow: var(--shadow-pop); } .settings-dialog-header { -@@ -1657,9 +1657,8 @@ +@@ -1417,7 +1427,7 @@ + flex-shrink: 0; + align-items: center; + min-height: 50px; +- padding: 0 52px 0 18px; ++ padding: 0 48px 0 16px; + border-bottom: 1px solid var(--border); + } + +@@ -1467,7 +1477,7 @@ + width: 24px; + height: 2px; + border-radius: 2px 2px 0 0; +- background: var(--accent); ++ background: var(--text-strong); + content: ""; + opacity: 0; + transform: translateX(-50%) scaleX(0.5); +@@ -1494,7 +1504,7 @@ + } + + .settings-section-tab:focus-visible:not([aria-current="page"]) { +- border-radius: 4px; ++ border-radius: var(--radius-xs); + background: var(--bg-hover); + color: var(--text); + } +@@ -1542,7 +1552,7 @@ + .settings-general { + width: 100%; + height: 100%; +- padding: 26px clamp(18px, 4vw, 40px) 40px; ++ padding: 18px clamp(16px, 3vw, 28px) 28px; + overflow-y: auto; + } + +@@ -1593,21 +1603,21 @@ + justify-content: center; + gap: 7px; + min-width: 0; +- min-height: 44px; ++ min-height: var(--size-control); + padding: 0 6px; + border: 0; +- border-radius: 5px; ++ border-radius: var(--radius-sm); + background: transparent; + color: var(--text-muted); + cursor: pointer; +- font-size: 12px; ++ font-size: 13px; + font-weight: 400; + } + + .settings-theme-option:has(input:checked) { + background: var(--bg-selected); +- color: var(--accent); +- font-weight: 600; ++ color: var(--text-strong); ++ font-weight: 500; + } + + .settings-theme-option:not(:has(input:checked)):hover { +@@ -1657,9 +1667,8 @@ height: 26px; margin: 0; padding: 0 8px; - border: 1px solid var(--border); - border-radius: 5px; +- border-radius: 5px; - background: var(--bg); ++ border-radius: var(--radius-sm); + background: var(--bg-surface); color: var(--text); font-family: var(--font-ui); font-weight: var(--font-ui-weight); -@@ -1722,8 +1721,9 @@ +@@ -1722,8 +1731,9 @@ .settings-chat-range-header output { color: var(--text-muted); @@ -3881,18 +4765,102 @@ index 0bf21eb..0dbe4c5 100644 } .settings-send-mode-options { -@@ -2172,9 +2172,8 @@ +@@ -1732,7 +1742,7 @@ + gap: 3px; + padding: 3px; + border: 1px solid var(--border); +- border-radius: 6px; ++ border-radius: var(--radius-sm); + } + + .settings-send-mode-option { +@@ -1744,7 +1754,7 @@ + min-height: 26px; + padding: 0 10px; + border: 0; +- border-radius: 4px; ++ border-radius: var(--radius-xs); + background: transparent; + color: var(--text-muted); + cursor: pointer; +@@ -1754,8 +1764,8 @@ + + .settings-send-mode-option:has(input:checked) { + background: var(--bg-selected); +- color: var(--accent); +- font-weight: 600; ++ color: var(--text-strong); ++ font-weight: 500; + } + + .settings-send-mode-option:not(:has(input:checked)):hover { +@@ -1781,7 +1791,7 @@ + display: block; + width: 100%; + margin-top: 2px; +- accent-color: var(--accent); ++ accent-color: var(--text-strong); + } + + .settings-language-options { +@@ -1799,7 +1809,7 @@ + height: 44px; + padding: 0 10px; + border: 0; +- border-radius: 5px; ++ border-radius: var(--radius-sm); + background: transparent; + color: var(--text); + cursor: pointer; +@@ -1857,7 +1867,7 @@ + max-width: 420px; + min-height: 44px; + padding: 0 10px; +- border-radius: 5px; ++ border-radius: var(--radius-sm); + background: var(--bg-panel); + color: var(--text); + font-size: 12px; +@@ -1923,7 +1933,7 @@ + margin-left: 14px; + padding: 0 9px; + border: 1px solid var(--border); +- border-radius: 5px; ++ border-radius: var(--radius-sm); + background: var(--bg-panel); + color: var(--text); + font-size: 16px; +@@ -2066,7 +2076,7 @@ + max-width: 100%; + padding: 0 5px; + border: 1px solid var(--border); +- border-radius: 3px; ++ border-radius: var(--radius-xs); + background: var(--bg-panel); + color: var(--text); + font-family: var(--font-mono); +@@ -2085,7 +2095,7 @@ + padding: 6px 8px; + overflow: auto; + border: 1px solid var(--border); +- border-radius: 4px; ++ border-radius: var(--radius-xs); + background: var(--bg-panel); + color: var(--text-muted); + font-family: var(--font-mono); +@@ -2172,9 +2182,8 @@ min-width: 0; height: 32px; padding: 0 10px; - border: 1px solid var(--border); - border-radius: 6px; +- border-radius: 6px; - background: var(--bg-panel); ++ border-radius: var(--radius-sm); + background: var(--bg-surface); color: var(--text); font-family: var(--font-mono); font-size: 12px; -@@ -2266,10 +2265,9 @@ select.mcp-add-input { +@@ -2266,10 +2275,9 @@ select.mcp-add-input { max-width: 100%; max-height: 100%; overflow: hidden; @@ -3900,14 +4868,109 @@ index 0bf21eb..0dbe4c5 100644 - border-radius: 8px; - background: var(--bg-panel); - box-shadow: 0 12px 36px rgba(0, 0, 0, 0.24); -+ border-radius: 14px; ++ border-radius: var(--radius-xl); + background: var(--bg-elevated); + box-shadow: var(--shadow-pop); } /* Focus moves to the dialog itself when it opens (tabIndex -1); it is not a control, so no ring. */ +@@ -2282,7 +2290,7 @@ select.mcp-add-input { + flex: 1 1 auto; + gap: 12px; + min-height: 0; +- padding: 18px 18px 14px; ++ padding: 14px 16px 10px; + overflow: auto; + overscroll-behavior: contain; + } +@@ -2315,7 +2323,7 @@ select.mcp-add-input { + margin-top: 10px; + padding: 8px 10px; + border: 1px solid var(--border); +- border-radius: 5px; ++ border-radius: var(--radius-sm); + background: var(--bg); + color: var(--text); + font-family: var(--font-mono); +@@ -2333,7 +2341,7 @@ select.mcp-add-input { + .project-trust-error { + flex-shrink: 0; + max-height: 30vh; +- padding: 0 18px 10px; ++ padding: 0 16px 8px; + overflow: auto; + color: #ef4444; + font-size: 12px; +@@ -2353,19 +2361,20 @@ select.mcp-add-input { + flex-shrink: 0; + justify-content: flex-end; + gap: 8px; +- padding: 10px 18px; ++ padding: 8px 16px; + border-top: 1px solid var(--border); + } + + .project-trust-button { +- height: 32px; ++ height: var(--size-control); + padding: 0 12px; +- border: 1px solid var(--border); +- border-radius: 5px; +- background: transparent; +- color: var(--text-muted); ++ border: 0; ++ border-radius: var(--radius-sm); ++ background: var(--bg-subtle); ++ color: var(--text-strong); + cursor: pointer; +- font-size: 12px; ++ font-size: 13px; ++ font-weight: 500; + } + + .project-trust-button:disabled { +@@ -2373,10 +2382,8 @@ select.mcp-add-input { + } + + .project-trust-button.is-primary { +- border-color: var(--accent); +- background: var(--accent); +- color: var(--accent-contrast); +- font-weight: 600; ++ background: var(--primary); ++ color: var(--primary-contrast); + } + + .project-trust-button.is-primary:disabled { +@@ -2450,7 +2457,7 @@ select.mcp-add-input { + min-width: 0; + padding: 8px 10px; + border: 1px solid var(--border); +- border-radius: 5px; ++ border-radius: var(--radius-sm); + background: var(--bg); + } + +@@ -2472,7 +2479,7 @@ select.mcp-add-input { + .project-trust-mcp-tag { + flex-shrink: 0; + padding: 1px 5px; +- border-radius: 3px; ++ border-radius: var(--radius-xs); + background: rgba(120, 120, 120, 0.12); + color: var(--text-dim); + font-size: 10px; +@@ -2493,7 +2500,7 @@ select.mcp-add-input { + display: inline-block; + margin: 2px 4px 0 0; + padding: 0 4px; +- border-radius: 3px; ++ border-radius: var(--radius-xs); + background: var(--bg-hover); + color: var(--text); + font-family: var(--font-mono); diff --git a/app/sidebar-menu.css b/app/sidebar-menu.css -index 737e7a5..86bdb58 100644 +index 737e7a5..19466a2 100644 --- a/app/sidebar-menu.css +++ b/app/sidebar-menu.css @@ -20,11 +20,10 @@ @@ -3917,7 +4980,7 @@ index 737e7a5..86bdb58 100644 - border: 1px solid var(--border); - border-radius: 8px; - background: var(--bg); -+ border-radius: 10px; ++ border-radius: var(--radius-lg); + background: var(--bg-elevated); color: var(--text); - box-shadow: 0 6px 20px rgba(0, 0, 0, 0.10); @@ -3925,6 +4988,15 @@ index 737e7a5..86bdb58 100644 font-size: 12px; animation: sidebar-menu-in 0.12s ease-out; } +@@ -53,7 +52,7 @@ + padding: 0 8px; + box-sizing: border-box; + border: 0; +- border-radius: 5px; ++ border-radius: var(--radius-sm); + background: transparent; + color: var(--text); + font: inherit; @@ -126,8 +125,9 @@ .sidebar-menu-shortcut { flex-shrink: 0; @@ -3936,6 +5008,15 @@ index 737e7a5..86bdb58 100644 } .sidebar-menu-separator { +@@ -166,7 +166,7 @@ + height: 28px; + padding: 0; + border: 0; +- border-radius: 5px; ++ border-radius: var(--radius-sm); + background: transparent; + color: var(--text-dim); + cursor: pointer; @@ -202,8 +202,7 @@ z-index: 1; margin: -4px -4px 4px; @@ -3952,13 +5033,31 @@ index 737e7a5..86bdb58 100644 box-sizing: border-box; - border: 1px solid var(--border); - border-radius: 5px; -+ border-radius: 7px; ++ border-radius: var(--radius-sm); outline: none; - background: var(--bg); + background: var(--bg-surface); color: var(--text); font: inherit; font-size: 12px; +@@ -346,7 +344,7 @@ + .sidebar-menu.is-classic .sidebar-worktree-cancel { + height: auto; + padding: 4px 0; +- border-radius: 5px; ++ border-radius: var(--radius-sm); + font-size: 11px; + } + +@@ -388,7 +386,7 @@ + flex: none; + height: auto; + padding: 3px 9px; +- border-radius: 5px; ++ border-radius: var(--radius-sm); + font-size: 11px; + } + @@ -583,11 +581,10 @@ html[data-keyboard-open] .sidebar-sheet { height: 36px; padding: 0 4px 0 12px; @@ -3966,7 +5065,7 @@ index 737e7a5..86bdb58 100644 - border: 1px solid var(--border); - border-radius: 8px; - background: var(--bg); -+ border-radius: 10px; ++ border-radius: var(--radius-lg); + background: var(--bg-elevated); color: var(--text); - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08); @@ -3974,8 +5073,53 @@ index 737e7a5..86bdb58 100644 font-size: 12px; pointer-events: auto; animation: sidebar-toast-in 0.16s ease-out; +@@ -634,7 +631,7 @@ html[data-keyboard-open] .sidebar-sheet { + height: 26px; + padding: 0 7px; + border: 0; +- border-radius: 5px; ++ border-radius: var(--radius-sm); + background: transparent; + color: var(--accent); + font: inherit; +@@ -657,7 +654,7 @@ html[data-keyboard-open] .sidebar-sheet { + height: 24px; + padding: 0; + border: 0; +- border-radius: 5px; ++ border-radius: var(--radius-sm); + background: transparent; + color: var(--text-dim); + cursor: pointer; +@@ -726,7 +723,7 @@ html[data-keyboard-open] .sidebar-sheet { + padding: 0 8px; + box-sizing: border-box; + border: 1px solid var(--accent); +- border-radius: 5px; ++ border-radius: var(--radius-sm); + outline: none; + background: var(--bg); + color: var(--text); +@@ -749,7 +746,7 @@ html[data-keyboard-open] .sidebar-sheet { + flex: 1; + height: 26px; + border: 1px solid var(--border); +- border-radius: 6px; ++ border-radius: var(--radius-sm); + background: var(--bg); + color: var(--text-muted); + font: inherit; +@@ -848,7 +845,7 @@ html[data-keyboard-open] .sidebar-sheet { + min-width: 0; + height: 26px; + border: 1px solid var(--border); +- border-radius: 6px; ++ border-radius: var(--radius-sm); + background: var(--bg); + color: var(--text-muted); + font: inherit; diff --git a/app/sidebar.css b/app/sidebar.css -index 86728a2..e5780e6 100644 +index 86728a2..fd45b6f 100644 --- a/app/sidebar.css +++ b/app/sidebar.css @@ -91,7 +91,7 @@ @@ -3987,6 +5131,15 @@ index 86728a2..e5780e6 100644 outline-offset: -2px; } +@@ -184,7 +184,7 @@ + right: var(--session-tree-inset-right); + left: var(--session-tree-inset-left); + padding: 0 4px 0 20px; +- border-radius: 7px; ++ border-radius: var(--radius-sm); + cursor: pointer; + overflow: hidden; + } @@ -197,32 +197,11 @@ background: var(--bg-selected); } @@ -4061,6 +5214,15 @@ index 86728a2..e5780e6 100644 } .session-tree-title-base { +@@ -393,7 +377,7 @@ a.session-tree-main { + height: 24px; + margin-left: 2px; + padding: 0; +- border-radius: 6px; ++ border-radius: var(--radius-sm); + color: var(--text-dim); + } + @@ -408,13 +392,17 @@ a.session-tree-main { background: var(--bg-hover); } @@ -4081,6 +5243,51 @@ index 86728a2..e5780e6 100644 .session-tree-meta.is-running { color: var(--accent); } +@@ -454,7 +442,7 @@ a.session-tree-main { + padding: 0 8px; + box-sizing: border-box; + border: 1px solid var(--accent); +- border-radius: 5px; ++ border-radius: var(--radius-sm); + outline: none; + background: var(--bg); + color: var(--text); +@@ -483,7 +471,7 @@ a.session-tree-main { + gap: 4px; + height: 24px; + padding: 0 10px; +- border-radius: 6px; ++ border-radius: var(--radius-sm); + font-size: 12px; + white-space: nowrap; + } +@@ -519,7 +507,7 @@ a.session-tree-main { + left: var(--session-tree-inset-left); + gap: 2px; + padding: 0 4px 0 0; +- border-radius: 7px; ++ border-radius: var(--radius-sm); + -webkit-user-select: none; + user-select: none; + -webkit-touch-callout: none; +@@ -548,7 +536,7 @@ a.session-tree-main { + position: absolute; + right: var(--session-tree-inset-right); + left: var(--session-tree-inset-left); +- border-radius: 7px; ++ border-radius: var(--radius-sm); + background: color-mix(in srgb, var(--bg-panel) 55%, transparent); + pointer-events: none; + } +@@ -589,7 +577,7 @@ a.session-tree-main { + height: 22px; + padding: 0 10px; + border: 1px solid var(--border); +- border-radius: 11px; ++ border-radius: var(--radius-lg); + background: var(--bg-panel); + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.18); + color: var(--text); @@ -618,7 +606,7 @@ a.session-tree-main { padding: 0 0 0 5px; color: var(--text-muted); @@ -4090,6 +5297,24 @@ index 86728a2..e5780e6 100644 text-align: left; white-space: nowrap; } +@@ -726,7 +714,7 @@ a.session-tree-main { + width: 22px; + height: 22px; + padding: 0; +- border-radius: 6px; ++ border-radius: var(--radius-sm); + color: var(--text-dim); + } + +@@ -787,7 +775,7 @@ a.session-tree-main { + gap: 3px; + min-width: 0; + padding: 0 4px 0 5px; +- border-radius: 7px; ++ border-radius: var(--radius-sm); + color: var(--text-dim); + font-size: 12px; + text-align: left; @@ -838,7 +826,10 @@ a.session-tree-main { /* Phone layout: 44px rows, a step larger type for titles and project names, @@ -4137,7 +5362,7 @@ index 86728a2..e5780e6 100644 border: 0; - border-top: 2px solid transparent; - border-radius: 0; -+ border-radius: 7px; ++ border-radius: var(--radius-sm); background: transparent; color: var(--text-muted); font-size: 12px; @@ -4202,7 +5427,8 @@ index 86728a2..e5780e6 100644 + margin: 0 0 0 6px; + padding: 0 5px; border: 0; - border-radius: 6px; +- border-radius: 6px; ++ border-radius: var(--radius-sm); background: transparent; - color: var(--text); - font-family: var(--font-mono); @@ -4274,19 +5500,56 @@ index 86728a2..e5780e6 100644 .sidebar-header[data-fit="1"] .sidebar-new-label, .sidebar-header[data-fit="2"] .sidebar-new-label, .sidebar-header[data-fit="2"] .sidebar-tab-label { +@@ -1246,7 +1234,7 @@ a.session-tree-main { + padding: 0 5px; + overflow: hidden; + border: 0; +- border-radius: 7px; ++ border-radius: var(--radius-sm); + background: transparent; + color: var(--text-muted); + font: inherit; +@@ -1304,7 +1292,7 @@ a.session-tree-main { + min-width: 18px; + max-width: 26px; + height: 26px; +- border-radius: 6px; ++ border-radius: var(--radius-sm); + } + + /* Under that row (or the whole files tab): the head, then the tree filling @@ -1329,9 +1317,8 @@ a.session-tree-main { height: 29px; padding: 0 10px; box-sizing: border-box; - border: 1px solid var(--border); - border-radius: 7px; +- border-radius: 7px; - background: var(--bg); ++ border-radius: var(--radius-sm); + background: var(--bg-surface); color: var(--text); font: inherit; font-size: 12px; +@@ -1413,7 +1400,7 @@ a.session-tree-main { + height: 26px; + padding: 0 6px; + border: 0; +- border-radius: 6px; ++ border-radius: var(--radius-sm); + background: transparent; + color: var(--text-muted); + font-size: 12px; +@@ -1502,7 +1489,7 @@ a.session-tree-main { + height: 32px; + padding: 0; + border: 0; +- border-radius: 7px; ++ border-radius: var(--radius-sm); + background: transparent; + color: var(--text-dim); + cursor: pointer; diff --git a/components/AgentSessionPanel.tsx b/components/AgentSessionPanel.tsx -index a899c62..051b2df 100644 +index a899c62..1e5fe9a 100644 --- a/components/AgentSessionPanel.tsx +++ b/components/AgentSessionPanel.tsx @@ -3,6 +3,8 @@ @@ -4340,8 +5603,17 @@ index a899c62..051b2df 100644 )} +@@ -205,7 +197,7 @@ export function AgentSessionPanel({ rootSession, subagents, selectedSessionId, r + aria-label={t("agentSwitcher.search")} + style={{ + width: "100%", height: 32, padding: "0 10px", +- border: "1px solid var(--border)", borderRadius: 6, ++ border: "1px solid var(--border)", borderRadius: "var(--radius-sm)", + background: "var(--bg)", color: "var(--text)", fontSize: 12, outline: "none", + }} + /> diff --git a/components/AgentsConfig.tsx b/components/AgentsConfig.tsx -index 861a1c6..e791a20 100644 +index 861a1c6..d5d466e 100644 --- a/components/AgentsConfig.tsx +++ b/components/AgentsConfig.tsx @@ -38,6 +38,8 @@ import { @@ -4353,6 +5625,15 @@ index 861a1c6..e791a20 100644 const TOOL_OPTIONS = ["read", "bash", "edit", "write", "grep", "find", "ls"]; const THINKING_OPTIONS = ["", "off", "minimal", "low", "medium", "high", "xhigh", "max"] as const; +@@ -67,7 +69,7 @@ const inputStyle: CSSProperties = { + height: 34, + padding: "0 9px", + border: "1px solid var(--border)", +- borderRadius: 5, ++ borderRadius: "var(--radius-sm)", + background: "var(--bg)", + color: "var(--text)", + fontSize: 12, @@ -727,9 +729,7 @@ export function AgentsConfig({ className={savedOk ? "is-success" : undefined} > @@ -4365,10 +5646,19 @@ index 861a1c6..e791a20 100644 {savedOk ? t("i18n.saved") : saving ? t("agents.saving") : t("agents.save")} diff --git a/components/AppShell.tsx b/components/AppShell.tsx -index c8c4ba9..ed04857 100644 +index c8c4ba9..fb61efb 100644 --- a/components/AppShell.tsx +++ b/components/AppShell.tsx -@@ -74,6 +74,8 @@ import type { FileViewerState } from "@/lib/file-viewer-state"; +@@ -3,6 +3,8 @@ + import { useState, useCallback, useRef, useEffect, useLayoutEffect, useMemo } from "react"; + import { useRouter, useSearchParams } from "next/navigation"; + import { useGlobalKeyboardShortcuts } from "@/hooks/useKeyboardShortcuts"; ++import { sessionRowTitle } from "./SessionTree"; ++import { skillExpansionToCommand } from "@/lib/slash-display"; + import { SessionSidebar, type SelectSessionOptions, type SessionSidebarControl } from "./SessionSidebar"; + import { ChatWindow } from "./ChatWindow"; + import { NewSessionContextBar, type NewSessionContextControl } from "./NewSessionContextBar"; +@@ -74,6 +76,8 @@ import type { FileViewerState } from "@/lib/file-viewer-state"; import { PRESET_READ_ONLY, type ToolEntry } from "@/lib/tool-presets"; import { getSessionFamily } from "@/lib/session-family"; import { getLastSettingsSection, settingsSectionRequiresProject, type SettingsSection } from "@/lib/settings-navigation"; @@ -4377,7 +5667,21 @@ index c8c4ba9..ed04857 100644 type SessionCopyField = "file" | "id" | "projectDir" | "gitBranch" | "gitWorktree"; type AutoNameStatus = -@@ -1422,7 +1424,7 @@ export function AppShell() { +@@ -83,6 +87,13 @@ type AutoNameStatus = + | { kind: "error"; message: string }; + + const TOP_BAR_ICON_BUTTON_SIZE = 36; ++ ++/** The header's title: the session's name, else its whole first line (the row's title stops at 50 characters). */ ++function sessionHeaderTitle(session: SessionInfo): string { ++ if (session.name) return session.name; ++ const first = (skillExpansionToCommand(session.firstMessage) ?? session.firstMessage ?? "").split("\n")[0].trim(); ++ return first || sessionRowTitle(session); ++} + const AGENT_PANEL_WIDTH = 420; + // pi's built-in tools that never change a file; any other tool may (#1144). + const READ_ONLY_TOOL_NAMES = new Set(PRESET_READ_ONLY); +@@ -1422,7 +1433,7 @@ export function AppShell() { onRunningSessionIdsChange={handleRunningSessionIdsChange} onSessionsChange={handleSessionsChange} /> @@ -4386,7 +5690,7 @@ index c8c4ba9..ed04857 100644 {([ ["models", translate("common.models")], ["skills", translate("common.skills")], -@@ -1432,19 +1434,11 @@ export function AppShell() { +@@ -1432,19 +1443,11 @@ export function AppShell() { ); -@@ -1542,17 +1518,16 @@ export function AppShell() { - justifyContent: "center", - gap: 6, - width: mobile ? TOP_BAR_ICON_BUTTON_SIZE : undefined, +@@ -1526,9 +1511,10 @@ export function AppShell() { + const renderChatToolbarActions = (mobile: boolean) => { + if (!mobile && !showChat) return null; + return ( +-
++
+ {(() => { -@@ -1625,14 +1585,13 @@ export function AppShell() { + // 上下文压缩后当前消息可能不再包含 user 消息,需同时参考会话文件的消息总数。 +@@ -1615,6 +1554,8 @@ export function AppShell() { + return ( + -@@ -1677,20 +1630,17 @@ export function AppShell() { - position: "relative", - display: "flex", alignItems: "center", justifyContent: "center", gap: 6, - width: mobile ? TOP_BAR_ICON_BUTTON_SIZE : undefined, + ); + })()} + {hasSubagentSessions && ( +
-@@ -1885,14 +1820,14 @@ export function AppShell() { - gap: mobile ? 7 : 10, - paddingLeft: mobile ? 6 : 12, - paddingRight: mobile ? 6 : 12, + ); +@@ -1880,19 +1733,20 @@ export function AppShell() { + style={{ + marginLeft: mobile ? 0 : "auto", + display: "flex", alignItems: "center", justifyContent: "flex-end", +- flex: mobile ? 1 : undefined, ++ // Desktop: the title gives way, never the numbers. ++ flex: mobile ? 1 : "none", + minWidth: 0, +- gap: mobile ? 7 : 10, +- paddingLeft: mobile ? 6 : 12, +- paddingRight: mobile ? 6 : 12, - height: "100%", ++ gap: mobile ? 7 : 8, ++ paddingLeft: mobile ? 6 : 8, ++ paddingRight: mobile ? 6 : 8, + height: 28, alignSelf: "center", overflow: "hidden", visibility: covered ? "hidden" : "visible", @@ -4671,12 +6063,20 @@ index c8c4ba9..ed04857 100644 border: "none", - borderTop: activeTopPanel === "session" ? "2px solid var(--accent)" : "2px solid transparent", - fontSize: 11, color: "var(--text-muted)", -+ borderTop: "none", borderRadius: 7, -+ fontSize: 12, color: "var(--text-muted)", ++ borderTop: "none", borderRadius: "var(--radius-sm)", ++ fontSize: 12, color: activeTopPanel === "session" ? "var(--text)" : "var(--text-dim)", whiteSpace: "nowrap", cursor: showChat ? "pointer" : "default", fontVariantNumeric: "tabular-nums", transition: "color 0.1s, background 0.1s", -@@ -1908,17 +1843,13 @@ export function AppShell() { +@@ -1901,24 +1755,20 @@ export function AppShell() { + if (showChat && !covered) event.currentTarget.style.color = "var(--text)"; + }} + onMouseLeave={(event) => { +- event.currentTarget.style.color = activeTopPanel === "session" ? "var(--text)" : "var(--text-muted)"; ++ event.currentTarget.style.color = activeTopPanel === "session" ? "var(--text)" : "var(--text-dim)"; + }} + > + {mobile ? ( <> {tokens && tokens.input > 0 && ( @@ -4696,7 +6096,7 @@ index c8c4ba9..ed04857 100644 {formatCompact(tokens.output)} )} -@@ -1942,25 +1873,19 @@ export function AppShell() { +@@ -1942,25 +1792,19 @@ export function AppShell() { <> {tokens && tokens.input > 0 && ( @@ -4725,7 +6125,7 @@ index c8c4ba9..ed04857 100644 {formatCompact(tokens.cacheRead)} )} -@@ -1971,9 +1896,7 @@ export function AppShell() { +@@ -1971,9 +1815,7 @@ export function AppShell() { )} {desktopContextText && ( @@ -4736,7 +6136,7 @@ index c8c4ba9..ed04857 100644 {desktopContextText} )} -@@ -2011,9 +1934,7 @@ export function AppShell() { +@@ -2011,9 +1853,7 @@ export function AppShell() { onMouseEnter={(event) => { if (!covered) event.currentTarget.style.color = "var(--text)"; }} onMouseLeave={(event) => { event.currentTarget.style.color = rightPanelOpen ? "var(--text)" : "var(--text-muted)"; }} > @@ -4747,7 +6147,7 @@ index c8c4ba9..ed04857 100644 ); }; -@@ -2178,13 +2099,9 @@ export function AppShell() { +@@ -2178,13 +2018,9 @@ export function AppShell() { onMouseLeave={(e) => { e.currentTarget.style.color = "var(--text-muted)"; }} > {sidebarOpen ? ( @@ -4763,7 +6163,7 @@ index c8c4ba9..ed04857 100644 )} {isMobile && ( -@@ -2221,13 +2138,9 @@ export function AppShell() { +@@ -2221,13 +2057,9 @@ export function AppShell() { }} > {mobileToolbarMoreOpen ? ( @@ -4779,7 +6179,31 @@ index c8c4ba9..ed04857 100644 )} )} -@@ -2443,14 +2356,9 @@ export function AppShell() { +@@ -2262,6 +2094,14 @@ export function AppShell() { + {!isMobile && ( + <> + {renderProjectTrustWarning(false)} ++ {/* The session's title leads the header, as in Claude Desktop; its actions ++ are quiet icons on the right, then the session's usage. */} ++
++ {showChat ? (selectedSession ? sessionHeaderTitle(selectedSession) : translate("i18n.newSession")) : null} ++
+ {renderChatToolbarActions(false)} + {renderSessionStatsButton(false)} + +@@ -2426,7 +2266,7 @@ export function AppShell() { + color: copied ? "var(--accent)" : "var(--text-dim)", + background: "transparent", + border: "1px solid var(--border)", +- borderRadius: 4, ++ borderRadius: "var(--radius-xs)", + cursor: "pointer", + flex: "0 0 auto", + transition: "color 0.12s, border-color 0.12s, background 0.12s", +@@ -2443,14 +2283,9 @@ export function AppShell() { }} > {copied ? ( @@ -4796,7 +6220,7 @@ index c8c4ba9..ed04857 100644 )} ); -@@ -2601,9 +2509,7 @@ export function AppShell() { +@@ -2601,9 +2436,7 @@ export function AppShell() {
) : (
@@ -4807,7 +6231,7 @@ index c8c4ba9..ed04857 100644
{translate("workspace.getStarted")}
-@@ -2673,11 +2579,7 @@ export function AppShell() { +@@ -2673,11 +2506,7 @@ export function AppShell() { title={translate(rightPanelFullWidth ? "files.restorePanelWidth" : "files.expandPanel")} aria-label={translate(rightPanelFullWidth ? "files.restorePanelWidth" : "files.expandPanel")} > @@ -4820,7 +6244,7 @@ index c8c4ba9..ed04857 100644
diff --git a/components/BranchNavigator.tsx b/components/BranchNavigator.tsx -index 0f8186b..e8bafeb 100644 +index 0f8186b..e7fde3d 100644 --- a/components/BranchNavigator.tsx +++ b/components/BranchNavigator.tsx @@ -3,6 +3,8 @@ @@ -4844,6 +6268,15 @@ index 0f8186b..e8bafeb 100644 interface Props { tree: SessionTreeNode[]; +@@ -209,7 +211,7 @@ function TreeNodeView({ node, activePathIds, depth, isLast, parentLines, onSelec + color: role === "user" ? "var(--accent)" : "var(--text-dim)", + background: role === "user" ? "rgba(37,99,235,0.08)" : "var(--bg-hover)", + border: `1px solid ${role === "user" ? "rgba(37,99,235,0.2)" : "var(--border)"}`, +- borderRadius: 3, ++ borderRadius: "var(--radius-xs)", + padding: "0 4px", + marginRight: 5, + flexShrink: 0, @@ -304,18 +306,11 @@ export function BranchNavigator({ tree, activeLeafId, onLeafChange, inline, cont const hasContent = !noBranchReason && topLevel.length > 0; @@ -4909,6 +6342,26 @@ index 0000000..00d78bd + + ); +} +diff --git a/components/ChatAppearance.test.mjs b/components/ChatAppearance.test.mjs +index 42d3545..8418d90 100644 +--- a/components/ChatAppearance.test.mjs ++++ b/components/ChatAppearance.test.mjs +@@ -6,9 +6,12 @@ const jiti = createJiti(import.meta.url); + const { clampChatContentWidth, clampChatContentFontSize } = await jiti.import("../hooks/useChatAppearance.ts"); + + test("chat width validation preserves the default and supported range", () => { +- assert.equal(clampChatContentWidth(undefined), 820); +- assert.equal(clampChatContentWidth("invalid"), 820); +- assert.equal(clampChatContentWidth(700), 820); ++ assert.equal(clampChatContentWidth(undefined), 720); ++ assert.equal(clampChatContentWidth(null), 720, "nothing saved in localStorage is the default"); ++ assert.equal(clampChatContentWidth(""), 720); ++ assert.equal(clampChatContentWidth("invalid"), 720); ++ assert.equal(clampChatContentWidth(600), 640); ++ assert.equal(clampChatContentWidth(700), 700); + assert.equal(clampChatContentWidth(1104), 1104); + assert.equal(clampChatContentWidth(2400), 2000); + }); diff --git a/components/ChatInput.test.mjs b/components/ChatInput.test.mjs index adfcc12..cb17981 100644 --- a/components/ChatInput.test.mjs @@ -4923,7 +6376,7 @@ index adfcc12..cb17981 100644 assert.doesNotMatch(html, /type="file"|Attach files|Change tool preset/); }); diff --git a/components/ChatInput.tsx b/components/ChatInput.tsx -index 9a0a1b7..a7549d0 100644 +index 9a0a1b7..7dd9112 100644 --- a/components/ChatInput.tsx +++ b/components/ChatInput.tsx @@ -35,6 +35,8 @@ import { useFontPreferences } from "@/hooks/useFontPreferences"; @@ -4935,7 +6388,15 @@ index 9a0a1b7..a7549d0 100644 export { filterModelOptions } from "./ModelSelector"; -@@ -572,22 +574,7 @@ function ModelNoticeBanner({ tone, title, body, onClose }: { tone: "error" | "wa +@@ -565,29 +567,14 @@ function ModelNoticeBanner({ tone, title, body, onClose }: { tone: "error" | "wa + padding: "7px 10px", + overflowY: "auto", + border: `1px solid rgba(${color},0.3)`, +- borderRadius: 6, ++ borderRadius: "var(--radius-sm)", + background: `rgba(${color},0.07)`, + color: `rgb(${color})`, + fontSize: 11, lineHeight: 1.45, }} > @@ -4959,6 +6420,33 @@ index 9a0a1b7..a7549d0 100644
{title}
{body}
+@@ -1729,7 +1716,7 @@ export const ChatInput = memo(forwardRef(function ChatIn + style={{ display: "none" }} + onChange={handleFilePick} + />} +-
++
+ + + {showImageUnsupportedWarning && (() => { +@@ -1748,7 +1735,7 @@ export const ChatInput = memo(forwardRef(function ChatIn +
+@@ -1781,7 +1768,7 @@ export const ChatInput = memo(forwardRef(function ChatIn + color: "var(--text)", + background: "transparent", + border: "1px solid var(--border)", +- borderRadius: 7, ++ borderRadius: "var(--radius-sm)", + cursor: "pointer", + transition: "background 0.12s, border-color 0.12s", + whiteSpace: "nowrap", @@ -1795,10 +1782,7 @@ export const ChatInput = memo(forwardRef(function ChatIn e.currentTarget.style.borderColor = "var(--border)"; }} @@ -4971,8 +6459,12 @@ index 9a0a1b7..a7549d0 100644 {t("chat.recall")} )} -@@ -1819,10 +1803,7 @@ export const ChatInput = memo(forwardRef(function ChatIn - borderRadius: 6, fontSize: 12, color: "rgba(180,130,0,0.9)", +@@ -1816,13 +1800,10 @@ export const ChatInput = memo(forwardRef(function ChatIn +
- @@ -4983,8 +6475,12 @@ index 9a0a1b7..a7549d0 100644 {t("chat.retrying", { attempt: retryInfo.attempt, max: retryInfo.maxAttempts })}{retryInfo.errorMessage && — {retryInfo.errorMessage}}
)} -@@ -1833,9 +1814,7 @@ export const ChatInput = memo(forwardRef(function ChatIn - borderRadius: 6, fontSize: 12, color: "rgba(5,150,105,0.95)", +@@ -1830,12 +1811,10 @@ export const ChatInput = memo(forwardRef(function ChatIn +
- @@ -4994,6 +6490,24 @@ index 9a0a1b7..a7549d0 100644 {compactResultText}
)} +@@ -1850,7 +1829,7 @@ export const ChatInput = memo(forwardRef(function ChatIn + padding: onDismissCompactError ? "3px 3px 3px 10px" : "7px 10px", + background: "rgba(239,68,68,0.07)", + border: "1px solid rgba(239,68,68,0.3)", +- borderRadius: 6, ++ borderRadius: "var(--radius-sm)", + color: "#ef4444", + fontFamily: "var(--font-mono)", + fontSize: 12, +@@ -1871,7 +1850,7 @@ export const ChatInput = memo(forwardRef(function ChatIn + + +
))} +@@ -1905,10 +1882,10 @@ export const ChatInput = memo(forwardRef(function ChatIn + right: 0, + bottom: "calc(100% + 8px)", + zIndex: 120, +- background: "var(--bg)", ++ background: "var(--bg-elevated)", + border: "1px solid var(--border)", +- borderRadius: 8, +- boxShadow: "0 -6px 20px rgba(0,0,0,0.12)", ++ borderRadius: "var(--radius-lg)", ++ boxShadow: "var(--shadow-pop)", + overflow: "hidden", + maxHeight: "min(44vh, 360px)", + }} @@ -1924,21 +1901,7 @@ export const ChatInput = memo(forwardRef(function ChatIn color: "var(--text-dim)", }} @@ -5028,11 +6556,85 @@ index 9a0a1b7..a7549d0 100644
{inputHistory.map((item, index) => { -@@ -2245,14 +2208,14 @@ export const ChatInput = memo(forwardRef(function ChatIn - flexDirection: compact ? "column" : "row", +@@ -1962,7 +1925,7 @@ export const ChatInput = memo(forwardRef(function ChatIn + gap: 8, + padding: "7px 8px", + border: "none", +- borderRadius: 6, ++ borderRadius: "var(--radius-sm)", + background: active ? "var(--bg-selected)" : "none", + color: "var(--text)", + cursor: "pointer", +@@ -1992,10 +1955,10 @@ export const ChatInput = memo(forwardRef(function ChatIn + right: 0, + bottom: "calc(100% + 8px)", + zIndex: 120, +- background: "var(--bg)", ++ background: "var(--bg-elevated)", + border: "1px solid var(--border)", +- borderRadius: 8, +- boxShadow: "0 -6px 20px rgba(0,0,0,0.12)", ++ borderRadius: "var(--radius-lg)", ++ boxShadow: "var(--shadow-pop)", + overflow: "hidden", + boxSizing: "border-box", + display: "flex", +@@ -2081,7 +2044,7 @@ export const ChatInput = memo(forwardRef(function ChatIn + justifyContent: "center", + padding: "9px 10px", + border: `1px solid ${active ? "var(--accent)" : "var(--border)"}`, +- borderRadius: 7, ++ borderRadius: "var(--radius-sm)", + background: active ? "var(--bg-selected)" : "var(--bg-panel)", + color: "var(--text)", + cursor: "pointer", +@@ -2102,7 +2065,7 @@ export const ChatInput = memo(forwardRef(function ChatIn + marginLeft: 6, + padding: "0 4px", + border: "1px solid var(--border)", +- borderRadius: 3, ++ borderRadius: "var(--radius-xs)", + fontSize: 9, + color: "var(--text-dim)", + whiteSpace: "nowrap", +@@ -2151,10 +2114,10 @@ export const ChatInput = memo(forwardRef(function ChatIn + right: 0, + bottom: "calc(100% + 8px)", + zIndex: 120, +- background: "var(--bg)", ++ background: "var(--bg-elevated)", + border: "1px solid var(--border)", +- borderRadius: 8, +- boxShadow: "0 -6px 20px rgba(0,0,0,0.12)", ++ borderRadius: "var(--radius-lg)", ++ boxShadow: "var(--shadow-pop)", + overflow: "hidden", + boxSizing: "border-box", + display: "flex", +@@ -2213,7 +2176,7 @@ export const ChatInput = memo(forwardRef(function ChatIn + gap: 8, + padding: "6px 8px", + border: "none", +- borderRadius: 6, ++ borderRadius: "var(--radius-sm)", + background: active ? "var(--bg-selected)" : "none", + color: "var(--text)", + cursor: "pointer", +@@ -2239,21 +2202,15 @@ export const ChatInput = memo(forwardRef(function ChatIn + ); + })()} +