tumwater 0.4.2 → 0.4.4

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 (354) 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} +27 -13
  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 +6 -27
  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 +8 -0
  14. package/dist/src/agent/codex/codex-args.js +5 -5
  15. package/dist/src/agent/codex/codex-backend.js +2 -2
  16. package/dist/src/agent/codex/codex-env.js +28 -15
  17. package/dist/src/agent/codex/codex-hook-trust.js +39 -20
  18. package/dist/src/agent/codex/codex-rollout.js +93 -56
  19. package/dist/src/agent/codex/codex-stream.js +1 -1
  20. package/dist/src/agent/env-scrub.js +18 -0
  21. package/dist/src/agent/image-input.js +68 -11
  22. package/dist/src/agent/notes-shim.js +2 -2
  23. package/dist/src/agent/opencode/opencode-backend.js +26 -24
  24. package/dist/src/agent/opencode/opencode-catalog.js +85 -36
  25. package/dist/src/agent/opencode/opencode-config.js +32 -14
  26. package/dist/src/agent/opencode/opencode-env.js +12 -13
  27. package/dist/src/agent/opencode/opencode-stream.js +47 -25
  28. package/dist/src/agent/session-ledger.js +23 -27
  29. package/dist/src/agent/subscription-account.js +2 -83
  30. package/dist/src/backlog/backlog-archive.js +3 -4
  31. package/dist/src/backlog/backlog-cli-view.js +81 -41
  32. package/dist/src/backlog/backlog-cli.js +57 -26
  33. package/dist/src/backlog/backlog-eligibility.js +56 -122
  34. package/dist/src/backlog/backlog-md.js +5 -5
  35. package/dist/src/backlog/backlog-parts.js +65 -27
  36. package/dist/src/backlog/{question-answer.js → backlog-question-answer.js} +1 -1
  37. package/dist/src/backlog/backlog-release.js +12 -12
  38. package/dist/src/backlog/backlog-structure.js +114 -61
  39. package/dist/src/backlog/backlog.js +3 -2
  40. package/dist/src/baseline/main-baseline.js +148 -84
  41. package/dist/src/baseline/main-guardrails.js +2 -2
  42. package/dist/src/baseline/main-watch.js +84 -50
  43. package/dist/src/brief/brief.js +26 -13
  44. package/dist/src/budget/budget-fallback-view.js +100 -243
  45. package/dist/src/budget/budget-wiring.js +29 -65
  46. package/dist/src/budget/budget.js +4 -5
  47. package/dist/src/budget/fallback-breaker.js +2 -2
  48. package/dist/src/build/build-check-detect.js +43 -14
  49. package/dist/src/build/build-check-events.js +2 -2
  50. package/dist/src/build/build-check-report.js +10 -0
  51. package/dist/src/build/build-check-scoped.js +40 -18
  52. package/dist/src/build/build-check.js +13 -5
  53. package/dist/src/build/build-stage.js +6 -4
  54. package/dist/src/change/change-data.js +1 -4
  55. package/dist/src/cli/cli-command-args.js +89 -52
  56. package/dist/src/cli/cli-completion-command.js +75 -0
  57. package/dist/src/cli/{config-commands.js → cli-config-commands.js} +82 -71
  58. package/dist/src/cli/cli-doctor-command.js +103 -0
  59. package/dist/src/cli/cli-flag-specs.js +42 -22
  60. package/dist/src/cli/cli-init.js +36 -14
  61. package/dist/src/cli/{log-commands.js → cli-log-commands.js} +43 -23
  62. package/dist/src/cli/{notify-command.js → cli-notify-command.js} +2 -2
  63. package/dist/src/cli/cli-query-commands.js +2 -2
  64. package/dist/src/cli/{question-commands.js → cli-question-commands.js} +86 -60
  65. package/dist/src/cli/cli-repo-gate.js +2 -2
  66. package/dist/src/cli/cli-role-command.js +2 -41
  67. package/dist/src/cli/cli-start-summary.js +4 -3
  68. package/dist/src/cli/cli-start-supervisor.js +190 -0
  69. package/dist/src/cli/cli-start.js +94 -244
  70. package/dist/src/cli/completion.js +85 -49
  71. package/dist/src/cli/follow-view.js +20 -2
  72. package/dist/src/cli/help.js +6 -13
  73. package/dist/src/cli.js +70 -176
  74. package/dist/src/collections.js +39 -27
  75. package/dist/src/concurrency/check-slots.js +70 -0
  76. package/dist/src/concurrency/lock.js +23 -1
  77. package/dist/src/concurrency/run-permit.js +49 -0
  78. package/dist/src/concurrency/semaphore.js +46 -250
  79. package/dist/src/config/config-editable-keys.js +4 -7
  80. package/dist/src/config/config-fallbacks.js +81 -101
  81. package/dist/src/config/config-field-checks.js +66 -37
  82. package/dist/src/config/config-keys.js +66 -40
  83. package/dist/src/config/config-live.js +4 -28
  84. package/dist/src/config/{model-selector.js → config-model-selector.js} +4 -4
  85. package/dist/src/config/config-request.js +163 -102
  86. package/dist/src/config/config-schema.js +10 -12
  87. package/dist/src/config/config-selector-checks.js +1 -1
  88. package/dist/src/config/config-validation-agents.js +2 -2
  89. package/dist/src/config/config-validation-roles.js +8 -72
  90. package/dist/src/config/config-validation.js +4 -39
  91. package/dist/src/config/config-views.js +26 -50
  92. package/dist/src/config/config-write.js +128 -102
  93. package/dist/src/config/config.js +5 -14
  94. package/dist/src/doctor/doctor-backlog-bugs.js +7 -6
  95. package/dist/src/doctor/doctor-backlog-plans.js +53 -120
  96. package/dist/src/doctor/doctor-backlog.js +16 -14
  97. package/dist/src/doctor/doctor-check-names.js +3 -6
  98. package/dist/src/doctor/doctor-checks.js +8 -8
  99. package/dist/src/doctor/doctor-claude-checks.js +70 -36
  100. package/dist/src/doctor/doctor-codex-checks.js +6 -6
  101. package/dist/src/doctor/doctor-contention-checks.js +57 -117
  102. package/dist/src/doctor/doctor-fallback-checks.js +48 -26
  103. package/dist/src/doctor/doctor-holds.js +8 -47
  104. package/dist/src/doctor/doctor-install.js +1 -1
  105. package/dist/src/doctor/doctor-launch-services.js +1 -1
  106. package/dist/src/doctor/doctor-model-checks.js +178 -94
  107. package/dist/src/doctor/doctor-notify.js +2 -2
  108. package/dist/src/doctor/doctor-opencode-checks.js +5 -4
  109. package/dist/src/doctor/doctor-orphans.js +122 -21
  110. package/dist/src/doctor/{outcome.js → doctor-outcome.js} +7 -0
  111. package/dist/src/doctor/doctor-pi-checks.js +2 -2
  112. package/dist/src/doctor/doctor-principles.js +3 -3
  113. package/dist/src/doctor/doctor-project-checks.js +24 -11
  114. package/dist/src/doctor/doctor-role-notes.js +1 -1
  115. package/dist/src/doctor/doctor.js +8 -11
  116. package/dist/src/events/event-format.js +44 -64
  117. package/dist/src/events/event-labels.js +1 -5
  118. package/dist/src/events/event-legacy.js +3 -5
  119. package/dist/src/events/{notify.js → event-notify.js} +6 -6
  120. package/dist/src/events/event-read.js +1 -1
  121. package/dist/src/events/event-window.js +1 -1
  122. package/dist/src/events/events.js +8 -0
  123. package/dist/src/failure/failure-cluster.js +9 -18
  124. package/dist/src/failure/failure-data.js +12 -8
  125. package/dist/src/failure/failure-render.js +46 -36
  126. package/dist/src/failure/failure-state-change.js +54 -120
  127. package/dist/src/failure/{time-spend.js → failure-time-spend.js} +117 -84
  128. package/dist/src/files/files.js +30 -0
  129. package/dist/src/files/json-object.js +8 -8
  130. package/dist/src/files/stat-cache.js +12 -8
  131. package/dist/src/gates/budget-gates.js +85 -84
  132. package/dist/src/gates/gate-polls.js +32 -74
  133. package/dist/src/gates/pause-gates.js +16 -2
  134. package/dist/src/gates/readiness.js +1 -1
  135. package/dist/src/gates/role-cap-gates.js +44 -35
  136. package/dist/src/gates/{startup-gate.js → startup-gates.js} +9 -8
  137. package/dist/src/gates/{streak-gate.js → streak-gates.js} +75 -52
  138. package/dist/src/git/git-diff.js +6 -2
  139. package/dist/src/git/git.js +75 -45
  140. package/dist/src/git/worktree-pool.js +3 -6
  141. package/dist/src/gui/gui-admin-endpoints.js +3 -3
  142. package/dist/src/gui/gui-args.js +12 -0
  143. package/dist/src/gui/gui-backlog-commands.js +4 -4
  144. package/dist/src/gui/gui-backlog-endpoints.js +61 -41
  145. package/dist/src/gui/gui-command.js +43 -24
  146. package/dist/src/gui/gui-config-commands.js +2 -64
  147. package/dist/src/gui/gui-endpoint-commands.js +1 -1
  148. package/dist/src/gui/gui-endpoint-common.js +19 -1
  149. package/dist/src/gui/gui-history-endpoints.js +100 -52
  150. package/dist/src/gui/gui-operator-commands.js +43 -48
  151. package/dist/src/gui/gui-prompt-commands.js +25 -25
  152. package/dist/src/gui/gui-report-endpoints.js +1 -1
  153. package/dist/src/gui/gui-server.js +1 -2
  154. package/dist/src/gui/gui-team-commands.js +2 -1
  155. package/dist/src/history/history-data.js +71 -44
  156. package/dist/src/history/history-queued.js +49 -27
  157. package/dist/src/history/history.js +54 -36
  158. package/dist/src/inbox/prompt-commands.js +59 -42
  159. package/dist/src/init/init.js +88 -40
  160. package/dist/src/merge/merge-backlog-conflicts.js +1 -1
  161. package/dist/src/merge/merge-check-failures.js +12 -9
  162. package/dist/src/merge/merge-core.js +7 -32
  163. package/dist/src/merge/merge-drain.js +11 -13
  164. package/dist/src/merge/merge-pipeline.js +6 -7
  165. package/dist/src/merge/merge-post-review-drain.js +11 -14
  166. package/dist/src/merge/merge-queue-render.js +3 -3
  167. package/dist/src/merge/merge-requeue.js +1 -1
  168. package/dist/src/merge/merge-slot.js +0 -8
  169. package/dist/src/merge/merge-stack.js +17 -18
  170. package/dist/src/merge/merge-to-main.js +13 -58
  171. package/dist/src/merge/merge-verify.js +12 -21
  172. package/dist/src/notes-cli.js +52 -42
  173. package/dist/src/operator/operator-commands.js +80 -47
  174. package/dist/src/operator/operator-intent.js +3 -3
  175. package/dist/src/orchestrator/orchestrator-launch.js +31 -63
  176. package/dist/src/orchestrator/orchestrator-poll.js +44 -69
  177. package/dist/src/orchestrator/orchestrator-scheduling.js +5 -9
  178. package/dist/src/orchestrator/orchestrator.js +8 -21
  179. package/dist/src/orchestrator/scheduling-admit.js +101 -68
  180. package/dist/src/orchestrator/scheduling-scope.js +18 -27
  181. package/dist/src/orchestrator/scheduling-skip.js +10 -14
  182. package/dist/src/orchestrator/startup-migrations.js +1 -1
  183. package/dist/src/paths.js +10 -9
  184. package/dist/src/pi/pi-args.js +5 -2
  185. package/dist/src/pi/pi-backend.js +2 -2
  186. package/dist/src/pi/pi-bin.js +3 -3
  187. package/dist/src/pi/pi-event-line.js +28 -1
  188. package/dist/src/pi/pi-listed-models.js +13 -0
  189. package/dist/src/pi/pi-models.js +43 -29
  190. package/dist/src/pi/pi-settings.js +43 -20
  191. package/dist/src/pi/pi-stream.js +41 -8
  192. package/dist/src/pi/pi-watchdogs.js +43 -15
  193. package/dist/src/pi/pi.js +2 -2
  194. package/dist/src/pi-extension/context-shake.js +32 -20
  195. package/dist/src/pi-extension/kill-guard.js +69 -0
  196. package/dist/src/process/process-group.js +51 -26
  197. package/dist/src/process/process.js +20 -12
  198. package/dist/src/process/run-marker.js +33 -15
  199. package/dist/src/process/supervisor.js +40 -28
  200. package/dist/src/prompt/dialect.js +1 -1
  201. package/dist/src/prompt/gate-notes.js +2 -2
  202. package/dist/src/prompt/gate-prompts.js +1 -44
  203. package/dist/src/prompt/notes-backlog.js +66 -37
  204. package/dist/src/prompt/principles.js +6 -4
  205. package/dist/src/prompt/prompt-followup.js +12 -4
  206. package/dist/src/prompt/prompt.js +88 -38
  207. package/dist/src/redeploy/redeployer.js +21 -1
  208. package/dist/src/report/report-data.js +53 -37
  209. package/dist/src/report/report-fold.js +16 -9
  210. package/dist/src/report/report-render.js +21 -194
  211. package/dist/src/report/report-since-data.js +4 -3
  212. package/dist/src/report/report-since-render.js +173 -0
  213. package/dist/src/report/report-usage-fold.js +8 -8
  214. package/dist/src/report/report.js +46 -22
  215. package/dist/src/review/diff-rules.js +35 -21
  216. package/dist/src/review/post-merge-review-render.js +9 -3
  217. package/dist/src/review/post-merge-review.js +48 -41
  218. package/dist/src/review/review-precheck.js +14 -4
  219. package/dist/src/review/review.js +48 -170
  220. package/dist/src/review/secret-leaks.js +26 -16
  221. package/dist/src/roles/legacy-role-migration.js +6 -3
  222. package/dist/src/roles/role-catalog.js +11 -3
  223. package/dist/src/roles/role-guidance.js +16 -12
  224. package/dist/src/roles/role-render.js +7 -46
  225. package/dist/src/roles/role-view.js +10 -33
  226. package/dist/src/run/acceptance-greps.js +88 -45
  227. package/dist/src/run/ci-parity.js +19 -8
  228. package/dist/src/run/run-apply.js +12 -15
  229. package/dist/src/run/run-detail-data.js +121 -68
  230. package/dist/src/run/run-detail-render.js +92 -35
  231. package/dist/src/run/run-detail.js +72 -41
  232. package/dist/src/run/run-prompt.js +63 -50
  233. package/dist/src/run/run-resume.js +76 -29
  234. package/dist/src/run/run-stage.js +141 -90
  235. package/dist/src/run/run-timing.js +8 -2
  236. package/dist/src/run/run-usage.js +31 -1
  237. package/dist/src/run/run-verdict.js +128 -50
  238. package/dist/src/run/stage-check.js +60 -50
  239. package/dist/src/run/stage-checks-shared.js +28 -0
  240. package/dist/src/run/stage-name-checks.js +111 -92
  241. package/dist/src/run/stage-path-checks.js +69 -42
  242. package/dist/src/run/stage-prose-checks.js +55 -40
  243. package/dist/src/runner/{leftover.js → runner-leftover.js} +9 -26
  244. package/dist/src/runner/runner-outcome.js +7 -2
  245. package/dist/src/runner/runner-pi.js +9 -22
  246. package/dist/src/runner/runner-recovery.js +5 -13
  247. package/dist/src/runner/{revision.js → runner-revision.js} +1 -2
  248. package/dist/src/runner/runner-run.js +103 -50
  249. package/dist/src/runner/runner-state-load.js +18 -28
  250. package/dist/src/runner/runner-state.js +0 -14
  251. package/dist/src/runner/runner.js +41 -25
  252. package/dist/src/scheduling/backoff.js +8 -3
  253. package/dist/src/scheduling/claim-entries.js +2 -1
  254. package/dist/src/scheduling/claims.js +0 -4
  255. package/dist/src/scheduling/scheduling.js +53 -66
  256. package/dist/src/status/status-budget.js +4 -43
  257. package/dist/src/status/status-data.js +35 -39
  258. package/dist/src/status/status-role-notes.js +2 -2
  259. package/dist/src/status/status-roles.js +9 -18
  260. package/dist/src/team/{error-storm.js → team-error-storm.js} +1 -1
  261. package/dist/src/team/{failure-spread.js → team-failure-spread.js} +5 -5
  262. package/dist/src/team/team-hold.js +105 -70
  263. package/dist/src/team/{orchestrator-info.js → team-orchestrator-info.js} +55 -36
  264. package/dist/src/team/team-polls.js +53 -201
  265. package/dist/src/team/{reclaim.js → team-reclaim.js} +61 -31
  266. package/dist/src/team/team-state.js +4 -13
  267. package/dist/src/text/datetime.js +16 -7
  268. package/dist/src/text/markdown.js +1 -1
  269. package/dist/src/text/phrases.js +29 -15
  270. package/dist/src/text/suggest.js +1 -1
  271. package/dist/src/text/text.js +6 -5
  272. package/dist/src/ui/alert-feeds.js +46 -111
  273. package/dist/src/ui/badges.js +17 -88
  274. package/dist/src/ui/gui/gui-client-boot.js +2 -8
  275. package/dist/src/ui/gui/gui-client-composer.js +1 -1
  276. package/dist/src/ui/gui/gui-client-drawer-config.js +8 -33
  277. package/dist/src/ui/gui/gui-client-drawer.js +1 -1
  278. package/dist/src/ui/gui/gui-client-feed.js +1 -1
  279. package/dist/src/ui/gui/gui-client-history.js +28 -14
  280. package/dist/src/ui/gui/gui-client-model.js +0 -12
  281. package/dist/src/ui/gui/gui-client-operator.js +7 -5
  282. package/dist/src/ui/gui/gui-client-pending.js +7 -6
  283. package/dist/src/ui/gui/gui-client-report-chart.js +5 -5
  284. package/dist/src/ui/gui/gui-client-report.js +1 -1
  285. package/dist/src/ui/gui/gui-client-roles.js +0 -1
  286. package/dist/src/ui/gui/gui-client-settings.js +43 -84
  287. package/dist/src/ui/gui/gui-client-team.js +12 -6
  288. package/dist/src/ui/gui/gui-client.js +7 -1
  289. package/dist/src/ui/progress-data.js +107 -44
  290. package/dist/src/ui/run-progress-model.js +20 -4
  291. package/dist/src/ui/status-model.js +2 -24
  292. package/dist/src/ui/status-payload.js +11 -30
  293. package/dist/src/ui/status-render.js +13 -24
  294. package/dist/src/ui/team-alert-backlog.js +14 -204
  295. package/dist/src/ui/team-alert-shared.js +6 -29
  296. package/dist/src/ui/team-alert-status.js +9 -170
  297. package/dist/src/ui/team-alert-tail.js +19 -215
  298. package/dist/src/ui/team-alerts.js +8 -15
  299. package/dist/src/ui/transcript-run.js +27 -16
  300. package/dist/src/ui/tui/tui-backlog.js +2 -2
  301. package/dist/src/ui/tui/tui-editor-answer.js +12 -10
  302. package/dist/src/ui/tui/tui-editor-attach.js +4 -3
  303. package/dist/src/ui/tui/tui-editor-backends.js +4 -3
  304. package/dist/src/ui/tui/tui-editor-brief.js +6 -9
  305. package/dist/src/ui/tui/tui-editor-budget.js +4 -3
  306. package/dist/src/ui/tui/tui-editor-config.js +80 -68
  307. package/dist/src/ui/tui/tui-editor-entry.js +12 -20
  308. package/dist/src/ui/tui/tui-editor-pause.js +51 -57
  309. package/dist/src/ui/tui/tui-editor-prompt.js +9 -13
  310. package/dist/src/ui/tui/tui-editor-queued.js +11 -14
  311. package/dist/src/ui/tui/tui-editor-quiet.js +4 -3
  312. package/dist/src/ui/tui/tui-editor-release.js +4 -3
  313. package/dist/src/ui/tui/tui-editor-retire.js +7 -7
  314. package/dist/src/ui/tui/tui-editor-role-enabled.js +4 -3
  315. package/dist/src/ui/tui/tui-editor-role-file.js +4 -4
  316. package/dist/src/ui/tui/tui-editor-role-key.js +4 -3
  317. package/dist/src/ui/tui/tui-editor-role-model.js +97 -15
  318. package/dist/src/ui/tui/tui-editor-search.js +4 -3
  319. package/dist/src/ui/tui/tui-editor-session.js +17 -4
  320. package/dist/src/ui/tui/tui-editors.js +5 -10
  321. package/dist/src/ui/tui/tui-frame.js +182 -134
  322. package/dist/src/ui/tui/tui-guard.js +20 -0
  323. package/dist/src/ui/tui/tui-input.js +167 -98
  324. package/dist/src/ui/tui/tui-keymap.js +51 -29
  325. package/dist/src/ui/tui/tui-keys.js +1 -1
  326. package/dist/src/ui/tui/tui-pane-activity.js +5 -4
  327. package/dist/src/ui/tui/tui-pane-backlog.js +74 -36
  328. package/dist/src/ui/tui/tui-pane-ctx.js +1 -1
  329. package/dist/src/ui/tui/tui-pane-health.js +252 -230
  330. package/dist/src/ui/tui/tui-pane-history.js +67 -74
  331. package/dist/src/ui/tui/tui-pane-pending.js +288 -247
  332. package/dist/src/ui/tui/tui-pane-queued.js +25 -27
  333. package/dist/src/ui/tui/tui-pane-report.js +2 -1
  334. package/dist/src/ui/tui/tui-pane-session.js +15 -10
  335. package/dist/src/ui/tui/tui-pane-transcript.js +19 -31
  336. package/dist/src/ui/tui/tui-pane.js +14 -11
  337. package/dist/src/ui/tui/tui-panes.js +38 -33
  338. package/dist/src/ui/tui/tui.js +4 -7
  339. package/dist/src/verdict/fix-claim.js +107 -56
  340. package/dist/src/verdict/refusal.js +1 -1
  341. package/dist/src/verdict/reply-contract.js +3 -4
  342. package/package.json +5 -5
  343. package/dist/src/concurrency/provider-lease.js +0 -105
  344. package/dist/src/review/review-followup.js +0 -87
  345. package/dist/src/review/review-run.js +0 -395
  346. package/dist/src/runner/model-fallback.js +0 -76
  347. package/dist/src/runner/runner-fallback.js +0 -66
  348. package/dist/src/ui/tui/tui-editor-claude-reserve.js +0 -24
  349. package/dist/src/ui/tui/tui-editor-provider-caps.js +0 -27
  350. /package/dist/src/cli/{brief-command.js → cli-brief-command.js} +0 -0
  351. /package/dist/src/doctor/{probe.js → doctor-probe.js} +0 -0
  352. /package/dist/src/gates/{disk-gate.js → disk-gates.js} +0 -0
  353. /package/dist/src/operator/{retire.js → operator-retire.js} +0 -0
  354. /package/dist/src/operator/{revert.js → operator-revert.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,8 @@
1
1
  import { REPORT_SINCE_MAX_MS } from "../../events/event-window.js";
2
+ import { DAY_MS, HOUR_MS } from "../../text/datetime.js";
2
3
  import { HISTORY_RESULT_CLASSES, HISTORY_RESULT_FLAG_NAMES } from "../../history/history-data.js";
3
4
  import { baseRoleOf } from "../../roles/runner-ids.js";
5
+ import { distinctOrdered } from "../../collections.js";
4
6
  /** Every Ctrl+<key> cycle walks its own ReadonlyArray<[key, label]> table one slot at a time,
5
7
  * wrapping past the last back to the first. Each handler holds a key the table carries (a null
6
8
  * default slot included), so findIndex always hits. */
@@ -18,9 +20,9 @@ export function cycleLabel(table, current) {
18
20
  * report --since`'s bound) rather than History's old hard-coded 604_800_000 magic number, which
19
21
  * was the same value but untethered from the cap it mirrors. */
20
22
  const SESSION_WINDOW_TAIL = [
21
- [3_600_000, "1h"],
22
- [21_600_000, "6h"],
23
- [86_400_000, "1d"],
23
+ [HOUR_MS, "1h"],
24
+ [6 * HOUR_MS, "6h"],
25
+ [DAY_MS, "1d"],
24
26
  [REPORT_SINCE_MAX_MS, "7d"],
25
27
  ];
26
28
  /** The History pane's session window cycle, Ctrl+S: the default newest-N read, then the same
@@ -40,13 +42,7 @@ export const HISTORY_WINDOWS = [
40
42
  * names that collect it identically. `feature`/`feature-2` therefore offer one slot, and the
41
43
  * name the tab strip and hint show is the name the collector honors. */
42
44
  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];
45
+ return [null, ...distinctOrdered(roleIds.map((id) => baseRoleOf(id)))];
50
46
  }
51
47
  /** The History pane's session outcome cycle, Ctrl+O: all runs, then the canonical classes
52
48
  * `tumwater history --result` and the dashboard's History filter speak. The class list comes
@@ -131,3 +127,12 @@ export function createTuiPaneSession(deps) {
131
127
  runPatch: deps.runPatch,
132
128
  };
133
129
  }
130
+ /** Re-enter the active pane at its head: reset the shared within-body scroll offset to the top
131
+ * and repaint. The panes' key-handler and refresh arms reset the scroll and request a render
132
+ * together every time they return a view to its start, so this single helper keeps that pair
133
+ * from drifting — and names the intent (the pane starts at its head) once instead of at each
134
+ * call site. A key-handler caller that consumed the key follows with its own `return true`. */
135
+ export function paneAtHead(session) {
136
+ session.paneScroll = 0;
137
+ session.requestRender();
138
+ }
@@ -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
  }
@@ -154,7 +154,15 @@ export function createTuiPanes(deps) {
154
154
  return true;
155
155
  return report.handleKey?.(key, ctx) ?? false;
156
156
  };
157
- const routeCtrlK = (key, ctx) => health.handleKey?.(key, ctx) ?? false;
157
+ const routeCtrlK = (key, ctx) => {
158
+ // Health owns Ctrl+K (kill orphans), routed ahead of every other pane like the other
159
+ // Health arms; its own two-step kill arm consumes the key on the Health view and falls
160
+ // through (returns false) off it. The explicit guard keeps the key in the Ctrl set the
161
+ // TUI keys docs check scans, matching the rest of these routers.
162
+ if (key.ctrl && key.name === "k")
163
+ return health.handleKey?.(key, ctx) ?? false;
164
+ return false;
165
+ };
158
166
  const routeCtrlW = (key, ctx) => history.handleKey?.(key, ctx) ?? false;
159
167
  const routeCtrlS = (key, ctx) => {
160
168
  // Health owns Ctrl+S (drop broken pins) ahead of the pane router's fallthrough; off the
@@ -318,6 +326,32 @@ export function createTuiPanes(deps) {
318
326
  return true;
319
327
  return queued.handleKey?.(key, ctx) ?? false;
320
328
  };
329
+ /** The ordered Ctrl dispatch table: each letter a pane router serves maps to its handler. */
330
+ const ctrlRoutes = {
331
+ n: routeCtrlN,
332
+ g: routeCtrlG,
333
+ e: routeCtrlE,
334
+ k: routeCtrlK,
335
+ w: routeCtrlW,
336
+ s: routeCtrlS,
337
+ p: routeCtrlP,
338
+ a: routeCtrlA,
339
+ u: routeCtrlU,
340
+ b: routeCtrlB,
341
+ l: routeCtrlL,
342
+ y: routeCtrlY,
343
+ o: routeCtrlO,
344
+ x: routeCtrlX,
345
+ r: routeCtrlR,
346
+ v: routeCtrlV,
347
+ };
348
+ /** Look up one bound Ctrl key in `ctrlRoutes`; null when the key is not a bound Ctrl chord. */
349
+ const ctrlRoute = (key, ctx) => {
350
+ if (!key.ctrl || key.name === undefined)
351
+ return null;
352
+ const route = ctrlRoutes[key.name];
353
+ return route === undefined ? null : route(key, ctx);
354
+ };
321
355
  /** Run the pane arms of the key dispatch; true when one consumed the key. `es` is the
322
356
  * editors' state, so the exclusion guards read the flags the editors hold. Each pane module
323
357
  * checks its own view/guard and returns true only when it consumed the key; this router keeps
@@ -327,38 +361,9 @@ export function createTuiPanes(deps) {
327
361
  // writes the line and return true whenever one is open — but each module restates its
328
362
  // mutual-exclusion guard so it reads on its own instead of leaning on the routing order.
329
363
  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);
364
+ const ctrl = ctrlRoute(key, ctx);
365
+ if (ctrl !== null)
366
+ return ctrl;
362
367
  const arrow = arrowDir(key.name);
363
368
  if (arrow !== null)
364
369
  return routeArrow(key, ctx);
@@ -2,13 +2,10 @@ import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { render as inkRender } from "ink";
3
3
  import { TuiApp } from "./tui-app.js";
4
4
  import { openQuestions } from "../../backlog/backlog.js";
5
- import { refusedEntries, stalledEntries } from "../../backlog/backlog-eligibility.js";
6
- import { capacityContention, instancePoolProblems, planDependencyAlert, providerCapProblems, regressedFixesAlert, staleOpenQuestions, staleWorkspacePins } from "../alert-feeds.js";
7
- import { strandedPlanEntriesFor } from "../../backlog/backlog-structure.js";
8
- import { duplicatedBacklogHeadings, fixClaimProblems, liveFinishedEntriesFor, validationGapProblems } from "../../doctor/doctor-backlog-plans.js";
5
+ import { refusedEntries } from "../../backlog/backlog-eligibility.js";
6
+ import { getOrphanAlert } from "../alert-feeds.js";
9
7
  import { snapshot } from "../../status/status-data.js";
10
8
  import { renderStatusSpans } from "../status-render.js";
11
- import { staleClaimEntries } from "../../scheduling/claim-entries.js";
12
9
  import { teamAlerts } from "../team-alerts.js";
13
10
  import { captureStartupBuild, createReloadWatch, reexecSelf, watchReloadSupervisor, } from "../../redeploy/self-reload.js";
14
11
  import { renderInputView, tuiTerminalError, } from "./tui-input.js";
@@ -93,7 +90,7 @@ export async function runTui(root, seams = {}) {
93
90
  const view = s.view;
94
91
  // What needs the operator — the dashboard's alert banners (team-alerts.ts's teamAlerts), as
95
92
  // 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);
93
+ const attention = alertLines(teamAlerts(snap, openQuestions(root), status.roles, Date.now(), refusedEntries(root), getOrphanAlert(root)), width);
97
94
  const statusLines = [status.lines[0] ?? [], ...attention, ...status.lines.slice(1)];
98
95
  // Numbered previews of prompts queued for the director, between the table and the
99
96
  // activity pane: what will run next, in execution order. Each line consumes exactly
@@ -124,7 +121,7 @@ export async function runTui(root, seams = {}) {
124
121
  // The frame as the ink tree draws it (tui-app.tsx): the same composed lines the
125
122
  // hand-painted string frame joined, as clipped StatusLines — one column row per line,
126
123
  // 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 };
124
+ 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
125
  const lines = [...statusLines, []]; // a blank row below the header
129
126
  for (const [i, preview] of queued.entries())
130
127
  lines.push(toneLine(`${i + 1}. ${preview}`, width));