tumwater 0.4.2 → 0.4.3

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 (305) hide show
  1. package/README.md +6 -6
  2. package/dist/build-info.json +2 -2
  3. package/dist/src/agent/agent-backend.js +6 -6
  4. package/dist/src/agent/{run-agent.js → agent-run.js} +25 -12
  5. package/dist/src/agent/{run-settle.js → agent-settle.js} +48 -24
  6. package/dist/src/agent/{run-spawn.js → agent-spawn.js} +21 -12
  7. package/dist/src/agent/{run-stream.js → agent-stream.js} +4 -4
  8. package/dist/src/agent/backend-traits.js +2 -2
  9. package/dist/src/agent/claude/claude-account.js +1 -8
  10. package/dist/src/agent/claude/claude-backend.js +3 -3
  11. package/dist/src/agent/claude/claude-env.js +27 -31
  12. package/dist/src/agent/claude/claude-stream.js +111 -54
  13. package/dist/src/agent/codex/codex-account.js +9 -0
  14. package/dist/src/agent/codex/codex-backend.js +2 -2
  15. package/dist/src/agent/codex/codex-env.js +28 -15
  16. package/dist/src/agent/codex/codex-hook-trust.js +39 -20
  17. package/dist/src/agent/codex/codex-rollout.js +93 -56
  18. package/dist/src/agent/codex/codex-stream.js +1 -1
  19. package/dist/src/agent/env-scrub.js +18 -0
  20. package/dist/src/agent/image-input.js +68 -11
  21. package/dist/src/agent/notes-shim.js +2 -2
  22. package/dist/src/agent/opencode/opencode-backend.js +2 -2
  23. package/dist/src/agent/opencode/opencode-catalog.js +50 -27
  24. package/dist/src/agent/opencode/opencode-config.js +32 -14
  25. package/dist/src/agent/opencode/opencode-env.js +12 -13
  26. package/dist/src/agent/opencode/opencode-stream.js +47 -25
  27. package/dist/src/agent/session-ledger.js +1 -1
  28. package/dist/src/agent/subscription-account.js +3 -3
  29. package/dist/src/backlog/backlog-cli-view.js +81 -41
  30. package/dist/src/backlog/backlog-cli.js +57 -26
  31. package/dist/src/backlog/backlog-eligibility.js +58 -102
  32. package/dist/src/backlog/backlog-md.js +5 -5
  33. package/dist/src/backlog/backlog-parts.js +65 -27
  34. package/dist/src/backlog/backlog-release.js +12 -12
  35. package/dist/src/backlog/backlog-structure.js +114 -61
  36. package/dist/src/backlog/backlog.js +1 -1
  37. package/dist/src/baseline/main-baseline.js +148 -84
  38. package/dist/src/budget/budget-fallback-view.js +100 -243
  39. package/dist/src/budget/budget-wiring.js +29 -65
  40. package/dist/src/budget/fallback-breaker.js +2 -2
  41. package/dist/src/build/build-check-detect.js +43 -14
  42. package/dist/src/build/build-check-events.js +2 -2
  43. package/dist/src/build/build-check-scoped.js +40 -16
  44. package/dist/src/build/build-check.js +13 -5
  45. package/dist/src/cli/cli-command-args.js +89 -52
  46. package/dist/src/cli/cli-flag-specs.js +42 -22
  47. package/dist/src/cli/cli-init.js +34 -12
  48. package/dist/src/cli/cli-query-commands.js +1 -1
  49. package/dist/src/cli/cli-repo-gate.js +2 -2
  50. package/dist/src/cli/cli-start.js +91 -58
  51. package/dist/src/cli/completion.js +82 -46
  52. package/dist/src/cli/config-commands.js +82 -71
  53. package/dist/src/cli/help.js +4 -6
  54. package/dist/src/cli/log-commands.js +43 -23
  55. package/dist/src/cli/notify-command.js +2 -2
  56. package/dist/src/cli/question-commands.js +86 -60
  57. package/dist/src/cli.js +181 -135
  58. package/dist/src/collections.js +31 -16
  59. package/dist/src/concurrency/check-slots.js +70 -0
  60. package/dist/src/concurrency/lock.js +22 -0
  61. package/dist/src/concurrency/run-permit.js +49 -0
  62. package/dist/src/concurrency/semaphore.js +46 -250
  63. package/dist/src/config/config-editable-keys.js +4 -7
  64. package/dist/src/config/config-fallbacks.js +81 -101
  65. package/dist/src/config/config-field-checks.js +66 -37
  66. package/dist/src/config/config-keys.js +5 -20
  67. package/dist/src/config/config-live.js +4 -28
  68. package/dist/src/config/{model-selector.js → config-model-selector.js} +4 -4
  69. package/dist/src/config/config-request.js +153 -102
  70. package/dist/src/config/config-schema.js +9 -11
  71. package/dist/src/config/config-selector-checks.js +1 -1
  72. package/dist/src/config/config-validation-agents.js +2 -2
  73. package/dist/src/config/config-validation-roles.js +4 -52
  74. package/dist/src/config/config-validation.js +4 -38
  75. package/dist/src/config/config-views.js +9 -34
  76. package/dist/src/config/config-write.js +6 -23
  77. package/dist/src/config/config.js +4 -5
  78. package/dist/src/doctor/doctor-backlog-bugs.js +7 -6
  79. package/dist/src/doctor/doctor-backlog-plans.js +24 -24
  80. package/dist/src/doctor/doctor-backlog.js +7 -5
  81. package/dist/src/doctor/doctor-check-names.js +3 -6
  82. package/dist/src/doctor/doctor-checks.js +5 -5
  83. package/dist/src/doctor/doctor-claude-checks.js +44 -28
  84. package/dist/src/doctor/doctor-codex-checks.js +6 -6
  85. package/dist/src/doctor/doctor-contention-checks.js +44 -112
  86. package/dist/src/doctor/doctor-fallback-checks.js +2 -2
  87. package/dist/src/doctor/doctor-holds.js +6 -45
  88. package/dist/src/doctor/doctor-install.js +1 -1
  89. package/dist/src/doctor/doctor-launch-services.js +1 -1
  90. package/dist/src/doctor/doctor-model-checks.js +178 -94
  91. package/dist/src/doctor/doctor-notify.js +2 -2
  92. package/dist/src/doctor/doctor-opencode-checks.js +5 -4
  93. package/dist/src/doctor/doctor-orphans.js +120 -20
  94. package/dist/src/doctor/{outcome.js → doctor-outcome.js} +7 -0
  95. package/dist/src/doctor/doctor-pi-checks.js +2 -2
  96. package/dist/src/doctor/doctor-principles.js +3 -3
  97. package/dist/src/doctor/doctor-project-checks.js +3 -3
  98. package/dist/src/doctor/doctor-role-notes.js +1 -1
  99. package/dist/src/doctor/doctor.js +8 -11
  100. package/dist/src/events/event-format.js +30 -54
  101. package/dist/src/events/event-labels.js +1 -5
  102. package/dist/src/events/event-legacy.js +1 -3
  103. package/dist/src/events/{notify.js → event-notify.js} +2 -2
  104. package/dist/src/events/event-read.js +1 -1
  105. package/dist/src/failure/failure-cluster.js +6 -6
  106. package/dist/src/failure/failure-data.js +8 -4
  107. package/dist/src/failure/failure-render.js +44 -34
  108. package/dist/src/failure/failure-state-change.js +4 -34
  109. package/dist/src/failure/{time-spend.js → failure-time-spend.js} +117 -84
  110. package/dist/src/files/files.js +30 -0
  111. package/dist/src/files/json-object.js +8 -8
  112. package/dist/src/files/stat-cache.js +12 -8
  113. package/dist/src/gates/budget-gates.js +85 -84
  114. package/dist/src/gates/gate-polls.js +27 -68
  115. package/dist/src/gates/pause-gates.js +16 -2
  116. package/dist/src/gates/readiness.js +1 -1
  117. package/dist/src/gates/role-cap-gates.js +44 -35
  118. package/dist/src/gates/{startup-gate.js → startup-gates.js} +7 -7
  119. package/dist/src/gates/{streak-gate.js → streak-gates.js} +75 -52
  120. package/dist/src/git/git.js +75 -45
  121. package/dist/src/git/worktree-pool.js +3 -6
  122. package/dist/src/gui/gui-admin-endpoints.js +3 -3
  123. package/dist/src/gui/gui-backlog-commands.js +4 -4
  124. package/dist/src/gui/gui-backlog-endpoints.js +61 -41
  125. package/dist/src/gui/gui-command.js +43 -24
  126. package/dist/src/gui/gui-history-endpoints.js +100 -52
  127. package/dist/src/gui/gui-operator-commands.js +22 -23
  128. package/dist/src/gui/gui-prompt-commands.js +23 -23
  129. package/dist/src/gui/gui-report-endpoints.js +1 -1
  130. package/dist/src/history/history-data.js +71 -44
  131. package/dist/src/history/history-queued.js +49 -27
  132. package/dist/src/history/history.js +51 -33
  133. package/dist/src/inbox/prompt-commands.js +59 -42
  134. package/dist/src/init/init.js +88 -40
  135. package/dist/src/merge/merge-backlog-conflicts.js +1 -1
  136. package/dist/src/merge/merge-core.js +1 -1
  137. package/dist/src/merge/merge-drain.js +8 -11
  138. package/dist/src/merge/merge-pipeline.js +6 -7
  139. package/dist/src/merge/merge-post-review-drain.js +10 -13
  140. package/dist/src/merge/merge-requeue.js +1 -1
  141. package/dist/src/merge/merge-slot.js +0 -8
  142. package/dist/src/merge/merge-verify.js +8 -18
  143. package/dist/src/notes-cli.js +52 -42
  144. package/dist/src/operator/operator-commands.js +76 -44
  145. package/dist/src/operator/operator-intent.js +3 -3
  146. package/dist/src/orchestrator/orchestrator-launch.js +31 -63
  147. package/dist/src/orchestrator/orchestrator-poll.js +27 -32
  148. package/dist/src/orchestrator/orchestrator.js +8 -21
  149. package/dist/src/orchestrator/scheduling-admit.js +101 -68
  150. package/dist/src/orchestrator/scheduling-scope.js +1 -1
  151. package/dist/src/orchestrator/scheduling-skip.js +1 -1
  152. package/dist/src/orchestrator/startup-migrations.js +1 -1
  153. package/dist/src/paths.js +8 -7
  154. package/dist/src/pi/pi-args.js +5 -2
  155. package/dist/src/pi/pi-backend.js +2 -2
  156. package/dist/src/pi/pi-bin.js +3 -3
  157. package/dist/src/pi/pi-event-line.js +1 -1
  158. package/dist/src/pi/pi-models.js +43 -29
  159. package/dist/src/pi/pi-settings.js +43 -20
  160. package/dist/src/pi/pi-stream.js +32 -6
  161. package/dist/src/pi/pi-watchdogs.js +12 -5
  162. package/dist/src/pi/pi.js +2 -2
  163. package/dist/src/pi-extension/context-shake.js +32 -20
  164. package/dist/src/pi-extension/kill-guard.js +69 -0
  165. package/dist/src/process/process-group.js +49 -5
  166. package/dist/src/process/process.js +8 -8
  167. package/dist/src/process/run-marker.js +31 -13
  168. package/dist/src/process/supervisor.js +40 -28
  169. package/dist/src/prompt/dialect.js +1 -1
  170. package/dist/src/prompt/gate-notes.js +2 -2
  171. package/dist/src/prompt/notes-backlog.js +66 -37
  172. package/dist/src/prompt/principles.js +6 -4
  173. package/dist/src/prompt/prompt-followup.js +12 -4
  174. package/dist/src/prompt/prompt.js +88 -38
  175. package/dist/src/report/report-data.js +53 -37
  176. package/dist/src/report/report-fold.js +16 -9
  177. package/dist/src/report/report-render.js +21 -194
  178. package/dist/src/report/report-since-data.js +4 -3
  179. package/dist/src/report/report-since-render.js +173 -0
  180. package/dist/src/report/report-usage-fold.js +8 -8
  181. package/dist/src/report/report.js +46 -22
  182. package/dist/src/review/post-merge-review-render.js +9 -3
  183. package/dist/src/review/post-merge-review.js +1 -1
  184. package/dist/src/review/review-precheck.js +11 -1
  185. package/dist/src/review/review.js +40 -153
  186. package/dist/src/review/secret-leaks.js +26 -16
  187. package/dist/src/roles/role-catalog.js +11 -3
  188. package/dist/src/roles/role-guidance.js +16 -12
  189. package/dist/src/roles/role-render.js +1 -14
  190. package/dist/src/roles/role-view.js +9 -21
  191. package/dist/src/run/acceptance-greps.js +88 -45
  192. package/dist/src/run/run-apply.js +12 -6
  193. package/dist/src/run/run-detail-data.js +121 -68
  194. package/dist/src/run/run-detail-render.js +92 -35
  195. package/dist/src/run/run-detail.js +72 -41
  196. package/dist/src/run/run-prompt.js +63 -50
  197. package/dist/src/run/run-resume.js +76 -29
  198. package/dist/src/run/run-stage.js +135 -84
  199. package/dist/src/run/run-timing.js +8 -2
  200. package/dist/src/run/run-usage.js +31 -1
  201. package/dist/src/run/run-verdict.js +119 -50
  202. package/dist/src/run/stage-check.js +49 -32
  203. package/dist/src/run/stage-checks-shared.js +28 -0
  204. package/dist/src/run/stage-name-checks.js +111 -92
  205. package/dist/src/run/stage-path-checks.js +69 -42
  206. package/dist/src/run/stage-prose-checks.js +55 -40
  207. package/dist/src/runner/runner-outcome.js +6 -1
  208. package/dist/src/runner/runner-pi.js +3 -10
  209. package/dist/src/runner/runner-recovery.js +1 -1
  210. package/dist/src/runner/{revision.js → runner-revision.js} +1 -1
  211. package/dist/src/runner/runner-run.js +97 -35
  212. package/dist/src/runner/runner-state-load.js +12 -9
  213. package/dist/src/runner/runner.js +41 -25
  214. package/dist/src/scheduling/claims.js +0 -4
  215. package/dist/src/scheduling/scheduling.js +53 -66
  216. package/dist/src/status/status-data.js +2 -2
  217. package/dist/src/status/status-role-notes.js +2 -2
  218. package/dist/src/status/status-roles.js +9 -17
  219. package/dist/src/team/{error-storm.js → team-error-storm.js} +1 -1
  220. package/dist/src/team/{failure-spread.js → team-failure-spread.js} +5 -5
  221. package/dist/src/team/team-hold.js +98 -65
  222. package/dist/src/team/{orchestrator-info.js → team-orchestrator-info.js} +2 -15
  223. package/dist/src/team/team-polls.js +28 -126
  224. package/dist/src/team/{reclaim.js → team-reclaim.js} +61 -31
  225. package/dist/src/team/team-state.js +4 -13
  226. package/dist/src/text/markdown.js +1 -1
  227. package/dist/src/text/phrases.js +20 -15
  228. package/dist/src/text/text.js +3 -3
  229. package/dist/src/ui/alert-feeds.js +49 -73
  230. package/dist/src/ui/badges.js +3 -13
  231. package/dist/src/ui/gui/gui-client-boot.js +2 -2
  232. package/dist/src/ui/gui/gui-client-composer.js +1 -1
  233. package/dist/src/ui/gui/gui-client-drawer-config.js +7 -17
  234. package/dist/src/ui/gui/gui-client-drawer.js +1 -1
  235. package/dist/src/ui/gui/gui-client-history.js +28 -14
  236. package/dist/src/ui/gui/gui-client-model.js +0 -12
  237. package/dist/src/ui/gui/gui-client-pending.js +7 -6
  238. package/dist/src/ui/gui/gui-client-report-chart.js +5 -5
  239. package/dist/src/ui/gui/gui-client-report.js +1 -1
  240. package/dist/src/ui/gui/gui-client-roles.js +0 -1
  241. package/dist/src/ui/gui/gui-client-settings.js +43 -84
  242. package/dist/src/ui/gui/gui-client-team.js +4 -4
  243. package/dist/src/ui/gui/gui-client.js +7 -1
  244. package/dist/src/ui/progress-data.js +53 -40
  245. package/dist/src/ui/run-progress-model.js +9 -2
  246. package/dist/src/ui/status-model.js +2 -24
  247. package/dist/src/ui/status-payload.js +3 -7
  248. package/dist/src/ui/status-render.js +5 -9
  249. package/dist/src/ui/team-alert-backlog.js +6 -6
  250. package/dist/src/ui/team-alert-status.js +9 -170
  251. package/dist/src/ui/team-alert-tail.js +19 -215
  252. package/dist/src/ui/team-alerts.js +7 -8
  253. package/dist/src/ui/transcript-run.js +27 -16
  254. package/dist/src/ui/tui/tui-editor-answer.js +12 -10
  255. package/dist/src/ui/tui/tui-editor-attach.js +4 -3
  256. package/dist/src/ui/tui/tui-editor-backends.js +4 -3
  257. package/dist/src/ui/tui/tui-editor-brief.js +6 -9
  258. package/dist/src/ui/tui/tui-editor-budget.js +4 -3
  259. package/dist/src/ui/tui/tui-editor-config.js +80 -68
  260. package/dist/src/ui/tui/tui-editor-entry.js +12 -20
  261. package/dist/src/ui/tui/tui-editor-pause.js +51 -57
  262. package/dist/src/ui/tui/tui-editor-prompt.js +9 -13
  263. package/dist/src/ui/tui/tui-editor-queued.js +11 -14
  264. package/dist/src/ui/tui/tui-editor-quiet.js +4 -3
  265. package/dist/src/ui/tui/tui-editor-release.js +4 -3
  266. package/dist/src/ui/tui/tui-editor-retire.js +7 -7
  267. package/dist/src/ui/tui/tui-editor-role-enabled.js +4 -3
  268. package/dist/src/ui/tui/tui-editor-role-file.js +4 -4
  269. package/dist/src/ui/tui/tui-editor-role-key.js +4 -3
  270. package/dist/src/ui/tui/tui-editor-role-model.js +97 -15
  271. package/dist/src/ui/tui/tui-editor-search.js +4 -3
  272. package/dist/src/ui/tui/tui-editor-session.js +17 -4
  273. package/dist/src/ui/tui/tui-editors.js +1 -7
  274. package/dist/src/ui/tui/tui-frame.js +182 -134
  275. package/dist/src/ui/tui/tui-guard.js +20 -0
  276. package/dist/src/ui/tui/tui-input.js +159 -92
  277. package/dist/src/ui/tui/tui-keymap.js +51 -29
  278. package/dist/src/ui/tui/tui-keys.js +1 -1
  279. package/dist/src/ui/tui/tui-pane-activity.js +3 -3
  280. package/dist/src/ui/tui/tui-pane-backlog.js +74 -36
  281. package/dist/src/ui/tui/tui-pane-ctx.js +1 -1
  282. package/dist/src/ui/tui/tui-pane-health.js +252 -228
  283. package/dist/src/ui/tui/tui-pane-history.js +61 -70
  284. package/dist/src/ui/tui/tui-pane-pending.js +284 -247
  285. package/dist/src/ui/tui/tui-pane-queued.js +25 -27
  286. package/dist/src/ui/tui/tui-pane-report.js +2 -1
  287. package/dist/src/ui/tui/tui-pane-session.js +11 -7
  288. package/dist/src/ui/tui/tui-pane-transcript.js +19 -31
  289. package/dist/src/ui/tui/tui-pane.js +14 -11
  290. package/dist/src/ui/tui/tui-panes.js +29 -32
  291. package/dist/src/ui/tui/tui.js +3 -4
  292. package/dist/src/verdict/fix-claim.js +76 -54
  293. package/dist/src/verdict/refusal.js +1 -1
  294. package/package.json +5 -5
  295. package/dist/src/concurrency/provider-lease.js +0 -105
  296. package/dist/src/runner/model-fallback.js +0 -76
  297. package/dist/src/runner/runner-fallback.js +0 -66
  298. package/dist/src/ui/tui/tui-editor-claude-reserve.js +0 -24
  299. package/dist/src/ui/tui/tui-editor-provider-caps.js +0 -27
  300. /package/dist/src/backlog/{question-answer.js → backlog-question-answer.js} +0 -0
  301. /package/dist/src/doctor/{probe.js → doctor-probe.js} +0 -0
  302. /package/dist/src/gates/{disk-gate.js → disk-gates.js} +0 -0
  303. /package/dist/src/operator/{retire.js → operator-retire.js} +0 -0
  304. /package/dist/src/operator/{revert.js → operator-revert.js} +0 -0
  305. /package/dist/src/runner/{leftover.js → runner-leftover.js} +0 -0
