@gavin-hu/my-pi-agent 0.19.0 → 0.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (158) hide show
  1. package/README.md +17 -11
  2. package/README.zh-CN.md +13 -8
  3. package/docs/extensions/README.md +1 -1
  4. package/docs/extensions/board.md +286 -123
  5. package/docs/extensions/bytefly.md +381 -223
  6. package/docs/extensions/file-browse.md +14 -7
  7. package/docs/extensions/job.md +1 -2
  8. package/docs/extensions/plan.md +56 -11
  9. package/docs/extensions/session-bus.md +23 -10
  10. package/docs/extensions/team.md +45 -17
  11. package/docs/extensions/wechat.md +2 -1
  12. package/docs/extensions/worktree.md +30 -15
  13. package/extensions/board/commands.ts +23 -5
  14. package/extensions/board/format.ts +107 -9
  15. package/extensions/board/join.ts +1 -1
  16. package/extensions/board/page.ts +170 -94
  17. package/extensions/board/result.ts +9 -0
  18. package/extensions/board/schema.ts +22 -7
  19. package/extensions/board/seat.ts +9 -10
  20. package/extensions/board/snapshot.ts +1 -0
  21. package/extensions/board/tools.ts +186 -21
  22. package/extensions/board/transport/router.ts +27 -108
  23. package/extensions/board/transport/server.ts +18 -29
  24. package/extensions/board/types.ts +67 -0
  25. package/extensions/board/view/client.ts +23 -13
  26. package/extensions/board/view/dialog.ts +43 -0
  27. package/extensions/board/view/keys.ts +28 -24
  28. package/extensions/board/view/live.ts +5 -171
  29. package/extensions/board/view/names.ts +91 -0
  30. package/extensions/board/view/page.ts +48 -88
  31. package/extensions/board/view/render.ts +213 -190
  32. package/extensions/board/view/styles.ts +65 -48
  33. package/extensions/board/view/team.ts +63 -41
  34. package/extensions/bytefly/footer/budget.ts +6 -5
  35. package/extensions/bytefly/footer/collect/entries.ts +12 -4
  36. package/extensions/bytefly/footer/collect/snapshot.ts +8 -17
  37. package/extensions/bytefly/footer/config.ts +19 -8
  38. package/extensions/bytefly/footer/price/price.ts +30 -8
  39. package/extensions/bytefly/footer/register.ts +5 -5
  40. package/extensions/bytefly/footer/render/format.ts +18 -0
  41. package/extensions/bytefly/footer/render/lines.ts +23 -7
  42. package/extensions/bytefly/footer/render/segments.ts +117 -31
  43. package/extensions/bytefly/footer/types.ts +29 -5
  44. package/extensions/bytefly/footer/unpriced.ts +42 -17
  45. package/extensions/bytefly/panel/commands.ts +28 -1
  46. package/extensions/bytefly/panel/filter.ts +68 -0
  47. package/extensions/bytefly/panel/format.ts +16 -15
  48. package/extensions/bytefly/panel/join.ts +56 -13
  49. package/extensions/bytefly/panel/prices.ts +43 -0
  50. package/extensions/bytefly/panel/settings.ts +72 -23
  51. package/extensions/bytefly/panel/tui/entries.ts +26 -8
  52. package/extensions/bytefly/panel/tui/frame.ts +130 -93
  53. package/extensions/bytefly/panel/tui/keys.ts +352 -0
  54. package/extensions/bytefly/panel/tui/list-view.ts +25 -46
  55. package/extensions/bytefly/panel/tui/panes.ts +76 -0
  56. package/extensions/bytefly/panel/tui/section-view.ts +126 -0
  57. package/extensions/bytefly/panel/tui/settings-section.ts +54 -125
  58. package/extensions/bytefly/panel/tui/settings-view.ts +30 -131
  59. package/extensions/bytefly/panel/tui/side-pane.ts +224 -0
  60. package/extensions/bytefly/panel/tui.ts +306 -259
  61. package/extensions/check/commands.ts +2 -1
  62. package/extensions/file-browse/files/classify.ts +136 -0
  63. package/extensions/file-browse/files/files.ts +8 -547
  64. package/extensions/file-browse/files/index.ts +90 -0
  65. package/extensions/file-browse/files/list.ts +261 -0
  66. package/extensions/file-browse/files/tree.ts +101 -0
  67. package/extensions/file-browse/transport/router.ts +0 -2
  68. package/extensions/file-browse/view/emit.ts +51 -0
  69. package/extensions/file-browse/view/html.ts +6 -7
  70. package/extensions/file-browse/view/markdown.ts +11 -13
  71. package/extensions/file-browse/view/raw-html.ts +18 -13
  72. package/extensions/job/runtime.ts +4 -4
  73. package/extensions/plan/feedback.ts +40 -16
  74. package/extensions/plan/format.ts +10 -3
  75. package/extensions/plan/index.ts +17 -1
  76. package/extensions/plan/plan-files.ts +88 -0
  77. package/extensions/plan/plans.ts +19 -23
  78. package/extensions/plan/policy.ts +10 -5
  79. package/extensions/plan/runtime.ts +31 -2
  80. package/extensions/plan/todos.ts +51 -3
  81. package/extensions/plan/tools/exit.ts +10 -1
  82. package/extensions/plan/tools/todo-seed.ts +4 -2
  83. package/extensions/plan/tools/write.ts +12 -1
  84. package/extensions/plan/types.ts +15 -0
  85. package/extensions/plan/web-blocks.ts +89 -0
  86. package/extensions/plan/web-chrome.ts +3 -5
  87. package/extensions/plan/web-client.ts +179 -330
  88. package/extensions/plan/web-collab.ts +3 -3
  89. package/extensions/plan/web-comments.ts +18 -8
  90. package/extensions/plan/web-composer.ts +80 -0
  91. package/extensions/plan/web-draft.ts +40 -0
  92. package/extensions/plan/web-keys.ts +134 -0
  93. package/extensions/plan/web-outline.ts +192 -0
  94. package/extensions/plan/web-page.ts +4 -1
  95. package/extensions/plan/web-plan.ts +150 -0
  96. package/extensions/plan/web-router.ts +0 -1
  97. package/extensions/plan/web-settle.ts +194 -0
  98. package/extensions/rewind/commands/review.ts +2 -1
  99. package/extensions/session-bus/rows.ts +7 -3
  100. package/extensions/session-bus/runtime.ts +100 -38
  101. package/extensions/session-bus/store.ts +14 -2
  102. package/extensions/session-bus/tui.ts +1 -1
  103. package/extensions/session-bus/types.ts +10 -1
  104. package/extensions/subagent/commands.ts +2 -1
  105. package/extensions/team/commands.ts +41 -8
  106. package/extensions/team/format.ts +14 -0
  107. package/extensions/team/index.ts +2 -1
  108. package/extensions/team/tools.ts +30 -9
  109. package/extensions/team/types.ts +17 -0
  110. package/extensions/todo/index.ts +4 -1
  111. package/extensions/todo/nudge.ts +8 -2
  112. package/extensions/wechat/bridge.ts +43 -172
  113. package/extensions/wechat/intake.ts +245 -0
  114. package/extensions/worktree/commands.ts +24 -17
  115. package/extensions/worktree/exit.ts +12 -6
  116. package/extensions/worktree/git.ts +22 -14
  117. package/extensions/worktree/guard.ts +15 -285
  118. package/extensions/worktree/handoff.ts +10 -2
  119. package/extensions/worktree/index.ts +2 -1
  120. package/extensions/worktree/lifecycle.ts +2 -0
  121. package/extensions/worktree/registry.ts +20 -11
  122. package/extensions/worktree/root-tools.ts +13 -3
  123. package/extensions/worktree/runtime.ts +8 -2
  124. package/extensions/worktree/screen.ts +15 -8
  125. package/extensions/worktree/shell.ts +303 -0
  126. package/extensions/worktree/state.ts +1 -1
  127. package/extensions/worktree/status.ts +42 -9
  128. package/extensions/worktree/tools.ts +19 -13
  129. package/lib/README.md +6 -5
  130. package/lib/board/card.ts +192 -3
  131. package/lib/board/links.ts +26 -2
  132. package/lib/board/page-pointer.ts +86 -32
  133. package/lib/board/round-text.ts +11 -2
  134. package/lib/board/round.ts +25 -20
  135. package/lib/board/rules.ts +144 -11
  136. package/lib/board/store.ts +239 -52
  137. package/lib/board/view.ts +94 -10
  138. package/lib/format.ts +22 -0
  139. package/lib/http/serve.ts +3 -0
  140. package/lib/http/sink.ts +34 -9
  141. package/lib/plan-entry.ts +11 -0
  142. package/lib/policy.ts +14 -1
  143. package/lib/session-roster.ts +74 -3
  144. package/lib/spawn.ts +37 -3
  145. package/lib/stray-fields.ts +45 -0
  146. package/lib/tool-names.ts +13 -0
  147. package/lib/tui/glyphs.ts +2 -3
  148. package/lib/web/README.md +52 -2
  149. package/lib/web/client-runtime.ts +123 -0
  150. package/lib/web/index.ts +4 -2
  151. package/lib/web/markup.ts +88 -1
  152. package/lib/web/names.ts +42 -0
  153. package/package.json +1 -1
  154. package/extensions/board/view/actions.ts +0 -306
  155. package/extensions/board/writes.ts +0 -99
  156. package/extensions/bytefly/panel/tui/extensions-view.ts +0 -40
  157. package/extensions/bytefly/panel/tui/skills-view.ts +0 -50
  158. package/extensions/job/process.ts +0 -35
@@ -17,8 +17,8 @@ Status records what a reader can rely on: **Shipped** is in a tagged release, **
17
17
  | Capability | How | What you get | Status |
18
18
  |---|---|---|---|
19
19
  | Startup header | `quietStartup` | One two-tone nameplate with the agent and chrome versions and the live help keys, in place of Pi's startup header and resource listing. | Shipped |
