Mike0021 commited on
Commit
242593c
·
verified ·
1 Parent(s): 382163c

Pi Web fork: focus as Claude Code Desktop draws it; sidebar, session info and Settings aligned

Browse files
Files changed (1) hide show
  1. piweb/hf-design.patch +777 -218
piweb/hf-design.patch CHANGED
@@ -1,5 +1,5 @@
1
  diff --git a/AGENTS.md b/AGENTS.md
2
- index a5c8983..749afce 100644
3
  --- a/AGENTS.md
4
  +++ b/AGENTS.md
5
  @@ -65,8 +65,13 @@ app/api/
@@ -156,7 +156,7 @@ index a5c8983..749afce 100644
156
 
157
  ## Pi Session File Format
158
 
159
- @@ -274,3 +317,38 @@ Location: `~/.pi/agent/sessions/<encoded-cwd>/<timestamp>_<uuid>.jsonl`
160
  --accent --user-bg --tool-bg
161
  --font-mono
162
  ```
@@ -179,22 +179,16 @@ index a5c8983..749afce 100644
179
  +newer parts, menus, tooltips and popovers in `app/ui.css`, the header and tiles in `app/shell.css`)
180
  +are styled there too.
181
  +`--accent` is a neutral gray in light and dark (no blue anywhere: links are told apart by their
182
- +underline, focus by a 2px ring), `--accent-soft` its tint; Mist, Rose and Pine keep their own hues.
183
  +All code (fences, the file viewer, tool inputs and outputs, diffs) is highlighted by Shiki in `lib/diffs-theme.ts`'s neutral themes (`components/highlighting.ts`); the terminal keeps its ANSI hues.
184
- +Keyboard focus is `app/focus.css`, imported last: one 2px `--focus-ring` ring, inset on controls,
185
- +rows and fields (a row draws it on the row; its link or toggle draws nothing), 2px off ink-filled
186
- +controls and text links. Hidden row actions fold to width 0 (never `display: none`: Tab would drop
 
 
 
 
187
  +focus); closed panels are `inert`; modals use `keepTabInside`.
188
- +
189
- +<!-- BEGIN:nextjs-agent-rules -->
190
- +
191
- +# This is NOT the Next.js you know
192
- +
193
- +This version has breaking changes — APIs, conventions, and file structure may all differ from your training data. Read the relevant guide in `node_modules/next/dist/docs/` (resolved from this file's directory; in monorepos the `next` package may not be visible from the repo root) before writing any code. Heed deprecation notices.
194
- +
195
- +This block is written and re-added by `next dev` — verify at `node_modules/next/dist/server/lib/generate-agent-files.js`. Removing it from a diff only re-creates the uncommitted change; committing it with your work keeps the tree clean.
196
- +
197
- +<!-- END:nextjs-agent-rules -->
198
  diff --git a/app/api/browser/events/route.ts b/app/api/browser/events/route.ts
199
  new file mode 100644
200
  index 0000000..84aa812
@@ -1198,34 +1192,54 @@ x=WyK>Q474TgO3Dx+=lt**@4jiTmoXh>QX;DqIUnCXbm^o4n^-mRR&Qm{tL_JHERF>
1198
 
1199
  diff --git a/app/focus.css b/app/focus.css
1200
  new file mode 100644
1201
- index 0000000..f8b1d1c
1202
  --- /dev/null
1203
  +++ b/app/focus.css
1204
- @@ -0,0 +1,293 @@
1205
  +/* Keyboard focus, one way everywhere (the design system's "Motion and states").
1206
  + Imported last (app/layout.tsx), so these rules win over each component's.
1207
  +
1208
- + One indicator: a 2px ring in --focus-ring, the neutral accent gray. Never a
 
 
 
1209
  + ring and a second ring, never a box around a row's text:
1210
- + - inset (-2px), following the control's own corners, on everything that is
1211
- + not filled with ink: ghost and icon buttons, pickers, tabs, options, menu
1212
- + items, fields. A row draws the ring on the whole row (its inner control
1213
- + draws nothing), so it fits the row's box and is never clipped;
1214
- + - 2px off the edge on what is filled with ink (primary and Send buttons, a
1215
- + switch, floating round buttons), where an inset ring would vanish into
1216
- + the fill;
1217
- + - 2px off, with small corners, around a link in running text.
 
 
 
1218
  + Mouse focus shows nothing (:focus-visible); text fields show their caret,
1219
  + and other fields the inset ring too, except the composer, which is always
1220
  + where typing goes. */
1221
  +
 
 
 
 
 
 
1222
  +:where(button, summary, [role="button"], [role="tab"], [role="menuitem"], [role="menuitemradio"], [role="menuitemcheckbox"], [role="option"], [role="switch"], [role="treeitem"], [tabindex]:not(input):not(textarea):not(select)):focus {
1223
  + outline: none;
1224
  +}
1225
  +
1226
- +:where(button, summary, [role="button"], [role="tab"], [role="menuitem"], [role="menuitemradio"], [role="menuitemcheckbox"], [role="option"], [role="switch"], [role="treeitem"], [tabindex]:not(input):not(textarea):not(select)):focus-visible {
1227
- + outline: 2px solid var(--focus-ring);
1228
- + outline-offset: -2px;
 
 
 
 
 
 
 
 
1229
  +}
1230
  +
1231
  +:where(a[href]):focus {
@@ -1233,18 +1247,19 @@ index 0000000..f8b1d1c
1233
  +}
1234
  +
1235
  +:where(a[href]):focus-visible {
1236
- + outline: 2px solid var(--focus-ring);
1237
  + outline-offset: 2px;
1238
  + border-radius: var(--radius-xs);
1239
  +}
1240
  +
1241
  +:where(input:not([type="range"]):not([type="checkbox"]):not([type="radio"]), select, textarea):focus-visible {
1242
- + outline: 2px solid var(--focus-ring);
1243
- + outline-offset: -2px;
 
1244
  +}
1245
  +
1246
  +:where(input[type="checkbox"], input[type="radio"]):focus-visible {
1247
- + outline: 2px solid var(--focus-ring);
1248
  + outline-offset: 2px;
1249
  +}
1250
  +
@@ -1252,12 +1267,14 @@ index 0000000..f8b1d1c
1252
  +.chat-input-textarea:focus,
1253
  +.chat-input-textarea:focus-visible {
1254
  + outline: none;
 
1255
  +}
1256
  +
1257
  +/* A visually hidden input inside an option (a theme, the send key) leaves the
1258
  + ring to the option. */
1259
  +input.sr-only:focus-visible {
1260
  + outline: none;
 
1261
  +}
1262
  +
1263
  +/* ---------------------------------------------------------------- filled with ink: 2px off */
@@ -1269,7 +1286,7 @@ index 0000000..f8b1d1c
1269
  +.web-login-form button:focus-visible,
1270
  +.chat-scroll-to-bottom:focus-visible,
1271
  +.image-preview-close:focus-visible {
1272
- + outline: 2px solid var(--focus-ring);
1273
  + outline-offset: 2px;
1274
  +}
1275
  +
@@ -1298,8 +1315,9 @@ index 0000000..f8b1d1c
1298
  +.extension-status-command:focus-visible,
1299
  +button.extension-widget-trigger:focus-visible,
1300
  +.new-session-update:focus-visible {
1301
- + outline: 2px solid var(--focus-ring);
1302
- + outline-offset: -2px;
 
1303
  +}
1304
  +
1305
  +.settings-section-tab:focus-visible,
@@ -1314,29 +1332,31 @@ index 0000000..f8b1d1c
1314
  +
1315
  +/* A Terminal tab being renamed: the tab is the field, its pill takes the ring (Desktop's
1316
  + has-[:focus-visible] on the tab), the bare input none. */
1317
- +.terminal-tab-input:focus-visible { outline: none; }
1318
  +.terminal-tab:has(.terminal-tab-input:focus-visible) {
1319
- + outline: 2px solid var(--focus-ring);
1320
- + outline-offset: -2px;
 
1321
  +}
1322
  +
1323
  +/* An option whose input is visually hidden: the option is the control. */
1324
  +.settings-theme-option:has(input:focus-visible),
1325
  +.settings-send-mode-option:has(input:focus-visible) {
1326
- + outline: 2px solid var(--focus-ring);
1327
- + outline-offset: -2px;
 
1328
  +}
1329
  +
1330
- +/* Fields, the filled ones too: inset, no glow, no border change. */
1331
  +.settings-font-input:focus-visible,
1332
  +.sidebar-search-input:focus,
1333
  +.sidebar-search-input:focus-visible,
1334
  +.sidebar-menu-filter-input:focus-visible,
1335
  +.directory-picker-path:focus-visible {
1336
  + border-color: transparent !important;
1337
- + box-shadow: none;
1338
- + outline: 2px solid var(--focus-ring);
1339
- + outline-offset: -2px;
1340
  +}
1341
  +
1342
  +.sidebar-search-input:focus:not(:focus-visible) {
@@ -1345,13 +1365,14 @@ index 0000000..f8b1d1c
1345
  +
1346
  +.web-login-composer:focus-within {
1347
  + border-color: transparent;
1348
- + box-shadow: none;
1349
- + outline: 2px solid var(--focus-ring);
1350
- + outline-offset: -2px;
1351
  +}
1352
  +
1353
  +.web-login-composer :is(input, textarea):focus-visible {
1354
  + outline: none;
 
1355
  +}
1356
  +
1357
  +/* The top bar's 36px edge buttons (the sidebar toggle): a
@@ -1362,8 +1383,9 @@ index 0000000..f8b1d1c
1362
  +}
1363
  +
1364
  +.topbar-edge-button:focus-visible {
1365
- + outline: 2px solid var(--focus-ring);
1366
  + outline-offset: -4px;
 
1367
  +}
1368
  +
1369
  +/* ---------------------------------------------------------------- rows: the ring is the row's */
@@ -1372,21 +1394,25 @@ index 0000000..f8b1d1c
1372
  +.session-tree .session-tree-main:focus-visible,
1373
  +.session-tree a.session-tree-main:focus-visible {
1374
  + outline: none;
 
1375
  +}
1376
  +
1377
  +.session-tree-session:has(> .session-tree-main:focus-visible) {
1378
- + outline: 2px solid var(--focus-ring);
1379
- + outline-offset: -2px;
 
1380
  +}
1381
  +
1382
  +/* A project header: the same, on the header's box (its + and ⋯ ring themselves). */
1383
  +.session-tree .session-tree-group-toggle:focus-visible {
1384
  + outline: none;
 
1385
  +}
1386
  +
1387
  +.session-tree-group:has(> .session-tree-group-toggle:focus-visible) {
1388
- + outline: 2px solid var(--focus-ring);
1389
- + outline-offset: -2px;
 
1390
  +}
1391
  +
1392
  +/* Rows whose control spans the row: the control's own box, with the row's corners. */
@@ -1395,18 +1421,21 @@ index 0000000..f8b1d1c
1395
  +.session-tree .session-tree-more-toggle:focus-visible,
1396
  +.sidebar-files-section-toggle:focus-visible {
1397
  + border-radius: var(--radius-sm);
1398
- + outline: 2px solid var(--focus-ring);
1399
- + outline-offset: -2px;
 
1400
  +}
1401
  +
1402
  +/* A tool call or a thought: the whole step line, its time included. */
1403
  +.tool-call-head:focus-visible {
1404
  + outline: none;
 
1405
  +}
1406
  +
1407
  +.tool-call-row:has(> .tool-call-head:focus-visible) {
1408
- + outline: 2px solid var(--focus-ring);
1409
- + outline-offset: -2px;
 
1410
  +}
1411
  +
1412
  +/* Browsers without :has() keep the ring on the inner control. */
@@ -1415,14 +1444,16 @@ index 0000000..f8b1d1c
1415
  + .session-tree .session-tree-group-toggle:focus-visible,
1416
  + .tool-call-head:focus-visible {
1417
  + border-radius: var(--radius-sm);
1418
- + outline: 2px solid var(--focus-ring);
1419
- + outline-offset: -2px;
 
1420
  + }
1421
  +}
1422
  +
1423
- +/* A resize handle: its line turns into the ring's colour, 2px, nothing around it. */
1424
  +.panel-resize-handle:focus-visible {
1425
  + outline: none;
 
1426
  +}
1427
  +
1428
  +/* ---------------------------------------------------------------- sliders
@@ -1439,6 +1470,7 @@ index 0000000..f8b1d1c
1439
  +input[type="range"]:focus,
1440
  +input[type="range"]:focus-visible {
1441
  + outline: none;
 
1442
  +}
1443
  +
1444
  +input[type="range"]::-webkit-slider-runnable-track {
@@ -1480,11 +1512,11 @@ index 0000000..f8b1d1c
1480
  +}
1481
  +
1482
  +input[type="range"]:focus-visible::-webkit-slider-thumb {
1483
- + box-shadow: 0 0 0 2px var(--bg-elevated, var(--bg)), 0 0 0 4px var(--focus-ring);
1484
  +}
1485
  +
1486
  +input[type="range"]:focus-visible::-moz-range-thumb {
1487
- + box-shadow: 0 0 0 2px var(--bg-elevated, var(--bg)), 0 0 0 4px var(--focus-ring);
1488
  +}
1489
  +
1490
  +input[type="range"]:disabled {
@@ -1493,8 +1525,109 @@ index 0000000..f8b1d1c
1493
  +}
1494
  +
1495
  +.panel-resize-handle:focus-visible::after {
1496
- + background: var(--focus-ring);
1497
  +}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1498
  diff --git a/app/fonts/Geist-Variable.woff2 b/app/fonts/Geist-Variable.woff2
1499
  new file mode 100644
1500
  index 0000000000000000000000000000000000000000..9983e92a03de482b5708201694101c330b6ae9b8
@@ -3822,7 +3955,7 @@ index 0000000..8d003fe
3822
  +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
3823
  +OTHER DEALINGS IN THE FONT SOFTWARE.
3824
  diff --git a/app/globals.css b/app/globals.css
3825
- index d6b5535..959b58f 100644
3826
  --- a/app/globals.css
3827
  +++ b/app/globals.css
3828
  @@ -24,46 +24,109 @@
@@ -4120,6 +4253,15 @@ index d6b5535..959b58f 100644
4120
  background: var(--accent);
4121
  color: var(--accent-contrast);
4122
  cursor: pointer;
 
 
 
 
 
 
 
 
 
4123
  @@ -332,110 +415,139 @@ html.dark .web-login-error {
4124
  cursor: not-allowed;
4125
  }
@@ -4412,7 +4554,19 @@ index d6b5535..959b58f 100644
4412
 
4413
 
4414
  .extension-status-shelf {
4415
- @@ -703,9 +797,9 @@ button.extension-widget-trigger:focus-visible {
 
 
 
 
 
 
 
 
 
 
 
 
4416
 
4417
  .extension-widget-placement-icon {
4418
  display: block;
@@ -4425,7 +4579,19 @@ index d6b5535..959b58f 100644
4425
  fill: currentColor;
4426
  }
4427
 
4428
- @@ -826,8 +920,8 @@ button.extension-widget-trigger:focus-visible {
 
 
 
 
 
 
 
 
 
 
 
 
4429
  }
4430
 
4431
  :root {
@@ -4436,7 +4602,7 @@ index d6b5535..959b58f 100644
4436
  --font-ui: var(--font-ui-default);
4437
  --font-mono: var(--font-mono-default);
4438
  --font-ui-weight: 400;
4439
- @@ -883,11 +977,11 @@ pre, code {
4440
  .markdown-body ol > li { list-style-type: decimal; }
4441
  .markdown-body li > p { margin: 0; }
4442
  .markdown-body li::marker {
@@ -4451,7 +4617,7 @@ index d6b5535..959b58f 100644
4451
  font-weight: 700;
4452
  }
4453
  .markdown-body em { color: var(--text-muted); }
4454
- @@ -896,24 +990,23 @@ pre, code {
4455
  text-decoration: underline;
4456
  text-decoration-thickness: 1px;
4457
  text-underline-offset: 3px;
@@ -4480,7 +4646,7 @@ index d6b5535..959b58f 100644
4480
  }
4481
  .markdown-body .contains-task-list {
4482
  padding-left: 0;
4483
- @@ -935,7 +1028,7 @@ pre, code {
4484
  height: 14px;
4485
  margin: 0;
4486
  border: 1px solid var(--border);
@@ -4489,7 +4655,7 @@ index d6b5535..959b58f 100644
4489
  background: var(--bg);
4490
  display: inline-grid;
4491
  place-content: center;
4492
- @@ -967,8 +1060,8 @@ pre, code {
4493
  margin: 8px 0;
4494
  overflow-x: auto;
4495
  overflow-y: hidden;
@@ -4500,7 +4666,7 @@ index d6b5535..959b58f 100644
4500
  }
4501
  .markdown-body table {
4502
  border-collapse: separate;
4503
- @@ -1088,7 +1181,7 @@ pre, code {
4504
  padding: 7px 8px;
4505
  overflow: auto;
4506
  border: 1px solid var(--border);
@@ -4509,7 +4675,7 @@ index d6b5535..959b58f 100644
4509
  background: var(--bg-panel);
4510
  font-family: var(--font-mono);
4511
  font-size: 11px;
4512
- @@ -1108,16 +1201,13 @@ pre, code {
4513
  min-width: 0;
4514
  max-width: 100%;
4515
  margin: 6px 0;
@@ -4529,7 +4695,7 @@ index d6b5535..959b58f 100644
4529
  color: var(--text-dim);
4530
  font-size: 11px;
4531
  display: flex;
4532
- @@ -1137,7 +1227,7 @@ pre, code {
4533
  }
4534
  .markdown-code-action {
4535
  border: 1px solid var(--border);
@@ -4538,7 +4704,19 @@ index d6b5535..959b58f 100644
4538
  padding: 2px 7px;
4539
  background: var(--bg);
4540
  color: var(--text-muted);
4541
- @@ -1228,7 +1318,7 @@ pre, code {
 
 
 
 
 
 
 
 
 
 
 
 
4542
  max-width: 100%;
4543
  max-height: 100%;
4544
  object-fit: contain;
@@ -4547,7 +4725,7 @@ index d6b5535..959b58f 100644
4547
  box-shadow: 0 20px 60px rgba(0, 0, 0, 0.4);
4548
  }
4549
 
4550
- @@ -1244,7 +1334,7 @@ pre, code {
4551
  height: 36px;
4552
  padding: 0;
4553
  border: 1px solid var(--border);
@@ -4556,7 +4734,16 @@ index d6b5535..959b58f 100644
4556
  background: var(--bg-panel);
4557
  color: var(--text-muted);
4558
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.28);
4559
- @@ -1320,7 +1410,7 @@ pre, code {
 
 
 
 
 
 
 
 
 
4560
  height: 24px;
4561
  overflow: hidden;
4562
  border: 1px solid var(--border);
@@ -4565,7 +4752,7 @@ index d6b5535..959b58f 100644
4565
  background: var(--bg);
4566
  }
4567
  .mermaid-zoom-stepper button,
4568
- @@ -1347,7 +1437,7 @@ pre, code {
4569
  .mermaid-zoom-icon-button {
4570
  flex: 0 0 24px;
4571
  border: 1px solid var(--border);
@@ -4574,7 +4761,19 @@ index d6b5535..959b58f 100644
4574
  }
4575
  .mermaid-zoom-stepper button:hover:not(:disabled),
4576
  .mermaid-zoom-icon-button:hover {
4577
- @@ -1384,7 +1474,7 @@ pre, code {
 
 
 
 
 
 
 
 
 
 
 
 
4578
  .mermaid-zoom-canvas {
4579
  padding: 12px;
4580
  border: 1px solid var(--border);
@@ -4583,7 +4782,7 @@ index d6b5535..959b58f 100644
4583
  background: var(--bg-panel);
4584
  margin: 0 auto;
4585
  transition: width 0.15s ease;
4586
- @@ -1460,7 +1550,7 @@ pre, code {
4587
  height: 24px;
4588
  overflow: hidden;
4589
  border: 1px solid var(--border);
@@ -4592,7 +4791,7 @@ index d6b5535..959b58f 100644
4592
  }
4593
 
4594
  .file-viewer-mode-button {
4595
- @@ -1503,12 +1593,11 @@ pre, code {
4596
  height: 32px;
4597
  padding: 0;
4598
  box-sizing: border-box;
@@ -4607,7 +4806,16 @@ index d6b5535..959b58f 100644
4607
  opacity: 0;
4608
  visibility: hidden;
4609
  pointer-events: none;
4610
- @@ -1566,11 +1655,13 @@ pre, code {
 
 
 
 
 
 
 
 
 
4611
  }
4612
  }
4613
 
@@ -4625,7 +4833,7 @@ index d6b5535..959b58f 100644
4625
  margin: 0 auto;
4626
  }
4627
 
4628
- @@ -1584,22 +1675,21 @@ pre, code {
4629
 
4630
  .new-session-logo {
4631
  flex: none;
@@ -4654,7 +4862,7 @@ index d6b5535..959b58f 100644
4654
  line-height: 1.15;
4655
  white-space: nowrap;
4656
  }
4657
- @@ -1613,7 +1703,8 @@ pre, code {
4658
  gap: 6px;
4659
  min-width: 0;
4660
  color: var(--text-dim);
@@ -4664,7 +4872,7 @@ index d6b5535..959b58f 100644
4664
  line-height: 1.4;
4665
  white-space: nowrap;
4666
  }
4667
- @@ -1638,7 +1729,7 @@ pre, code {
4668
  gap: 3px;
4669
  margin: -3px -4px;
4670
  padding: 3px 4px;
@@ -4673,16 +4881,18 @@ index d6b5535..959b58f 100644
4673
  color: var(--accent);
4674
  font-weight: 600;
4675
  text-decoration: none;
4676
- @@ -1652,7 +1743,7 @@ pre, code {
4677
 
4678
  /* The ring inside the box, clear of the "→" and "·" beside it. */
4679
  .new-session-update:focus-visible {
4680
  - outline: 2px solid var(--accent);
4681
- + outline: 2px solid var(--focus-ring);
4682
- outline-offset: -2px;
 
 
4683
  }
4684
 
4685
- @@ -1660,30 +1751,18 @@ pre, code {
4686
  flex: none;
4687
  }
4688
 
@@ -4716,7 +4926,7 @@ index d6b5535..959b58f 100644
4716
  }
4717
  }
4718
 
4719
- @@ -1700,8 +1779,8 @@ pre, code {
4720
  tab's stacked boxes, the row below the sessions), as the sidebar drew it
4721
  on main: two grey boxes in the header buttons' material, 11px, the
4722
  project's whole path and the worktree's branch in code type, cut at their
@@ -4727,7 +4937,7 @@ index d6b5535..959b58f 100644
4727
  .project-picker {
4728
  display: flex;
4729
  align-items: center;
4730
- @@ -1722,52 +1801,14 @@ pre, code {
4731
  min-width: 0;
4732
  height: 29px;
4733
  padding: 0 10px;
@@ -4783,7 +4993,7 @@ index d6b5535..959b58f 100644
4783
  }
4784
 
4785
  /* The project's name (its display name, else its folder's), cut at its
4786
- @@ -1781,8 +1822,9 @@ pre, code {
4787
  max-width: 28ch;
4788
  overflow: hidden;
4789
  color: var(--text-dim);
@@ -4794,7 +5004,7 @@ index d6b5535..959b58f 100644
4794
  }
4795
 
4796
  .project-picker-parent {
4797
- @@ -1800,10 +1842,6 @@ pre, code {
4798
  text-overflow: ellipsis;
4799
  }
4800
 
@@ -4805,7 +5015,19 @@ index d6b5535..959b58f 100644
4805
  .project-picker-button:hover,
4806
  .project-picker-button[aria-expanded="true"] {
4807
  background: var(--bg-selected);
4808
- @@ -1851,7 +1889,8 @@ pre, code {
 
 
 
 
 
 
 
 
 
 
 
 
4809
  path's color and code type, cut at its end. */
4810
  .project-picker-label.is-alias {
4811
  color: var(--text);
@@ -4815,7 +5037,7 @@ index d6b5535..959b58f 100644
4815
  }
4816
 
4817
  /* A path or a branch, cut at its left (rtl), the text itself kept left to
4818
- @@ -1901,7 +1940,8 @@ pre, code {
4819
  border-color: color-mix(in srgb, var(--accent) 40%, transparent);
4820
  background: color-mix(in srgb, var(--accent) 6%, transparent);
4821
  color: var(--text-dim);
@@ -4825,7 +5047,7 @@ index d6b5535..959b58f 100644
4825
  }
4826
 
4827
  /* No worktree list here (the files tab): a box that says why, and does nothing. */
4828
- @@ -1978,7 +2018,7 @@ pre, code {
4829
  box-sizing: border-box;
4830
  flex: 0 0 24px;
4831
  border: 1px solid var(--border);
@@ -4834,7 +5056,19 @@ index d6b5535..959b58f 100644
4834
  background: transparent;
4835
  color: var(--text-muted);
4836
  cursor: pointer;
4837
- @@ -2037,7 +2077,7 @@ pre, code {
 
 
 
 
 
 
 
 
 
 
 
 
4838
  .markdown-file-preview pre {
4839
  background: var(--bg-panel);
4840
  border: 1px solid var(--border);
@@ -4843,7 +5077,7 @@ index d6b5535..959b58f 100644
4843
  padding: 12px 16px;
4844
  overflow-x: auto;
4845
  font-family: var(--font-mono);
4846
- @@ -2049,7 +2089,7 @@ pre, code {
4847
  font-size: 0.9em;
4848
  background: var(--bg-panel);
4849
  padding: 1px 5px;
@@ -4852,7 +5086,7 @@ index d6b5535..959b58f 100644
4852
  }
4853
  .markdown-file-preview pre code {
4854
  background: none;
4855
- @@ -2061,7 +2101,7 @@ pre, code {
4856
  margin-bottom: 20px;
4857
  padding: 14px 16px 12px;
4858
  border: 1px solid var(--border);
@@ -4861,7 +5095,7 @@ index d6b5535..959b58f 100644
4861
  background: var(--bg-panel);
4862
  }
4863
  .markdown-frontmatter-title {
4864
- @@ -2114,18 +2154,6 @@ pre, code {
4865
  text-underline-offset: 2px;
4866
  }
4867
 
@@ -4880,7 +5114,7 @@ index d6b5535..959b58f 100644
4880
  @keyframes drop-zone-in {
4881
  from { opacity: 0; transform: scale(0.97); }
4882
  to { opacity: 1; transform: scale(1); }
4883
- @@ -2230,10 +2258,6 @@ span.token.table {
4884
  .panel-resize-handle.is-resizing::after {
4885
  background: color-mix(in srgb, var(--text-muted) 70%, var(--border));
4886
  }
@@ -4891,7 +5125,7 @@ index d6b5535..959b58f 100644
4891
  /* Desktop sidebar: animate width */
4892
  @media (min-width: 641px) {
4893
  .sidebar-overlay-backdrop {
4894
- @@ -2264,119 +2288,6 @@ span.token.table {
4895
  }
4896
  }
4897
 
@@ -5011,7 +5245,7 @@ index d6b5535..959b58f 100644
5011
  /* Mobile sidebar: slide in/out as overlay */
5012
  @media (max-width: 640px) {
5013
  .chat-stats-center {
5014
- @@ -2401,31 +2312,7 @@ span.token.table {
5015
  box-shadow: none;
5016
  }
5017
 
@@ -5044,7 +5278,7 @@ index d6b5535..959b58f 100644
5044
  display: none;
5045
  }
5046
  .directory-picker-back,
5047
- @@ -2474,3 +2361,475 @@ span.token.table {
5048
  display: none !important;
5049
  }
5050
  }
@@ -5192,7 +5426,7 @@ index d6b5535..959b58f 100644
5192
  +}
5193
  +.user-bubble.is-editing {
5194
  + background: color-mix(in srgb, var(--accent) 14%, var(--user-bg));
5195
- + box-shadow: 0 0 0 2px var(--focus-ring);
5196
  +}
5197
  +/* The actions under a message: icon buttons that show on hover or focus. */
5198
  +.message-actions {
@@ -5447,8 +5681,12 @@ index d6b5535..959b58f 100644
5447
  +.reply-notice.is-error { background: var(--danger-soft); color: var(--danger); }
5448
  +.reply-notice.is-warning { background: color-mix(in srgb, var(--warning) 12%, transparent); color: var(--warning); }
5449
  +
5450
- +/* The sidebar's bottom row (AppShell): Models, Skills, Settings; under it,
5451
- + the row a host may fill ([data-host-slot="sidebar-footer"]). */
 
 
 
 
5452
  +.sidebar-footer {
5453
  + display: flex;
5454
  + flex-shrink: 0;
@@ -5470,11 +5708,11 @@ index d6b5535..959b58f 100644
5470
  + display: flex;
5471
  + flex: 1 1 0;
5472
  + align-items: center;
5473
- + justify-content: center;
5474
- + gap: 5px;
5475
  + min-width: 0;
5476
  + height: var(--size-control-sm);
5477
- + padding: 0 6px;
5478
  + border: 0;
5479
  + border-radius: var(--radius-sm);
5480
  + background: none;
@@ -5645,7 +5883,7 @@ index 79c8282..97731f1 100644
5645
  lang: "en",
5646
  icons: [
5647
  diff --git a/app/settings.css b/app/settings.css
5648
- index 0bf21eb..e8788d8 100644
5649
  --- a/app/settings.css
5650
  +++ b/app/settings.css
5651
  @@ -40,9 +40,9 @@
@@ -5679,7 +5917,43 @@ index 0bf21eb..e8788d8 100644
5679
  background: transparent;
5680
  color: var(--text-muted);
5681
  cursor: pointer;
5682
- @@ -153,8 +153,9 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5683
  }
5684
 
5685
  .config-sidebar-group-count {
@@ -5690,7 +5964,7 @@ index 0bf21eb..e8788d8 100644
5690
  }
5691
 
5692
  /* Three lines, then scroll: the phone layout gives the whole sidebar 190px. */
5693
- @@ -188,7 +189,7 @@
5694
  height: 30px;
5695
  padding: 0 8px;
5696
  border: 0;
@@ -5699,7 +5973,19 @@ index 0bf21eb..e8788d8 100644
5699
  outline: none;
5700
  background: transparent;
5701
  color: var(--text);
5702
- @@ -236,8 +237,9 @@
 
 
 
 
 
 
 
 
 
 
 
 
5703
  .models-sidebar-badge {
5704
  flex-shrink: 0;
5705
  color: var(--text-dim);
@@ -5710,7 +5996,7 @@ index 0bf21eb..e8788d8 100644
5710
  }
5711
 
5712
  .oauth-paste-panel {
5713
- @@ -277,9 +279,8 @@
5714
  /* An input's intrinsic width would otherwise push the button out of a narrow pane. */
5715
  min-width: 0;
5716
  padding: 6px 9px;
@@ -5722,7 +6008,7 @@ index 0bf21eb..e8788d8 100644
5722
  color: var(--text);
5723
  font-family: var(--font-mono);
5724
  font-size: 12px;
5725
- @@ -290,9 +291,9 @@
5726
  flex-shrink: 0;
5727
  padding: 6px 12px;
5728
  border: none;
@@ -5735,7 +6021,7 @@ index 0bf21eb..e8788d8 100644
5735
  cursor: pointer;
5736
  font-size: 12px;
5737
  font-weight: 600;
5738
- @@ -370,8 +371,9 @@
5739
  .enabled-models-count {
5740
  margin-right: auto;
5741
  color: var(--text-dim);
@@ -5746,7 +6032,7 @@ index 0bf21eb..e8788d8 100644
5746
  }
5747
 
5748
  .enabled-models-note {
5749
- @@ -383,7 +385,7 @@
5750
  .enabled-models-error {
5751
  padding: 7px 9px;
5752
  border: 1px solid rgba(239, 68, 68, 0.3);
@@ -5755,7 +6041,7 @@ index 0bf21eb..e8788d8 100644
5755
  color: #ef4444;
5756
  font-size: 11px;
5757
  line-height: 1.4;
5758
- @@ -401,10 +403,9 @@
5759
  box-sizing: border-box;
5760
  height: 30px;
5761
  padding: 0 9px;
@@ -5768,7 +6054,7 @@ index 0bf21eb..e8788d8 100644
5768
  color: var(--text);
5769
  font-family: inherit;
5770
  font-size: 12px;
5771
- @@ -413,7 +414,7 @@
5772
  .enabled-models-list {
5773
  max-height: 360px;
5774
  border: 1px solid var(--border);
@@ -5777,7 +6063,7 @@ index 0bf21eb..e8788d8 100644
5777
  background: var(--bg-panel);
5778
  overflow-y: auto;
5779
  }
5780
- @@ -457,9 +458,9 @@
5781
  .enabled-models-pin {
5782
  flex-shrink: 0;
5783
  padding: 1px 4px;
@@ -5790,7 +6076,7 @@ index 0bf21eb..e8788d8 100644
5790
  font-size: 9px;
5791
  }
5792
 
5793
- @@ -497,7 +498,7 @@
5794
  justify-content: space-between;
5795
  gap: 18px;
5796
  min-height: 68px;
@@ -5799,7 +6085,7 @@ index 0bf21eb..e8788d8 100644
5799
  border-bottom: 1px solid var(--border);
5800
  background: var(--bg-panel);
5801
  }
5802
- @@ -541,7 +542,7 @@
5803
  height: 28px;
5804
  padding: 0 6px;
5805
  border: 1px solid var(--border);
@@ -5808,7 +6094,7 @@ index 0bf21eb..e8788d8 100644
5808
  background: var(--bg);
5809
  color: var(--text);
5810
  font: inherit;
5811
- @@ -566,7 +567,7 @@
5812
  flex: 1;
5813
  min-width: 0;
5814
  min-height: 0;
@@ -5817,7 +6103,24 @@ index 0bf21eb..e8788d8 100644
5817
  overflow-y: auto;
5818
  font-family: inherit;
5819
  font-size: 12px;
5820
- @@ -662,7 +663,7 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5821
  background: var(--border);
5822
  border: 5px solid transparent;
5823
  background-clip: padding-box;
@@ -5826,7 +6129,7 @@ index 0bf21eb..e8788d8 100644
5826
  }
5827
 
5828
  .config-field-label {
5829
- @@ -697,15 +698,15 @@
5830
  .config-scope-tag {
5831
  flex-shrink: 0;
5832
  padding: 1px 5px;
@@ -5845,7 +6148,7 @@ index 0bf21eb..e8788d8 100644
5845
  }
5846
 
5847
  .config-scope-switch-field {
5848
- @@ -730,7 +731,7 @@
5849
  height: 30px;
5850
  overflow: hidden;
5851
  border: 1px solid var(--border);
@@ -5854,7 +6157,7 @@ index 0bf21eb..e8788d8 100644
5854
  }
5855
 
5856
  .config-scope-switch-option {
5857
- @@ -760,7 +761,7 @@
5858
  .config-scope-switch.is-small {
5859
  display: flex;
5860
  height: auto;
@@ -5863,7 +6166,7 @@ index 0bf21eb..e8788d8 100644
5863
  font-size: 12px;
5864
  }
5865
 
5866
- @@ -891,9 +892,8 @@
5867
  width: 100%;
5868
  height: 36px;
5869
  padding: 0 11px;
@@ -5875,7 +6178,7 @@ index 0bf21eb..e8788d8 100644
5875
  color: var(--text);
5876
  font-family: var(--font-mono);
5877
  font-size: 12px;
5878
- @@ -935,7 +935,7 @@
5879
  min-height: 30px;
5880
  padding: 6px 9px;
5881
  border: 1px solid var(--border);
@@ -5884,7 +6187,7 @@ index 0bf21eb..e8788d8 100644
5884
  background: var(--bg-panel);
5885
  color: var(--text-dim);
5886
  cursor: pointer;
5887
- @@ -976,7 +976,7 @@
5888
  }
5889
 
5890
  .config-notice {
@@ -5893,7 +6196,7 @@ index 0bf21eb..e8788d8 100644
5893
  border-bottom: 1px solid var(--border);
5894
  background: var(--bg-panel);
5895
  color: var(--text-muted);
5896
- @@ -1104,7 +1104,7 @@
5897
  align-items: center;
5898
  gap: 8px;
5899
  min-height: 52px;
@@ -5902,7 +6205,7 @@ index 0bf21eb..e8788d8 100644
5902
  border-top: 1px solid var(--border);
5903
  }
5904
 
5905
- @@ -1159,7 +1159,7 @@
5906
  gap: 6px;
5907
  border-width: 1px;
5908
  border-style: solid;
@@ -5911,7 +6214,7 @@ index 0bf21eb..e8788d8 100644
5911
  cursor: pointer;
5912
  font-family: inherit;
5913
  letter-spacing: 0;
5914
- @@ -1168,35 +1168,39 @@
5915
  transition: background-color 0.14s ease, border-color 0.14s ease, color 0.14s ease, box-shadow 0.14s ease;
5916
  }
5917
 
@@ -5967,7 +6270,7 @@ index 0bf21eb..e8788d8 100644
5967
  }
5968
 
5969
  .config-button-ghost {
5970
- @@ -1206,20 +1210,27 @@
5971
  }
5972
 
5973
  .config-button-primary:not(:disabled):hover {
@@ -6002,7 +6305,12 @@ index 0bf21eb..e8788d8 100644
6002
  }
6003
 
6004
  .config-button:focus-visible {
6005
- @@ -1254,9 +1265,9 @@
 
 
 
 
 
6006
  width: 32px;
6007
  height: 18px;
6008
  padding: 0;
@@ -6014,7 +6322,16 @@ index 0bf21eb..e8788d8 100644
6014
  cursor: pointer;
6015
  transition: background 0.15s, border-color 0.15s;
6016
  }
6017
- @@ -1342,7 +1353,7 @@
 
 
 
 
 
 
 
 
 
6018
  min-height: 30px;
6019
  padding: 0 8px;
6020
  border: 0;
@@ -6023,7 +6340,7 @@ index 0bf21eb..e8788d8 100644
6023
  background: transparent;
6024
  color: var(--text-dim);
6025
  cursor: pointer;
6026
- @@ -1358,7 +1369,7 @@
6027
 
6028
  .config-list-action-button[aria-current="page"] {
6029
  background: var(--bg-selected);
@@ -6032,7 +6349,7 @@ index 0bf21eb..e8788d8 100644
6032
  }
6033
 
6034
  .config-list-action-button:disabled {
6035
- @@ -1405,10 +1416,9 @@
6036
  height: 84vh;
6037
  max-height: calc(100dvh - 16px);
6038
  overflow: hidden;
@@ -6046,7 +6363,7 @@ index 0bf21eb..e8788d8 100644
6046
  }
6047
 
6048
  .settings-dialog-header {
6049
- @@ -1417,7 +1427,7 @@
6050
  flex-shrink: 0;
6051
  align-items: center;
6052
  min-height: 50px;
@@ -6055,7 +6372,7 @@ index 0bf21eb..e8788d8 100644
6055
  border-bottom: 1px solid var(--border);
6056
  }
6057
 
6058
- @@ -1467,7 +1477,7 @@
6059
  width: 24px;
6060
  height: 2px;
6061
  border-radius: 2px 2px 0 0;
@@ -6064,7 +6381,7 @@ index 0bf21eb..e8788d8 100644
6064
  content: "";
6065
  opacity: 0;
6066
  transform: translateX(-50%) scaleX(0.5);
6067
- @@ -1494,7 +1504,7 @@
6068
  }
6069
 
6070
  .settings-section-tab:focus-visible:not([aria-current="page"]) {
@@ -6073,16 +6390,23 @@ index 0bf21eb..e8788d8 100644
6073
  background: var(--bg-hover);
6074
  color: var(--text);
6075
  }
6076
- @@ -1542,7 +1552,7 @@
 
 
 
 
 
 
 
6077
  .settings-general {
6078
  width: 100%;
6079
  height: 100%;
6080
  - padding: 26px clamp(18px, 4vw, 40px) 40px;
6081
- + padding: 18px clamp(16px, 3vw, 28px) 28px;
6082
  overflow-y: auto;
6083
  }
6084
 
6085
- @@ -1593,21 +1603,21 @@
6086
  justify-content: center;
6087
  gap: 7px;
6088
  min-width: 0;
@@ -6109,7 +6433,19 @@ index 0bf21eb..e8788d8 100644
6109
  }
6110
 
6111
  .settings-theme-option:not(:has(input:checked)):hover {
6112
- @@ -1657,9 +1667,8 @@
 
 
 
 
 
 
 
 
 
 
 
 
6113
  height: 26px;
6114
  margin: 0;
6115
  padding: 0 8px;
@@ -6121,7 +6457,19 @@ index 0bf21eb..e8788d8 100644
6121
  color: var(--text);
6122
  font-family: var(--font-ui);
6123
  font-weight: var(--font-ui-weight);
6124
- @@ -1722,8 +1731,9 @@
 
 
 
 
 
 
 
 
 
 
 
 
6125
 
6126
  .settings-chat-range-header output {
6127
  color: var(--text-muted);
@@ -6132,7 +6480,7 @@ index 0bf21eb..e8788d8 100644
6132
  }
6133
 
6134
  .settings-send-mode-options {
6135
- @@ -1732,7 +1742,7 @@
6136
  gap: 3px;
6137
  padding: 3px;
6138
  border: 1px solid var(--border);
@@ -6141,7 +6489,7 @@ index 0bf21eb..e8788d8 100644
6141
  }
6142
 
6143
  .settings-send-mode-option {
6144
- @@ -1744,7 +1754,7 @@
6145
  min-height: 26px;
6146
  padding: 0 10px;
6147
  border: 0;
@@ -6150,7 +6498,7 @@ index 0bf21eb..e8788d8 100644
6150
  background: transparent;
6151
  color: var(--text-muted);
6152
  cursor: pointer;
6153
- @@ -1754,8 +1764,8 @@
6154
 
6155
  .settings-send-mode-option:has(input:checked) {
6156
  background: var(--bg-selected);
@@ -6161,7 +6509,19 @@ index 0bf21eb..e8788d8 100644
6161
  }
6162
 
6163
  .settings-send-mode-option:not(:has(input:checked)):hover {
6164
- @@ -1781,7 +1791,7 @@
 
 
 
 
 
 
 
 
 
 
 
 
6165
  display: block;
6166
  width: 100%;
6167
  margin-top: 2px;
@@ -6170,7 +6530,7 @@ index 0bf21eb..e8788d8 100644
6170
  }
6171
 
6172
  .settings-language-options {
6173
- @@ -1799,7 +1809,7 @@
6174
  height: 44px;
6175
  padding: 0 10px;
6176
  border: 0;
@@ -6179,7 +6539,7 @@ index 0bf21eb..e8788d8 100644
6179
  background: transparent;
6180
  color: var(--text);
6181
  cursor: pointer;
6182
- @@ -1857,7 +1867,7 @@
6183
  max-width: 420px;
6184
  min-height: 44px;
6185
  padding: 0 10px;
@@ -6188,7 +6548,7 @@ index 0bf21eb..e8788d8 100644
6188
  background: var(--bg-panel);
6189
  color: var(--text);
6190
  font-size: 12px;
6191
- @@ -1923,7 +1933,7 @@
6192
  margin-left: 14px;
6193
  padding: 0 9px;
6194
  border: 1px solid var(--border);
@@ -6197,7 +6557,7 @@ index 0bf21eb..e8788d8 100644
6197
  background: var(--bg-panel);
6198
  color: var(--text);
6199
  font-size: 16px;
6200
- @@ -2066,7 +2076,7 @@
6201
  max-width: 100%;
6202
  padding: 0 5px;
6203
  border: 1px solid var(--border);
@@ -6206,7 +6566,7 @@ index 0bf21eb..e8788d8 100644
6206
  background: var(--bg-panel);
6207
  color: var(--text);
6208
  font-family: var(--font-mono);
6209
- @@ -2085,7 +2095,7 @@
6210
  padding: 6px 8px;
6211
  overflow: auto;
6212
  border: 1px solid var(--border);
@@ -6215,7 +6575,7 @@ index 0bf21eb..e8788d8 100644
6215
  background: var(--bg-panel);
6216
  color: var(--text-muted);
6217
  font-family: var(--font-mono);
6218
- @@ -2172,9 +2182,8 @@
6219
  min-width: 0;
6220
  height: 32px;
6221
  padding: 0 10px;
@@ -6227,7 +6587,7 @@ index 0bf21eb..e8788d8 100644
6227
  color: var(--text);
6228
  font-family: var(--font-mono);
6229
  font-size: 12px;
6230
- @@ -2266,10 +2275,9 @@ select.mcp-add-input {
6231
  max-width: 100%;
6232
  max-height: 100%;
6233
  overflow: hidden;
@@ -6241,7 +6601,7 @@ index 0bf21eb..e8788d8 100644
6241
  }
6242
 
6243
  /* Focus moves to the dialog itself when it opens (tabIndex -1); it is not a control, so no ring. */
6244
- @@ -2282,7 +2290,7 @@ select.mcp-add-input {
6245
  flex: 1 1 auto;
6246
  gap: 12px;
6247
  min-height: 0;
@@ -6250,7 +6610,7 @@ index 0bf21eb..e8788d8 100644
6250
  overflow: auto;
6251
  overscroll-behavior: contain;
6252
  }
6253
- @@ -2315,7 +2323,7 @@ select.mcp-add-input {
6254
  margin-top: 10px;
6255
  padding: 8px 10px;
6256
  border: 1px solid var(--border);
@@ -6259,7 +6619,7 @@ index 0bf21eb..e8788d8 100644
6259
  background: var(--bg);
6260
  color: var(--text);
6261
  font-family: var(--font-mono);
6262
- @@ -2333,7 +2341,7 @@ select.mcp-add-input {
6263
  .project-trust-error {
6264
  flex-shrink: 0;
6265
  max-height: 30vh;
@@ -6268,7 +6628,7 @@ index 0bf21eb..e8788d8 100644
6268
  overflow: auto;
6269
  color: #ef4444;
6270
  font-size: 12px;
6271
- @@ -2353,19 +2361,20 @@ select.mcp-add-input {
6272
  flex-shrink: 0;
6273
  justify-content: flex-end;
6274
  gap: 8px;
@@ -6296,7 +6656,7 @@ index 0bf21eb..e8788d8 100644
6296
  }
6297
 
6298
  .project-trust-button:disabled {
6299
- @@ -2373,10 +2382,8 @@ select.mcp-add-input {
6300
  }
6301
 
6302
  .project-trust-button.is-primary {
@@ -6309,7 +6669,7 @@ index 0bf21eb..e8788d8 100644
6309
  }
6310
 
6311
  .project-trust-button.is-primary:disabled {
6312
- @@ -2450,7 +2457,7 @@ select.mcp-add-input {
6313
  min-width: 0;
6314
  padding: 8px 10px;
6315
  border: 1px solid var(--border);
@@ -6318,7 +6678,7 @@ index 0bf21eb..e8788d8 100644
6318
  background: var(--bg);
6319
  }
6320
 
6321
- @@ -2472,7 +2479,7 @@ select.mcp-add-input {
6322
  .project-trust-mcp-tag {
6323
  flex-shrink: 0;
6324
  padding: 1px 5px;
@@ -6327,7 +6687,7 @@ index 0bf21eb..e8788d8 100644
6327
  background: rgba(120, 120, 120, 0.12);
6328
  color: var(--text-dim);
6329
  font-size: 10px;
6330
- @@ -2493,7 +2500,7 @@ select.mcp-add-input {
6331
  display: inline-block;
6332
  margin: 2px 4px 0 0;
6333
  padding: 0 4px;
@@ -6338,7 +6698,7 @@ index 0bf21eb..e8788d8 100644
6338
  font-family: var(--font-mono);
6339
  diff --git a/app/shell.css b/app/shell.css
6340
  new file mode 100644
6341
- index 0000000..7cefafc
6342
  --- /dev/null
6343
  +++ b/app/shell.css
6344
  @@ -0,0 +1,720 @@
@@ -6879,7 +7239,7 @@ index 0000000..7cefafc
6879
  + color: var(--text);
6880
  + font: inherit;
6881
  +}
6882
- +.review-comment-input:focus { box-shadow: 0 0 0 2px var(--focus-ring); }
6883
  +.review-comment-actions { display: flex; align-items: center; justify-content: flex-end; gap: 4px; }
6884
  +.review-comment-hint { flex: 1 1 auto; color: var(--text-dim); font-size: 11.5px; }
6885
  +
@@ -6923,7 +7283,7 @@ index 0000000..7cefafc
6923
  + font: inherit;
6924
  + font-size: 12.5px;
6925
  +}
6926
- +.browser-address input:focus { box-shadow: 0 0 0 2px var(--focus-ring); }
6927
  +.browser-error-count { color: var(--danger); font-size: 11.5px; font-variant-numeric: tabular-nums; }
6928
  +.browser-tabs {
6929
  + display: flex;
@@ -6994,7 +7354,7 @@ index 0000000..7cefafc
6994
  + background: #fff;
6995
  + cursor: default;
6996
  +}
6997
- +.browser-canvas:focus-visible { box-shadow: inset 0 0 0 2px var(--focus-ring); }
6998
  +.browser-canvas.is-picking { cursor: crosshair; }
6999
  +.browser-agent-pill, .browser-picking-hint {
7000
  + position: absolute;
@@ -7063,7 +7423,7 @@ index 0000000..7cefafc
7063
  +.browser-console-text { flex: 1 1 auto; min-width: 0; white-space: pre-wrap; overflow-wrap: anywhere; }
7064
  +.browser-console-location { flex: none; max-width: 40%; overflow: hidden; color: var(--text-dim); text-overflow: ellipsis; white-space: nowrap; direction: rtl; }
7065
  diff --git a/app/sidebar-menu.css b/app/sidebar-menu.css
7066
- index 737e7a5..d0fd174 100644
7067
  --- a/app/sidebar-menu.css
7068
  +++ b/app/sidebar-menu.css
7069
  @@ -19,13 +19,14 @@
@@ -7553,7 +7913,19 @@ index 737e7a5..d0fd174 100644
7553
  font-size: 15px;
7554
  }
7555
 
7556
- @@ -583,11 +493,10 @@ html[data-keyboard-open] .sidebar-sheet {
 
 
 
 
 
 
 
 
 
 
 
 
7557
  height: 36px;
7558
  padding: 0 4px 0 12px;
7559
  box-sizing: border-box;
@@ -7568,7 +7940,7 @@ index 737e7a5..d0fd174 100644
7568
  font-size: 12px;
7569
  pointer-events: auto;
7570
  animation: sidebar-toast-in 0.16s ease-out;
7571
- @@ -634,7 +543,7 @@ html[data-keyboard-open] .sidebar-sheet {
7572
  height: 26px;
7573
  padding: 0 7px;
7574
  border: 0;
@@ -7577,7 +7949,7 @@ index 737e7a5..d0fd174 100644
7577
  background: transparent;
7578
  color: var(--accent);
7579
  font: inherit;
7580
- @@ -657,7 +566,7 @@ html[data-keyboard-open] .sidebar-sheet {
7581
  height: 24px;
7582
  padding: 0;
7583
  border: 0;
@@ -7586,7 +7958,19 @@ index 737e7a5..d0fd174 100644
7586
  background: transparent;
7587
  color: var(--text-dim);
7588
  cursor: pointer;
7589
- @@ -716,23 +625,23 @@ html[data-keyboard-open] .sidebar-sheet {
 
 
 
 
 
 
 
 
 
 
 
 
7590
  /* A worktree menu's "New worktree…" form (a custom menu body; components/WorktreeCreateForm.tsx),
7591
  and its removal body. */
7592
  .sidebar-worktree-form {
@@ -7616,7 +8000,7 @@ index 737e7a5..d0fd174 100644
7616
  user-select: text;
7617
  -webkit-user-select: text;
7618
  }
7619
- @@ -747,33 +656,31 @@ html[data-keyboard-open] .sidebar-sheet {
7620
  .sidebar-worktree-force,
7621
  .sidebar-worktree-cancel {
7622
  flex: 1;
@@ -7662,7 +8046,16 @@ index 737e7a5..d0fd174 100644
7662
  }
7663
 
7664
  .sidebar-worktree-create:disabled,
7665
- @@ -791,22 +698,21 @@ html[data-keyboard-open] .sidebar-sheet {
 
 
 
 
 
 
 
 
 
7666
 
7667
  /* A worktree menu's removal body (components/ProjectWorktreePicker.tsx): what Force would do. */
7668
  .sidebar-worktree-message {
@@ -7689,7 +8082,7 @@ index 737e7a5..d0fd174 100644
7689
  }
7690
 
7691
  .sidebar-sheet .sidebar-worktree-input,
7692
- @@ -820,19 +726,18 @@ html[data-keyboard-open] .sidebar-sheet {
7693
  /* A project menu's "Delete all sessions" question (a custom menu body;
7694
  components/SessionSidebar.tsx): what goes, then Delete and Cancel. */
7695
  .sidebar-menu-question {
@@ -7713,7 +8106,7 @@ index 737e7a5..d0fd174 100644
7713
  line-height: 1.4;
7714
  }
7715
 
7716
- @@ -846,26 +751,25 @@ html[data-keyboard-open] .sidebar-sheet {
7717
  .sidebar-menu-question-cancel {
7718
  flex: 1;
7719
  min-width: 0;
@@ -7749,7 +8142,15 @@ index 737e7a5..d0fd174 100644
7749
  }
7750
 
7751
  .sidebar-menu-question-confirm:disabled {
7752
- @@ -880,7 +784,7 @@ html[data-keyboard-open] .sidebar-sheet {
 
 
 
 
 
 
 
 
7753
  }
7754
 
7755
  .sidebar-sheet .sidebar-menu-question {
@@ -7759,18 +8160,34 @@ index 737e7a5..d0fd174 100644
7759
 
7760
  .sidebar-sheet .sidebar-menu-question-text {
7761
  diff --git a/app/sidebar.css b/app/sidebar.css
7762
- index 86728a2..98fb8f2 100644
7763
  --- a/app/sidebar.css
7764
  +++ b/app/sidebar.css
7765
- @@ -91,7 +91,7 @@
7766
 
7767
  .session-tree button:focus-visible,
7768
  .session-tree a.session-tree-main:focus-visible {
7769
  - outline: 2px solid var(--accent);
7770
- + outline: 2px solid var(--focus-ring);
7771
- outline-offset: -2px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7772
  }
7773
 
 
7774
  @@ -171,20 +171,22 @@
7775
  }
7776
 
@@ -8273,18 +8690,20 @@ index 86728a2..98fb8f2 100644
8273
  }
8274
 
8275
  .sidebar-brand:focus {
8276
- @@ -1064,8 +1090,8 @@ a.session-tree-main {
8277
  }
8278
 
8279
  .sidebar-brand:focus-visible {
8280
  - outline: 2px solid var(--accent);
8281
  - outline-offset: 1px;
8282
- + outline: 2px solid var(--focus-ring);
 
 
8283
  + outline-offset: 0;
8284
  }
8285
 
8286
  .sidebar-header-spacer {
8287
- @@ -1078,7 +1104,7 @@ a.session-tree-main {
8288
  }
8289
 
8290
  .sidebar-search-toggle {
@@ -8293,7 +8712,7 @@ index 86728a2..98fb8f2 100644
8293
  padding: 0;
8294
  }
8295
 
8296
- @@ -1093,7 +1119,7 @@ a.session-tree-main {
8297
  label is still each one's name. */
8298
  .sidebar-header[data-fit="1"] .sidebar-new-button,
8299
  .sidebar-header[data-fit="2"] .sidebar-new-button {
@@ -8302,7 +8721,7 @@ index 86728a2..98fb8f2 100644
8302
  padding: 0;
8303
  }
8304
 
8305
- @@ -1105,6 +1131,11 @@ a.session-tree-main {
8306
  font-size: 11px;
8307
  }
8308
 
@@ -8314,7 +8733,19 @@ index 86728a2..98fb8f2 100644
8314
  .sidebar-header[data-fit="1"] .sidebar-new-label,
8315
  .sidebar-header[data-fit="2"] .sidebar-new-label,
8316
  .sidebar-header[data-fit="2"] .sidebar-tab-label {
8317
- @@ -1202,7 +1233,7 @@ a.session-tree-main {
 
 
 
 
 
 
 
 
 
 
 
 
8318
  the root too), and its time 4px inside the row. The header row's keys and
8319
  the picker's boxes end there. */
8320
  .session-sidebar.is-files-below {
@@ -8323,7 +8754,7 @@ index 86728a2..98fb8f2 100644
8323
  }
8324
 
8325
  /* The files section's header row (files below the sessions only), one row
8326
- @@ -1246,7 +1277,7 @@ a.session-tree-main {
8327
  padding: 0 5px;
8328
  overflow: hidden;
8329
  border: 0;
@@ -8332,7 +8763,19 @@ index 86728a2..98fb8f2 100644
8332
  background: transparent;
8333
  color: var(--text-muted);
8334
  font: inherit;
8335
- @@ -1304,7 +1335,7 @@ a.session-tree-main {
 
 
 
 
 
 
 
 
 
 
 
 
8336
  min-width: 18px;
8337
  max-width: 26px;
8338
  height: 26px;
@@ -8341,7 +8784,7 @@ index 86728a2..98fb8f2 100644
8341
  }
8342
 
8343
  /* Under that row (or the whole files tab): the head, then the tree filling
8344
- @@ -1329,16 +1360,16 @@ a.session-tree-main {
8345
  height: 29px;
8346
  padding: 0 10px;
8347
  box-sizing: border-box;
@@ -8358,12 +8801,13 @@ index 86728a2..98fb8f2 100644
8358
  -.sidebar-search-input:focus {
8359
  - outline: 2px solid var(--accent);
8360
  +.sidebar-search-input:focus-visible {
8361
- + outline: 2px solid var(--focus-ring);
8362
- + outline-offset: -2px;
 
8363
  }
8364
 
8365
  /* The tree and the archive view under the search, in one cell: the archive
8366
- @@ -1413,7 +1444,7 @@ a.session-tree-main {
8367
  height: 26px;
8368
  padding: 0 6px;
8369
  border: 0;
@@ -8372,7 +8816,7 @@ index 86728a2..98fb8f2 100644
8372
  background: transparent;
8373
  color: var(--text-muted);
8374
  font-size: 12px;
8375
- @@ -1502,7 +1533,7 @@ a.session-tree-main {
8376
  height: 32px;
8377
  padding: 0;
8378
  border: 0;
@@ -8383,7 +8827,7 @@ index 86728a2..98fb8f2 100644
8383
  cursor: pointer;
8384
  diff --git a/app/ui.css b/app/ui.css
8385
  new file mode 100644
8386
- index 0000000..69db8d7
8387
  --- /dev/null
8388
  +++ b/app/ui.css
8389
  @@ -0,0 +1,634 @@
@@ -8609,7 +9053,7 @@ index 0000000..69db8d7
8609
  + white-space: nowrap;
8610
  + cursor: pointer;
8611
  +}
8612
- +.ui-dropdown-row:focus-visible { outline: none; }
8613
  +.ui-dropdown-row[aria-selected="true"] { color: var(--text-strong); }
8614
  +.ui-dropdown-row.has-gutter { padding-right: calc(var(--popup-row-pad) + 28px); }
8615
  +.ui-dropdown-icon { display: inline-flex; flex: none; align-items: center; justify-content: center; width: var(--popup-icon); height: var(--popup-icon); color: var(--text-muted); }
@@ -9169,7 +9613,7 @@ index e232a55..0000000
9169
  - assert.deepEqual(saved, { tabs: [{ ...tab, restored: true }], activeId: null, open: false });
9170
  -});
9171
  diff --git a/components/AppShell.tsx b/components/AppShell.tsx
9172
- index c8c4ba9..a7151eb 100644
9173
  --- a/components/AppShell.tsx
9174
  +++ b/components/AppShell.tsx
9175
  @@ -3,6 +3,8 @@
@@ -10902,7 +11346,16 @@ index c8c4ba9..a7151eb 100644
10902
  <div style={{ display: "grid", gridTemplateColumns: "auto minmax(0, 1fr) auto", columnGap: 12, rowGap: 8, alignItems: "start" }}>
10903
  {sessionRows.map((row) => (
10904
  <div key={`session-info:${row.label}`} style={{ display: "contents" }}>
10905
- @@ -2477,7 +2218,7 @@ export function AppShell() {
 
 
 
 
 
 
 
 
 
10906
  );
10907
  const projectInfoSection = projectRows.length > 0 ? (
10908
  <div style={{ minWidth: 0 }}>
@@ -10911,7 +11364,32 @@ index c8c4ba9..a7151eb 100644
10911
  <div style={{ display: "grid", gridTemplateColumns: "auto minmax(0, 1fr) auto", columnGap: 12, rowGap: 8, alignItems: "start" }}>
10912
  {projectRows.map((row) => (
10913
  <div key={`project-info:${row.label}`} style={{ display: "contents" }}>
10914
- @@ -2529,8 +2270,14 @@ export function AppShell() {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10915
  {isMobile && renderProjectTrustWarning(true)}
10916
  </div>
10917
 
@@ -10928,7 +11406,7 @@ index c8c4ba9..a7151eb 100644
10928
  {showChat ? (
10929
  <ChatWindow
10930
  key={sessionKey}
10931
- @@ -2568,7 +2315,6 @@ export function AppShell() {
10932
  initialPrompt={pendingQuotePrompt?.sessionId === selectedSession?.id ? pendingQuotePrompt?.text : undefined}
10933
  onInitialPromptConsumed={() => setPendingQuotePrompt(null)}
10934
  soundEnabled={soundEnabled}
@@ -10936,7 +11414,7 @@ index c8c4ba9..a7151eb 100644
10936
  playDoneSound={playDoneSound}
10937
  unlockAudio={unlockAudio}
10938
  holdAudio={holdAudio}
10939
- @@ -2601,9 +2347,7 @@ export function AppShell() {
10940
  </div>
10941
  ) : (
10942
  <div style={{ position: "absolute", top: 12, left: 12, display: "flex", alignItems: "flex-start", gap: 8, userSelect: "none", pointerEvents: "none" }}>
@@ -10947,7 +11425,7 @@ index c8c4ba9..a7151eb 100644
10947
  <div>
10948
  <div style={{ fontSize: 18, fontWeight: 600, color: "var(--text)", marginBottom: 8 }}>{translate("workspace.getStarted")}</div>
10949
  <div style={{ fontSize: 12, color: "var(--text-muted)", lineHeight: 1.8 }}>
10950
- @@ -2614,146 +2358,15 @@ export function AppShell() {
10951
  </div>
10952
  )
10953
  ) : null}
@@ -11101,7 +11579,7 @@ index c8c4ba9..a7151eb 100644
11101
  </div>
11102
  </div>
11103
  {settingsSection && (
11104
- @@ -2763,6 +2376,8 @@ export function AppShell() {
11105
  initialSection={settingsSection}
11106
  quoteSelectionEnabled={quoteSelectionEnabled}
11107
  onQuoteSelectionChange={handleQuoteSelectionChange}
@@ -11110,7 +11588,7 @@ index c8c4ba9..a7151eb 100644
11110
  onClose={() => {
11111
  setSettingsSection(null);
11112
  setModelsRefreshKey((key) => key + 1);
11113
- @@ -2786,6 +2401,6 @@ export function AppShell() {
11114
  onStatus={setProjectTrust}
11115
  />
11116
  )}
@@ -20712,7 +21190,7 @@ index ba912ee..0283a47 100644
20712
  onSelect={() => choose(option)}
20713
  star={onSetDefault ? {
20714
  diff --git a/components/ModelsConfig.tsx b/components/ModelsConfig.tsx
20715
- index 9f6b556..470db1f 100644
20716
  --- a/components/ModelsConfig.tsx
20717
  +++ b/components/ModelsConfig.tsx
20718
  @@ -54,6 +54,8 @@ import { providerBadgeLabel } from "./enabled-models-helpers";
@@ -20892,6 +21370,15 @@ index 9f6b556..470db1f 100644
20892
  background: "var(--bg-panel)",
20893
  color: !model.id.trim() || catalogState.phase === "loading" ? "var(--text-dim)" : "var(--text-muted)",
20894
  cursor: !model.id.trim() || catalogState.phase === "loading" ? "not-allowed" : "pointer",
 
 
 
 
 
 
 
 
 
20895
  @@ -1227,20 +1220,7 @@ function ModelDetail({
20896
  {advancedSummary}
20897
  </span>
@@ -20976,7 +21463,16 @@ index 9f6b556..470db1f 100644
20976
  <input
20977
  ref={inputRef}
20978
  value={search}
20979
- @@ -1782,10 +1758,8 @@ function AddProviderPicker({
 
 
 
 
 
 
 
 
 
20980
  <div style={{ fontSize: 12, fontWeight: 600, color: "var(--text)", lineHeight: 1.3, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>OpenAI / Anthropic compatible</div>
20981
  <div style={{ fontSize: 10, color: "var(--text-dim)", marginTop: 2 }}>{t("i18n.customEndpoint")}</div>
20982
  </div>
@@ -20989,6 +21485,22 @@ index 9f6b556..470db1f 100644
20989
  </span>
20990
  </button>
20991
  )}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
20992
  @@ -2177,13 +2151,7 @@ export function ModelsConfig({ onClose, embedded = false, cwd = null }: {
20993
  onClick={() => setSelection({ type: "provider", name: pName })}
20994
  active={isProviderSelected}
@@ -21242,7 +21754,7 @@ index b9e6dde..1a67841 100644
21242
  );
21243
  }
21244
  diff --git a/components/PluginsConfig.tsx b/components/PluginsConfig.tsx
21245
- index 83fddc9..69cbd44 100644
21246
  --- a/components/PluginsConfig.tsx
21247
  +++ b/components/PluginsConfig.tsx
21248
  @@ -1,5 +1,6 @@
@@ -21252,7 +21764,20 @@ index 83fddc9..69cbd44 100644
21252
  import { useCallback, useEffect, useId, useMemo, useRef, useState } from "react";
21253
  import { sendAgentCommand } from "@/lib/agent-client";
21254
  import type {
21255
- @@ -275,14 +276,7 @@ function AddPluginPanel({
 
 
 
 
 
 
 
 
 
 
 
 
 
21256
  href: "https://pi.dev/packages",
21257
  label: "pi.dev/packages",
21258
  icon: (
@@ -21268,7 +21793,7 @@ index 83fddc9..69cbd44 100644
21268
  ),
21269
  }]}
21270
  target={
21271
- @@ -370,7 +364,7 @@ function PackageDetail({
21272
  style={{
21273
  fontSize: 10,
21274
  padding: "1px 5px",
@@ -21277,7 +21802,7 @@ index 83fddc9..69cbd44 100644
21277
  background: "rgba(120,120,120,0.12)",
21278
  color: "var(--text-dim)",
21279
  }}
21280
- @@ -382,7 +376,7 @@ function PackageDetail({
21281
  style={{
21282
  fontSize: 10,
21283
  padding: "1px 5px",
@@ -25477,7 +26002,7 @@ index 0000000..3d8fca8
25477
  +});
25478
  diff --git a/components/highlighting.ts b/components/highlighting.ts
25479
  new file mode 100644
25480
- index 0000000..ae9fdae
25481
  --- /dev/null
25482
  +++ b/components/highlighting.ts
25483
  @@ -0,0 +1,157 @@
@@ -25562,7 +26087,7 @@ index 0000000..ae9fdae
25562
  +[data-separator=line-info-basic] { height: 24px; }
25563
  +[data-separator-content] { font-size: 12px; }
25564
  +[data-expand-index] [data-separator-wrapper] { grid-template-columns: 28px auto; }
25565
- +[data-expand-button]:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: -2px; }
25566
  +`;