@@ -4,6 +4,8 @@ import { movePromptToTop } from "../../inbox/inbox-top.js";
4
4
  import { moveEntrySelection } from "./tui-backlog.js";
5
5
  import { arrowDir } from "./tui-keymap.js";
6
6
  import { promptLineFree } from "./tui-pane-ctx.js";
7
+ import { guardError } from "./tui-guard.js";
8
+ import { paneAtHead } from "./tui-pane-session.js";
7
9
  /** The Queued pane (view roleIds.length + 6) of the TUI (src/ui/tui/tui.tsx): the prompt row
8
10
  * selection, the up/down select and Esc-to-list arms, and the Ctrl+X cancel and Ctrl+V
9
11
  * move-to-top arms. Split out of the pane-navigation monolith; it owns the pane's
@@ -41,11 +43,11 @@ export function createQueuedPane(session) {
41
43
  // (`tumwater prompt --cancel <n> --role <id>`): cancelRolePrompt removes that role's Nth
42
44
  // queue file and logs the prompt_cancelled event, so the pane, the CLI, and the dashboard
43
45
  // cancel cannot drift. It then re-reads the queue so the pane's list and rows reflect the
44
- // removal and the selection clamps. Its write and flash stay under one try/catch; the
45
- // caller guards on the row being defined (a no-selection list-mode press, or any other
46
- // view, leaves the queue untouched).
46
+ // removal and the selection clamps. Its write and flash stay under one guard; the caller
47
+ // guards on the row being defined (a no-selection list-mode press, or any other view,
48
+ // leaves the queue untouched).
47
49
  const cancelQueuedRow = (row) => {
48
- try {
50
+ guardError(session, () => {
49
51
  const outcome = cancelRolePrompt(session.root, row.role, row.position);
50
52
  // A gone outcome is the concurrent-dequeue race (cancelRolePrompt's own contract),
51
53
  // not a failure: flash it honestly and still refresh the listing.
@@ -53,21 +55,18 @@ export function createQueuedPane(session) {
53
55
  ? `cancelled inbox message #${row.position} for ${row.role}`
54
56
  : `inbox message #${row.position} for ${row.role} was already gone`);
55
57
  refreshQueued();
56
- }
57
- catch (err) {
58
- session.flashError(err);
59
- }
58
+ });
60
59
  };
61
60
  // Ctrl+V moves the selected queued prompt to the head of its role's queue through the
62
61
  // CLI's own per-role path (`tumwater prompt --top <n>`): movePromptToTop renames that
63
62
  // role's Nth queue file to the head and logs the prompt_topped event, so the pane, the
64
63
  // CLI, and the dashboard cannot drift. A position-1 row is already the head (a no-op); a
65
64
  // gone outcome is the concurrent-dequeue race (movePromptToTop's own contract), not a
66
- // failure, so flash it honestly. Its write and flash stay under one try/catch; the caller
65
+ // failure, so flash it honestly. Its write and flash stay under one guard; the caller
67
66
  // guards on the row being defined (a no-selection list-mode press, or any other view,
68
67
  // leaves the queues untouched).
69
68
  const moveQueuedRow = (row) => {
70
- try {
69
+ guardError(session, () => {
71
70
  if (row.position === 1) {
72
71
  // A head row is a no-op, not movePromptToTop's throw: it is already where the
73
72
  // reorder would put it, and throwing would flash an error for a press that
@@ -81,30 +80,30 @@ export function createQueuedPane(session) {
81
80
  session.flashMessage(outcome.status === "topped"
82
81
  ? `moved inbox message #${row.position} for ${row.role} to the top: ${outcome.preview}`
83
82
  : `inbox message #${row.position} for ${row.role} was already gone`);
84
- }
85
- catch (err) {
86
- session.flashError(err);
87
- }
83
+ });
88
84
  };
89
- const handleKey = (key, ctx) => {
90
- if (key.ctrl && key.name === "x" &&
85
+ // Ctrl+X cancels the selected queued prompt and Ctrl+V moves it to its role queue's head,
86
+ // each through the CLI's own per-role path (cancelRolePrompt / movePromptToTop). The two
87
+ // arms share one shape — a guarded ctrl-keypress that resolves the clamped selected row,
88
+ // runs cancel or move, and repaints — differing only in the key and the action, so one
89
+ // helper owns the guard, the clamp, and the render.
90
+ const handleQueuedRowAction = (key, ctx, keyName, action) => {
91
+ if (key.ctrl && key.name === keyName &&
91
92
  promptLineFree(ctx.es) &&
92
93
  session.view === session.roleIds.length + 6 && selectedQueued !== null) {
93
94
  const row = queuedRows[Math.min(selectedQueued, queuedRows.length - 1)];
94
95
  if (row !== undefined)
95
- cancelQueuedRow(row);
96
+ action(row);
96
97
  session.requestRender();
97
98
  return true;
98
99
  }
99
- if (key.ctrl && key.name === "v" &&
100
- promptLineFree(ctx.es) &&
101
- session.view === session.roleIds.length + 6 && selectedQueued !== null) {
102
- const row = queuedRows[Math.min(selectedQueued, queuedRows.length - 1)];
103
- if (row !== undefined)
104
- moveQueuedRow(row);
105
- session.requestRender();
100
+ return false;
101
+ };
102
+ const handleKey = (key, ctx) => {
103
+ if (handleQueuedRowAction(key, ctx, "x", cancelQueuedRow))
104
+ return true;
105
+ if (handleQueuedRowAction(key, ctx, "v", moveQueuedRow))
106
106
  return true;
107
- }
108
107
  const arrow = arrowDir(key.name);
109
108
  if (session.view === session.roleIds.length + 6 && arrow !== null) {
110
109
  // In the Queued pane, up/down select a queued prompt row in full (wrapping, the backlog
@@ -129,8 +128,7 @@ export function createQueuedPane(session) {
129
128
  // was cancelled), so the pane shows the same renderQueuedList it entered with. The
130
129
  // shared paneScroll resets with the body switch, like a selection move.
131
130
  selectedQueued = null;
132
- session.paneScroll = 0;
133
- session.requestRender();
131
+ paneAtHead(session);
134
132
  return true;
135
133
  }
136
134
  return false;
@@ -1,6 +1,7 @@
1
1
  import { collectReport } from "../../report/report-data.js";
2
2
  import { collectReportSince } from "../../report/report-since-data.js";
3
- import { renderReportMarkdown, renderSinceReportMarkdown } from "../../report/report-render.js";
3
+ import { renderReportMarkdown } from "../../report/report-render.js";
4
+ import { renderSinceReportMarkdown } from "../../report/report-since-render.js";
4
5
  import { REPORT_DEFAULT_DAYS, REPORT_SINCE_MAX_MS } from "../../events/event-window.js";
5
6
  import { collectFailureReport, collectFailureReportSince } from "../../failure/failure-data.js";
6
7
  import { renderFailureMarkdown } from "../../failure/failure-render.js";
@@ -1,6 +1,7 @@
1
1
  import { REPORT_SINCE_MAX_MS } from "../../events/event-window.js";
2
2
  import { HISTORY_RESULT_CLASSES, HISTORY_RESULT_FLAG_NAMES } from "../../history/history-data.js";
3
3
  import { baseRoleOf } from "../../roles/runner-ids.js";
4
+ import { distinctOrdered } from "../../collections.js";
4
5
  /** Every Ctrl+<key> cycle walks its own ReadonlyArray<[key, label]> table one slot at a time,
5
6
  * wrapping past the last back to the first. Each handler holds a key the table carries (a null
6
7
  * default slot included), so findIndex always hits. */