20
- | Two-line footer | every `tui` session | Location, branch, context, spend, and model on two lines that shed parts rather than clip. | Shipped |
21
- | Extension status chips | `footerData.getExtensionStatuses()` | Worktree, browse, plan, and alert statuses appear in the footer, using each extension's own colour. | Shipped |
20
+ | Two-line footer | every `tui` session | State on one line — location, modes, and alerts — and meters on the other — context, spend, tokens, cache, and model — each shedding parts rather than clipping. | Shipped |
21
+ | Extension status chips | `footerData.getExtensionStatuses()` | Worktree, browse, plan, and alert statuses appear in the footer, using each extension's own colour. A failed job is the one chip no width drops. | Shipped |
22
22
  | Priced session total | the `price` section of `bytefly.json` | The session's spend in yuan or dollars, with DeepSeek priced out of the box. | Shipped |
23
23
  | Delegated-run cost roll-up | the `↳` footer segment | A finished subagent's cost appears beside the session total, without merging the two ledgers. | Shipped |
24
24
  | User-message nameplate | every `tui` session | Each user message opens with a two-row `You` label and rule. | Shipped |
@@ -32,12 +32,12 @@ Status records what a reader can rely on: **Shipped** is in a tagged release, **
32
32
 
33
33
  | Capability | How | What you get | Status |
34
34
  |---|---|---|---|
35
- | Extension list | `/bytefly` | Every resolved extension with an enable checkbox, one health verdict, and its findings. | Shipped |
36
- | Settings list | `/bytefly settings` | The curated preferences with each value beside its label, editable in place. | Shipped |
35
+ | One frame, three sections | `/bytefly`, `/bytefly skills`, `/bytefly settings` | Extensions, skills, and settings in one dock: the worst problem in the header, each section's count on the tabs, the write scope at the right, and one body height for all three, so changing section never resizes the screen. | Shipped |
36
+ | Resource rows and the block | `Space`, `Enter`, `?` | A name-first row — `worktree`, not `worktree/index.ts` — with one checkbox, one status glyph and one verdict word, and the focused row's findings, path, source, state, and health beside the list at 96 columns and under it below. | Shipped |
37
+ | Grouped settings | `/bytefly settings` | Twelve preferences under `Startup`, `Notifications`, `Session`, and `Price`, each value in one aligned column and editable in place; `J` shows the stored JSON section. | Shipped |
37
38
  | Theme selection | the `Theme` row in `/bytefly settings` | `system` and the package's `bytefly-dark` and `bytefly-light` palettes are one selection; a reload applies it. | Shipped |
38
- | Skill list | `/bytefly skills` | Every resolved skill with the same checkbox and the description its `SKILL.md` declares. | Shipped |
39
- | Scoped writes | `Tab`, `Space`, `x` | A toggle lands in the personal or project `settings.json`, and `x` clears a project override. | Shipped |
40
- | Reload after changes | `/bytefly` after a pending write | The screen offers the reload that applies extension, skill, or setting changes instead of leaving the session to be restarted by hand. | Shipped |
39
+ | Filters and scope | `/`, `!`, `s`, `x` | Narrow the rows by text or to what needs attention — the chips on the tab row say which — and choose where a toggle lands, the personal or project `settings.json`, with `x` clearing a project override. | Shipped |
40
+ | Reload after changes | `R` | `unsaved changes` joins the header while a write is pending, and `R` reloads to apply it instead of leaving the session to be restarted by hand. | Shipped |
41
41
  | Health report | `run_doctor`, `/bytefly doctor` | Prerequisites, parse errors, and aged rate tables as text and typed data. | Shipped |
42
42
 
43
43
  ### Upkeep
@@ -45,7 +45,7 @@ Status records what a reader can rely on: **Shipped** is in a tagged release, **
45
45
  | Capability | How | What you get | Status |
46
46
  |---|---|---|---|
47
47
  | Currency and peak tariff | `price.currency.code`, `price.peak` | `CNY` and `USD` are separate pricing methods with published rates; DeepSeek's peak windows apply per token record. | Shipped |
48
- | Session budget alert | `price.budget` | One notice, and one OS notification, when the session total reaches the threshold. | Shipped |
48
+ | Session budget alert | `price.budget` | The session total shows as `spent/limit` in the footer, and one notice plus one OS notification fires when it reaches the threshold. | Partial |
49
49
  | Unpriced-record notice | `price.currency.code` | A `CNY` session names the records it could not price, and the two ways to fix it. | Shipped |
50
50
  | Desktop notifications | `notify.enabled`, `notify.turnFinished`, `notify.needsInput`, `notify.jobFinished`, `notify.matchFound` | An OS notification when a run settles, the agent needs you, a job finishes, or a watcher matches. | Shipped |
51
51
  | Proactive health check | `doctor.proactive` | At session start, when the startup header is on, a hard requirement is probed once and reported before the first turn. | Shipped |
@@ -97,12 +97,13 @@ One command runs the screen, prints the report, and re-checks updates:
97
97
  | `/bytefly settings` | Open the control center on Settings. Outside `tui` it prints one `Label: Value` line per setting. |
98
98
  | `/bytefly skills` | Open the control center on Skills. Outside `tui` it prints the grouped skills listing with one row per skill. |
99
99
  | `/bytefly doctor [extension] [refresh]` | Print the doctor report. Outside `tui` it prints `run_doctor`'s text for every extension, or one named extension's findings; in `tui` it mounts the same docked screen. |
100
+ | `/bytefly prices` | Name the models this session could not price: the `price.rates` key for each, the records behind it, and where to add it. It prices the session when asked, so it answers in every mode — the footer that carries the same fact exists only in `tui`. No rate is suggested. |
100
101
  | `/bytefly update` | Re-run the package update check now, ignoring the cache window, and report what it found — the updateable packages, this package's version and release URL when known, `PI_OFFLINE`, or an unreadable list. It resolves no packages and probes no extension. |
101
102
  | `/bytefly upgrade` | Install what the check found, behind a confirmation: Pi's own package manager updates every configured package, the marker is re-read, and one line reports the result and the reload it needs. `PI_OFFLINE` and a pinned npm version skip the work. It resolves no packages and mounts no screen. |
102
103
 
103
104
  Tab completes the subcommands, extension names after `doctor`, and `refresh`;
104
- `/bytefly settings`, `/bytefly skills`, `/bytefly update`, and `/bytefly upgrade`
105
- take no name.
105
+ `/bytefly settings`, `/bytefly skills`, `/bytefly prices`, `/bytefly update`, and
106
+ `/bytefly upgrade` take no name.
106
107
 
107
108
  ## Behaviour by mode
108
109
 
@@ -193,7 +194,7 @@ catch-all prices everything).
193
194
  | Section | Read at | Applies to |
194
195
  |---|---|---|
195
196
  | `chrome` | `session_start`, `session_tree` | the rail glyph |
196
- | `price` | the footer install (`session_start`, `session_tree`) | the cost total |
197
+ | `price` | the footer install (`session_start`, `session_tree`) | the cost total and the budget ceiling beside it |
197
198
  | `notify` | `session_start` | which events are announced, and how |
198
199
  | `doctor` | `session_start` | the proactive check |
199
200
 
@@ -231,7 +232,7 @@ resolve live from Pi's keybindings, so a rebound key shows the user's chord; the
231
232
  identity and help text are `meta` / `faint` so the mark keeps the eye. It opens
232
233
  with a blank row, so the nameplate clears the top edge. It is installed only in
233
234
  interactive (`tui`) sessions when Pi's `quietStartup` is `true` — the Settings
234
- row **Bytefly startup TUI** — and `false`, `"header"`, and unset leave Pi's
235
+ row **Startup screen** — and `false`, `"header"`, and unset leave Pi's
235
236
  built-in header in place; on terminals that cannot render half blocks the mark
236
237
  falls back to a two-tone `Pi` wordmark.
237
238
 
@@ -260,13 +261,15 @@ identity, and grouped help are tuned in
260
261
 
261
262
  Two lines, each with a left and right zone:
262
263
 
263
- - **Line 1 — identity:** `pwd` and the session name on the left; git state
264
- (`⎇ branch · ⑂ worktree`) and the `◉` browse chip on the right.
264
+ - **Line 1 — identity:** `pwd` and the session name on the left; on the right the
265
+ alert, then the mode chips (`≡ plan`), then `⎇ branch · ⑂ worktree`, then the
266
+ ports (`⊞ 5043 · ◉ 4909`) while a page is being served. The board page and the
267
+ file browser each publish one status key, and the bar reads both as a single
268
+ segment, so they share a weight, a position, and a floor.
265
269
  - **Line 2 — resources:** the context gauge and usage meters on the left, the
266
270
  child-run roll-up (`↳ 3 · ¥0.12`, present only once a subagent has finished),
267
- the mode/alert slot (`≡ plan`) trailing after a `│` when present, and the model
268
- and thinking level on the right. The gauge is anchored at column 0, so `%` is
269
- always in the same place.
271
+ and the model and thinking level on the right. The gauge is anchored at column
272
+ 0, so `%` is always in the same place.
270
273
  The roll-up is its own number, never folded into the session's cost: a subagent
271
274
  runs in its own process, and the footer prices those runs with the same rate
272
275
  table and currency as the session so a currency change moves both. It covers
@@ -274,8 +277,8 @@ Two lines, each with a left and right zone:
274
277
  `/resume` (the events are not session entries).
275
278
 
276
279
  ```
277
- ~/repo/my-pi-agent ⎇ main · ⑂ smoke
278
- ▰▰▰▰▰▰▱▱▱▱ 62%/200k · ¥0.31 · ↳ 2 · ¥0.06 · ↑42k ↓8.0k · R96k CH 87% │ ≡ plan opus-4.5 · high
280
+ ~/repo/my-pi-agent ≡ plan · ⎇ main · ⑂ smoke
281
+ ▰▰▰▰▰▰▱▱▱▱ 62%/200k · ¥0.31 · ↳ 2 · ¥0.06 · ↑42k ↓8.0k · R96k CH 87% opus-4.5 · high
279
282
  ```
280
283
 