25567
  +
25568
  +// The library's expand controls are role=button divs out of the tab order: give them a place in
@@ -33727,7 +34252,7 @@ index fa76036..0000000
33727
  - };
33728
  -}
33729
  diff --git a/lib/i18n/messages/en.ts b/lib/i18n/messages/en.ts
33730
- index cd39a31..132c15c 100644
33731
  --- a/lib/i18n/messages/en.ts
33732
  +++ b/lib/i18n/messages/en.ts
33733
  @@ -22,8 +22,8 @@ export const enLocale: LocalePlugin = {
@@ -33768,7 +34293,31 @@ index cd39a31..132c15c 100644
33768
  "subagent.tabLabel": "Sub-agent {name}",
33769
  "subagent.refresh": "Refresh",
33770
  "subagent.viewer.starting": "Sub-agent session is starting…",
33771
- @@ -246,8 +246,8 @@ export const enLocale: LocalePlugin = {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
33772
  "session.copied": "Copied",
33773
  "session.title": "Session info",
33774
  "session.load": "Send a message or run /session to load session info",
@@ -33778,7 +34327,17 @@ index cd39a31..132c15c 100644
33778
  + "session.projectSection": "Project info",
33779
  "session.messages": "Messages",
33780
  "session.tokens": "Tokens",
33781
- "session.totalActive": "Active Time",
 
 
 
 
 
 
 
 
 
 
33782
  @@ -269,13 +269,9 @@ export const enLocale: LocalePlugin = {
33783
  "workspace.stepModels": "Open Settings at the bottom, then choose Models",
33784
  "workspace.newSessionContext": "New session location",
 
1
  diff --git a/AGENTS.md b/AGENTS.md
2
+ index a5c8983..1b769c5 100644
3
  --- a/AGENTS.md
4
  +++ b/AGENTS.md
5
  @@ -65,8 +65,13 @@ app/api/
 
156
 
157
  ## Pi Session File Format
158
 
159
+ @@ -274,3 +317,32 @@ Location: `~/.pi/agent/sessions/<encoded-cwd>/<timestamp>_<uuid>.jsonl`
160
  --accent --user-bg --tool-bg
161
  --font-mono
162
  ```
 
179
  +newer parts, menus, tooltips and popovers in `app/ui.css`, the header and tiles in `app/shell.css`)
180
  +are styled there too.
181
  +`--accent` is a neutral gray in light and dark (no blue anywhere: links are told apart by their
182
+ +underline, focus by its line and halo), `--accent-soft` its tint; Mist, Rose and Pine keep their own hues.
183
  +All code (fences, the file viewer, tool inputs and outputs, diffs) is highlighted by Shiki in `lib/diffs-theme.ts`'s neutral themes (`components/highlighting.ts`); the terminal keeps its ANSI hues.
184
+ +Keyboard focus is `app/focus.css`, imported last, drawn as Claude Code Desktop's (its shadow-focus):
185
+ +a 1px line at 70% of `--focus-ring` (`--focus-line`) with a 3px halo at 16% (`--focus-glow`), never
186
+ +a thick ring. The line sits 1px inside controls, rows and fields with the halo outside (a row draws
187
+ +them on the row; its link or toggle draws nothing, halo included); what is focusable by tabindex
188
+ +alone (a menu's surface, a tile) takes the line only, keeping its shadow; ink-filled controls and
189
+ +text links take the line 2px off, no halo. `app/focus.test.mjs` checks that no 2px ring is left
190
+ +(the gateway's sheets too) and that the line keeps 3:1 on every surface. Hidden row actions fold to width 0 (never `display: none`: Tab would drop
191
  +focus); closed panels are `inert`; modals use `keepTabInside`.
 
 
 
 
 
 
 
 
 
 
192
  diff --git a/app/api/browser/events/route.ts b/app/api/browser/events/route.ts
193
  new file mode 100644
194
  index 0000000..84aa812
 
1192
 
1193
  diff --git a/app/focus.css b/app/focus.css
1194
  new file mode 100644
1195
+ index 0000000..4aa9f5b
1196
  --- /dev/null
1197
  +++ b/app/focus.css
1198
+ @@ -0,0 +1,331 @@
1199
  +/* Keyboard focus, one way everywhere (the design system's "Motion and states").
1200
  + Imported last (app/layout.tsx), so these rules win over each component's.
1201
  +
1202
+ + One indicator, as Claude Code Desktop draws it (its shadow-focus: a 1px
1203
+ + ring and a soft glow, no thick outline) in Pi Web's neutral ink: a 1px line
1204
+ + at 70% of --focus-ring (--focus-line) and a 3px halo at 16% (--focus-glow).
1205
+ + A 2px ring at full strength drew a heavy white box in dark themes. Never a
1206
  + ring and a second ring, never a box around a row's text:
1207
+ + - on the edge (the line 1px inside, following the control's own corners;
1208
+ + the halo outside) on everything that is not filled with ink: ghost and
1209
+ + icon buttons, pickers, tabs, options, menu items, fields. A row draws it
1210
+ + on the whole row (its inner control draws nothing), so the line fits the
1211
+ + row's box and is never clipped;
1212
+ + - the line 2px off the edge, no halo, on what is filled with ink (primary
1213
+ + and Send buttons, a switch, floating round buttons), where a line on the
1214
+ + edge would vanish into the fill;
1215
+ + - the line 2px off, with small corners, around a link in running text.
1216
+ + The line keeps at least 3:1 against every surface it sits on (4.5:1 and up
1217
+ + on canvas and surface in dark, 4:1 in light).
1218
  + Mouse focus shows nothing (:focus-visible); text fields show their caret,
1219
  + and other fields the inset ring too, except the composer, which is always
1220
  + where typing goes. */
1221
  +
1222
+ +:root {
1223
+ + --focus-line: color-mix(in srgb, var(--focus-ring) 70%, transparent);
1224
+ + --focus-halo: color-mix(in srgb, var(--focus-ring) 16%, transparent);
1225
+ + --focus-glow: 0 0 0 3px var(--focus-halo);
1226
+ +}
1227
+ +
1228
  +:where(button, summary, [role="button"], [role="tab"], [role="menuitem"], [role="menuitemradio"], [role="menuitemcheckbox"], [role="option"], [role="switch"], [role="treeitem"], [tabindex]:not(input):not(textarea):not(select)):focus {
1229
  + outline: none;
1230
  +}
1231
  +
1232
+ +/* Anything focusable by tabindex alone (a menu's or a sheet's surface, a tile, a
1233
+ + scroll region) takes the line only: its own shadow stays. Controls add the halo. */
1234
+ +:where([tabindex]:not(input):not(textarea):not(select)):focus-visible {
1235
+ + outline: 1px solid var(--focus-line);
1236
+ + outline-offset: -1px;
1237
+ +}
1238
+ +
1239
+ +:where(button, summary, [role="button"], [role="tab"], [role="menuitem"], [role="menuitemradio"], [role="menuitemcheckbox"], [role="option"], [role="switch"], [role="treeitem"]):focus-visible {
1240
+ + outline: 1px solid var(--focus-line);
1241
+ + outline-offset: -1px;
1242
+ + box-shadow: var(--focus-glow);
1243
  +}
1244
  +
1245
  +:where(a[href]):focus {
 
1247
  +}
1248
  +
1249
  +:where(a[href]):focus-visible {
1250
+ + outline: 1px solid var(--focus-line);
1251
  + outline-offset: 2px;
1252
  + border-radius: var(--radius-xs);
1253
  +}
1254
  +
1255
  +:where(input:not([type="range"]):not([type="checkbox"]):not([type="radio"]), select, textarea):focus-visible {
1256
+ + outline: 1px solid var(--focus-line);
1257
+ + outline-offset: -1px;
1258
+ + box-shadow: var(--focus-glow);
1259
  +}
1260
  +
1261
  +:where(input[type="checkbox"], input[type="radio"]):focus-visible {
1262
+ + outline: 1px solid var(--focus-line);
1263
  + outline-offset: 2px;
1264
  +}
1265
  +
 
1267
  +.chat-input-textarea:focus,
1268
  +.chat-input-textarea:focus-visible {
1269
  + outline: none;
1270
+ + box-shadow: none;
1271
  +}
1272
  +
1273
  +/* A visually hidden input inside an option (a theme, the send key) leaves the
1274
  + ring to the option. */
1275
  +input.sr-only:focus-visible {
1276
  + outline: none;
1277
+ + box-shadow: none;
1278
  +}
1279
  +
1280
  +/* ---------------------------------------------------------------- filled with ink: 2px off */
 
1286
  +.web-login-form button:focus-visible,
1287
  +.chat-scroll-to-bottom:focus-visible,
1288
  +.image-preview-close:focus-visible {
1289
+ + outline: 1px solid var(--focus-line);
1290
  + outline-offset: 2px;
1291
  +}
1292
  +
 
1315
  +.extension-status-command:focus-visible,
1316
  +button.extension-widget-trigger:focus-visible,
1317
  +.new-session-update:focus-visible {
1318
+ + outline: 1px solid var(--focus-line);
1319
+ + outline-offset: -1px;
1320
+ + box-shadow: var(--focus-glow);
1321
  +}
1322
  +
1323
  +.settings-section-tab:focus-visible,
 
1332
  +
1333
  +/* A Terminal tab being renamed: the tab is the field, its pill takes the ring (Desktop's
1334
  + has-[:focus-visible] on the tab), the bare input none. */
1335
+ +.terminal-tab-input:focus-visible { outline: none; box-shadow: none; }
1336
  +.terminal-tab:has(.terminal-tab-input:focus-visible) {
1337
+ + outline: 1px solid var(--focus-line);
1338
+ + outline-offset: -1px;
1339
+ + box-shadow: var(--focus-glow);
1340
  +}
1341
  +
1342
  +/* An option whose input is visually hidden: the option is the control. */
1343
  +.settings-theme-option:has(input:focus-visible),
1344
  +.settings-send-mode-option:has(input:focus-visible) {
1345
+ + outline: 1px solid var(--focus-line);
1346
+ + outline-offset: -1px;
1347
+ + box-shadow: var(--focus-glow);
1348
  +}
1349
  +
1350
+ +/* Fields, the filled ones too: the line inside, the halo, no border change. */
1351
  +.settings-font-input:focus-visible,
1352
  +.sidebar-search-input:focus,
1353
  +.sidebar-search-input:focus-visible,
1354
  +.sidebar-menu-filter-input:focus-visible,
1355
  +.directory-picker-path:focus-visible {
1356
  + border-color: transparent !important;
1357
+ + box-shadow: var(--focus-glow);
1358
+ + outline: 1px solid var(--focus-line);
1359
+ + outline-offset: -1px;
1360
  +}
1361
  +
1362
  +.sidebar-search-input:focus:not(:focus-visible) {
 
1365
  +
1366
  +.web-login-composer:focus-within {
1367
  + border-color: transparent;
1368
+ + box-shadow: var(--focus-glow);
1369
+ + outline: 1px solid var(--focus-line);
1370
+ + outline-offset: -1px;
1371
  +}
1372
  +
1373
  +.web-login-composer :is(input, textarea):focus-visible {
1374
  + outline: none;
1375
+ + box-shadow: none;
1376
  +}
1377
  +
1378
  +/* The top bar's 36px edge buttons (the sidebar toggle): a
 
1383
  +}
1384
  +
1385
  +.topbar-edge-button:focus-visible {
1386
+ + outline: 1px solid var(--focus-line);
1387
  + outline-offset: -4px;
1388
+ + box-shadow: none;
1389
  +}
1390
  +
1391
  +/* ---------------------------------------------------------------- rows: the ring is the row's */
 
1394
  +.session-tree .session-tree-main:focus-visible,
1395
  +.session-tree a.session-tree-main:focus-visible {
1396
  + outline: none;
1397
+ + box-shadow: none;
1398
  +}
1399
  +
1400
  +.session-tree-session:has(> .session-tree-main:focus-visible) {
1401
+ + outline: 1px solid var(--focus-line);
1402
+ + outline-offset: -1px;
1403
+ + box-shadow: var(--focus-glow);
1404
  +}
1405
  +
1406
  +/* A project header: the same, on the header's box (its + and ⋯ ring themselves). */
1407
  +.session-tree .session-tree-group-toggle:focus-visible {
1408
  + outline: none;
1409
+ + box-shadow: none;
1410
  +}
1411
  +
1412
  +.session-tree-group:has(> .session-tree-group-toggle:focus-visible) {
1413
+ + outline: 1px solid var(--focus-line);
1414
+ + outline-offset: -1px;
1415
+ + box-shadow: var(--focus-glow);
1416
  +}
1417
  +
1418
  +/* Rows whose control spans the row: the control's own box, with the row's corners. */
 
1421
  +.session-tree .session-tree-more-toggle:focus-visible,
1422
  +.sidebar-files-section-toggle:focus-visible {
1423
  + border-radius: var(--radius-sm);
1424
+ + outline: 1px solid var(--focus-line);
1425
+ + outline-offset: -1px;
1426
+ + box-shadow: var(--focus-glow);
1427
  +}
1428
  +
1429
  +/* A tool call or a thought: the whole step line, its time included. */
1430
  +.tool-call-head:focus-visible {
1431
  + outline: none;
1432
+ + box-shadow: none;
1433
  +}
1434
  +
1435
  +.tool-call-row:has(> .tool-call-head:focus-visible) {
1436
+ + outline: 1px solid var(--focus-line);
1437
+ + outline-offset: -1px;
1438
+ + box-shadow: var(--focus-glow);
1439
  +}
1440
  +
1441
  +/* Browsers without :has() keep the ring on the inner control. */
 
1444
  + .session-tree .session-tree-group-toggle:focus-visible,
1445
  + .tool-call-head:focus-visible {
1446
  + border-radius: var(--radius-sm);
1447
+ + outline: 1px solid var(--focus-line);
1448
+ + outline-offset: -1px;
1449
+ + box-shadow: var(--focus-glow);
1450
  + }
1451
  +}
1452
  +
1453
+ +/* A resize handle: its line turns into the focus line's colour, nothing around it. */
1454
  +.panel-resize-handle:focus-visible {
1455
  + outline: none;
1456
+ + box-shadow: none;
1457
  +}
1458
  +
1459
  +/* ---------------------------------------------------------------- sliders
 
1470
  +input[type="range"]:focus,
1471
  +input[type="range"]:focus-visible {
1472
  + outline: none;
1473
+ + box-shadow: none;
1474
  +}
1475
  +
1476
  +input[type="range"]::-webkit-slider-runnable-track {
 
1512
  +}
1513
  +
1514
  +input[type="range"]:focus-visible::-webkit-slider-thumb {
1515
+ + box-shadow: 0 0 0 2px var(--bg-elevated, var(--bg)), 0 0 0 3px var(--focus-line), 0 0 0 6px var(--focus-halo);
1516
  +}
1517
  +
1518
  +input[type="range"]:focus-visible::-moz-range-thumb {
1519
+ + box-shadow: 0 0 0 2px var(--bg-elevated, var(--bg)), 0 0 0 3px var(--focus-line), 0 0 0 6px var(--focus-halo);
1520
  +}
1521
  +
1522
  +input[type="range"]:disabled {
 
1525
  +}
1526
  +
1527
  +.panel-resize-handle:focus-visible::after {
1528
+ + background: var(--focus-line);
1529
  +}
1530
+ diff --git a/app/focus.test.mjs b/app/focus.test.mjs
1531
+ new file mode 100644
1532
+ index 0000000..0b281f4
1533
+ --- /dev/null
1534
+ +++ b/app/focus.test.mjs
1535
+ @@ -0,0 +1,95 @@
1536
+ +import assert from "node:assert/strict";
1537
+ +import { existsSync, readFileSync, readdirSync } from "node:fs";
1538
+ +import test from "node:test";
1539
+ +
1540
+ +const read = (name) => readFileSync(new URL(name, import.meta.url), "utf8");
1541
+ +const focus = read("./focus.css");
1542
+ +const globals = read("./globals.css");
1543
+ +const sheets = readdirSync(new URL("./", import.meta.url)).filter((name) => name.endsWith(".css")).map((name) => [name, read(`./${name}`)]);
1544
+ +// The Hugging Face gateway's own sheets, where this checkout sits beside them (the Space's folder).
1545
+ +const gatewayDir = new URL("../../space/gateway/static/", import.meta.url);
1546
+ +const gateway = existsSync(gatewayDir) ? ["pi-overlay.css", "app.css"].map((name) => [name, readFileSync(new URL(name, gatewayDir), "utf8")]) : [];
1547
+ +
1548
+ +function rules(css) {
1549
+ + const clean = css.replace(/\/\*[\s\S]*?\*\//g, "");
1550
+ + return [...clean.matchAll(/([^{}]+)\{([^{}]*)\}/g)].map(([, selector, body]) => ({ selector: selector.trim(), body }));
1551
+ +}
1552
+ +
1553
+ +test("no focus state draws the thick 2px ring any more: a 1px line and a soft halo, as Desktop's", () => {
1554
+ + for (const [name, css] of [...sheets, ...gateway]) {
1555
+ + for (const { selector, body } of rules(css)) {
1556
+ + if (!selector.includes(":focus")) continue;
1557
+ + assert.doesNotMatch(body, /outline:\s*2px solid/, `${name}: ${selector}`);
1558
+ + assert.doesNotMatch(body, /box-shadow:[^;]*\b0 0 0 2px var\(--(focus-ring|accent)\)/, `${name}: ${selector}`);
1559
+ + }
1560
+ + }
1561
+ + assert.match(focus, /--focus-line: color-mix\(in srgb, var\(--focus-ring\) 70%, transparent\);/);
1562
+ + assert.match(focus, /--focus-glow: 0 0 0 3px var\(--focus-halo\);/);
1563
+ + // The message being edited was a 2px ring too.
1564
+ + assert.match(globals, /\.user-bubble\.is-editing \{[^}]*box-shadow: 0 0 0 1px var\(--focus-line\), var\(--focus-glow\);/);
1565
+ +});
1566
+ +
1567
+ +test("controls take the halo; what is focusable by tabindex alone keeps its own shadow", () => {
1568
+ + const all = rules(focus);
1569
+ + const containers = all.find(({ selector }) => selector.startsWith(":where([tabindex]"));
1570
+ + assert.ok(containers && !/box-shadow/.test(containers.body), "a menu's or a tile's surface keeps its shadow");
1571
+ + const controls = all.find(({ selector }) => selector.startsWith(":where(button, summary") && selector.endsWith(":focus-visible"));
1572
+ + assert.match(controls.body, /box-shadow: var\(--focus-glow\)/);
1573
+ + // An inner control whose row draws the ring draws neither the line nor the halo.
1574
+ + for (const inner of [".session-tree .session-tree-group-toggle:focus-visible", ".tool-call-head:focus-visible", ".chat-input-textarea:focus-visible", ".panel-resize-handle:focus-visible"]) {
1575
+ + const rule = all.find(({ selector }) => selector.split(",").map((part) => part.trim()).includes(inner));
1576
+ + assert.ok(rule, inner);
1577
+ + assert.match(rule.body, /outline: none;[\s\S]*box-shadow: none;/, inner);
1578
+ + }
1579
+ +});
1580
+ +
1581
+ +function hex(value) {
1582
+ + const n = Number.parseInt(value.slice(1), 16);
1583
+ + return [(n >> 16) & 255, (n >> 8) & 255, n & 255];
1584
+ +}
1585
+ +function luminance([r, g, b]) {
1586
+ + const lin = (c) => { const s = c / 255; return s <= 0.03928 ? s / 12.92 : ((s + 0.055) / 1.055) ** 2.4; };
1587
+ + return 0.2126 * lin(r) + 0.7152 * lin(g) + 0.0722 * lin(b);
1588
+ +}
1589
+ +function contrast(a, b) {
1590
+ + const [x, y] = [luminance(a), luminance(b)].sort((p, q) => q - p);
1591
+ + return (x + 0.05) / (y + 0.05);
1592
+ +}
1593
+ +
1594
+ +test("the focus line keeps 3:1 or more on every surface it sits on, in light and dark", () => {
1595
+ + const tokens = globals.slice(globals.indexOf("/* tokens:begin */"), globals.indexOf("/* tokens:end */"));
1596
+ + const blocks = [tokens.slice(0, tokens.indexOf("html.dark")), tokens.slice(tokens.indexOf("html.dark"))];
1597
+ + for (const block of blocks) {
1598
+ + const get = (name) => hex(block.match(new RegExp(`--${name}: (#[0-9a-f]{6});`))[1]);
1599
+ + const ring = get("focus-ring");
1600
+ + for (const surface of ["bg", "bg-surface", "bg-elevated"]) {
1601
+ + const under = get(surface);
1602
+ + const line = ring.map((c, i) => Math.round(c * 0.7 + under[i] * 0.3));
1603
+ + assert.ok(contrast(line, under) >= 3, `${surface}: ${contrast(line, under).toFixed(2)}:1`);
1604
+ + }
1605
+ + }
1606
+ +});
1607
+ +
1608
+ +test("the sidebar's footer rows stand on the brand's columns, and Settings' pages under its title", () => {
1609
+ + const footer = globals.match(/\.sidebar-footer-button \{([^}]*)\}/)[1];
1610
+ + assert.match(footer, /justify-content: flex-start;/);
1611
+ + assert.match(footer, /gap: 9px;/);
1612
+ + assert.match(footer, /padding: 0 5px;/);
1613
+ + if (gateway.length) {
1614
+ + const account = gateway[0][1].match(/\.hfpi-account-button \{([^}]*)\}/)[1];
1615
+ + assert.match(account, /gap: 9px;/);
1616
+ + assert.match(account, /padding: 0 5px;/);
1617
+ + }
1618
+ + const settings = read("./settings.css");
1619
+ + const header = settings.match(/\.settings-dialog-header \{[^}]*padding: 0 48px 0 (\d+)px;/)[1];
1620
+ + const general = settings.match(/\.settings-general \{[^}]*padding: 18px (\d+)px 28px;/)[1];
1621
+ + assert.equal(general, header);
1622
+ +});
1623
+ +
1624
+ +test("section labels are quiet and in sentence case, never small bold capitals", () => {
1625
+ + for (const [name, css] of sheets) {
1626
+ + for (const { selector, body } of rules(css)) {
1627
+ + if (/text-transform:\s*uppercase/.test(body)) assert.match(selector, /::first-letter/, `${name}: ${selector}`);
1628
+ + }
1629
+ + }
1630
+ +});
1631
  diff --git a/app/fonts/Geist-Variable.woff2 b/app/fonts/Geist-Variable.woff2