@@ -40,13 +41,7 @@ export const HISTORY_WINDOWS = [
40
41
  * names that collect it identically. `feature`/`feature-2` therefore offer one slot, and the
41
42
  * name the tab strip and hint show is the name the collector honors. */
42
43
  export function scopeCycle(roleIds) {
43
- const bases = [];
44
- for (const id of roleIds) {
45
- const base = baseRoleOf(id);
46
- if (!bases.includes(base))
47
- bases.push(base);
48
- }
49
- return [null, ...bases];
44
+ return [null, ...distinctOrdered(roleIds.map((id) => baseRoleOf(id)))];
50
45
  }
51
46
  /** The History pane's session outcome cycle, Ctrl+O: all runs, then the canonical classes
52
47
  * `tumwater history --result` and the dashboard's History filter speak. The class list comes
@@ -131,3 +126,12 @@ export function createTuiPaneSession(deps) {
131
126
  runPatch: deps.runPatch,
132
127
  };
133
128
  }
129
+ /** Re-enter the active pane at its head: reset the shared within-body scroll offset to the top
130
+ * and repaint. The panes' key-handler and refresh arms reset the scroll and request a render
131
+ * together every time they return a view to its start, so this single helper keeps that pair
132
+ * from drifting — and names the intent (the pane starts at its head) once instead of at each
133
+ * call site. A key-handler caller that consumed the key follows with its own `return true`. */
134
+ export function paneAtHead(session) {
135
+ session.paneScroll = 0;
136
+ session.requestRender();
137
+ }
@@ -3,17 +3,19 @@ import { roleNotesPath } from "../../paths.js";
3
3
  import { renderRoleChange } from "../../change/change-render.js";
4
4
  import { readMergeQueue } from "../../merge/merge-queue.js";
5
5
  import { renderMergeQueue, scopeMergeQueue } from "../../merge/merge-queue-render.js";
6
- import { orchestratorAlive } from "../../team/orchestrator-info.js";
6
+ import { orchestratorAlive } from "../../team/team-orchestrator-info.js";
7
7
  import { queueInFlight } from "../../status/status-polls.js";
8
8
  import { roleInspectMarkdown } from "./tui-pane.js";
9
9
  import { pageMarkdownBody } from "./tui-backlog.js";
10
10
  import { pageDir } from "./tui-keymap.js";
11
11
  import { promptLineFree } from "./tui-pane-ctx.js";
12
+ import { guardError } from "./tui-guard.js";
13
+ import { paneAtHead } from "./tui-pane-session.js";
12
14
  /** The per-role transcript panes (views 1..roleIds.length) of the TUI: the Ctrl+N
13
- * transcript→notes→change cycle and its two-step Ctrl+X notes-clear arm, plus the
14
- * notes/change PgUp/PgDn body window, and the `/` search over the live transcript step. Split
15
- * out of the pane-navigation monolith; it owns the notes/change mode state and the arms that
16
- * touch them. */
15
+ * transcript→notes→change cycle and its two-step Ctrl+X notes-clear arm, the Ctrl+Y
16
+ * change-diff toggle, plus the notes/change PgUp/PgDn body window, and the `/` search over
17
+ * the live transcript step. Split out of the pane-navigation monolith; it owns the
18
+ * notes/change mode state and the arms that touch them. */
17
19
  export function createTranscriptPane(session) {
18
20
  // Ctrl+N toggles the viewed role's pane between its live transcript and a read-only
19
21
  // notes view (its note and next run's prompt). Like paneCache, noteCache is computed
@@ -53,17 +55,13 @@ export function createTranscriptPane(session) {
53
55
  const target = role;
54
56
  // The role's waiting merges head the change, exact-matching this one runner id.
55
57
  let queueText = "";
56
- try {
58
+ guardError(session, () => {
57
59
  const { view, scope } = scopeMergeQueue(readMergeQueue(session.root), [role], (r) => r === role);
58
60
  if (view.entries.length > 0) {
59
61
  const inFlight = queueInFlight(session.root, view.entries, orchestratorAlive(session.root));
60
62
  queueText = renderMergeQueue(view, session.now(), inFlight, scope) + "\n\n";
61
63
  }
62
- }
63
- catch (err) {
64
- queueText = "";
65
- session.flashError(err);
66
- }
64
+ });
67
65
  void session.collectChange(session.root, target, changeStat ? { stat: true } : undefined).then((change) => {
68
66
  // changeGen drops a load superseded by a re-load or a leave; the role match drops one
69
67
  // a roster change (syncRoster mutates session.roleIds without resetting this pane)
@@ -83,9 +81,8 @@ export function createTranscriptPane(session) {
83
81
  // because routeCtrlN in tui-panes.ts routes it last and relies on this pane consuming
84
82
  // Ctrl+N as the fallback.
85
83
  const transcriptCycle = (ctx) => {
86
- // Ctrl+N cycles the viewed role's pane live transcript → notes → change → transcript
87
- // (PLANS.md "Inspect a loop's notebook and next tick prompt in the TUI" and "Show a
88
- // loop's pending change in the TUI transcript pane"). The change step renders the same
84
+ // Ctrl+N cycles the viewed role's pane live transcript → notes → change → transcript.
85
+ // The change step renders the same
89
86
  // collectRoleChange/renderRoleChange view the CLI's `tumwater diff --role <id>` and the
90
87
  // dashboard drawer use. Guarded to a transcript view and out of budget-edit and
91
88
  // role-prompt modes, the same way the per-role controls below are; the History view's
@@ -123,17 +120,13 @@ export function createTranscriptPane(session) {
123
120
  // rolePayload reads the notes and assembles the next run's prompt off disk; a
124
121
  // throw leaves the transcript shown (the mode stays off) and flashes the reason
125
122
  // instead of escaping the handler and killing the TUI.
126
- try {
123
+ noteCache = null;
124
+ guardError(session, () => {
127
125
  noteCache = roleInspectMarkdown(session.root, role);
128
126
  noteMode = true;
129
- }
130
- catch (err) {
131
- noteCache = null;
132
- session.flashError(err);
133
- }
127
+ });
134
128
  }
135
- session.paneScroll = 0; // either way the pane starts at its head
136
- session.requestRender();
129
+ paneAtHead(session);
137
130
  return true;
138
131
  };
139
132
  // The Ctrl+Y diffstat toggle, live only on the change step (mnemonic: summarY). Returns
@@ -166,14 +159,11 @@ export function createTranscriptPane(session) {
166
159
  }
167
160
  const role = viewedRole();
168
161
  if (session.armedNotes === role) {
169
- try {
162
+ guardError(session, () => {
170
163
  session.flashMessage(`${role}: ${saveRoleNote(roleNotesPath(session.root, role), "")}`);
171
164
  noteCache = roleInspectMarkdown(session.root, role);
172
165
  session.paneScroll = 0;
173
- }
174
- catch (err) {
175
- session.flashError(err);
176
- }
166
+ });
177
167
  }
178
168
  else {
179
169
  session.notesClearArmed = role;
@@ -216,8 +206,7 @@ export function createTranscriptPane(session) {
216
206
  if (!noteMode || viewedRole() !== role)
217
207
  return;
218
208
  noteCache = roleInspectMarkdown(session.root, role);
219
- session.paneScroll = 0;
220
- session.requestRender();
209
+ paneAtHead(session);
221
210
  },
222
211
  reset() {
223
212
  // A cycle away leaves notes and change modes and drops their snapshots; changeGen
@@ -238,8 +227,7 @@ export function createTranscriptPane(session) {
238
227
  if (session.view < 1 || session.view > session.roleIds.length)
239
228
  return;
240
229
  transcriptSearch = pattern === null ? null : { role: viewedRole(), pattern };
241
- session.paneScroll = 0;
242
- session.requestRender();
230
+ paneAtHead(session);
243
231
  },
244
232
  state() {
245
233
  const transcriptGrep = transcriptSearch !== null && viewedRole() === transcriptSearch.role
@@ -11,7 +11,7 @@ import { eventMatchesGrep, eventResult, formatEvent } from "../../events/event-f
11
11
  import { eventTypeLabel } from "../../events/event-labels.js";
12
12
  import { eventTypeTally } from "../../events/event-tally.js";
13
13
  import { backlogLines, entryBodyWindow, browsableBacklogEntries, isOpenBacklogLabel, planListLines } from "./tui-backlog.js";
14
- import { answeredQuestionSummaries } from "../../backlog/question-answer.js";
14
+ import { answeredQuestionSummaries } from "../../backlog/backlog-question-answer.js";
15
15
  import { DASHBOARD_ANSWERED_QUESTIONS, recentFinished } from "../status-payload.js";
16
16
  import { donePlanEntries, DONE_DATE_RE, entryMatches, FIXED_DATE_RE, fixedBugEntries } from "../../backlog/backlog.js";
17
17
  import { isNotableEvent } from "../tone.js";
@@ -42,6 +42,14 @@ export function activityEvents(root, role, grep, type, notable = false) {
42
42
  export function roleInspectMarkdown(root, role) {
43
43
  return inspectMarkdownLines(rolePayload(root, role), Date.now()).join("\n");
44
44
  }
45
+ /** The pane's cached body windowed to its clipped lines, or no lines while the cache is still
46
+ * null (the async collector has not resolved yet) — so the frame prints the pane's own
47
+ * loading/empty note rather than entryBodyWindow's "(no details for this entry)" placeholder.
48
+ * Shared by the history, pending, health, brief, and queued panes, whose bodies differ only in
49
+ * that note, so the null-cache shape cannot drift between them. */
50
+ function paneCacheLines(state, budget, width) {
51
+ return state.paneCache ? entryBodyWindow(state.paneCache, state.paneScroll, budget, width).lines : [];
52
+ }
45
53
  /** Compose the body lines for one pane at this frame's width and budgets. Same rules
46
54
  * tui.tsx's inline builder followed: transcripts keep the tail, the backlog list keeps
47
55
  * the head with the selected entry's full body in entry mode, usage/failures re-window
@@ -187,9 +195,8 @@ function historyBody(pane, opts) {
187
195
  // historyPaneText returns "" when the log holds no completed runs; return no lines
188
196
  // (rather than windowing an empty string, which entryBodyWindow replaces with its
189
197
  // "no details" placeholder) so the frame prints the pane's own empty note instead.
190
- const win = state.paneCache ? entryBodyWindow(state.paneCache, state.paneScroll, eventBudget, width) : { lines: [] };
191
198
  return {
192
- body: win.lines.map((l) => toneLine(l, width)),
199
+ body: paneCacheLines(state, eventBudget, width).map((l) => toneLine(l, width)),
193
200
  emptyNote: "(no runs yet)",
194
201
  };
195
202
  }
@@ -207,25 +214,22 @@ function pendingBody(_pane, opts) {
207
214
  emptyNote: "(loading pending changes…)",
208
215
  };
209
216
  }
210
- const win = state.paneCache ? entryBodyWindow(state.paneCache, state.paneScroll, eventBudget, width) : { lines: [] };
211
217
  return {
212
- body: win.lines.map((l) => toneLine(l, width)),
218
+ body: paneCacheLines(state, eventBudget, width).map((l) => toneLine(l, width)),
213
219
  emptyNote: "(loading pending changes…)",
214
220
  };
215
221
  }
216
222
  function healthBody(_pane, opts) {
217
223
  const { width, eventBudget, state } = opts;
218
- const win = state.paneCache ? entryBodyWindow(state.paneCache, state.paneScroll, eventBudget, width) : { lines: [] };
219
224
  return {
220
- body: win.lines.map((l) => toneLine(l, width)),
225
+ body: paneCacheLines(state, eventBudget, width).map((l) => toneLine(l, width)),
221
226
  emptyNote: "(running health checks…)",
222
227
  };
223
228
  }
224
229
  function briefBody(_pane, opts) {
225
230
  const { width, eventBudget, state } = opts;
226
- const win = state.paneCache ? entryBodyWindow(state.paneCache, state.paneScroll, eventBudget, width) : { lines: [] };
227
231
  return {
228
- body: win.lines.map((l) => toneLine(l, width)),
232
+ body: paneCacheLines(state, eventBudget, width).map((l) => toneLine(l, width)),
229
233
  emptyNote: "(no project brief with a prompt block — run `tumwater init` first)",
230
234
  };
231
235
  }
@@ -257,9 +261,8 @@ function queuedBody(pane, opts) {
257
261
  return { body: [...searchLine, toneLine(`(no queued prompt matches "${pane.grep}")`, width)], emptyNote: "(inbox empty)" };
258
262
  }
259
263
  const listBudget = Math.max(1, eventBudget - searchLine.length);
260
- const win = state.paneCache ? entryBodyWindow(state.paneCache, state.paneScroll, listBudget, width) : { lines: [] };
261
264
  return {
262
- body: [...searchLine, ...win.lines.map((l) => toneLine(l, width))],
265
+ body: [...searchLine, ...paneCacheLines(state, listBudget, width).map((l) => toneLine(l, width))],
263
266
  emptyNote: "(inbox empty)",
264
267
  };
265
268
  }
@@ -318,6 +318,32 @@ export function createTuiPanes(deps) {
318
318
  return true;
319
319
  return queued.handleKey?.(key, ctx) ?? false;
320
320
  };
321
+ /** The ordered Ctrl dispatch table: each letter a pane router serves maps to its handler. */
322
+ const ctrlRoutes = {
323
+ n: routeCtrlN,
324
+ g: routeCtrlG,
325
+ e: routeCtrlE,
326
+ k: routeCtrlK,
327
+ w: routeCtrlW,
328
+ s: routeCtrlS,
329
+ p: routeCtrlP,
330
+ a: routeCtrlA,
331
+ u: routeCtrlU,
332
+ b: routeCtrlB,
333
+ l: routeCtrlL,
334
+ y: routeCtrlY,
335
+ o: routeCtrlO,
336
+ x: routeCtrlX,
337
+ r: routeCtrlR,
338
+ v: routeCtrlV,
339
+ };
340
+ /** Look up one bound Ctrl key in `ctrlRoutes`; null when the key is not a bound Ctrl chord. */
341
+ const ctrlRoute = (key, ctx) => {
342
+ if (!key.ctrl || key.name === undefined)
343
+ return null;
344
+ const route = ctrlRoutes[key.name];
345
+ return route === undefined ? null : route(key, ctx);
346
+ };
321
347
  /** Run the pane arms of the key dispatch; true when one consumed the key. `es` is the
322
348
  * editors' state, so the exclusion guards read the flags the editors hold. Each pane module
323
349
  * checks its own view/guard and returns true only when it consumed the key; this router keeps
@@ -327,38 +353,9 @@ export function createTuiPanes(deps) {
327
353
  // writes the line and return true whenever one is open — but each module restates its
328
354
  // mutual-exclusion guard so it reads on its own instead of leaning on the routing order.
329
355
  const ctx = { es };
330
- if (key.ctrl && key.name === "n")
331
- return routeCtrlN(key, ctx);
332
- if (key.ctrl && key.name === "g")
333
- return routeCtrlG(key, ctx);
334
- if (key.ctrl && key.name === "e")
335
- return routeCtrlE(key, ctx);
336
- if (key.ctrl && key.name === "k")
337
- return routeCtrlK(key, ctx);
338
- if (key.ctrl && key.name === "w")
339
- return routeCtrlW(key, ctx);
340
- if (key.ctrl && key.name === "s")
341
- return routeCtrlS(key, ctx);
342
- if (key.ctrl && key.name === "p")
343
- return routeCtrlP(key, ctx);
344
- if (key.ctrl && key.name === "a")
345
- return routeCtrlA(key, ctx);
346
- if (key.ctrl && key.name === "u")
347
- return routeCtrlU(key, ctx);
348
- if (key.ctrl && key.name === "b")
349
- return routeCtrlB(key, ctx);
350
- if (key.ctrl && key.name === "l")
351
- return routeCtrlL(key, ctx);
352
- if (key.ctrl && key.name === "y")
353
- return routeCtrlY(key, ctx);
354
- if (key.ctrl && key.name === "o")
355
- return routeCtrlO(key, ctx);
356
- if (key.ctrl && key.name === "x")
357
- return routeCtrlX(key, ctx);
358
- if (key.ctrl && key.name === "r")
359
- return routeCtrlR(key, ctx);
360
- if (key.ctrl && key.name === "v")
361
- return routeCtrlV(key, ctx);
356
+ const ctrl = ctrlRoute(key, ctx);
357
+ if (ctrl !== null)
358
+ return ctrl;
362
359
  const arrow = arrowDir(key.name);
363
360
  if (arrow !== null)
364
361
  return routeArrow(key, ctx);
@@ -3,12 +3,11 @@ import { render as inkRender } from "ink";
3
3
  import { TuiApp } from "./tui-app.js";
4
4
  import { openQuestions } from "../../backlog/backlog.js";
5
5
  import { refusedEntries, stalledEntries } from "../../backlog/backlog-eligibility.js";
6
- import { capacityContention, instancePoolProblems, planDependencyAlert, providerCapProblems, regressedFixesAlert, staleOpenQuestions, staleWorkspacePins } from "../alert-feeds.js";
6
+ import { getOrphanAlert, planDependencyAlert, staleOpenQuestions } from "../alert-feeds.js";
7
7
  import { strandedPlanEntriesFor } from "../../backlog/backlog-structure.js";
8
8
  import { duplicatedBacklogHeadings, fixClaimProblems, liveFinishedEntriesFor, validationGapProblems } from "../../doctor/doctor-backlog-plans.js";
9
9
  import { snapshot } from "../../status/status-data.js";
10
10
  import { renderStatusSpans } from "../status-render.js";
11
- import { staleClaimEntries } from "../../scheduling/claim-entries.js";
12
11
  import { teamAlerts } from "../team-alerts.js";
13
12
  import { captureStartupBuild, createReloadWatch, reexecSelf, watchReloadSupervisor, } from "../../redeploy/self-reload.js";
14
13
  import { renderInputView, tuiTerminalError, } from "./tui-input.js";
@@ -93,7 +92,7 @@ export async function runTui(root, seams = {}) {
93
92
  const view = s.view;
94
93
  // What needs the operator — the dashboard's alert banners (team-alerts.ts's teamAlerts), as
95
94
  // attention lines under the header. Each consumes one line of the height budget.
96
- const attention = alertLines(teamAlerts(snap, openQuestions(root), status.roles, Date.now(), refusedEntries(root), planDependencyAlert(root), stalledEntries(root), strandedPlanEntriesFor(root), duplicatedBacklogHeadings(root), validationGapProblems(root), capacityContention(root), regressedFixesAlert(root, Date.now()), staleClaimEntries(root, status.roles.map((r) => r.role), Date.now()), staleOpenQuestions(root), fixClaimProblems(root), staleWorkspacePins(root, Date.now()), liveFinishedEntriesFor(root), instancePoolProblems(root), providerCapProblems(root)), width);
95
+ const attention = alertLines(teamAlerts(snap, openQuestions(root), status.roles, Date.now(), refusedEntries(root), planDependencyAlert(root), stalledEntries(root), strandedPlanEntriesFor(root), duplicatedBacklogHeadings(root), validationGapProblems(root), staleOpenQuestions(root), fixClaimProblems(root), liveFinishedEntriesFor(root), getOrphanAlert(root)), width);
97
96
  const statusLines = [status.lines[0] ?? [], ...attention, ...status.lines.slice(1)];
98
97
  // Numbered previews of prompts queued for the director, between the table and the
99
98
  // activity pane: what will run next, in execution order. Each line consumes exactly
@@ -124,7 +123,7 @@ export async function runTui(root, seams = {}) {
124
123
  // The frame as the ink tree draws it (tui-app.tsx): the same composed lines the
125
124
  // hand-painted string frame joined, as clipped StatusLines — one column row per line,
126
125
  // one styled Text per span, colors through tui-app.tsx's toneColor.
127
- const mode = { budget: s.budgetMode, concurrencyMode: s.concurrencyMode, checksMode: s.checksMode, checkMode: s.checkMode, fallbackMode: s.fallbackMode, modelMode: s.modelMode, rolePromptFor: s.rolePromptFor, attachMode: s.attachMode, pauseEditMode: s.pauseEditMode, pauseEditRole: s.pauseEditRole, pauseEditWake: s.pauseEditWake, pauseEditStop: s.pauseEditStop, queuedEditFor: s.queuedEditFor, answerFor: s.answerFor, entryEditFor: s.entryEditFor, notesEditFor: s.notesEditFor, briefEditMode: s.briefEditMode, retireMode: s.retireMode, releaseMode: s.releaseMode, roleEnabledMode: s.roleEnabledMode, quietMode: s.quietMode, quietTeam: s.quietTeam, providerCapsMode: s.providerCapsMode, claudeReserveMode: s.claudeReserveMode, intervalMode: s.intervalMode, roleModelMode: s.roleModelMode, instructionsEditFor: s.instructionsEditFor, slotsMode: s.slotsMode, images: s.images, notifyMode: s.notifyMode, backendsMode: s.backendsMode, instancesMode: s.instancesMode };
126
+ const mode = { budget: s.budgetMode, concurrencyMode: s.concurrencyMode, checksMode: s.checksMode, checkMode: s.checkMode, fallbackMode: s.fallbackMode, modelMode: s.modelMode, rolePromptFor: s.rolePromptFor, attachMode: s.attachMode, pauseEditMode: s.pauseEditMode, pauseEditRole: s.pauseEditRole, pauseEditWake: s.pauseEditWake, pauseEditStop: s.pauseEditStop, queuedEditFor: s.queuedEditFor, answerFor: s.answerFor, entryEditFor: s.entryEditFor, notesEditFor: s.notesEditFor, briefEditMode: s.briefEditMode, retireMode: s.retireMode, releaseMode: s.releaseMode, roleEnabledMode: s.roleEnabledMode, quietMode: s.quietMode, quietTeam: s.quietTeam, intervalMode: s.intervalMode, roleModelMode: s.roleModelMode, instructionsEditFor: s.instructionsEditFor, slotsMode: s.slotsMode, images: s.images, notifyMode: s.notifyMode, backendsMode: s.backendsMode, instancesMode: s.instancesMode };
128
127
  const lines = [...statusLines, []]; // a blank row below the header
129
128
  for (const [i, preview] of queued.entries())
130
129
  lines.push(toneLine(`${i + 1}. ${preview}`, width));