281
284
  (The examples show the default `¥` symbol. A model that neither `rates` nor the
@@ -283,11 +286,19 @@ built-in table covers contributes nothing in `CNY` mode, so the bar shows no
283
286
  figure rather than a guessed one; `USD` mode adds Pi's recorded dollars for that
284
287
  record instead — see [Cost calculation](#cost-calculation).)
285
288
 
286
- With no plan mode there is no mode slot, so the `│` is omitted:
289
+ With no plan mode the mode slot is simply absent:
287
290
 
288
291
  ```
289
- ~/repo/my-pi-agent ⎇ main · ⑂ smoke
290
- ▰▰▰▰▰▰▱▱▱▱ 62%/200k · ¥0.31 · ↑42k ↓8.0k · R96k CH 87% opus-4.5 · high
292
+ ~/repo/my-pi-agent ⎇ main · ⑂ smoke
293
+ ▰▰▰▰▰▰▱▱▱▱ 62%/200k · ¥0.31 · ↑42k ↓8.0k · R96k CH 87% opus-4.5 · high
294
+ ```
295
+
296
+ While a page is served, its port joins the run beside the worktree — both pages as
297
+ one cluster, in a fixed order, so the pair reads as a pair:
298
+
299
+ ```
300
+ ~/repo/my-pi-agent ⎇ main · ⊞ 5043 · ◉ 4909
301
+ ▰▰▰▰▰▰▱▱▱▱ 62%/200k · ¥0.31 · ↑42k ↓8.0k · R96k CH 87% opus-4.5 · high
291
302
  ```
292
303
 
293
304
  As the terminal narrows, segments shrink and drop by priority rather than
@@ -295,20 +306,29 @@ clipping. The branch keeps its name and is dropped when the name no longer
295
306
  fits, rather than collapsing to a bare glyph:
296
307
 
297
308
  ```
298
- ~/repo/project ⎇ main · ⑂ smoke
299
- ▰▰▰▰▰▰▱▱▱▱ 62% · ¥0.31 │ ≡ plan opus-4.5 · high
309
+ ~/repo/my-pi-agent ≡ plan · ⎇ main · ⑂
310
+ ▰▰▰▰▰▰▱▱▱▱ 62%/200k · ¥0.31 opus-4.5 · high
300
311
  ```
301
312
 
302
313
  ```
303
- project ⎇ main
304
- 62% ≡ opus-4.5
314
+ ~/my-pi-agent ⎇ main
315
+ ▰▰▰▰▰▰▱▱▱▱ 62% opus-4.5
316
+ ```
317
+
318
+ A port is dropped before the pages it names are: the cluster compacts to
319
+ `⊞ ◉` once the digits no longer fit, so the narrowest bar that fits the location
320
+ chips still says a page is up.
321
+
322
+ ```
323
+ ~/repo/my-pi-agent ⎇ main · ⊞ ◉
324
+ ▰▰▰▰▰▰▱▱▱▱ 62% · ¥0.31 opus-4.5 · high
305
325
  ```
306
326
 
307
327
  With a named session, the name follows the path and truncates before it drops:
308
328
 
309
329
  ```
310
- ~/repo/project · feature-refac… ⎇ main
311
- ▰▰▰▰▰▰▱▱▱▱ 62% │ ≡ plan opus-4.5
330
+ ~/repo/project · [feature-refac…] ⎇ main
331
+ ▰▰▰▰▰▰▱▱▱▱ 62%/200k · ¥0.31 opus-4.5 · high
312
332
  ```
313
333
 
314
334
  Zero-value meters are omitted, so a fresh session shows only `?/window` instead
@@ -324,29 +344,36 @@ Each segment reads a theme role and carries its own drop order:
324
344
  |---|---|---|---|
325
345
  | pwd | 1 left | dim | last |
326
346
  | session | 1 left | muted, bracketed | after worktree |
327
- | browse | 1 right | success glyph + accent port | first |
328
347
  | branch | 1 right | muted (warning when detached) | last |
329
348
  | worktree | 1 right | success | first |
349
+ | ports `⊞ 5043 · ◉ 4909` | 1 right | success glyphs + accent ports | first, in one step |
330
350
  | context gauge + `%` | 2 left | muted ≤70 · warning 71–90 · error >90 | never |
331
351
  | `/window` | 2 left | dim | before cost |
332
352
  | cost | 2 left | muted | after tokens |
333
353
  | tokens `↑in ↓out` | 2 left | dim | after cache |
334
354
  | cache `R… CH …%` | 2 left | dim | first |
335
- | statuses | 2 left | as emitted by each extension | before the model clips |
355
+ | statuses | 1 right | as emitted by each extension | before branch |
336
356
  | model | 2 right | accent | never |
337
357
  | thinking level | 2 right | thinking token | before cost |
338
358
 
339
359
  The worktree status is read from the `worktree` status key, so the
340
- [worktree](./worktree.md) extension and the bar agree. The browse chip is read
341
- from the `browse` status key published by [file-browse](./file-browse.md) while
342
- its server runs, and shows the two-tone `◉ <port>` (green glyph, accent port).
343
- The trailing slot shows plan-style alerts when present and is omitted otherwise;
344
- a plan chip's trailing plan file name is dropped, so the slot reads `≡ plan`
345
- even though the built-in footer keeps the name. An alert (a failed job) sorts to
346
- the front of the slot. The slot trails the meters after a `│`, so the context
347
- gauge always starts the line. It keeps each extension's own colour when it
348
- compacts, and it is droppable: a tight bar drops the slot rather than truncating
349
- the model, which sits at the end of the line.
360
+ [worktree](./worktree.md) extension and the bar agree. The ports are the `board`
361
+ and `browse` status keys, published by [board](./board.md) and
362
+ [file-browse](./file-browse.md) while their pages are served. One segment reads
363
+ both, in a fixed order (board, then browse), each a two-tone `<glyph> <port>`
364
+ with a green glyph and an accent port. That is one fact — "a page is up on this
365
+ port" — so it gets one weight and one floor: the pair compacts from
366
+ `⊞ 5043 · ◉ 4909` to `⊞ 5043 ◉ 4909` to `⊞ ◉`, and it drops as a unit, where
367
+ separate chips once let the board port outlive the browse one by four columns
368
+ for no reason a reader could see.
369
+
370
+ The mode slot holds everything else an extension publishes: a plan chip, a
371
+ job count, a rewind timeline, the `✆` bridge, and an alert's front position. A
372
+ plan chip's trailing plan file name is dropped, so the slot reads `≡ plan` even
373
+ though the built-in footer keeps the name, and an alert (a failed job) sorts to
374
+ the front of it. It keeps each extension's own colour when it compacts, and it is
375
+ droppable: a tight bar drops the slot rather than truncating the model, which
376
+ sits at the end of the line.
350
377
 
351
378
  ## Cost calculation
352
379
 
@@ -407,14 +434,27 @@ turns it off. The check reads the total the footer already priced, and the
407
434
  footer exists only in `tui` sessions, so a print or JSON run has no budget
408
435
  alert; the footer's own numbers are visible in every interactive session.
409
436
 
410
- `CNY` has no fallback, so a record for a model with no `price.rates` entry
411
- contributes nothing to the total — with nothing on screen saying so. The footer
412
- counts those records on the pass it already prices, and one
413
- `bytefly · Records with no CNY rate` notice names the count and the two fixes:
414
- switch `price.currency.code` to `USD` (Pi's catalog is per model, tiers included)
415
- or add yuan `price.rates` entries. It fires once per session, on the run that
416
- settles, like the budget alert. `USD` never counts one: Pi's recorded amount
417
- covers every model.
437
+ A record nothing prices contributes nothing to the total. `CNY` has no fallback at
438
+ all; `USD` falls back to Pi's recorded amount, which is `0` for a model its catalog
439
+ does not know either. So "unpriced" means the same thing in both modes — no
440
+ `price.rates` key matched **and** nothing else priced it — and it is counted by
441
+ *model*, because a count says the total is short while a key says what to do:
442
+
443
+ - The footer's cost segment marks it: `¥1.23 · 2 unpriced`, dropping to the count
444
+ alone on a narrow bar. A session where nothing could be priced shows `2 unpriced`
445
+ beside no price at all, where it used to show nothing.
446
+ - `/bytefly prices` lists them — the key, the records behind it, and the line saying
447
+ where the keys go. It prices the session when asked, so it works in every mode,
448
+ including the ones with no footer.
449
+ - One `bytefly · Records with no rate` notice fires per session, on the run that
450
+ settles, naming up to three keys. The two fixes it offers are both honest: switch
451
+ `price.currency.code` to `USD` (Pi's catalog is per model, tiers included) or add
452
+ `price.rates` entries for those keys.
453
+
454
+ The key is the `price.rates` vocabulary, so it is copy-pasteable: `provider/model`,
455
+ `*` for a side the history does not name, and a model id containing a slash kept
456
+ whole (`openrouter/anthropic/claude-fable-5`). A genuinely free model — a local one
457
+ whose recorded cost is `0` — is listed too; adding its key costs nothing.
418
458
 
419
459
  For example, DeepSeek flash at `¥2` in and `¥8` out per million, with the
420
460
  published peak windows and `offPeakMultiplier: 0.5`, prices a 1M-in + 1M-out
@@ -621,39 +661,122 @@ in [`working/config.ts`](../../extensions/bytefly/working/config.ts).
621
661
 
622
662
  ## The control center
623
663
 
624
- <img src="../../assets/screens/bytefly.png" alt="The /bytefly control center: the Extensions, Skills, and Settings section bar with the `Global · 10 extensions · 2 off · 1 missing` counts, one checkbox row per extension with its verdict, the focused extension's report, and the key hints" width="100%">
664
+ <img src="../../assets/screens/bytefly.png" alt="The /bytefly control center at 120 columns: a `Bytefly` rule with `1 missing` at its right edge, the `Extensions 9 · Skills 0 · Settings 12` tabs with `writes Global` at the right, one name-first row per extension with its checkbox and verdict, and the focused extension's block beside the list — its findings and their fix, Path, Source, State, and Health" width="100%">
625
665
 
626
666
  `/bytefly [extension] [refresh]` opens the control center on **Extensions**: a
627
- keyboard-driven dock list of every extension Pi resolves for the session, so the
628
- list matches what actually loads. `←`/`→` move to **Skills** and then
629
- **Settings**, one section per press and stopping at the ends, where the same
630
- frame lists every resolved skill and the curated preferences. `/bytefly skills`
631
- and `/bytefly settings` open their section directly. It merges the former
632
- extensions picker and doctor report into one row per extension: the picker's
633
- enable checkbox and label, then one verdict — `off` when the row is switched off
634
- in settings, `env disabled` when `PI_DISABLED_EXTENSIONS` excludes it, otherwise
635
- the doctor's worst-severity glyph and summary. The screen is drawn from the
636
- design system: a label-first `Bytefly ────` title rule,
637
- the `Extensions · Skills · Settings` section bar beside the section's counts, one
638
- row grammar (`❯ ◼ name … summary`), the shared `◼` / `◻` checkbox, and the closing
639
- rule under the key hints that Pi's own list screens draw.
640
- `/bytefly doctor [extension] [refresh]` selects the doctor report, the same text
641
- the `run_doctor` tool returns; in `tui` it opens the same screen. `[extension]`
642
- narrows the Extensions section to one extension and `refresh` forces the first
643
- probe. Tab completes the subcommands, extension names after `doctor`, and
644
- `refresh`; `/bytefly settings` and `/bytefly skills` take no name.
667
+ keyboard-driven dock of every resource Pi resolves for the session, so the list
668
+ matches what actually loads. `/bytefly skills` and `/bytefly settings` open the
669
+ other two sections directly, and `←`/`→` (or `h`/`l`, or `Tab`) change section,
670
+ wrapping at the ends because the tabs read as a ring. `/bytefly <extension>`
671
+ narrows the list to one extension, and `refresh` forces the first probe. One
672
+ frame draws all three sections.
645
673
 
646
674
  The screen opens on the findings the session already has, and the Extensions
647
675
  section probes once on its first view (the hint shows `refreshing…` while it
648
676
  runs). `/bytefly settings` therefore never waits for a probe, and a failed probe
649
677
  is a notice with the screen still open rather than a refused command. A refusal
650
- or a failed write appears in the screen's own hint row (the key hints step
651
- aside) and clears on the next key, so nothing is written behind the screen.
678
+ or a failed write appears in the screen's own hint row — the key hints step
679
+ aside — and clears on the next key, so nothing is written behind the screen.
652
680
 
653
681
  The screen reuses Pi's own settings and package resolution (`SettingsManager`
654
682
  plus `DefaultPackageManager` with `onMissing: "skip"`), so opening it never
655
683
  installs or fetches a package.
656
684
 
685
+ **One frame.** The `Bytefly ────` header rule carries the screen's attention at
686
+ its right edge — `1 missing` or `2 degraded` in the severity's own colour, else
687
+ `2 off`, else `all clear`, with `unsaved changes` after it while a write is
688
+ pending. The tab row below names every section with the rows it holds,
689
+ `Extensions 9 · Skills 0 · Settings 12`, the active tab in the accent and the
690
+ counts faint, then any filter chips (`/git`, `needs attention`, `changed only`),
691
+ with `writes Global` (or `writes Project`) at the right edge. The scope is a word
692
+ there because no count line has to carry it any more: the counts are on the tabs,
693
+ the problems are in the header, and the scope is at the right.
694
+
695
+ The body is padded to the tallest section's row count, group headings counted, so
696
+ all three sections draw the same height and the dock does not resize when the
697
+ tabs change. A shorter section pads its last rows with blanks; a longer one
698
+ scrolls inside the same height and puts `showing 3–14 of 19` on its last body
699
+ row. The height is measured from both resource lists and the visible scope's
700
+ settings rows, so changing tab does not resize the dock: only a resize of the
701
+ terminal moves the hint row, or the Project scope's one-line note on a terminal
702
+ too short to hold both.
703
+
704
+ **Two panes.** At 96 columns and wider the focused row's block sits in a side
705
+ column after a `╎` divider: the row's name and verdict; the doctor's findings,
706
+ under a `── findings ──` rule and each with its fix; `Path`, `Source`, and
707
+ `State` for a resolved resource; `Health` when the row has a report; and
708
+ `Saved · reload to apply` once something is pending. Below 96 columns the same
709
+ block is drawn under the list in three rows, clipped, ending with
710
+ `… N more lines` when it has more to say; it leaves out what the row above
711
+ already says — the name and verdict on a resource row, the label and value on a
712
+ settings row, and the path when there are findings to show instead.
713
+
714
+ The picture above is 120 columns wide; the two frames below are the same screen
715
+ at 100 and 80.
716
+
717
+ ```
718
+ Bytefly ────────────────────────────────────────────────────────────────────────────────── 1 missing
719
+ Extensions 9 · Skills 0 · Settings 12 writes Global
720
+
721
+ npm:pkg (user) (9) ╎ worktree 1 missing
722
+ ◼ file-read ╎ ── findings ────────────────────────────
723
+ ◼ job ╎ git: not found on PATH — worktree
724
+ ◼ memory ╎ isolation
725
+ ◼ plan ╎ → install git and make sure it is on
726
+ ◼ subagent ╎ PATH (https://git-scm.com/downloads).
727
+ ◼ todo ╎ symlink: the host cannot create symlinks
728
+ ◻ ∅ web-access off ╎ → copy the included files by hand.
729
+ ◻ ∅ wechat off ╎ Path /pkg/extensions/worktree/ind…
730
+ ❯ ◼ ✕ worktree 1 missing ╎ Source npm:pkg (user)
731
+ Not resolved (1) ╎ State enabled
732
+ bytefly ok ╎ Health 1 missing
733
+ ╎
734
+ ╎
735
+ ╎
736
+ ╎
737
+
738
+ Esc close · ←/→ section · Space toggle · Enter detail · / filter · ? keys · r refresh · s scope
739
+ ────────────────────────────────────────────────────────────────────────────────────────────────────
740
+ ```
741
+
742
+ ```
743
+ Bytefly ────────────────────────────────────────────────────────────── 1 missing
744
+ Extensions 9 · Skills 0 · Settings 12 writes Global
745
+
746
+ npm:pkg (user) (9)
747
+ ◼ file-read
748
+ ◼ job
749
+ ◼ memory
750
+ ◼ plan
751
+ ◼ subagent
752
+ ◼ todo
753
+ ◻ ∅ web-access off
754
+ ◻ ∅ wechat off
755
+ ❯ ◼ ✕ worktree 1 missing
756
+ Not resolved (1)
757
+ bytefly ok
758
+
759
+ git: not found on PATH — worktree isolation
760
+ → install git and make sure it is on PATH (https://git-scm.com/downloads).
761
+ … 2 more lines
762
+
763
+ Esc close · ←/→ section · Space toggle · Enter detail · / filter · ? keys
764
+ ────────────────────────────────────────────────────────────────────────────────
765
+ ```
766
+
767
+ At 80 columns the block's three rows clip to the first finding and its fix and
768
+ say how many lines are left out, and the hint row has already shed `r refresh`
769
+ and `s scope` from its right edge.
770
+
771
+ **Rows.** A row is named for the resource, not for its file: `worktree`, not
772
+ `worktree/index.ts`. The picker's `folder/file.ts` label survives only where two
773
+ resources share a directory name, because the two rows would otherwise read
774
+ alike. What is left is one checkbox, one status glyph and one verdict word —
775
+ `off`, `1 missing`, `2 degraded`, `env disabled`, `not loaded` for a skill Pi
776
+ refused, `hidden` for one its frontmatter keeps from the model — and a clean row
777
+ carries no glyph and no word at all, so the column reads as one shape and the eye
778
+ finds the rows that need it.
779
+
657
780
  - Lists every resolved extension, grouped by source (each package, user
658
781
  settings, project settings), sorted packages-first and then by display name.
659
782
  - Omits `bytefly`'s own entrypoint from the picker groups, so the tool that
@@ -664,11 +787,10 @@ installs or fetches a package.
664
787
  glyph (`!` degraded, `✕` missing) and its summary. A row that is `◻` reads
665
788
  `off` instead, because it will not load; a row the session's
666
789
  `PI_DISABLED_EXTENSIONS` excludes reads `env disabled`, so the two axes cannot
667
- contradict each other. The glyph marks what needs attention, so a row with
668
- nothing to report — a clean report or none at all — leaves the glyph column
790
+ contradict each other. A row with nothing to report leaves the glyph column
669
791
  blank and says `ok` in words when a report exists.
670
792
  - Toggles the focused extension with `Space`, writing `+pattern` / `-pattern`
671
- into `extensions` (and package filter objects) in the active scope. A row in
793
+ into `extensions` (and package filter objects) in the write scope. A row in
672
794
  the trailing `Not resolved` group has no pattern to write, so `Space` says so
673
795
  instead of doing nothing.
674
796
  - Clears the focused extension's project override with `x` in Project scope: the
@@ -677,30 +799,37 @@ installs or fetches a package.
677
799
  created), and the row returns to its inherited state. A row with no override
678
800
  says so instead of writing, and Global scope does not offer the key — the
679
801
  personal file is the base layer.
680
- - Switches the write scope with `Tab`: **Global** (personal `settings.json`) or
681
- **Project** (`.pi/settings.json`, only when the project is trusted; an
682
- untrusted project refuses with a notice).
683
802
  - Expands or collapses the focused extension's findings with `Enter` — per row,
684
- so another row stays as it was — and grows the pane to the free terminal
685
- height while expanded. Re-probes with `r`; while a probe is in flight the hint
686
- shows `refreshing…` and a second `r` is ignored, so probes cannot stack.
803
+ so another row stays as it was — and gives the block the screen's whole body
804
+ height while it is open, so its findings are not clipped to three rows.
805
+ - Re-probes with `r`; while a probe is in flight the hint shows `refreshing…` and
806
+ a second `r` is ignored, so probes cannot stack.
687
807
  - Checks an `<extension>` name against the discovered set before probing, so a
688
808
  typo fails immediately instead of waiting for every prerequisite check.
689
- - Offers a reload after pending changes, so the new selection takes effect
690
- without leaving the session.
809
+ - Reloads with `R`, so a new selection takes effect without leaving the session.
810
+
811
+ **Filters.** `/` opens an editor seeded with the current query. The query applies
812
+ as it is typed — a filter that only took effect on `Enter` would make the reader
813
+ wait to see it work — and matches a row's name, path, and source (Settings: its
814
+ label, description, and value). `Esc` clears it rather than leaving a hidden
815
+ narrowing behind. `!` keeps only the rows worth acting on: a resource row that
816
+ needs attention, or on Settings a value that differs from its default. The chips
817
+ on the tab row say which is on, and an emptied section says
818
+ `Nothing matches the filter.`
691
819
 