1632
  new file mode 100644
1633
  index 0000000000000000000000000000000000000000..9983e92a03de482b5708201694101c330b6ae9b8
 
3955
  +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
3956
  +OTHER DEALINGS IN THE FONT SOFTWARE.
3957
  diff --git a/app/globals.css b/app/globals.css
3958
+ index d6b5535..9f01813 100644
3959
  --- a/app/globals.css
3960
  +++ b/app/globals.css
3961
  @@ -24,46 +24,109 @@
 
4253
  background: var(--accent);
4254
  color: var(--accent-contrast);
4255
  cursor: pointer;
4256
+ @@ -322,7 +405,7 @@ html.dark .web-login-error {
4257
+ }
4258
+
4259
+ .web-login-form button:focus-visible {
4260
+ - outline: 2px solid var(--accent);
4261
+ + outline: 1px solid var(--focus-line);
4262
+ outline-offset: 2px;
4263
+ }
4264
+
4265
  @@ -332,110 +415,139 @@ html.dark .web-login-error {
4266
  cursor: not-allowed;
4267
  }
 
4554
 
4555
 
4556
  .extension-status-shelf {
4557
+ @@ -686,8 +780,9 @@ button.extension-widget-trigger:hover,
4558
+ }
4559
+
4560
+ button.extension-widget-trigger:focus-visible {
4561
+ - outline: 2px solid var(--accent);
4562
+ - outline-offset: -2px;
4563
+ + outline: 1px solid var(--focus-line);
4564
+ + outline-offset: -1px;
4565
+ + box-shadow: var(--focus-glow);
4566
+ }
4567
+
4568
+ .extension-widget-placement {
4569
+ @@ -703,9 +798,9 @@ button.extension-widget-trigger:focus-visible {
4570
 
4571
  .extension-widget-placement-icon {
4572
  display: block;
 
4579
  fill: currentColor;
4580
  }
4581
 
4582
+ @@ -803,8 +898,9 @@ button.extension-widget-trigger:focus-visible {
4583
+ }
4584
+
4585
+ .extension-status-command:focus-visible {
4586
+ - outline: 2px solid var(--accent);
4587
+ - outline-offset: -2px;
4588
+ + outline: 1px solid var(--focus-line);
4589
+ + outline-offset: -1px;
4590
+ + box-shadow: var(--focus-glow);
4591
+ }
4592
+
4593
+ .extension-status-command:disabled {
4594
+ @@ -826,8 +922,8 @@ button.extension-widget-trigger:focus-visible {
4595
  }
4596
 
4597
  :root {
 
4602
  --font-ui: var(--font-ui-default);
4603
  --font-mono: var(--font-mono-default);
4604
  --font-ui-weight: 400;
4605
+ @@ -883,11 +979,11 @@ pre, code {
4606
  .markdown-body ol > li { list-style-type: decimal; }
4607
  .markdown-body li > p { margin: 0; }
4608
  .markdown-body li::marker {
 
4617
  font-weight: 700;
4618
  }
4619
  .markdown-body em { color: var(--text-muted); }
4620
+ @@ -896,24 +992,23 @@ pre, code {
4621
  text-decoration: underline;
4622
  text-decoration-thickness: 1px;
4623
  text-underline-offset: 3px;
 
4646
  }
4647
  .markdown-body .contains-task-list {
4648
  padding-left: 0;
4649
+ @@ -935,7 +1030,7 @@ pre, code {
4650
  height: 14px;
4651
  margin: 0;
4652
  border: 1px solid var(--border);
 
4655
  background: var(--bg);
4656
  display: inline-grid;
4657
  place-content: center;
4658
+ @@ -967,8 +1062,8 @@ pre, code {
4659
  margin: 8px 0;
4660
  overflow-x: auto;
4661
  overflow-y: hidden;
 
4666
  }
4667
  .markdown-body table {
4668
  border-collapse: separate;
4669
+ @@ -1088,7 +1183,7 @@ pre, code {
4670
  padding: 7px 8px;
4671
  overflow: auto;
4672
  border: 1px solid var(--border);
 
4675
  background: var(--bg-panel);
4676
  font-family: var(--font-mono);
4677
  font-size: 11px;
4678
+ @@ -1108,16 +1203,13 @@ pre, code {
4679
  min-width: 0;
4680
  max-width: 100%;
4681
  margin: 6px 0;
 
4695
  color: var(--text-dim);
4696
  font-size: 11px;
4697
  display: flex;
4698
+ @@ -1137,7 +1229,7 @@ pre, code {
4699
  }
4700
  .markdown-code-action {
4701
  border: 1px solid var(--border);
 
4704
  padding: 2px 7px;
4705
  background: var(--bg);
4706
  color: var(--text-muted);
4707
+ @@ -1191,8 +1283,9 @@ pre, code {
4708
+ opacity: 0.85;
4709
+ }
4710
+ .mermaid-preview-button:focus-visible {
4711
+ - outline: 2px solid var(--accent);
4712
+ - outline-offset: -2px;
4713
+ + outline: 1px solid var(--focus-line);
4714
+ + outline-offset: -1px;
4715
+ + box-shadow: var(--focus-glow);
4716
+ }
4717
+
4718
+ .image-preview-dialog {
4719
+ @@ -1228,7 +1321,7 @@ pre, code {
4720
  max-width: 100%;
4721
  max-height: 100%;
4722
  object-fit: contain;
 
4725
  box-shadow: 0 20px 60px rgba(0, 0, 0, 0.4);
4726
  }
4727
 
4728
+ @@ -1244,7 +1337,7 @@ pre, code {
4729
  height: 36px;
4730
  padding: 0;
4731
  border: 1px solid var(--border);
 
4734
  background: var(--bg-panel);
4735
  color: var(--text-muted);
4736
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.28);
4737
+ @@ -1259,7 +1352,7 @@ pre, code {
4738
+ }
4739
+
4740
+ .image-preview-close:focus-visible {
4741
+ - outline: 2px solid var(--accent);
4742
+ + outline: 1px solid var(--focus-line);
4743
+ outline-offset: 2px;
4744
+ }
4745
+
4746
+ @@ -1320,7 +1413,7 @@ pre, code {
4747
  height: 24px;
4748
  overflow: hidden;
4749
  border: 1px solid var(--border);
 
4752
  background: var(--bg);
4753
  }
4754
  .mermaid-zoom-stepper button,
4755
+ @@ -1347,7 +1440,7 @@ pre, code {
4756
  .mermaid-zoom-icon-button {
4757
  flex: 0 0 24px;
4758
  border: 1px solid var(--border);
 
4761
  }
4762
  .mermaid-zoom-stepper button:hover:not(:disabled),
4763
  .mermaid-zoom-icon-button:hover {
4764
+ @@ -1360,8 +1453,9 @@ pre, code {
4765
+ }
4766
+ .mermaid-zoom-stepper button:focus-visible,
4767
+ .mermaid-zoom-icon-button:focus-visible {
4768
+ - outline: 2px solid var(--accent);
4769
+ - outline-offset: -2px;
4770
+ + outline: 1px solid var(--focus-line);
4771
+ + outline-offset: -1px;
4772
+ + box-shadow: var(--focus-glow);
4773
+ }
4774
+ .mermaid-zoom-value {
4775
+ min-width: 48px;
4776
+ @@ -1384,7 +1478,7 @@ pre, code {
4777
  .mermaid-zoom-canvas {
4778
  padding: 12px;
4779
  border: 1px solid var(--border);
 
4782
  background: var(--bg-panel);
4783
  margin: 0 auto;
4784
  transition: width 0.15s ease;
4785
+ @@ -1460,7 +1554,7 @@ pre, code {
4786
  height: 24px;
4787
  overflow: hidden;
4788
  border: 1px solid var(--border);
 
4791
  }
4792
 
4793
  .file-viewer-mode-button {
4794
+ @@ -1503,12 +1597,11 @@ pre, code {
4795
  height: 32px;
4796
  padding: 0;
4797
  box-sizing: border-box;
 
4806
  opacity: 0;
4807
  visibility: hidden;
4808
  pointer-events: none;
4809
+ @@ -1542,7 +1635,7 @@ pre, code {
4810
+ }
4811
+
4812
+ .chat-scroll-to-bottom:focus-visible {
4813
+ - outline: 2px solid var(--accent);
4814
+ + outline: 1px solid var(--focus-line);
4815
+ outline-offset: 2px;
4816
+ }
4817
+
4818
+ @@ -1566,11 +1659,13 @@ pre, code {
4819
  }
4820
  }
4821
 
 
4833
  margin: 0 auto;
4834
  }
4835
 
4836
+ @@ -1584,22 +1679,21 @@ pre, code {
4837
 
4838
  .new-session-logo {
4839
  flex: none;
 
4862
  line-height: 1.15;
4863
  white-space: nowrap;
4864
  }
4865
+ @@ -1613,7 +1707,8 @@ pre, code {
4866
  gap: 6px;
4867
  min-width: 0;
4868
  color: var(--text-dim);
 
4872
  line-height: 1.4;
4873
  white-space: nowrap;
4874
  }
4875
+ @@ -1638,7 +1733,7 @@ pre, code {
4876
  gap: 3px;
4877
  margin: -3px -4px;
4878
  padding: 3px 4px;
 
4881
  color: var(--accent);
4882
  font-weight: 600;
4883
  text-decoration: none;
4884
+ @@ -1652,38 +1747,27 @@ pre, code {
4885
 
4886
  /* The ring inside the box, clear of the "→" and "·" beside it. */
4887
  .new-session-update:focus-visible {
4888
  - outline: 2px solid var(--accent);
4889
+ - outline-offset: -2px;
4890
+ + outline: 1px solid var(--focus-line);
4891
+ + outline-offset: -1px;
4892
+ + box-shadow: var(--focus-glow);
4893
  }
4894
 
4895
+ .new-session-update svg {
4896
  flex: none;
4897
  }
4898
 
 
4926
  }
4927
  }
4928
 
4929
+ @@ -1700,8 +1784,8 @@ pre, code {
4930
  tab's stacked boxes, the row below the sessions), as the sidebar drew it
4931
  on main: two grey boxes in the header buttons' material, 11px, the
4932
  project's whole path and the worktree's branch in code type, cut at their
 
4937
  .project-picker {
4938
  display: flex;
4939
  align-items: center;
4940
+ @@ -1722,52 +1806,14 @@ pre, code {
4941
  min-width: 0;
4942
  height: 29px;
4943
  padding: 0 10px;
 
4993
  }
4994
 
4995
  /* The project's name (its display name, else its folder's), cut at its
4996
+ @@ -1781,8 +1827,9 @@ pre, code {
4997
  max-width: 28ch;
4998
  overflow: hidden;
4999
  color: var(--text-dim);
 
5004
  }
5005
 
5006
  .project-picker-parent {
5007
+ @@ -1800,10 +1847,6 @@ pre, code {
5008
  text-overflow: ellipsis;
5009
  }
5010
 
 
5015
  .project-picker-button:hover,
5016
  .project-picker-button[aria-expanded="true"] {
5017
  background: var(--bg-selected);
5018
+ @@ -1811,8 +1854,9 @@ pre, code {
5019
+ }
5020
+
5021
+ .project-picker-button:focus-visible {
5022
+ - outline: 2px solid var(--accent);
5023
+ - outline-offset: 1px;
5024
+ + outline: 1px solid var(--focus-line);
5025
+ + outline-offset: -1px;
5026
+ + box-shadow: var(--focus-glow);
5027
+ }
5028
+
5029
+ .project-picker-icon,
5030
+ @@ -1851,7 +1895,8 @@ pre, code {
5031
  path's color and code type, cut at its end. */
5032
  .project-picker-label.is-alias {
5033
  color: var(--text);
 
5037
  }
5038
 
5039
  /* A path or a branch, cut at its left (rtl), the text itself kept left to
5040
+ @@ -1901,7 +1946,8 @@ pre, code {
5041
  border-color: color-mix(in srgb, var(--accent) 40%, transparent);
5042
  background: color-mix(in srgb, var(--accent) 6%, transparent);
5043
  color: var(--text-dim);
 
5047
  }
5048
 
5049
  /* No worktree list here (the files tab): a box that says why, and does nothing. */
5050
+ @@ -1978,7 +2024,7 @@ pre, code {
5051
  box-sizing: border-box;
5052
  flex: 0 0 24px;
5053
  border: 1px solid var(--border);
 
5056
  background: transparent;
5057
  color: var(--text-muted);
5058
  cursor: pointer;
5059
+ @@ -2003,8 +2049,9 @@ pre, code {
5060
+
5061
+ .file-viewer-mode-button:focus-visible,
5062
+ .file-viewer-icon-button:focus-visible {
5063
+ - outline: 2px solid var(--accent);
5064
+ - outline-offset: -2px;
5065
+ + outline: 1px solid var(--focus-line);
5066
+ + outline-offset: -1px;
5067
+ + box-shadow: var(--focus-glow);
5068
+ }
5069
+
5070
+ .directory-picker-path:focus-visible {
5071
+ @@ -2037,7 +2084,7 @@ pre, code {
5072
  .markdown-file-preview pre {
5073
  background: var(--bg-panel);
5074
  border: 1px solid var(--border);
 
5077
  padding: 12px 16px;
5078
  overflow-x: auto;
5079
  font-family: var(--font-mono);
5080
+ @@ -2049,7 +2096,7 @@ pre, code {
5081
  font-size: 0.9em;
5082
  background: var(--bg-panel);
5083
  padding: 1px 5px;
 
5086
  }
5087
  .markdown-file-preview pre code {
5088
  background: none;
5089
+ @@ -2061,7 +2108,7 @@ pre, code {
5090
  margin-bottom: 20px;
5091
  padding: 14px 16px 12px;
5092
  border: 1px solid var(--border);
 
5095
  background: var(--bg-panel);
5096
  }
5097
  .markdown-frontmatter-title {
5098
+ @@ -2114,18 +2161,6 @@ pre, code {
5099
  text-underline-offset: 2px;
5100
  }
5101
 
 
5114
  @keyframes drop-zone-in {
5115
  from { opacity: 0; transform: scale(0.97); }
5116
  to { opacity: 1; transform: scale(1); }
5117
+ @@ -2230,10 +2265,6 @@ span.token.table {
5118
  .panel-resize-handle.is-resizing::after {
5119
  background: color-mix(in srgb, var(--text-muted) 70%, var(--border));
5120
  }
 
5125
  /* Desktop sidebar: animate width */
5126
  @media (min-width: 641px) {
5127
  .sidebar-overlay-backdrop {
5128
+ @@ -2264,119 +2295,6 @@ span.token.table {
5129
  }
5130
  }
5131
 
 
5245
  /* Mobile sidebar: slide in/out as overlay */
5246
  @media (max-width: 640px) {
5247
  .chat-stats-center {
5248
+ @@ -2401,31 +2319,7 @@ span.token.table {
5249
  box-shadow: none;
5250
  }
5251
 
 
5278
  display: none;
5279
  }
5280
  .directory-picker-back,
5281
+ @@ -2474,3 +2368,479 @@ span.token.table {
5282
  display: none !important;
5283
  }
5284
  }
 
5426
  +}
5427
  +.user-bubble.is-editing {
5428
  + background: color-mix(in srgb, var(--accent) 14%, var(--user-bg));
5429
+ + box-shadow: 0 0 0 1px var(--focus-line), var(--focus-glow);
5430
  +}
5431
  +/* The actions under a message: icon buttons that show on hover or focus. */
5432
  +.message-actions {
 
5681
  +.reply-notice.is-error { background: var(--danger-soft); color: var(--danger); }
5682
  +.reply-notice.is-warning { background: color-mix(in srgb, var(--warning) 12%, transparent); color: var(--warning); }
5683
  +
5684
+ +/* The sidebar's bottom row (AppShell): Settings; under it, the row a host may
5685
+ + fill ([data-host-slot="sidebar-footer"], the gateway's Workspace row). Both
5686
+ + on the brand's columns: the icon 11px from the sidebar's edge, the label at
5687
+ + 34px (the 6px row inset, 5px padding, a 14px icon, a 9px gap), so the
5688
+ + sidebar's top and bottom line up; a lone centred Settings once floated
5689
+ + between them. */
5690
  +.sidebar-footer {
5691
  + display: flex;
5692
  + flex-shrink: 0;
 
5708
  + display: flex;
5709
  + flex: 1 1 0;
5710
  + align-items: center;
5711
+ + justify-content: flex-start;
5712
+ + gap: 9px;
5713
  + min-width: 0;
5714
  + height: var(--size-control-sm);
5715
+ + padding: 0 5px;
5716
  + border: 0;
5717
  + border-radius: var(--radius-sm);
5718
  + background: none;
 
5883
  lang: "en",
5884
  icons: [
5885
  diff --git a/app/settings.css b/app/settings.css
5886
+ index 0bf21eb..11530a5 100644
5887
  --- a/app/settings.css
5888
  +++ b/app/settings.css
5889
  @@ -40,9 +40,9 @@
 
5917
  background: transparent;
5918
  color: var(--text-muted);
5919
  cursor: pointer;
5920
+ @@ -122,6 +122,7 @@
5921
+ margin-bottom: 6px;
5922
+ }
5923
+
5924
+ +/* A section label as every dropdown's and the design system's: quiet, 12px, sentence case (no small bold capitals). */
5925
+ .config-sidebar-group-label {
5926
+ display: flex;
5927
+ align-items: center;
5928
+ @@ -129,11 +130,10 @@
5929
+ padding: 4px 8px 3px;
5930
+ color: var(--text-dim);
5931
+ font-family: inherit;
5932
+ - font-size: 10px;
5933
+ - font-weight: 600;
5934
+ + font-size: 12px;
5935
+ + font-weight: 400;
5936
+ line-height: 1.4;
5937
+ letter-spacing: 0;
5938
+ - text-transform: uppercase;
5939
+ }
5940
+
5941
+ .config-sidebar-group-label-text {
5942
+ @@ -144,6 +144,13 @@
5943
+ white-space: nowrap;
5944
+ }
5945
+
5946
+ +/* Sentence case: the scopes they name are lowercase words elsewhere (a profile's
5947
+ + "built-in" chip), so the label capitalises its own first letter. */
5948
+ +.config-sidebar-group-label-text::first-letter,
5949
+ +.config-section-title::first-letter {
5950
+ + text-transform: uppercase;
5951
+ +}
5952
+ +
5953
+ /* Sits in the heading row, so the sidebar gains no height for it. */
5954
+ .config-sidebar-group-switch {
5955
+ display: inline-flex;
5956
+ @@ -153,8 +160,9 @@
5957
  }
5958
 
5959
  .config-sidebar-group-count {
 
5964
  }
5965
 
5966
  /* Three lines, then scroll: the phone layout gives the whole sidebar 190px. */
5967
+ @@ -188,7 +196,7 @@
5968
  height: 30px;
5969
  padding: 0 8px;
5970
  border: 0;
 
5973
  outline: none;
5974
  background: transparent;
5975
  color: var(--text);
5976
+ @@ -206,8 +214,9 @@
5977
+ }
5978
+
5979
+ .config-sidebar-item:focus-visible {
5980
+ - outline: 2px solid var(--accent);
5981
+ - outline-offset: -2px;
5982
+ + outline: 1px solid var(--focus-line);
5983
+ + outline-offset: -1px;
5984
+ + box-shadow: var(--focus-glow);
5985
+ }
5986
+
5987
+ .config-sidebar-item[aria-current="page"] {
5988
+ @@ -236,8 +245,9 @@
5989
  .models-sidebar-badge {
5990
  flex-shrink: 0;
5991
  color: var(--text-dim);
 
5996
  }
5997
 
5998
  .oauth-paste-panel {
5999
+ @@ -277,9 +287,8 @@
6000
  /* An input's intrinsic width would otherwise push the button out of a narrow pane. */
6001
  min-width: 0;
6002
  padding: 6px 9px;
 
6008
  color: var(--text);
6009
  font-family: var(--font-mono);
6010
  font-size: 12px;
6011
+ @@ -290,9 +299,9 @@
6012
  flex-shrink: 0;
6013
  padding: 6px 12px;
6014
  border: none;
 
6021
  cursor: pointer;
6022
  font-size: 12px;
6023
  font-weight: 600;
6024
+ @@ -370,8 +379,9 @@
6025
  .enabled-models-count {
6026
  margin-right: auto;
6027
  color: var(--text-dim);
 
6032
  }
6033
 
6034
  .enabled-models-note {
6035
+ @@ -383,7 +393,7 @@
6036
  .enabled-models-error {
6037
  padding: 7px 9px;
6038
  border: 1px solid rgba(239, 68, 68, 0.3);
 
6041
  color: #ef4444;
6042
  font-size: 11px;
6043
  line-height: 1.4;
6044
+ @@ -401,10 +411,9 @@
6045
  box-sizing: border-box;
6046
  height: 30px;
6047
  padding: 0 9px;
 
6054
  color: var(--text);
6055
  font-family: inherit;
6056
  font-size: 12px;
6057
+ @@ -413,7 +422,7 @@
6058
  .enabled-models-list {
6059
  max-height: 360px;
6060
  border: 1px solid var(--border);
 
6063
  background: var(--bg-panel);
6064
  overflow-y: auto;
6065
  }
6066
+ @@ -457,9 +466,9 @@
6067
  .enabled-models-pin {
6068
  flex-shrink: 0;
6069
  padding: 1px 4px;
 
6076
  font-size: 9px;
6077
  }
6078
 
6079
+ @@ -497,7 +506,7 @@
6080
  justify-content: space-between;
6081
  gap: 18px;
6082
  min-height: 68px;
 
6085
  border-bottom: 1px solid var(--border);
6086
  background: var(--bg-panel);
6087
  }
6088
+ @@ -541,7 +550,7 @@
6089
  height: 28px;
6090
  padding: 0 6px;
6091
  border: 1px solid var(--border);
 
6094
  background: var(--bg);
6095
  color: var(--text);
6096
  font: inherit;
6097
+ @@ -566,7 +575,7 @@
6098
  flex: 1;
6099
  min-width: 0;
6100
  min-height: 0;
 
6103
  overflow-y: auto;
6104
  font-family: inherit;
6105
  font-size: 12px;
6106
+ @@ -628,13 +637,13 @@
6107
+ line-height: 1.35;
6108
+ }
6109
+
6110
+ +/* A section label as every dropdown's and the design system's: quiet, 12px, sentence case (no small bold capitals). */
6111
+ .config-section-title {
6112
+ color: var(--text-dim);
6113
+ - font-size: 11px;
6114
+ - font-weight: 600;
6115
+ + font-size: 12px;
6116
+ + font-weight: 400;
6117
+ line-height: 1.35;
6118
+ letter-spacing: 0;
6119
+ - text-transform: uppercase;
6120
+ }
6121
+
6122
+ .config-field {
6123
+ @@ -662,7 +671,7 @@
6124
  background: var(--border);
6125
  border: 5px solid transparent;
6126
  background-clip: padding-box;
 
6129
  }
6130
 
6131
  .config-field-label {
6132
+ @@ -697,15 +706,15 @@
6133
  .config-scope-tag {
6134
  flex-shrink: 0;
6135
  padding: 1px 5px;
 
6148
  }
6149
 
6150
  .config-scope-switch-field {
6151
+ @@ -730,7 +739,7 @@
6152
  height: 30px;
6153
  overflow: hidden;
6154
  border: 1px solid var(--border);
 
6157
  }
6158
 
6159
  .config-scope-switch-option {
6160
+ @@ -760,7 +769,7 @@
6161
  .config-scope-switch.is-small {
6162
  display: flex;
6163
  height: auto;
 
6166
  font-size: 12px;
6167
  }
6168
 
6169
+ @@ -891,9 +900,8 @@
6170
  width: 100%;
6171
  height: 36px;
6172
  padding: 0 11px;
 
6178
  color: var(--text);
6179
  font-family: var(--font-mono);
6180
  font-size: 12px;
6181
+ @@ -935,7 +943,7 @@
6182
  min-height: 30px;
6183
  padding: 6px 9px;
6184
  border: 1px solid var(--border);
 
6187
  background: var(--bg-panel);
6188
  color: var(--text-dim);
6189
  cursor: pointer;
6190
+ @@ -976,7 +984,7 @@
6191
  }
6192
 
6193
  .config-notice {
 
6196
  border-bottom: 1px solid var(--border);
6197
  background: var(--bg-panel);
6198
  color: var(--text-muted);
6199
+ @@ -1104,7 +1112,7 @@
6200
  align-items: center;
6201
  gap: 8px;
6202
  min-height: 52px;
 
6205
  border-top: 1px solid var(--border);
6206
  }
6207
 
6208
+ @@ -1159,7 +1167,7 @@
6209
  gap: 6px;
6210
  border-width: 1px;
6211
  border-style: solid;
 
6214
  cursor: pointer;
6215
  font-family: inherit;
6216
  letter-spacing: 0;
6217
+ @@ -1168,35 +1176,39 @@
6218
  transition: background-color 0.14s ease, border-color 0.14s ease, color 0.14s ease, box-shadow 0.14s ease;
6219
  }
6220
 
 
6270
  }
6271
 
6272
  .config-button-ghost {
6273
+ @@ -1206,24 +1218,31 @@
6274
  }
6275
 
6276
  .config-button-primary:not(:disabled):hover {
 
6305
  }
6306
 
6307
  .config-button:focus-visible {
6308
+ - outline: 2px solid var(--accent);
6309
+ + outline: 1px solid var(--focus-line);
6310
+ outline-offset: 2px;
6311
+ }
6312
+
6313
+ @@ -1254,9 +1273,9 @@
6314
  width: 32px;
6315
  height: 18px;
6316
  padding: 0;
 
6322
  cursor: pointer;
6323
  transition: background 0.15s, border-color 0.15s;
6324
  }
6325
+ @@ -1322,7 +1341,7 @@
6326
+ }
6327
+
6328
+ .config-switch:focus-visible {
6329
+ - outline: 2px solid var(--accent);
6330
+ + outline: 1px solid var(--focus-line);
6331
+ outline-offset: 2px;
6332
+ }
6333
+
6334
+ @@ -1342,7 +1361,7 @@
6335
  min-height: 30px;
6336
  padding: 0 8px;
6337
  border: 0;
 
6340
  background: transparent;
6341
  color: var(--text-dim);
6342
  cursor: pointer;
6343
+ @@ -1358,7 +1377,7 @@
6344
 
6345
  .config-list-action-button[aria-current="page"] {
6346
  background: var(--bg-selected);
 
6349
  }
6350
 
6351
  .config-list-action-button:disabled {
6352
+ @@ -1405,10 +1424,9 @@
6353
  height: 84vh;
6354
  max-height: calc(100dvh - 16px);
6355
  overflow: hidden;
 
6363
  }
6364
 
6365
  .settings-dialog-header {
6366
+ @@ -1417,7 +1435,7 @@
6367
  flex-shrink: 0;
6368
  align-items: center;
6369
  min-height: 50px;
 
6372
  border-bottom: 1px solid var(--border);
6373
  }
6374
 
6375
+ @@ -1467,7 +1485,7 @@
6376
  width: 24px;
6377
  height: 2px;
6378
  border-radius: 2px 2px 0 0;
 
6381
  content: "";
6382
  opacity: 0;
6383
  transform: translateX(-50%) scaleX(0.5);
6384
+ @@ -1494,7 +1512,7 @@
6385
  }
6386
 
6387
  .settings-section-tab:focus-visible:not([aria-current="page"]) {
 
6390
  background: var(--bg-hover);
6391
  color: var(--text);
6392
  }
6393
+ @@ -1538,11 +1556,13 @@
6394
+ }
6395
+
6396
+ /* The page scrolls across the dialog's full width, so its scrollbar sits at the
6397
+ - dialog's edge; only the content keeps a readable column. */
6398
+ + dialog's edge; only the content keeps a readable column. Its sides are the
6399
+ + header's (16px), so the headings stand under "Settings": a 3vw side (28px on
6400
+ + a desktop) once set them 12px in from the title and the other tabs' lists. */
6401
  .settings-general {
6402
  width: 100%;
6403
  height: 100%;
6404
  - padding: 26px clamp(18px, 4vw, 40px) 40px;
6405
+ + padding: 18px 16px 28px;
6406
  overflow-y: auto;
6407
  }
6408
 
6409
+ @@ -1593,21 +1613,21 @@
6410
  justify-content: center;
6411
  gap: 7px;
6412
  min-width: 0;
 
6433
  }
6434
 
6435
  .settings-theme-option:not(:has(input:checked)):hover {
6436
+ @@ -1615,8 +1635,9 @@
6437
+ }
6438
+
6439
+ .settings-theme-option:has(input:focus-visible) {
6440
+ - outline: 2px solid var(--accent);
6441
+ - outline-offset: 1px;
6442
+ + outline: 1px solid var(--focus-line);
6443
+ + outline-offset: -1px;
6444
+ + box-shadow: var(--focus-glow);
6445
+ }
6446
+
6447
+ .settings-theme-option-label {
6448
+ @@ -1657,9 +1678,8 @@
6449
  height: 26px;
6450
  margin: 0;
6451
  padding: 0 8px;
 
6457
  color: var(--text);
6458
  font-family: var(--font-ui);
6459
  font-weight: var(--font-ui-weight);
6460
+ @@ -1676,8 +1696,9 @@
6461
+ }
6462
+
6463
+ .settings-font-input:focus-visible {
6464
+ - outline: 2px solid var(--accent);
6465
+ - outline-offset: 1px;
6466
+ + outline: 1px solid var(--focus-line);
6467
+ + outline-offset: -1px;
6468
+ + box-shadow: var(--focus-glow);
6469
+ }
6470
+
6471
+ @media (max-width: 640px) {
6472
+ @@ -1722,8 +1743,9 @@
6473
 
6474
  .settings-chat-range-header output {
6475
  color: var(--text-muted);
 
6480
  }
6481
 
6482
  .settings-send-mode-options {
6483
+ @@ -1732,7 +1754,7 @@
6484
  gap: 3px;
6485
  padding: 3px;
6486
  border: 1px solid var(--border);
 
6489
  }
6490
 
6491
  .settings-send-mode-option {
6492
+ @@ -1744,7 +1766,7 @@
6493
  min-height: 26px;
6494
  padding: 0 10px;
6495
  border: 0;
 
6498
  background: transparent;
6499
  color: var(--text-muted);
6500
  cursor: pointer;
6501
+ @@ -1754,8 +1776,8 @@
6502
 
6503
  .settings-send-mode-option:has(input:checked) {
6504
  background: var(--bg-selected);
 
6509
  }
6510
 
6511
  .settings-send-mode-option:not(:has(input:checked)):hover {
6512
+ @@ -1763,8 +1785,9 @@
6513
+ }
6514
+
6515
+ .settings-send-mode-option:has(input:focus-visible) {
6516
+ - outline: 2px solid var(--accent);
6517
+ - outline-offset: 1px;
6518
+ + outline: 1px solid var(--focus-line);
6519
+ + outline-offset: -1px;
6520
+ + box-shadow: var(--focus-glow);
6521
+ }
6522
+
6523
+ .settings-send-mode-label {
6524
+ @@ -1781,7 +1804,7 @@
6525
  display: block;
6526
  width: 100%;
6527
  margin-top: 2px;
 
6530
  }
6531
 
6532
  .settings-language-options {
6533
+ @@ -1799,7 +1822,7 @@
6534
  height: 44px;
6535
  padding: 0 10px;
6536
  border: 0;
 
6539
  background: transparent;
6540
  color: var(--text);
6541
  cursor: pointer;
6542
+ @@ -1857,7 +1880,7 @@
6543
  max-width: 420px;
6544
  min-height: 44px;
6545
  padding: 0 10px;
 
6548
  background: var(--bg-panel);
6549
  color: var(--text);
6550
  font-size: 12px;
6551
+ @@ -1923,7 +1946,7 @@
6552
  margin-left: 14px;
6553
  padding: 0 9px;
6554
  border: 1px solid var(--border);
 
6557
  background: var(--bg-panel);
6558
  color: var(--text);
6559
  font-size: 16px;
6560
+ @@ -2066,7 +2089,7 @@
6561
  max-width: 100%;
6562
  padding: 0 5px;
6563
  border: 1px solid var(--border);
 
6566
  background: var(--bg-panel);
6567
  color: var(--text);
6568
  font-family: var(--font-mono);
6569
+ @@ -2085,7 +2108,7 @@
6570
  padding: 6px 8px;
6571
  overflow: auto;
6572
  border: 1px solid var(--border);
 
6575
  background: var(--bg-panel);
6576
  color: var(--text-muted);
6577
  font-family: var(--font-mono);
6578
+ @@ -2172,9 +2195,8 @@
6579
  min-width: 0;
6580
  height: 32px;
6581
  padding: 0 10px;
 
6587
  color: var(--text);
6588
  font-family: var(--font-mono);
6589
  font-size: 12px;
6590
+ @@ -2266,10 +2288,9 @@ select.mcp-add-input {
6591
  max-width: 100%;
6592
  max-height: 100%;
6593
  overflow: hidden;
 
6601
  }
6602
 
6603
  /* Focus moves to the dialog itself when it opens (tabIndex -1); it is not a control, so no ring. */
6604
+ @@ -2282,7 +2303,7 @@ select.mcp-add-input {
6605
  flex: 1 1 auto;
6606
  gap: 12px;
6607
  min-height: 0;
 
6610
  overflow: auto;
6611
  overscroll-behavior: contain;
6612
  }
6613
+ @@ -2315,7 +2336,7 @@ select.mcp-add-input {
6614
  margin-top: 10px;
6615
  padding: 8px 10px;
6616
  border: 1px solid var(--border);
 
6619
  background: var(--bg);
6620
  color: var(--text);
6621
  font-family: var(--font-mono);
6622
+ @@ -2333,7 +2354,7 @@ select.mcp-add-input {
6623
  .project-trust-error {
6624
  flex-shrink: 0;
6625
  max-height: 30vh;
 
6628
  overflow: auto;
6629
  color: #ef4444;
6630
  font-size: 12px;
6631
+ @@ -2353,19 +2374,20 @@ select.mcp-add-input {
6632
  flex-shrink: 0;
6633
  justify-content: flex-end;
6634
  gap: 8px;
 
6656
  }
6657
 
6658
  .project-trust-button:disabled {
6659
+ @@ -2373,10 +2395,8 @@ select.mcp-add-input {
6660
  }
6661
 
6662
  .project-trust-button.is-primary {
 
6669
  }
6670
 
6671
  .project-trust-button.is-primary:disabled {
6672
+ @@ -2450,7 +2470,7 @@ select.mcp-add-input {
6673
  min-width: 0;
6674
  padding: 8px 10px;
6675
  border: 1px solid var(--border);
 
6678
  background: var(--bg);
6679
  }
6680
 
6681
+ @@ -2472,7 +2492,7 @@ select.mcp-add-input {
6682
  .project-trust-mcp-tag {
6683
  flex-shrink: 0;
6684
  padding: 1px 5px;
 
6687
  background: rgba(120, 120, 120, 0.12);
6688
  color: var(--text-dim);
6689
  font-size: 10px;
6690
+ @@ -2493,7 +2513,7 @@ select.mcp-add-input {
6691
  display: inline-block;
6692
  margin: 2px 4px 0 0;
6693
  padding: 0 4px;
 
6698
  font-family: var(--font-mono);
6699
  diff --git a/app/shell.css b/app/shell.css
6700
  new file mode 100644
6701
+ index 0000000..a3dc745
6702
  --- /dev/null
6703
  +++ b/app/shell.css
6704
  @@ -0,0 +1,720 @@
 
7239
  + color: var(--text);
7240
  + font: inherit;
7241
  +}
7242
+ +.review-comment-input:focus { box-shadow: inset 0 0 0 1px var(--focus-line), var(--focus-glow); }
7243
  +.review-comment-actions { display: flex; align-items: center; justify-content: flex-end; gap: 4px; }
7244
  +.review-comment-hint { flex: 1 1 auto; color: var(--text-dim); font-size: 11.5px; }
7245
  +
 
7283
  + font: inherit;
7284
  + font-size: 12.5px;
7285
  +}
7286
+ +.browser-address input:focus { box-shadow: inset 0 0 0 1px var(--focus-line), var(--focus-glow); }
7287
  +.browser-error-count { color: var(--danger); font-size: 11.5px; font-variant-numeric: tabular-nums; }
7288
  +.browser-tabs {
7289
  + display: flex;
 
7354
  + background: #fff;
7355
  + cursor: default;
7356
  +}
7357
+ +.browser-canvas:focus-visible { box-shadow: inset 0 0 0 1px var(--focus-line); }
7358
  +.browser-canvas.is-picking { cursor: crosshair; }
7359
  +.browser-agent-pill, .browser-picking-hint {
7360
  + position: absolute;
 
7423
  +.browser-console-text { flex: 1 1 auto; min-width: 0; white-space: pre-wrap; overflow-wrap: anywhere; }
7424
  +.browser-console-location { flex: none; max-width: 40%; overflow: hidden; color: var(--text-dim); text-overflow: ellipsis; white-space: nowrap; direction: rtl; }
7425
  diff --git a/app/sidebar-menu.css b/app/sidebar-menu.css
7426
+ index 737e7a5..c098317 100644
7427
  --- a/app/sidebar-menu.css
7428
  +++ b/app/sidebar-menu.css
7429
  @@ -19,13 +19,14 @@
 
7913
  font-size: 15px;
7914
  }
7915
 
7916
+ @@ -562,8 +472,9 @@ html[data-keyboard-open] .sidebar-sheet {
7917
+ }
7918
+
7919
+ .sidebar-sheet-cancel:focus-visible {
7920
+ - outline: 2px solid var(--accent);
7921
+ - outline-offset: -2px;
7922
+ + outline: 1px solid var(--focus-line);
7923
+ + outline-offset: -1px;
7924
+ + box-shadow: var(--focus-glow);
7925
+ }
7926
+
7927
+ /* Undo toast: in place at the bottom of the sidebar root (position: relative). */
7928
+ @@ -583,11 +494,10 @@ html[data-keyboard-open] .sidebar-sheet {
7929
  height: 36px;
7930
  padding: 0 4px 0 12px;
7931
  box-sizing: border-box;
 
7940
  font-size: 12px;
7941
  pointer-events: auto;
7942
  animation: sidebar-toast-in 0.16s ease-out;
7943
+ @@ -634,7 +544,7 @@ html[data-keyboard-open] .sidebar-sheet {
7944
  height: 26px;
7945
  padding: 0 7px;
7946
  border: 0;
 
7949
  background: transparent;
7950
  color: var(--accent);
7951
  font: inherit;
7952
+ @@ -657,7 +567,7 @@ html[data-keyboard-open] .sidebar-sheet {
7953
  height: 24px;
7954
  padding: 0;
7955
  border: 0;
 
7958
  background: transparent;
7959
  color: var(--text-dim);
7960
  cursor: pointer;
7961
+ @@ -670,8 +580,9 @@ html[data-keyboard-open] .sidebar-sheet {
7962
+
7963
+ .sidebar-toast-action:focus-visible,
7964
+ .sidebar-toast-dismiss:focus-visible {
7965
+ - outline: 2px solid var(--accent);
7966
+ - outline-offset: -2px;
7967
+ + outline: 1px solid var(--focus-line);
7968
+ + outline-offset: -1px;
7969
+ + box-shadow: var(--focus-glow);
7970
+ }
7971
+
7972
+ @media (pointer: coarse) {
7973
+ @@ -716,23 +627,23 @@ html[data-keyboard-open] .sidebar-sheet {
7974
  /* A worktree menu's "New worktree…" form (a custom menu body; components/WorktreeCreateForm.tsx),
7975
  and its removal body. */
7976
  .sidebar-worktree-form {
 
8000
  user-select: text;
8001
  -webkit-user-select: text;
8002
  }
8003
+ @@ -747,33 +658,31 @@ html[data-keyboard-open] .sidebar-sheet {
8004
  .sidebar-worktree-force,
8005
  .sidebar-worktree-cancel {
8006
  flex: 1;
 
8046
  }
8047
 
8048
  .sidebar-worktree-create:disabled,
8049
+ @@ -785,28 +694,28 @@ html[data-keyboard-open] .sidebar-sheet {
8050
+ .sidebar-worktree-create:focus-visible,
8051
+ .sidebar-worktree-force:focus-visible,
8052
+ .sidebar-worktree-cancel:focus-visible {
8053
+ - outline: 2px solid var(--accent);
8054
+ - outline-offset: 1px;
8055
+ + outline: 1px solid var(--focus-line);
8056
+ + outline-offset: -1px;
8057
+ + box-shadow: var(--focus-glow);
8058
+ }
8059
 
8060
  /* A worktree menu's removal body (components/ProjectWorktreePicker.tsx): what Force would do. */
8061
  .sidebar-worktree-message {
 
8082
  }
8083
 
8084
  .sidebar-sheet .sidebar-worktree-input,
8085
+ @@ -820,19 +729,18 @@ html[data-keyboard-open] .sidebar-sheet {
8086
  /* A project menu's "Delete all sessions" question (a custom menu body;
8087
  components/SessionSidebar.tsx): what goes, then Delete and Cancel. */
8088
  .sidebar-menu-question {
 
8106
  line-height: 1.4;
8107
  }
8108
 
8109
+ @@ -846,26 +754,25 @@ html[data-keyboard-open] .sidebar-sheet {
8110
  .sidebar-menu-question-cancel {
8111
  flex: 1;
8112
  min-width: 0;
 
8142
  }
8143
 
8144
  .sidebar-menu-question-confirm:disabled {
8145
+ @@ -875,12 +782,13 @@ html[data-keyboard-open] .sidebar-sheet {
8146
+
8147
+ .sidebar-menu-question-confirm:focus-visible,
8148
+ .sidebar-menu-question-cancel:focus-visible {
8149
+ - outline: 2px solid var(--accent);
8150
+ - outline-offset: 1px;
8151
+ + outline: 1px solid var(--focus-line);
8152
+ + outline-offset: -1px;
8153
+ + box-shadow: var(--focus-glow);
8154
  }
8155
 
8156
  .sidebar-sheet .sidebar-menu-question {
 
8160
 
8161
  .sidebar-sheet .sidebar-menu-question-text {
8162
  diff --git a/app/sidebar.css b/app/sidebar.css
8163
+ index 86728a2..e2f5bba 100644
8164
  --- a/app/sidebar.css
8165
  +++ b/app/sidebar.css
8166
+ @@ -91,8 +91,9 @@
8167
 
8168
  .session-tree button:focus-visible,
8169
  .session-tree a.session-tree-main:focus-visible {
8170
  - outline: 2px solid var(--accent);
8171
+ - outline-offset: -2px;
8172
+ + outline: 1px solid var(--focus-line);
8173
+ + outline-offset: -1px;
8174
+ + box-shadow: var(--focus-glow);
8175
+ }
8176
+
8177
+ /*
8178
+ @@ -146,10 +147,9 @@
8179
+ color: var(--text);
8180
+ }
8181
+
8182
+ +/* Quiet and in sentence case, as every section label is (no small bold capitals). */
8183
+ .session-tree-pinned-label {
8184
+ - font-weight: 600;
8185
+ - letter-spacing: 0.05em;
8186
+ - text-transform: uppercase;
8187
+ + font-weight: 400;
8188
  }
8189
 
8190
+ .session-tree-pinned-count {
8191
  @@ -171,20 +171,22 @@
8192
  }
8193
 
 
8690
  }
8691
 
8692
  .sidebar-brand:focus {
8693
+ @@ -1064,8 +1090,10 @@ a.session-tree-main {
8694
  }
8695
 
8696
  .sidebar-brand:focus-visible {
8697
  - outline: 2px solid var(--accent);
8698
  - outline-offset: 1px;
8699
+ + outline: 1px solid var(--focus-line);
8700
+ + outline-offset: -1px;
8701
+ + box-shadow: var(--focus-glow);
8702
  + outline-offset: 0;
8703
  }
8704
 
8705
  .sidebar-header-spacer {
8706
+ @@ -1078,7 +1106,7 @@ a.session-tree-main {
8707
  }
8708
 
8709
  .sidebar-search-toggle {
 
8712
  padding: 0;
8713
  }
8714
 
8715
+ @@ -1093,7 +1121,7 @@ a.session-tree-main {
8716
  label is still each one's name. */
8717
  .sidebar-header[data-fit="1"] .sidebar-new-button,
8718
  .sidebar-header[data-fit="2"] .sidebar-new-button {
 
8721
  padding: 0;
8722
  }
8723
 
8724
+ @@ -1105,6 +1133,11 @@ a.session-tree-main {
8725
  font-size: 11px;
8726
  }
8727
 
 
8733
  .sidebar-header[data-fit="1"] .sidebar-new-label,
8734
  .sidebar-header[data-fit="2"] .sidebar-new-label,
8735
  .sidebar-header[data-fit="2"] .sidebar-tab-label {
8736
+ @@ -1122,8 +1155,9 @@ a.session-tree-main {
8737
+ .sidebar-archive-back:focus-visible,
8738
+ .sidebar-tool-button:focus-visible,
8739
+ .sidebar-files-section-toggle:focus-visible {
8740
+ - outline: 2px solid var(--accent);
8741
+ - outline-offset: -2px;
8742
+ + outline: 1px solid var(--focus-line);
8743
+ + outline-offset: -1px;
8744
+ + box-shadow: var(--focus-glow);
8745
+ }
8746
+
8747
+ .sidebar-panel {
8748
+ @@ -1202,7 +1236,7 @@ a.session-tree-main {
8749
  the root too), and its time 4px inside the row. The header row's keys and
8750
  the picker's boxes end there. */
8751
  .session-sidebar.is-files-below {
 
8754
  }
8755
 
8756
  /* The files section's header row (files below the sessions only), one row
8757
+ @@ -1246,7 +1280,7 @@ a.session-tree-main {
8758
  padding: 0 5px;
8759
  overflow: hidden;
8760
  border: 0;
 
8763
  background: transparent;
8764
  color: var(--text-muted);
8765
  font: inherit;
8766
+ @@ -1272,10 +1306,8 @@ a.session-tree-main {
8767
+ .sidebar-files-section-label {
8768
+ min-width: 0;
8769
+ overflow: hidden;
8770
+ - font-weight: 600;
8771
+ - letter-spacing: 0.05em;
8772
+ + font-weight: 400;
8773
+ text-overflow: ellipsis;
8774
+ - text-transform: uppercase;
8775
+ }
8776
+
8777
+ /* The changed files' count, as the pinned section's (.session-tree-pinned-count). */
8778
+ @@ -1304,7 +1336,7 @@ a.session-tree-main {
8779
  min-width: 18px;
8780
  max-width: 26px;
8781
  height: 26px;
 
8784
  }
8785
 
8786
  /* Under that row (or the whole files tab): the head, then the tree filling
8787
+ @@ -1329,16 +1361,17 @@ a.session-tree-main {
8788
  height: 29px;
8789
  padding: 0 10px;
8790
  box-sizing: border-box;
 
8801
  -.sidebar-search-input:focus {
8802
  - outline: 2px solid var(--accent);
8803
  +.sidebar-search-input:focus-visible {
8804
+ + outline: 1px solid var(--focus-line);
8805
+ + outline-offset: -1px;
8806
+ + box-shadow: var(--focus-glow);
8807
  }
8808
 
8809
  /* The tree and the archive view under the search, in one cell: the archive
8810
+ @@ -1413,7 +1446,7 @@ a.session-tree-main {
8811
  height: 26px;
8812
  padding: 0 6px;
8813
  border: 0;
 
8816
  background: transparent;
8817
  color: var(--text-muted);
8818
  font-size: 12px;
8819
+ @@ -1502,7 +1535,7 @@ a.session-tree-main {
8820
  height: 32px;
8821
  padding: 0;
8822
  border: 0;
 
8827
  cursor: pointer;
8828
  diff --git a/app/ui.css b/app/ui.css
8829
  new file mode 100644
8830
+ index 0000000..b4b8a1d
8831
  --- /dev/null
8832
  +++ b/app/ui.css
8833
  @@ -0,0 +1,634 @@
 
9053
  + white-space: nowrap;
9054
  + cursor: pointer;
9055
  +}
9056
+ +.ui-dropdown-row:focus-visible { outline: none; box-shadow: none; }
9057
  +.ui-dropdown-row[aria-selected="true"] { color: var(--text-strong); }
9058
  +.ui-dropdown-row.has-gutter { padding-right: calc(var(--popup-row-pad) + 28px); }
9059
  +.ui-dropdown-icon { display: inline-flex; flex: none; align-items: center; justify-content: center; width: var(--popup-icon); height: var(--popup-icon); color: var(--text-muted); }
 
9613
  - assert.deepEqual(saved, { tabs: [{ ...tab, restored: true }], activeId: null, open: false });
9614
  -});
9615
  diff --git a/components/AppShell.tsx b/components/AppShell.tsx
9616
+ index c8c4ba9..51d811c 100644
9617
  --- a/components/AppShell.tsx
9618
  +++ b/components/AppShell.tsx
9619
  @@ -3,6 +3,8 @@
 
11346
  <div style={{ display: "grid", gridTemplateColumns: "auto minmax(0, 1fr) auto", columnGap: 12, rowGap: 8, alignItems: "start" }}>
11347
  {sessionRows.map((row) => (
11348
  <div key={`session-info:${row.label}`} style={{ display: "contents" }}>
11349
+ @@ -2468,6 +2209,8 @@ export function AppShell() {
11350
+ overflowWrap: "anywhere",
11351
+ wordBreak: "break-word",
11352
+ whiteSpace: "normal",
11353
+ + // Paths, the ID and the branch are code; a name or a duration is not.
11354
+ + fontFamily: row.copyField ? "var(--font-mono)" : undefined,
11355
+ }}>{row.value}</div>
11356
+ <div>{row.copyField ? copyButton(row.copyField, row.value) : null}</div>
11357
+ </div>
11358
+ @@ -2477,7 +2220,7 @@ export function AppShell() {
11359
  );
11360
  const projectInfoSection = projectRows.length > 0 ? (
11361
  <div style={{ minWidth: 0 }}>
 
11364
  <div style={{ display: "grid", gridTemplateColumns: "auto minmax(0, 1fr) auto", columnGap: 12, rowGap: 8, alignItems: "start" }}>
11365
  {projectRows.map((row) => (
11366
  <div key={`project-info:${row.label}`} style={{ display: "contents" }}>
11367
+ @@ -2488,6 +2231,8 @@ export function AppShell() {
11368
+ overflowWrap: "anywhere",
11369
+ wordBreak: "break-word",
11370
+ whiteSpace: "normal",
11371
+ + // Paths, the ID and the branch are code; a name or a duration is not.
11372
+ + fontFamily: row.copyField ? "var(--font-mono)" : undefined,
11373
+ }}>{row.value}</div>
11374
+ <div>{row.copyField ? copyButton(row.copyField, row.value) : null}</div>
11375
+ </div>
11376
+ @@ -2505,13 +2250,13 @@ export function AppShell() {
11377
+ gap: isMobile ? 16 : 24,
11378
+ fontSize: 12,
11379
+ lineHeight: 1.5,
11380
+ - fontFamily: "var(--font-mono)",
11381
+ + fontVariantNumeric: "tabular-nums",
11382
+ }}>
11383
+ <div style={{ display: "flex", flexDirection: "column", gap: isMobile ? 16 : 20 }}>
11384
+ {sessionInfoSection}
11385
+ {projectInfoSection}
11386
+ </div>
11387
+ - {section(translate("session.messages"), messageRows)}
11388
+ + {section(translate("session.messages"), messageRows, "right", true)}
11389
+ {section(translate("session.tokens"), [...tokenRows, ...extraTokenRows], "right", true)}
11390
+ </div>
11391
+ );
11392
+ @@ -2529,8 +2274,14 @@ export function AppShell() {
11393
  {isMobile && renderProjectTrustWarning(true)}
11394
  </div>
11395
 
 
11406
  {showChat ? (
11407
  <ChatWindow
11408
  key={sessionKey}
11409
+ @@ -2568,7 +2319,6 @@ export function AppShell() {
11410
  initialPrompt={pendingQuotePrompt?.sessionId === selectedSession?.id ? pendingQuotePrompt?.text : undefined}
11411
  onInitialPromptConsumed={() => setPendingQuotePrompt(null)}
11412
  soundEnabled={soundEnabled}
 
11414
  playDoneSound={playDoneSound}
11415
  unlockAudio={unlockAudio}
11416
  holdAudio={holdAudio}
11417
+ @@ -2601,9 +2351,7 @@ export function AppShell() {
11418
  </div>
11419
  ) : (
11420
  <div style={{ position: "absolute", top: 12, left: 12, display: "flex", alignItems: "flex-start", gap: 8, userSelect: "none", pointerEvents: "none" }}>
 
11425
  <div>
11426
  <div style={{ fontSize: 18, fontWeight: 600, color: "var(--text)", marginBottom: 8 }}>{translate("workspace.getStarted")}</div>
11427
  <div style={{ fontSize: 12, color: "var(--text-muted)", lineHeight: 1.8 }}>
11428
+ @@ -2614,146 +2362,15 @@ export function AppShell() {
11429
  </div>
11430
  )
11431
  ) : null}
 
11579
  </div>
11580
  </div>
11581
  {settingsSection && (
11582
+ @@ -2763,6 +2380,8 @@ export function AppShell() {
11583
  initialSection={settingsSection}
11584
  quoteSelectionEnabled={quoteSelectionEnabled}
11585
  onQuoteSelectionChange={handleQuoteSelectionChange}
 
11588
  onClose={() => {
11589
  setSettingsSection(null);
11590
  setModelsRefreshKey((key) => key + 1);
11591
+ @@ -2786,6 +2405,6 @@ export function AppShell() {
11592
  onStatus={setProjectTrust}
11593
  />
11594
  )}
 
21190
  onSelect={() => choose(option)}
21191
  star={onSetDefault ? {
21192
  diff --git a/components/ModelsConfig.tsx b/components/ModelsConfig.tsx
21193
+ index 9f6b556..c1f7dd6 100644
21194
  --- a/components/ModelsConfig.tsx
21195
  +++ b/components/ModelsConfig.tsx
21196
  @@ -54,6 +54,8 @@ import { providerBadgeLabel } from "./enabled-models-helpers";
 
21370
  background: "var(--bg-panel)",
21371
  color: !model.id.trim() || catalogState.phase === "loading" ? "var(--text-dim)" : "var(--text-muted)",
21372
  cursor: !model.id.trim() || catalogState.phase === "loading" ? "not-allowed" : "pointer",
21373
+ @@ -1175,7 +1168,7 @@ function ModelDetail({
21374
+ </div>
21375
+
21376
+ <div style={{ marginTop: 16 }}>
21377
+ - <div style={{ fontSize: 10, color: "var(--text-dim)", fontWeight: 600, textTransform: "uppercase" }}>
21378
+ + <div style={{ fontSize: 12, color: "var(--text-dim)", fontWeight: 400 }}>
21379
+ {t("models.costPerMillion")}
21380
+ </div>
21381
+ {costEditing ? (
21382
  @@ -1227,20 +1220,7 @@ function ModelDetail({
21383
  {advancedSummary}
21384
  </span>
 
21463
  <input
21464
  ref={inputRef}
21465
  value={search}
21466
+ @@ -1769,7 +1745,7 @@ function AddProviderPicker({
21467
+ ) : (
21468
+ <div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(min(240px, 100%), 1fr))", gap: 8 }}>
21469
+ {showCustom && (
21470
+ - <div style={{ gridColumn: "1 / -1", fontSize: 10, fontWeight: 600, color: "var(--text-dim)", textTransform: "uppercase", letterSpacing: "0.07em" }}>{t("i18n.custom")}</div>
21471
+ + <div style={{ gridColumn: "1 / -1", fontSize: 12, fontWeight: 400, color: "var(--text-dim)" }}>{t("i18n.custom")}</div>
21472
+ )}
21473
+ {showCustom && (
21474
+ <button
21475
+ @@ -1782,16 +1758,14 @@ function AddProviderPicker({
21476
  <div style={{ fontSize: 12, fontWeight: 600, color: "var(--text)", lineHeight: 1.3, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>OpenAI / Anthropic compatible</div>
21477
  <div style={{ fontSize: 10, color: "var(--text-dim)", marginTop: 2 }}>{t("i18n.customEndpoint")}</div>
21478
  </div>
 
21485
  </span>
21486
  </button>
21487
  )}
21488
+
21489
+ {availableOAuth.length > 0 && (
21490
+ - <div style={{ gridColumn: "1 / -1", paddingTop: showCustom ? 6 : 0, fontSize: 10, fontWeight: 600, color: "var(--text-dim)", textTransform: "uppercase", letterSpacing: "0.07em" }}>{t("i18n.subscriptions")}</div>
21491
+ + <div style={{ gridColumn: "1 / -1", paddingTop: showCustom ? 6 : 0, fontSize: 12, fontWeight: 400, color: "var(--text-dim)" }}>{t("i18n.subscriptions")}</div>
21492
+ )}
21493
+ {availableOAuth.map((p) => (
21494
+ <button key={p.id} onClick={() => { onSelectOAuth(p.id); onClose(); }}
21495
+ @@ -1808,7 +1782,7 @@ function AddProviderPicker({
21496
+ ))}
21497
+
21498
+ {availableApiKey.length > 0 && (
21499
+ - <div style={{ gridColumn: "1 / -1", paddingTop: availableOAuth.length > 0 ? 6 : 0, fontSize: 10, fontWeight: 600, color: "var(--text-dim)", textTransform: "uppercase", letterSpacing: "0.07em" }}>API Key</div>
21500
+ + <div style={{ gridColumn: "1 / -1", paddingTop: availableOAuth.length > 0 ? 6 : 0, fontSize: 12, fontWeight: 400, color: "var(--text-dim)" }}>API key</div>
21501
+ )}
21502
+ {availableApiKey.map((p) => (
21503
+ <button key={p.id} onClick={() => { onSelectApiKey(p.id); onClose(); }}
21504
  @@ -2177,13 +2151,7 @@ export function ModelsConfig({ onClose, embedded = false, cwd = null }: {
21505
  onClick={() => setSelection({ type: "provider", name: pName })}
21506
  active={isProviderSelected}
 
21754
  );
21755
  }
21756
  diff --git a/components/PluginsConfig.tsx b/components/PluginsConfig.tsx
21757
+ index 83fddc9..5926818 100644
21758
  --- a/components/PluginsConfig.tsx
21759
  +++ b/components/PluginsConfig.tsx
21760
  @@ -1,5 +1,6 @@
 
21764
  import { useCallback, useEffect, useId, useMemo, useRef, useState } from "react";
21765
  import { sendAgentCommand } from "@/lib/agent-client";
21766
  import type {
21767
+ @@ -197,10 +198,9 @@ function ResourceList({ pkg }: { pkg: PluginPackageInfo }) {
21768
+ >
21769
+ <div
21770
+ style={{
21771
+ - fontSize: 10,
21772
+ - fontWeight: 700,
21773
+ + fontSize: 12,
21774
+ + fontWeight: 400,
21775
+ color: "var(--text-dim)",
21776
+ - textTransform: "uppercase",
21777
+ marginBottom: 6,
21778
+ }}
21779
+ >
21780
+ @@ -275,14 +275,7 @@ function AddPluginPanel({
21781
  href: "https://pi.dev/packages",
21782
  label: "pi.dev/packages",
21783
  icon: (
 
21793
  ),
21794
  }]}
21795
  target={
21796
+ @@ -370,7 +363,7 @@ function PackageDetail({
21797
  style={{
21798
  fontSize: 10,
21799
  padding: "1px 5px",
 
21802
  background: "rgba(120,120,120,0.12)",
21803
  color: "var(--text-dim)",
21804
  }}
21805
+ @@ -382,7 +375,7 @@ function PackageDetail({
21806
  style={{
21807
  fontSize: 10,
21808
  padding: "1px 5px",
 
26002
  +});
26003
  diff --git a/components/highlighting.ts b/components/highlighting.ts
26004
  new file mode 100644
26005
+ index 0000000..773b858
26006
  --- /dev/null
26007
  +++ b/components/highlighting.ts
26008
  @@ -0,0 +1,157 @@
 
26087
  +[data-separator=line-info-basic] { height: 24px; }
26088
  +[data-separator-content] { font-size: 12px; }
26089
  +[data-expand-index] [data-separator-wrapper] { grid-template-columns: 28px auto; }
26090
+ +[data-expand-button]:focus-visible { outline: 1px solid var(--focus-line); outline-offset: -1px; box-shadow: var(--focus-glow); }
26091
  +`;
26092
  +
26093
  +// The library's expand controls are role=button divs out of the tab order: give them a place in
 
34252
  - };
34253
  -}
34254
  diff --git a/lib/i18n/messages/en.ts b/lib/i18n/messages/en.ts
34255
+ index cd39a31..078f061 100644
34256
  --- a/lib/i18n/messages/en.ts
34257
  +++ b/lib/i18n/messages/en.ts
34258
  @@ -22,8 +22,8 @@ export const enLocale: LocalePlugin = {
 
34293
  "subagent.tabLabel": "Sub-agent {name}",
34294
  "subagent.refresh": "Refresh",
34295
  "subagent.viewer.starting": "Sub-agent session is starting…",
34296
+ @@ -227,18 +227,18 @@ export const enLocale: LocalePlugin = {
34297
+ "tools.guidelines": "Prompt guidelines",
34298
+ "session.info": "Session info",
34299
+ "session.name": "Name",
34300
+ - "session.file": "Session File",
34301
+ + "session.file": "Session file",
34302
+ "session.inMemory": "In-memory",
34303
+ "session.id": "ID",
34304
+ "session.user": "User",
34305
+ "session.assistant": "Assistant",
34306
+ - "session.toolCalls": "Tool Calls",
34307
+ - "session.toolResults": "Tool Results",
34308
+ + "session.toolCalls": "Tool calls",
34309
+ + "session.toolResults": "Tool results",
34310
+ "session.total": "Total",
34311
+ "session.input": "Input",
34312
+ "session.output": "Output",
34313
+ - "session.cacheRead": "Cache Read",
34314
+ - "session.cacheWrite": "Cache Write",
34315
+ + "session.cacheRead": "Cache read",
34316
+ + "session.cacheWrite": "Cache write",
34317
+ "session.cost": "Cost",
34318
+ "session.context": "Context",
34319
+ "session.cacheHitRate": "Avg cache hit rate",
34320
+ @@ -246,15 +246,15 @@ export const enLocale: LocalePlugin = {
34321
  "session.copied": "Copied",
34322
  "session.title": "Session info",
34323
  "session.load": "Send a message or run /session to load session info",
 
34327
  + "session.projectSection": "Project info",
34328
  "session.messages": "Messages",
34329
  "session.tokens": "Tokens",
34330
+ - "session.totalActive": "Active Time",
34331
+ + "session.totalActive": "Active time",
34332
+ "session.copyFile": "Copy file path",
34333
+ "session.copyId": "Copy session ID",
34334
+ - "session.projectDir": "Project Dir",
34335
+ - "session.gitBranch": "Git Branch",
34336
+ + "session.projectDir": "Project folder",
34337
+ + "session.gitBranch": "Git branch",
34338
+ "session.gitWorktree": "Worktree",
34339
+ "session.copyProjectDir": "Copy project directory",
34340
+ "session.copyGitBranch": "Copy git branch",
34341
  @@ -269,13 +269,9 @@ export const enLocale: LocalePlugin = {
34342
  "workspace.stepModels": "Open Settings at the bottom, then choose Models",
34343
  "workspace.newSessionContext": "New session location",