692
820
  **Skills.** The second section is the same list over Pi's skills: one row per
693
821
  skill, grouped by source and sorted by name, labelled with the name its
694
822
  `SKILL.md` declares (or the folder name when it declares none). `Space` writes
695
823
  `+pattern` / `-pattern` into `skills` — or a package's `skills` filter — in the
696
- active scope, and `Tab` and `x` behave as they do for extensions. A row's verdict
697
- comes from the skill's own file: `off` when it is disabled, `hidden` when
698
- frontmatter sets `disable-model-invocation` so only `/skill:<name>` loads it, and
699
- `not loaded` with Pi's reason when the file was refused. The detail pane carries
700
- the resolved path and the declared description, one line until `Enter` expands
701
- it. There is nothing to probe, so `r` is not offered here; the metadata is read
702
- once when the section first appears, so an edited `SKILL.md` shows on the next
703
- open.
824
+ write scope, and `x` behaves as it does for extensions. A row's verdict comes
825
+ from the skill's own file: `off` when it is disabled, `hidden` when frontmatter
826
+ sets `disable-model-invocation` so only `/skill:<name>` loads it, and
827
+ `not loaded` with Pi's reason when the file was refused. The block carries
828
+ `Path`, `Call` (`/skill:<name>`), `Model` when the skill is hidden from the
829
+ model, `Problem` when Pi refused the file, and the declared description — one
830
+ line until `Enter` expands it. There is nothing to probe, so `r` is not offered
831
+ here; the metadata is read once when the section first appears, so an edited
832
+ `SKILL.md` shows on the next open.
704
833
 
705
834
  Themes and prompts have no section of their own, by decision rather than by
706
835
  omission: a theme is one active selection — a scalar, global setting — and a
@@ -708,53 +837,40 @@ prompt template is a file Pi discovers from a directory, so neither is a scoped
708
837
  enable list this screen's checkbox rows can write. Themes stay with `pi config`;
709
838
  `ROADMAP.md` carries the reason under *Deliberately left thin*.
710
839
 
711
- **Settings.** The third section uses the same frame and the same `Tab` scope
712
- switch, with its own cursor. It is the shortlist a session changes: twelve
713
- preferences on one flat line each, the value beside its label, and no group
714
- headings. The whole list fits a 24-row terminal, and the frame stops growing
715
- there however tall the terminal is.
716
-
717
- ```
718
- Bytefly ──────────────────────────────────────────────────────────────
719
- Extensions · Skills · Settings Global · 12 settings
720
-
721
- ❯ ◻ Bytefly startup TUI Off
722
- ◼ Theme system
723
- ◻ Desktop notifications Off
724
- ◼ Turn finished On
725
- ◼ Needs input On
726
- ◼ Job finished On
727
- ◼ Watcher matched On
728
- ◻ Wake when a job finishes Off
729
- ◼ Automatic snapshots On
730
- ◼ Proactive health check On
731
- Currency CNY (¥)
732
- Session budget 0
733
-
734
- Bytefly startup TUI — Pi setting
735
- Use bytefly's startup header in place of Pi's startup TUI.
736
- Default · ~/.pi/agent/settings.json
737
-
738
- Esc close · ←/→ · Space change · Tab scope · u reset · J JSON · ↑/↓ select
739
- ──────────────────────────────────────────────────────────────────────
740
- ```
741
-
742
- <img src="../../assets/screens/bytefly-settings.png" alt="The /bytefly Settings section: one flat list of twelve preferences with each value beside its label, the `Global · 12 settings` counts, and a three-row detail pane naming the focused row's source, description, and file" width="100%">
743
-
744
- | Row | Source | Values |
745
- |---|---|---|
746
- | Bytefly startup TUI | Pi `settings.json` (`quietStartup`) | Off, On |
747
- | Theme | Pi `settings.json` (`theme`) | `system`, `bytefly-dark`, `bytefly-light` |
748
- | Desktop notifications | `notify.enabled` | Off, On |
749
- | Turn finished | `notify.turnFinished` | Off, On |
750
- | Needs input | `notify.needsInput` | Off, On |
751
- | Job finished | `notify.jobFinished` | Off, On |
752
- | Watcher matched | `notify.matchFound` | Off, On |
753
- | Wake when a job finishes | `job.wakeOnFinish` | Off, On |
754
- | Automatic snapshots | `rewind.autoSnapshots` | Off, On |
755
- | Proactive health check | `doctor.proactive` | Off, On |
756
- | Currency | `price.currency.code` | CNY, USD |
757
- | Session budget | `price.budget` | Number, `0` = off |
840
+ **Settings.** The third section is the same frame over the curated preferences,
841
+ grouped by what they do rather than by which file holds them: `Startup (2)`,
842
+ `Notifications (5)`, `Session (3)`, and `Price (2)`. It is the shortlist a
843
+ session changes: twelve preferences, each with its value in one aligned column
844
+ instead of beside its label, so a column of values can be read down. Sixteen
845
+ entries — twelve rows and their four headings — fit a 24-row terminal when the
846
+ block sits beside the list; on a narrower one the list scrolls and the block
847
+ keeps its three rows. The frame never grows past the tallest section, however
848
+ tall the terminal is. In Project scope the two app rows drop out and the note
849
+ `App settings are Global only.` stands above the list.
850
+
851
+ The block carries `Now` with the value, `About` with the preference's own
852
+ description, and `Stored in <origin> · <file>` — `Global`, `Project`,
853
+ `Inherited from Global`, or `Default`, with the file that supplied the value
854
+ (`~/.pi/agent/settings.json` for an app row, the active scope's `bytefly.json`
855
+ otherwise). `J` on a `bytefly.json` row appends that section as the active scope
856
+ stores it, under a `── notify ──` rule.
857
+
858
+ <img src="../../assets/screens/bytefly-settings.png" alt="The /bytefly Settings section at 120 columns: the `Startup (2)`, `Notifications (5)`, `Session (3)`, and `Price (2)` groups with each value in one aligned column, the `Extensions 9 · Skills 0 · Settings 12` tabs with `writes Global` at the right, and the focused row's block beside the list — its value, description, and the file the value is stored in" width="100%">
859
+
860
+ | Group | Row | Source | Values |
861
+ |---|---|---|---|
862
+ | Startup | Startup screen | Pi `settings.json` (`quietStartup`) | Off, On |
863
+ | Startup | Theme | Pi `settings.json` (`theme`) | `system`, `bytefly-dark`, `bytefly-light` |
864
+ | Notifications | Desktop notifications | `notify.enabled` | Off, On |
865
+ | Notifications | When a turn ends | `notify.turnFinished` | Off, On |
866
+ | Notifications | When the agent needs you | `notify.needsInput` | Off, On |
867
+ | Notifications | When a job ends | `notify.jobFinished` | Off, On |
868
+ | Notifications | When a watcher matches | `notify.matchFound` | Off, On |
869
+ | Session | Resume after a job | `job.wakeOnFinish` | Off, On |
870
+ | Session | Snapshot before a turn | `rewind.autoSnapshots` | Off, On |
871
+ | Session | Health check at startup | `doctor.proactive` | Off, On |
872
+ | Price | Currency | `price.currency.code` | `CNY (¥)`, `USD ($)` |
873
+ | Price | Session budget | `price.budget` | `Off`, or the amount as `¥5.00` |
758
874
 
759
875
  Choosing **Currency** also writes that currency's default precision
760
876
  (`price.currency.decimals`). Both stay editable in `bytefly.json` afterwards, so
@@ -771,20 +887,16 @@ and is replaced by the next choice here, so manage those in Pi's `/settings` →
771
887
 
772
888
  `Space` (or `Enter`) changes the focused row: a boolean toggles, an enum cycles
773
889
  forward, and the one text row — **Session budget** — opens an inline editor on its
774
- amount. `u` deletes the focused row's key so its default applies again (`Already
775
- at its default.` when nothing is stored in that scope; an inherited Project value
776
- says so instead), and `J` swaps the detail pane for the focused row's whole
777
- section as the active scope stores it — the only view of the keys this screen
778
- leaves to JSON. The detail pane names the source, describes the preference, and
779
- shows where the value comes from — `Global · ~/.pi/agent/bytefly.json`, `Project ·
780
- .pi/bytefly.json`, `Inherited from Global · ~/.pi/agent/bytefly.json`, or
781
- `Default · <the active scope's file>`; an app row names the personal
782
- `settings.json`. A successful change marks the screen `Saved · reload to apply`
783
- — even when the terminal is too short for the detail body — and closing offers
784
- the same reload confirm as an extension or skill toggle.
890
+ amount. `u` deletes the focused row's key so its default applies again (`<row>
891
+ is already at its default.` when nothing is stored in that scope; an inherited
892
+ Project value says so instead, and an app row answers that Pi has no unset), and
893
+ `J` appends the focused row's whole section as the active scope stores it — the
894
+ only view of the keys this screen leaves to JSON. A successful change marks the
895
+ block `Saved · reload to apply` and puts `unsaved changes` in the header, and
896
+ closing offers the same reload confirm as an extension or skill toggle.
785
897
 
786
898
  The list is capped at twelve rows so the screen keeps fitting a 24-row
787
- terminal: adding a setting means removing one (`panel/panel.test.ts` holds the
899
+ terminal: adding a setting means removing one (`panel/settings.test.ts` holds the
788
900
  ceiling). Everything else stays in `bytefly.json` — the tuning numbers, the
789
901
  cosmetic labels, and the Pi preferences Pi's own `/settings` owns — and
790
902
  `test/settings-coverage.test.ts` keeps the split honest: every loader key is
@@ -800,11 +912,11 @@ screen cannot import their normalizers, so it reports the leaf as
800
912
  the file states it and declares the loader's default; the same test holds the
801
913
  catalogue to every loader's defaults and bounds in both directions.
802
914
 
803
- **Bytefly startup TUI** is the one row whose label differs from the Pi key it
804
- writes: On writes `"quietStartup": true` (bytefly's header replaces Pi's startup
805
- detail), and Off writes `false` (Pi's built-in header stays). A hand-set
806
- `"header"` reads Off, because it keeps Pi's header; the next toggle replaces it
807
- with `true` or `false`.
915
+ **Startup screen** is the one row whose label differs from the Pi key it writes:
916
+ On writes `"quietStartup": true` (bytefly's header replaces Pi's startup detail),
917
+ and Off writes `false` (Pi's built-in header stays). A hand-set `"header"` reads
918
+ Off, because it keeps Pi's header; the next toggle replaces it with `true` or
919
+ `false`.
808
920
 
809
921
  App rows are **Global only**: Pi's setters for them write the personal
810
922
  `settings.json`, and only resource paths have public project setters. Project
@@ -821,18 +933,18 @@ screen order:
821
933
 
822
934
  ```
823
935
  bytefly: Settings · Global · 12 settings
824
- Bytefly startup TUI: Off
936
+ Startup screen: Off
825
937
  Theme: bytefly-dark
826
938
  Desktop notifications: On
827
- Turn finished: On
828
- Needs input: On
829
- Job finished: On
830
- Watcher matched: On
831
- Wake when a job finishes: On
832
- Automatic snapshots: On
833
- Proactive health check: On
939
+ When a turn ends: On
940
+ When the agent needs you: On
941
+ When a job ends: On
942
+ When a watcher matches: On
943
+ Resume after a job: On
944
+ Snapshot before a turn: On
945
+ Health check at startup: On
834
946
  Currency: CNY (¥)
835
- Session budget: 5
947
+ Session budget: ¥5.00
836
948
  ```
837
949
 
838
950
  The doctor side probes every declared extension and reports each check as `ok`,
@@ -865,27 +977,38 @@ headline; a named extension prints its full findings instead.
865
977
 
866
978
  ### Keys
867
979
 
980
+ One table in [`panel/tui/keys.ts`](../../extensions/bytefly/panel/tui/keys.ts)
981
+ drives the bindings, the hint row, and the `?` key list, so a key cannot drift
982
+ from its hint or its help line. The hint row is per section and drops whole hints
983
+ from the right as the width runs out, so `Esc` and the section keys survive on a
984
+ narrow terminal; the Settings section's row is the shorter one.
985
+
868
986
  | Key | Effect |
869
987
  |---|---|
870
- | `←` / `→` | Show the previous or next section — Extensions, Skills, Settings; the ends stop. |
871
- | `Space` | Toggle the focused extension or skill; change the focused setting, or open the editor on a text row. |
872
- | `x` | Clear the focused resource's project override (Extensions and Skills, Project scope). |
873
- | `Enter` | Expand or collapse the focused extension's findings, or the focused skill's description; change the focused setting, or open the editor on a text row (Settings). |
988
+ | `Esc` (`Ctrl+C`) | Close the screen. |
989
+ | `←` / `→`, `h` / `l`, `Tab` / `Shift+Tab` | Previous or next section — Extensions, Skills, Settings — wrapping at the ends. |
990
+ | `↑` / `↓`, `j` / `k` | Move to the previous or next row. |
991
+ | `PgUp` / `PgDn` | Move a page. |
992
+ | `Home` / `End`, `g` / `G` | Jump to the first or last row. |
993
+ | `[` / `]` | Jump to the previous or next group; a group heading is never the focus. |
994
+ | `Space` | Toggle the focused extension or skill; change the focused value (Settings). |
995
+ | `Enter` | Expand or collapse the focused row's findings, or a skill's description; change the focused value, or open the editor on a text row (Settings). |
996
+ | `/` | Filter the rows by text; the query applies as it is typed and `Esc` clears it. |
997
+ | `!` | Show only the rows that need attention; on Settings, only values changed from their default. |
998
+ | `s` | Switch where a change is written: Global or Project. |
999
+ | `x` | Drop the focused resource's Project override, so Global applies again (Extensions and Skills, Project scope only). |
1000
+ | `r` | Re-probe every extension's requirements (Extensions only); a second `r` while one is in flight is ignored. |
1001
+ | `R` | Reload now, to apply what you changed; with nothing pending it says so instead. |
874
1002
  | `u` | Delete the focused setting's key so its default applies again (Settings, `bytefly.json` rows). |
875
- | `J` | Show or hide the focused row's whole section as stored (Settings, `bytefly.json` rows). |
876
- | `[` / `]` | Jump to the previous or next group (Extensions and Skills). |
877
- | `←` / `→`, `Home` / `End` (`Ctrl+A` / `Ctrl+E`) | Move the editor's caret while a text row is being edited. |
1003
+ | `J` | Show or hide the focused row's whole `bytefly.json` section as stored (Settings). |
1004
+ | `?` | Show the key list in the focused row's block; `?` again hides it. |
1005
+ | `←` / `→`, `Home` / `End` (`Ctrl+A` / `Ctrl+E`) | Move the editor's caret while a text row or the filter is being edited. |
878
1006
  | `Backspace` / `Delete`, `Ctrl+U` | Edit the buffer: delete a character, or clear it. |
879
1007
  | `Enter` / `Esc` (editing) | Save the buffer, or cancel and restore the stored value. |
880
- | `Tab` | Switch between Global and Project scope, in any section. |
881
- | `r` | Re-probe the doctor (Extensions). |
882
- | `↑`/`↓`, `j`/`k` | Move the selection, in any section. |
883
- | `PgUp`/`PgDn` | Move a page, in any section. |
884
- | `g` / `G`, `Home` / `End` | Jump to the first or last row, in any section. |
885
- | `Esc` / `Ctrl+C` | Close the screen. |
886
1008
 
887
- Mouse wheel scrolling and clicking a row to focus it work; every action also
888
- has a keyboard path.
1009
+ `Esc` clears an open filter or editor before it closes the screen, so nothing
1010
+ typed is left narrowing the list or half-written. Mouse wheel scrolling and
1011
+ clicking a row to focus it work; every action also has a keyboard path.
889
1012
 
890
1013
  ### What is checked
891
1014
 
@@ -1059,12 +1182,12 @@ why the line says so, and each source the manager reports is shown while it runs
1059
1182
 
1060
1183
  ## Limitations
1061
1184
 
1062
- - **Bytefly startup TUI is the startup surface only.** It installs or removes
1185
+ - **The Startup screen row is the startup surface only.** It installs or removes
1063
1186
  bytefly's startup header and the proactive doctor notice; the footer, turn
1064
1187
  rule, and transcript renderers stay on. Use `PI_DISABLED_EXTENSIONS=bytefly`
1065
1188
  (or `pi config`) to turn off all of bytefly.
1066
1189
  - **App settings are Global only.** Pi's setters for the two app preferences
1067
- the screen carries — `quietStartup` (the **Bytefly startup TUI** row) and
1190
+ the screen carries — `quietStartup` (the **Startup screen** row) and
1068
1191
  `theme` — write the personal `settings.json`; the screen hides those rows in
1069
1192
  Project scope rather than writing a file Pi cannot manage.
1070
1193
  - **The Theme row offers only this package's palettes.** `system`,
@@ -1139,10 +1262,11 @@ why the line says so, and each source the manager reports is shown while it runs
1139
1262
  Anthropic's 2× one-hour cache write are not modelled; switch to `USD` mode
1140
1263
  to use Pi's recorded cost for those models.
1141
1264
  - **No FX conversion.** The two modes are separate methods; neither converts
1142
- one currency into the other. A model is priced only by the selected method. A
1143
- `CNY` session names the records it could not price instead of silently leaving
1144
- them out — switch to `USD` for Pi's per-model cost, or add yuan `price.rates`
1145
- entries for the models you use.
1265
+ one currency into the other. A model is priced only by the selected method, and
1266
+ a model neither method can price is named — by key, in the footer, in
1267
+ `/bytefly prices`, and in one notice — instead of being quietly left out. What
1268
+ is *not* built is a table for another provider: rates are sourced data, and
1269
+ `price.rates` is where they go.
1146
1270
  - **Two built-in footer indicators cannot be reproduced.** Pi's `(auto)` marks
1147
1271
  that auto-compaction is on, and its `(sub)` marks a subscription-backed
1148
1272
  provider. Auto-compaction is session state on Pi's own session object, and
@@ -1218,7 +1342,7 @@ rather than showing a wrong value.
1218
1342
 
1219
1343
  | Integration point | Details |
1220
1344
  |---|---|
1221
- | Header | `ctx.ui.setHeader` with a custom component, replacing Pi's built-in header; installed only when `ctx.mode === "tui"` and `pi.getSettings().quietStartup === true` (the **Bytefly startup TUI** row). `false`, `"header"`, and unset call `setHeader(undefined)`, leaving Pi's built-in header in place. The nameplate insets by Pi's `outputPad` (read through `pi.getSettings()`), so it sits on the transcript margin, and the help hangs one body indent inside it. The mark and text are drawn from the active theme's tokens, the help keys resolve live through `keyText`, and the mark falls back to a wordmark on Apple Terminal (`isAppleTerminalSession`), mirroring Pi's built-in. The factory reads `tui.terminal.rows` on every render, so the height ladder in `header/config.ts` sheds help groups and then the leading blank row as the terminal shortens (unknown height keeps the full header). The chrome version gains `↑<latest>` from the `UpdateSource`, the agent line gains Pi's own `↑<latest>` from the second store, and `registerHeader` returns a `refresh()` handle that re-installs the last context's header when either value arrives. |
1345
+ | Header | `ctx.ui.setHeader` with a custom component, replacing Pi's built-in header; installed only when `ctx.mode === "tui"` and `pi.getSettings().quietStartup === true` (the **Startup screen** row). `false`, `"header"`, and unset call `setHeader(undefined)`, leaving Pi's built-in header in place. The nameplate insets by Pi's `outputPad` (read through `pi.getSettings()`), so it sits on the transcript margin, and the help hangs one body indent inside it. The mark and text are drawn from the active theme's tokens, the help keys resolve live through `keyText`, and the mark falls back to a wordmark on Apple Terminal (`isAppleTerminalSession`), mirroring Pi's built-in. The factory reads `tui.terminal.rows` on every render, so the height ladder in `header/config.ts` sheds help groups and then the leading blank row as the terminal shortens (unknown height keeps the full header). The chrome version gains `↑<latest>` from the `UpdateSource`, the agent line gains Pi's own `↑<latest>` from the second store, and `registerHeader` returns a `refresh()` handle that re-installs the last context's header when either value arrives. |
1222
1346
  | Footer | `ctx.ui.setFooter` with a custom component, replacing Pi's built-in footer; installed only when `ctx.mode === "tui"`. |
1223
1347
  | User message | `pi.registerMarkdownTransformer` puts a two-row `You` nameplate (the label over a full-width rule) above each user message; one padded level-2 heading carries the accent through `mdHeading` for both rows, and the shipped themes set `userMessageBg` to the terminal default, so the block has no fill. Pi renders the result as Markdown, so the surface adds no component or color of its own. |
1224
1348
  | Notice | `registerMessageRenderer(NOTICE_TYPE)` draws every shared transcript notice at the margin from its `details`; the headline stays at the margin with its title painted by the notice's own severity, and its rows hang from the transcript rail, painted by the notice's worst severity, whose glyph comes from the shared `RailSource`. `job` and `watch` post one when their work settles, and `doctor` posts one at `session_start` for a missing hard requirement. The type and shape come from `lib/notice.ts`. A message without `details` falls back to Pi's default custom-message box. |
@@ -1318,9 +1442,9 @@ rather than showing a wrong value.
1318
1442
  entry renderer none and adds its component raw, so bytefly's blocks inset
1319
1443
  themselves by that pad: the tool frame and the notice take it from Pi, and the
1320
1444
  turn separator reads the last value a sibling observed (`pad.ts`). Pi's setting
1321
- is the only knob — bytefly adds no padding of its own. The footer's group
1322
- separator draws `│` between chips — a divider, not the rail (`╎`), which stays
1323
- a transcript-only glyph.
1445
+ is the only knob — bytefly adds no padding of its own. The footer draws no
1446
+ rule of its own: it joins its chips with the shared ` · `, and the rail (`╎`)
1447
+ stays a transcript-only glyph.
1324
1448
  - **The header is a nameplate, not a dashboard.** Pi's built-in brand colors are
1325
1449
  fixed, so the mark ignores the active theme; bytefly's header draws a two-tone
1326
1450
  mark from the `accent`/`warn` roles, a signature rule whose accent lead sits
@@ -1332,24 +1456,41 @@ rather than showing a wrong value.
1332
1456
  terminal: the height ladder drops whole help groups, then the leading blank
1333
1457
  row, before the nameplate gives up anything, so a short pane spends its rows
1334
1458
  on the transcript instead of chrome.
1335
- - **Two lines, four zones.** Line 1 is identity (`pwd` left, branch + worktree
1336
- right); line 2 is resources (meters left, model + level right). This keeps
1337
- "where am I / what state" separate from "how much am I burning".
1459
+ - **Two lines, one job each: state and meters.** Line 1 is state (`pwd` and the
1460
+ session name left; the alert, the modes, then `⎇` branch, `⑂` worktree and the
1461
+ ports right); line 2 is meters (the gauge and usage left; model and level
1462
+ right). The state chips change while a session runs, so they ride the line that
1463
+ has slack: the meters are the dense row, and a fact that moves to the top row is
1464
+ one the bottom row stops dropping to fit. It keeps "where am I / what state"
1465
+ separate from "how much am I burning".
1338
1466
  - **Child spend is a second figure.** The footer never adds a subagent's tokens to
1339
1467
  the session's; it prices the `my-pi-agent/subagent-finished` events with the
1340
1468
  same pricer and renders them as `↳`, so "this session" keeps meaning the
1341
1469
  dispatching process and the delegated work stays visible beside it.
1342
- - **The gauge is anchored at column 0.** A leading mode slot would shift the
1343
- context percentage left and right as plan mode came and went; trailing it
1344
- after a `│` keeps the percentage in a fixed place while the mode stays
1345
- visible as the meters drop.
1470
+ - **The gauge is anchored at column 0, and nothing can displace it.** A leading
1471
+ mode slot used to push the context percentage left and right as plan mode came
1472
+ and went, which is why the mode chip trailed the meters after a `│`; moving the
1473
+ mode slot to the state line leaves the gauge as the resource line's first
1474
+ segment, so the percentage cannot move at all.
1346
1475
  - **Statuses are routed, not repeated.** The built-in footer renders every
1347
1476
  `setStatus` value on its own line, so the bar sorts them by meaning instead:
1348
- `worktree` and `browse` are location, so the `◉ <port>` chip sits beside `⎇`
1349
- branch and `⑂` worktree and the left zone stays the filesystem identity; a
1350
- plan or a failed job is mode or alert, so it takes the slot after the gauge.
1351
- The [wechat](./wechat.md) extension publishes `✆ wechat` while its bridge runs,
1477
+ `worktree` and `browse` are location, so the `◉ <port>` chip sits behind `⎇`
1478
+ branch and `⑂` worktree; the `board` key is location too, and the two ports
1479
+ are one segment because they are one fact, so they share a weight and a floor;
1480
+ a plan is a mode, so it leads the state line beside them; a failed job is an
1481
+ alert, so it is its own segment ahead of both. The
1482
+ [wechat](./wechat.md) extension publishes `✆ wechat` while its bridge runs,
1352
1483
  which is why the bar has to render `getExtensionStatuses()` at all.
1484
+ - **A state chip leads its zone, so the zone does not move under it.** The right
1485
+ zone is right-aligned: a chip appended to it would push the branch and the
1486
+ worktree label left every time a mode came and went, while a chip prepended to
1487
+ it grows into the empty columns. That is the same stability argument the gauge
1488
+ gets from sitting at column 0, applied to the row that changes.
1489
+ - **A failure is never the thing that drops.** The alert segment is the one
1490
+ non-droppable chip: every other fact can be read again — the branch from git,
1491
+ the mode from the session, the spend from `/bytefly prices` — while a failed
1492
+ job that scrolled off the bar is a failure nobody is looking at. It keeps its
1493
+ count (`✗1`) when it compacts.
1353
1494
  - **Segments degrade; they do not clip.** Each `Segment` carries progressively
1354
1495
  shorter `forms` and a `weight`; the layout advances the lowest-priority form
1355
1496
  or drops the lowest-priority segment, and only truncates once everything is
@@ -1359,15 +1500,23 @@ rather than showing a wrong value.
1359
1500
  non-zero usage, so it reads full only at 100% and a small percentage never
1360
1501
  disappears; unknown usage draws no gauge at all. Only at extreme widths does
1361
1502
  `truncateToWidth` clip the remaining text.
1362
- - **Separators shrink to a single space.** Padded separators (` · `, ` │ `)
1363
- collapse to one space before `truncateToWidth` applies, so adjacent segments
1364
- never collide and the ellipsis never replaces a `│` with nothing after it.
1503
+ - **Separators shrink to a single space.** Every segment is joined by the same
1504
+ padded ` · `, which collapses to one space before `truncateToWidth` applies, so
1505
+ adjacent segments never collide and the ellipsis never replaces a separator
1506
+ with nothing after it.
1365
1507
  - **Statuses compact with their colour.** A themed status's compact icon form
1366
1508
  reattaches its opening and closing SGR sequences around the visible text, so a
1367
1509
  red `✗1` stays red instead of losing its colour exactly when the bar tightens.
1368
1510
  A two-token `icon count` badge (for `rewind`: `↺ 2`) compacts to `↺2`, keeping
1369
- the count. The slot sorts an alert (a failed job) ahead of the modes and is
1370
- droppable, so it goes before the model is clipped.
1511
+ the count. A mode is droppable, so it goes before the branch and long before
1512
+ the model is clipped; only an alert is not.
1513
+ - **A budget is a position, not a second figure.** With `price.budget` set, the
1514
+ cost segment reads `$0.31/$5` and turns `warn` once the spend reaches the
1515
+ ceiling, which is the same `budgetState` condition the notice fires on, so the
1516
+ bar cannot call a session over budget that the notice has not. The ceiling is
1517
+ formatted by its own `formatLimit` — the number the user typed, with trailing
1518
+ zeros dropped — while the spend keeps the currency's precision; both the bar
1519
+ and the notice use it, so the two cannot disagree about one figure.
1371
1520
  - **Neutral by default; color means something.** A tool block carries no status
1372
1521
  colour except the accent tool name and the status-coloured result rail; an
1373
1522
  informational notice leads with `muted`; a failure is red only where the
@@ -1510,7 +1659,7 @@ columns, gauge), so the look follows the active theme.
1510
1659
  | `header/identity.ts` | `IdentityInput` and `identityCells`: the agent and chrome cells, each with its `↑` marker, in the ladder that sheds the chrome marker, the chrome version, the chrome line, and finally the agent's marker with the row. |
1511
1660
  | `header/register.ts` | Installs/restores the header and owns its session lifecycle. |
1512
1661
  | `footer/register.ts` | Installs/restores the footer and owns its session lifecycle. |
1513
- | `footer/config.ts` | Bar tuning (`CONFIG`): thresholds, gauge, separators, icons, status keys, labels. |
1662
+ | `footer/config.ts` | Bar tuning (`CONFIG`): thresholds, gauge, separators, icons, alert and plan status keys, labels. |
1514
1663
  | `footer/types.ts` | `StatusSnapshot`, `Segment`, `LineSpec`, `UsageRecord`, and footer/TUI shapes. |
1515
1664
  | `footer/collect/entries.ts` | Session entries → usage records, each resolved to the model in effect. |
1516
1665
  | `footer/collect/snapshot.ts` | Snapshot assembly; memoized pricing scan, context estimate, and session name per branch leaf. |
@@ -1519,7 +1668,7 @@ columns, gauge), so the look follows the active theme.
1519
1668
  | `footer/price/rates.ts` | Model-key matching: `createRateLookup` splits every key once, splits a key at its first slash (so an OpenRouter model id may contain one), matches exact and wildcard most-specific-first, and remembers the last record's model. |
1520
1669
  | `footer/price/schedule.ts` | Peak-window parsing and evaluation in a configured zone. |
1521
1670
  | `footer/price/price.ts` | Records → totals: rated cost in both modes, Pi's recorded cost as USD's fallback, the peak multiplier, and the count of records CNY could not rate. |
1522
- | `footer/unpriced.ts` | The one notice a CNY session posts for records it could not price, and its OS-notification body. |
1671
+ | `footer/unpriced.ts` | The one notice a session posts for records nothing could price: the models by key, the count that the two agree on (`recordsText`), and its OS-notification body. |
1523
1672
  | `footer/render/format.ts` | Generic formatters (tokens, cost in the configured currency, cwd, the context role, truncation). |
1524
1673
  | `footer/render/status.ts` | Status-chip transforms: `compactStatus` and `dropStatusDetail`. |
1525
1674
  | `footer/render/segments.ts` | The segment builders: forms, weights, separators, roles. |
@@ -1545,21 +1694,25 @@ columns, gauge), so the look follows the active theme.
1545
1694
  | `working/format.ts` | Pure `formatElapsed` (whole seconds) and the working-message shape: the plain count and the `tool · N tools · elapsed` run line. |
1546
1695
  | `working/register.ts` | Starts the tick on `turn_start`, tracks top-level tools on `tool_execution_start`, and cancels on `agent_end`/`session_shutdown`. |
1547
1696
  | `panel/types.ts` | `PanelSection` (`extensions` \| `skills` \| `settings`), `PanelRow` with its optional skill info, `SkillInfo`, and `PanelModel`, the merged row model. |
1548
- | `panel/join.ts` | The item/report union with the unambiguous-name health rule and unique row keys, groups, and ordering; the skills model that labels rows from `SKILL.md`. |
1549
- | `panel/format.ts` | Non-TUI text: the resource headline and grouped listing with a per-row verdict, the section segments and their order, the Settings counts/headline, and the flat Settings listing. |
1550
- | `panel/settings.ts` | The curated Settings catalogue: the twelve descriptors in screen order, snapshot views and origins, value cycling, stored-value mapping, validation, plus the per-section readers that resolve a row's leaf over the global and project documents. |
1697
+ | `panel/join.ts` | The item/report union with the unambiguous-name naming rule — a row is `worktree`, and a directory claims a report only when one resource uses that name — plus unique row keys, groups, ordering, and the skills model that labels rows from `SKILL.md`. |
1698
+ | `panel/filter.ts` | The two filters as predicates and chips: a text query over a row's name, path, and source, the attention switch, and what the tab row says is on. |
1699
+ | `panel/format.ts` | Non-TUI text: the resource headline and grouped listing with a per-row verdict, the header's attention segments, the one `rowBadge` the screen and the listing share, the section segments and their order, the Settings counts/headline, and the flat Settings listing. |
1700
+ | `panel/settings.ts` | The curated Settings catalogue: the twelve descriptors in screen order, their four groups, the value labels (`Off`, `¥5.00`) and display origins, value cycling, stored-value mapping, validation, plus the per-section readers that resolve a row's leaf over the global and project documents. |
1551
1701
  | `panel/settings-runtime.ts` | The Settings runtime: snapshot cache, scope guards, app setters, per-leaf `bytefly.json` writes, dirty state, and change listeners. |
1552
1702
  | `panel/themes.ts` | Which themes the Theme row offers: the host's resolved list narrowed to the palettes under this package's `themes/`, identified by directory rather than name. |
1703
+ | `panel/prices.ts` | The `/bytefly prices` report: the unpriced models by key, the records behind each, and the one line saying where the keys go. It quotes no price — a rate is the user's to state. |
1553
1704
  | `panel/skills.ts` | Skill metadata through Pi's own `loadSkills`, keyed by resolved path, plus the deduplicated paths both discovery views read. |
1554
- | `panel/tui.ts` | `PanelComponent`, the three-section dock screen: the section switch, the cursors, the toggle chain, and the two list sections through one `LIST_VIEWS` table. |
1555
- | `panel/tui/list-view.ts` | The shared list grammar: the section headline, one resource row, and the key-hint row both sections offer. |
1556
- | `panel/tui/extensions-view.ts` | The Extensions detail pane: the doctor's findings, including the resource path when a row has no verdict. |
1557
- | `panel/tui/skills-view.ts` | The Skills detail pane: the path, the description, and Pi's refusal. |
1558
- | `panel/tui/settings-view.ts` | The Settings rows, the value's source and description pane, the JSON pane, and the editing cell. |
1705
+ | `panel/tui.ts` | `PanelComponent`: the section switch, the three cursors and focus keys, the filter and help state, the toggle chain, and the one render that hands the frame its header, tabs, rows, and block. |
1706
+ | `panel/tui/keys.ts` | The one key table: each binding with the sections that offer it, the hint row they produce, and the `?` help block, so a key cannot drift from its hint or its help line. |
1707
+ | `panel/tui/panes.ts` | The two-pane arithmetic: the 96-column breakpoint, the side-pane width, one composed row with its `╎` divider, and the clip that ends a block with `… N more lines`. |
1708
+ | `panel/tui/side-pane.ts` | The focused row's block in both layouts: name and verdict, findings, `Path`, `Source`, `State`, `Health`, a setting's `Now`/`About`/`Stored in`, the JSON pane, and the saved marker. |
1709
+ | `panel/tui/section-view.ts` | What the screen derives from the session: the tab counts, the one body height, the header's attention, the filter chips, the empty label, and the merged model for a list section. |
1710
+ | `panel/tui/list-view.ts` | The resource row grammar only: the checkbox, the status glyph, the name, and the verdict column. |
1711
+ | `panel/tui/settings-view.ts` | The settings row — checkbox, label, value in its measured column, the editing cell — and the editor's hint row. |
1559
1712
  | `panel/tui/entries.ts` | The flat display entries per section and the stable focus keys. |
1560
1713
  | `panel/tui/editing.ts` | The text-row editor's key map: caret moves, delete, clear, commit, cancel. |
1561
- | `panel/tui/frame.ts` | The shared dock frame, the section bar, the detail-row budget, and the optional rows that give way on a short terminal. |
1562
- | `panel/tui/settings-section.ts` | The Settings section's editor and JSON-pane state, its key handling, and the screen it draws. |
1714
+ | `panel/tui/frame.ts` | The chrome — the header rule, the tab row with its counts, chips and scope, the group heading, the hint or notice row, and the saved marker — and the height law: one body target for all three sections, the range row, and the rows a short terminal sheds. |
1715
+ | `panel/tui/settings-section.ts` | The Settings section's state only: the text editor, the JSON pane, the write chain, and the reset. |
1563
1716
  | `panel/tui/nav.ts` | The panel's cursor arithmetic and one read of a navigation key. |
1564
1717
  | `panel/commands.ts` | `/bytefly`: one command for the screen sections, the `doctor` report, the settings and skills listings, and their text modes. |
1565
1718
  | `picker/types.ts` | Picker host seams (`SettingsStore`, `ResourceResolver`, `Discovery`, `ResourceKind`), the app-settings setters, and row types. |
@@ -1616,15 +1769,20 @@ receipt's width ladder), its register/append/render/disable lifecycle
1616
1769
  covered by the pure helpers (`picker/resources`), the toggle
1617
1770
  transforms (`picker/toggle`), discovery with an injected factory
1618
1771
  (`picker/discovery`), and the runtime state machine (`picker/runtime`). The
1619
- unified screen is covered by its pure model merge (`panel/join`), its text
1620
- formatter (`panel/format`), the settings catalogue and its cycling
1621
- (`panel/settings`), the scope-aware settings runtime (`panel/settings-runtime`,
1622
- including a nested leaf write that keeps its siblings), the skill metadata join
1623
- and loader (`panel/skills`, on real `SKILL.md` files),
1624
- the screen's rendering (`panel/tui`), its list grammar (`tui/list-view`), the
1625
- Skills pane (`tui/skills-view`), the Settings section state (`tui/settings-section`),
1626
- its editing key map (`tui/editing`), the shared frame (`tui/frame`), and the
1627
- command through the bytefly factory (`panel/commands`).
1772
+ unified screen is covered by its pure model merge (`panel/join`), its two
1773
+ filters and their chips (`panel/filter`), its text formatter (`panel/format`),
1774
+ the settings catalogue and its cycling (`panel/settings`), the scope-aware
1775
+ settings runtime (`panel/settings-runtime`, including a nested leaf write that
1776
+ keeps its siblings), the skill metadata join and loader (`panel/skills`, on real
1777
+ `SKILL.md` files), the key table whose hints and help it holds to one source
1778
+ (`panel/keys`), the frame and the one height all three sections agree on
1779
+ (`panel/layout`), the row grammar and the focused-row block beside and under the
1780
+ list (`panel/views`), the two-pane split and its `… N more lines` clip
1781
+ (`panel/panes`), the editing key map (`tui/editing`), the component's frames,
1782
+ keys, sections, settings, and probe lifecycle (`panel/component`,
1783
+ `panel/component-input`, `panel/component-sections`,
1784
+ `panel/component-settings`, `panel/component-probing`), and the command through
1785
+ the bytefly factory (`panel/commands`) — eighteen suites in all.
1628
1786
  The doctor surface is covered by its pure report
1629
1787
  formatting (`doctor/format`), its config loader (`doctor/config`), and
1630
1788
  registration, the proactive notice, the session cache, and `run_doctor`