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
@@ -8,10 +8,15 @@ import { brokenPinDropLines, leftoverRetireLines } from "../../doctor/doctor-con
8
8
  import { describeArchivedFile } from "../../backlog/backlog-archive.js";
9
9
  import { shortSha } from "../../text/format.js";
10
10
  import { promptLineFree } from "./tui-pane-ctx.js";
11
- /** The Health pane (view roleIds.length + 7) of the TUI (src/ui/tui/tui.tsx): the problems-only
12
- * Ctrl+O toggle, the Ctrl+A commit-backlog, the Ctrl+B
13
- * backlog archive, and the runDoctor collector it re-reads on every entry. Split out of the
14
- * pane-navigation monolith; it owns the problems flag and the last resolved report. */
11
+ import { paneAtHead } from "./tui-pane-session.js";
12
+ /** The Health pane (view roleIds.length + 7) of the TUI (src/ui/tui/tui.tsx): the doctor
13
+ * re-run (Ctrl+R), reclaim (Ctrl+E), notify test (Ctrl+N), commit-backlog (Ctrl+A),
14
+ * kill-orphans (Ctrl+K), broken-pin drop (Ctrl+S), resync-install (Ctrl+U),
15
+ * retire-leftovers (Ctrl+L), backlog archive (Ctrl+B) and problems-only toggle (Ctrl+O),
16
+ * beside the runDoctor collector they re-read on every entry. Split out of the
17
+ * pane-navigation monolith; it owns the problems flag and the last resolved report.
18
+ * Each key arm lives in its own named inner const below so its guard, flash wording and
19
+ * repaint are found by name; handleKey is only the ordered dispatch of those arms. */
15
20
  export function createHealthPane(session) {
16
21
  // Within the Health pane (Ctrl+O), whether the cached report paints only its warn and fail
17
22
  // checks, the same problems-only trim `tumwater doctor --problems` applies. Session-only
@@ -46,247 +51,266 @@ export function createHealthPane(session) {
46
51
  session.requestRender();
47
52
  });
48
53
  };
49
- const handleKey = (key, ctx) => {
50
- if (key.ctrl && key.name === "r" && promptLineFree(ctx.es) && session.view === session.roleIds.length + 7) {
51
- // Ctrl+R on the Health pane re-runs the same doctor checks in place — the terminal
52
- // twin of the dashboard Health card's Re-run button — repainting through the current
53
- // problems-only toggle, and re-enters at the head so the changed body starts at the
54
- // top. Every other view keeps today's Ctrl+R (the backlog release).
55
- void run();
56
- session.paneScroll = 0;
57
- session.requestRender();
58
- return true;
59
- }
60
- if (key.ctrl && key.name === "e" && promptLineFree(ctx.es) && session.view === session.roleIds.length + 7) {
61
- // Ctrl+E on the Health pane runs one manual reclaim pass now — the terminal twin of the
62
- // dashboard disk row's reclaim button and the core `tumwater reclaim`, both reached
63
- // through the same runManualReclaim. With a live team the pass is requested through the
64
- // marker a running orchestrator consumes within one poll; with none running it runs
65
- // in-process, since nothing is in use. The confirmation flashes verbatim; the doctor
66
- // re-runs so the free-disk row reflects what the pass freed, and a write failure flashes
67
- // instead of killing the TUI. Every other view keeps Ctrl+E's trailing-window cycle.
68
- void runManualReclaim(session.root).then((message) => {
69
- session.flashMessage(message);
54
+ // The Health key-arm guard every arm shares: the prompt line is free (an editor or typed
55
+ // text would keep the key for its own view's meaning) and the Health view is current; the
56
+ // armed arms (the five two-press/edit actions) additionally require a truly empty prompt
57
+ // line so a typed draft cannot arm on Ctrl+K/S/U/L/B. True means the arm may run.
58
+ const onHealthView = (ctx, armed) => promptLineFree(ctx.es) &&
59
+ (!armed || (ctx.es.input ?? "") === "") &&
60
+ session.view === session.roleIds.length + 7;
61
+ // Run one Health-pane action: when the promise resolves its value is flashed through
62
+ // `done`, then — unless `rerun` is cleared (the notify test, whose outcome needs no doctor
63
+ // refresh) — the checks re-run in place and the pane re-enters at its head, the tail every
64
+ // action arm repeated; when it rejects, the failure flashes instead of killing the TUI. The
65
+ // starting notice and any pre-action render stay with each arm (some, like reclaim, show
66
+ // none), so the helper owns only the shared then/error plumbing. An arm whose guard fails
67
+ // returns false so the key falls through to the rest of the dispatch exactly as today.
68
+ const healthAction = (promise, done, rerun = true) => {
69
+ void promise.then((value) => {
70
+ done(value);
71
+ if (rerun) {
70
72
  void run();
71
73
  session.paneScroll = 0;
72
- session.requestRender();
73
- }, (err) => {
74
- session.flashError(err);
75
- session.requestRender();
76
- });
77
- return true;
78
- }
79
- if (key.ctrl && key.name === "n" && promptLineFree(ctx.es) && session.view === session.roleIds.length + 7) {
80
- // Ctrl+N on the Health pane runs the saved notify hook once with the synthetic test event —
81
- // the terminal twin of `tumwater notify --test` and the dashboard Settings card's Send test,
82
- // both reached through the same testNotifyHook (src/cli/notify-command.ts) — so a terminal
83
- // operator can check the push channel before a real page. The hook runs with stdio "ignore"
84
- // (the dashboard's choice) so a chatty hook's output cannot corrupt the alternate screen; the
85
- // running notice flashes first, then the outcome (the not-configured wording when no hook is
86
- // set, the hook's own outcome line otherwise), and a hook or write failure flashes instead of
87
- // killing the TUI. The action never blocks the render. Every other view keeps Ctrl+N (the
88
- // transcript notes/change cycle, the History run trail, and the backlog new-entry form).
89
- session.flashMessage("running the notify hook …");
90
- session.requestRender();
91
- void testNotifyHook(session.root, "ignore").then((outcome) => {
92
- session.flashMessage(outcome.status === "not_configured"
93
- ? "notify is not configured (set `notify` in tumwater.json)"
94
- : outcome.message);
95
- session.requestRender();
96
- }, (err) => {
97
- session.flashError(err);
98
- session.requestRender();
99
- });
100
- return true;
101
- }
102
- if (key.ctrl && key.name === "a" && promptLineFree(ctx.es) && session.view === session.roleIds.length + 7) {
103
- // Ctrl+A on the Health pane commits each uncommitted tracked backlog or brief file in the
104
- // primary checkout — the terminal twin of `tumwater doctor --commit-backlog` and the
105
- // dashboard Health card's Commit backlog button, all reached through the same
106
- // commitUncommittedBacklog core. It flashes the same message the CLI prints (the shared
107
- // backlogCommitMessage: the backlogCommitLines, or the nothing-to-commit line when the
108
- // checkout is clean), then re-runs the checks so the `uncommitted backlog` row repaints. A
109
- // failure flashes instead of killing the TUI. Every other view keeps Ctrl+A's backlog
110
- // answer editor (routed ahead of this pane) — the key dispatcher consults the editors
111
- // before the pane router, so this arm never shadows it.
112
- session.flashMessage("committing the uncommitted backlog/brief files …");
113
- session.requestRender();
114
- void session.commitBacklog(session.root).then((report) => {
115
- session.flashMessage(backlogCommitMessage(report));
116
- void run();
117
- session.paneScroll = 0;
118
- session.requestRender();
119
- }, (err) => {
120
- session.flashError(err);
121
- session.requestRender();
122
- });
123
- return true;
124
- }
125
- if (key.ctrl && key.name === "k" && promptLineFree(ctx.es) && (ctx.es.input ?? "") === "" && session.view === session.roleIds.length + 7) {
126
- // Ctrl+K on the Health pane, pressed twice, kills the orphaned processes the orphans check
127
- // lists, each with its descendants — the terminal twin of `tumwater doctor --kill-orphans`
128
- // and the dashboard Health card's Kill orphans, all reached through the same killOrphans.
129
- // The first press only arms (beginKey disarms on any other key) and flashes what a second
130
- // press does; the second flashes the same lines the CLI prints, joined with "; ", then
131
- // re-runs the checks so the orphans row reflects the kill. A failure flashes instead of
132
- // killing the TUI. Ctrl+K stays the prompt-line editor's kill-line while text is typed
133
- // or an editor holds the line, and is unbound on every other view.
134
- if (!session.armedKillOrphans) {
135
- session.killOrphansArmed = true;
136
- session.flashMessage("press Ctrl+K again to kill the orphaned processes the orphans check lists — any other key keeps them");
137
- session.requestRender();
138
- return true;
139
74
  }
140
- session.flashMessage("killing orphaned processes …");
141
75
  session.requestRender();
142
- void session.killOrphans(session.root).then((report) => {
143
- session.flashMessage(orphanKillLines(report).join("; "));
144
- void run();
145
- session.paneScroll = 0;
146
- session.requestRender();
147
- }, (err) => {
148
- session.flashError(err);
149
- session.requestRender();
150
- });
151
- return true;
152
- }
153
- if (key.ctrl && key.name === "s" && promptLineFree(ctx.es) && (ctx.es.input ?? "") === "" && session.view === session.roleIds.length + 7) {
154
- // Ctrl+S on the Health pane, pressed twice, drops each broken refs/tumwater/ pin (one
155
- // whose commit is gone) — the terminal twin of `tumwater doctor --drop-broken-pins` and
156
- // the dashboard Health card's Drop broken pins, all reached through the same
157
- // dropBrokenPins. The first press only arms (beginKey disarms on any other key) and
158
- // flashes what a second press does; the second flashes the same lines the CLI prints,
159
- // joined with "; ", then re-runs the checks so the tumwater pins row reflects the drop.
160
- // A failure flashes instead of killing the TUI. Ctrl+S stays the prompt-line editor's
161
- // save-to-wherever while text is typed or an editor holds the line, and keeps its
162
- // quiet-hours (Activity), trailing-window (History) and min-run-interval (Transcript)
163
- // meanings off the Health view.
164
- if (!session.armedDropBrokenPins) {
165
- session.dropBrokenPinsArmed = true;
166
- session.flashMessage("press Ctrl+S again to drop each broken refs/tumwater/ pin whose commit is gone — any other key keeps them");
167
- session.requestRender();
168
- return true;
169
- }
170
- session.flashMessage("dropping broken pins …");
76
+ }, (err) => {
77
+ session.flashError(err);
171
78
  session.requestRender();
172
- void session.dropBrokenPins(session.root).then((report) => {
173
- const lines = brokenPinDropLines(report);
174
- session.flashMessage(lines.length > 0 ? lines.join("; ") : "no broken pins to drop");
175
- void run();
176
- session.paneScroll = 0;
177
- session.requestRender();
178
- }, (err) => {
179
- session.flashError(err);
180
- session.requestRender();
181
- });
182
- return true;
183
- }
184
- if (key.ctrl && key.name === "u" && promptLineFree(ctx.es) && (ctx.es.input ?? "") === "" && session.view === session.roleIds.length + 7) {
185
- // Ctrl+U on the Health pane re-syncs the root checkout's install now — the terminal twin
186
- // of `tumwater doctor --resync-install` and the dashboard Health card's Re-sync install,
187
- // all reached through the same resyncRootInstall, so the `install` row/alert's advice is
188
- // one action and clears once the install lands. It flashes the same line the CLI prints
189
- // (rootInstallResyncLines, joined with "; "), then re-runs the checks so the `root
190
- // install` row refreshes. A failure flashes instead of killing the TUI. Ctrl+U stays the
191
- // prompt-line editor's kill-to-start while text is typed or an editor holds the line, and
192
- // is unbound on every other view.
193
- session.flashMessage("re-syncing the root install …");
79
+ });
80
+ };
81
+ const healthRefresh = (ctx) => {
82
+ if (!onHealthView(ctx, false))
83
+ return false;
84
+ // Ctrl+R on the Health pane re-runs the same doctor checks in place — the terminal
85
+ // twin of the dashboard Health card's Re-run button — repainting through the current
86
+ // problems-only toggle, and re-enters at the head so the changed body starts at the
87
+ // top. Every other view keeps today's Ctrl+R (the backlog release).
88
+ void run();
89
+ paneAtHead(session);
90
+ return true;
91
+ };
92
+ const healthReclaim = (ctx) => {
93
+ if (!onHealthView(ctx, false))
94
+ return false;
95
+ // Ctrl+E on the Health pane runs one manual reclaim pass now — the terminal twin of the
96
+ // dashboard disk row's reclaim button and the core `tumwater reclaim`, both reached
97
+ // through the same runManualReclaim. With a live team the pass is requested through the
98
+ // marker a running orchestrator consumes within one poll; with none running it runs
99
+ // in-process, since nothing is in use. The confirmation flashes verbatim; the doctor
100
+ // re-runs so the free-disk row reflects what the pass freed, and a write failure flashes
101
+ // instead of killing the TUI. Every other view keeps Ctrl+E's trailing-window cycle.
102
+ healthAction(runManualReclaim(session.root), (message) => {
103
+ session.flashMessage(message);
104
+ });
105
+ return true;
106
+ };
107
+ const healthNotifyTest = (ctx) => {
108
+ if (!onHealthView(ctx, false))
109
+ return false;
110
+ // Ctrl+N on the Health pane runs the saved notify hook once with the synthetic test event —
111
+ // the terminal twin of `tumwater notify --test` and the dashboard Settings card's Send test,
112
+ // both reached through the same testNotifyHook (src/cli/notify-command.ts) — so a terminal
113
+ // operator can check the push channel before a real page. The hook runs with stdio "ignore"
114
+ // (the dashboard's choice) so a chatty hook's output cannot corrupt the alternate screen; the
115
+ // running notice flashes first, then the outcome (the not-configured wording when no hook is
116
+ // set, the hook's own outcome line otherwise), and a hook or write failure flashes instead of
117
+ // killing the TUI. The action never blocks the render. Every other view keeps Ctrl+N (the
118
+ // transcript notes/change cycle, the History run trail, and the backlog new-entry form).
119
+ session.flashMessage("running the notify hook …");
120
+ session.requestRender();
121
+ healthAction(testNotifyHook(session.root, "ignore"), (outcome) => {
122
+ session.flashMessage(outcome.status === "not_configured"
123
+ ? "notify is not configured (set `notify` in tumwater.json)"
124
+ : outcome.message);
125
+ }, false);
126
+ return true;
127
+ };
128
+ const healthCommitBacklog = (ctx) => {
129
+ if (!onHealthView(ctx, false))
130
+ return false;
131
+ // Ctrl+A on the Health pane commits each uncommitted tracked backlog or brief file in the
132
+ // primary checkout — the terminal twin of `tumwater doctor --commit-backlog` and the
133
+ // dashboard Health card's Commit backlog button, all reached through the same
134
+ // commitUncommittedBacklog core. It flashes the same message the CLI prints (the shared
135
+ // backlogCommitMessage: the backlogCommitLines, or the nothing-to-commit line when the
136
+ // checkout is clean), then re-runs the checks so the `uncommitted backlog` row repaints. A
137
+ // failure flashes instead of killing the TUI. Every other view keeps Ctrl+A's backlog
138
+ // answer editor (routed ahead of this pane) — the key dispatcher consults the editors
139
+ // before the pane router, so this arm never shadows it.
140
+ session.flashMessage("committing the uncommitted backlog/brief files …");
141
+ session.requestRender();
142
+ healthAction(session.commitBacklog(session.root), (report) => {
143
+ session.flashMessage(backlogCommitMessage(report));
144
+ });
145
+ return true;
146
+ };
147
+ const healthKillOrphans = (ctx) => {
148
+ if (!onHealthView(ctx, true))
149
+ return false;
150
+ // Ctrl+K on the Health pane, pressed twice, kills the orphaned processes the orphans check
151
+ // lists, each with its descendants — the terminal twin of `tumwater doctor --kill-orphans`
152
+ // and the dashboard Health card's Kill orphans, all reached through the same killOrphans.
153
+ // The first press only arms (beginKey disarms on any other key) and flashes what a second
154
+ // press does; the second flashes the same lines the CLI prints, joined with "; ", then
155
+ // re-runs the checks so the orphans row reflects the kill. A failure flashes instead of
156
+ // killing the TUI. Ctrl+K stays the prompt-line editor's kill-line while text is typed
157
+ // or an editor holds the line, and is unbound on every other view.
158
+ if (!session.armedKillOrphans) {
159
+ session.killOrphansArmed = true;
160
+ session.flashMessage("press Ctrl+K again to kill the orphaned processes the orphans check lists — any other key keeps them");
194
161
  session.requestRender();
195
- void resyncRootInstall(session.root).then((r) => {
196
- session.flashMessage(rootInstallResyncLines(r).join("; "));
197
- void run();
198
- session.paneScroll = 0;
199
- session.requestRender();
200
- }, (err) => {
201
- session.flashError(err);
202
- session.requestRender();
203
- });
204
162
  return true;
205
163
  }
206
- if (key.ctrl && key.name === "l" && promptLineFree(ctx.es) && (ctx.es.input ?? "") === "" && session.view === session.roleIds.length + 7) {
207
- // Ctrl+L on the Health pane, pressed twice, retires every disabled role's leftover
208
- // worktree, branch and merge ref in one pass — the terminal twin of `tumwater doctor
209
- // --retire-leftovers` and the dashboard Health card's Retire leftovers, all reached
210
- // through the same retireLeftovers (with the loaded config, never `--force`). The first
211
- // press only arms (beginKey disarms on any other key) and flashes what a second press
212
- // does; the second flashes the same lines the CLI prints, joined with "; " (or
213
- // `no leftover roles to retire` when nothing is retirable), then re-runs the checks so
214
- // the leftover-role-branches row reflects the reap. A failure flashes instead of killing
215
- // the TUI. Off the Health view Ctrl+L keeps the Activity fallback editor (view 0) and the
216
- // instructions editor on a role's Transcript.
217
- if (!session.armedRetireLeftovers) {
218
- session.retireLeftoversArmed = true;
219
- session.flashMessage("press Ctrl+L again to retire every disabled role's leftover worktree, branch and merge ref — any other key keeps them");
220
- session.requestRender();
221
- return true;
222
- }
223
- session.flashMessage("retiring every disabled role's leftovers …");
164
+ session.flashMessage("killing orphaned processes …");
165
+ session.requestRender();
166
+ healthAction(session.killOrphans(session.root), (report) => {
167
+ session.flashMessage(orphanKillLines(report).join("; "));
168
+ });
169
+ return true;
170
+ };
171
+ const healthDropBrokenPins = (ctx) => {
172
+ if (!onHealthView(ctx, true))
173
+ return false;
174
+ // Ctrl+S on the Health pane, pressed twice, drops each broken refs/tumwater/ pin (one
175
+ // whose commit is gone) — the terminal twin of `tumwater doctor --drop-broken-pins` and
176
+ // the dashboard Health card's Drop broken pins, all reached through the same
177
+ // dropBrokenPins. The first press only arms (beginKey disarms on any other key) and
178
+ // flashes what a second press does; the second flashes the same lines the CLI prints,
179
+ // joined with "; ", then re-runs the checks so the tumwater pins row reflects the drop.
180
+ // A failure flashes instead of killing the TUI. Ctrl+S stays the prompt-line editor's
181
+ // save-to-wherever while text is typed or an editor holds the line, and keeps its
182
+ // quiet-hours (Activity), trailing-window (History) and min-run-interval (Transcript)
183
+ // meanings off the Health view.
184
+ if (!session.armedDropBrokenPins) {
185
+ session.dropBrokenPinsArmed = true;
186
+ session.flashMessage("press Ctrl+S again to drop each broken refs/tumwater/ pin whose commit is gone — any other key keeps them");
224
187
  session.requestRender();
225
- void session.retireLeftovers(session.root).then((report) => {
226
- const lines = leftoverRetireLines(report);
227
- session.flashMessage(lines.length > 0 ? lines.join("; ") : "no leftover roles to retire");
228
- void run();
229
- session.paneScroll = 0;
230
- session.requestRender();
231
- }, (err) => {
232
- session.flashError(err);
233
- session.requestRender();
234
- });
235
188
  return true;
236
189
  }
237
- if (key.ctrl && key.name === "b" && promptLineFree(ctx.es) && (ctx.es.input ?? "") === "" && session.view === session.roleIds.length + 7) {
238
- // Ctrl+B on the Health pane, pressed twice, archives the finished entries still under
239
- // PLANS.md's ## Done and BUGS.md's ## Fixed into PLANS-DONE.md and BUGS-FIXED.md and
240
- // commits them on the base branch — the terminal twin of `tumwater archive` and the
241
- // dashboard Health card's Archive backlog, all reached through the same archiveFinished-.
242
- // OnMain. The first press only arms (beginKey disarms on any other key) and flashes what
243
- // a second press does; the second flashes the same lines the CLI prints, joined with
244
- // "; " (the nothing line verbatim when there is nothing to archive), then re-runs the
245
- // checks so the `backlog archive` row reflects the move. A failure flashes instead of
246
- // killing the TUI. Ctrl+B stays the prompt-line editor's line-kill (budget editor) while
247
- // text is typed or an editor holds the line, and is unbound on every other view.
248
- if (!session.armedArchiveBacklog) {
249
- session.archiveBacklogArmed = true;
250
- session.flashMessage("press Ctrl+B again to archive the finished entries under PLANS.md ## Done and BUGS.md ## Fixed — any other key keeps them");
251
- session.requestRender();
252
- return true;
253
- }
254
- session.flashMessage("archiving finished backlog entries …");
190
+ session.flashMessage("dropping broken pins …");
191
+ session.requestRender();
192
+ healthAction(session.dropBrokenPins(session.root), (report) => {
193
+ const lines = brokenPinDropLines(report);
194
+ session.flashMessage(lines.length > 0 ? lines.join("; ") : "no broken pins to drop");
195
+ });
196
+ return true;
197
+ };
198
+ const healthResyncInstall = (ctx) => {
199
+ if (!onHealthView(ctx, true))
200
+ return false;
201
+ // Ctrl+U on the Health pane re-syncs the root checkout's install now — the terminal twin
202
+ // of `tumwater doctor --resync-install` and the dashboard Health card's Re-sync install,
203
+ // all reached through the same resyncRootInstall, so the `install` row/alert's advice is
204
+ // one action and clears once the install lands. It flashes the same line the CLI prints
205
+ // (rootInstallResyncLines, joined with "; "), then re-runs the checks so the `root
206
+ // install` row refreshes. A failure flashes instead of killing the TUI. Ctrl+U stays the
207
+ // prompt-line editor's kill-to-start while text is typed or an editor holds the line, and
208
+ // is unbound on every other view.
209
+ session.flashMessage("re-syncing the root install …");
210
+ session.requestRender();
211
+ healthAction(resyncRootInstall(session.root), (r) => {
212
+ session.flashMessage(rootInstallResyncLines(r).join("; "));
213
+ });
214
+ return true;
215
+ };
216
+ const healthRetireLeftovers = (ctx) => {
217
+ if (!onHealthView(ctx, true))
218
+ return false;
219
+ // Ctrl+L on the Health pane, pressed twice, retires every disabled role's leftover
220
+ // worktree, branch and merge ref in one pass — the terminal twin of `tumwater doctor
221
+ // --retire-leftovers` and the dashboard Health card's Retire leftovers, all reached
222
+ // through the same retireLeftovers (with the loaded config, never `--force`). The first
223
+ // press only arms (beginKey disarms on any other key) and flashes what a second press
224
+ // does; the second flashes the same lines the CLI prints, joined with "; " (or
225
+ // `no leftover roles to retire` when nothing is retirable), then re-runs the checks so
226
+ // the leftover-role-branches row reflects the reap. A failure flashes instead of killing
227
+ // the TUI. Off the Health view Ctrl+L keeps the Activity fallback editor (view 0) and the
228
+ // instructions editor on a role's Transcript.
229
+ if (!session.armedRetireLeftovers) {
230
+ session.retireLeftoversArmed = true;
231
+ session.flashMessage("press Ctrl+L again to retire every disabled role's leftover worktree, branch and merge ref — any other key keeps them");
255
232
  session.requestRender();
256
- void session.archiveBacklog(session.root).then((outcome) => {
257
- if (outcome.status === "nothing") {
258
- session.flashMessage("nothing to archive: PLANS.md ## Done and BUGS.md ## Fixed hold no finished entries");
259
- }
260
- else if (outcome.status === "skipped") {
261
- session.flashMessage(`nothing archived: ${outcome.reason}`);
262
- }
263
- else {
264
- session.flashMessage([...outcome.moved.map(describeArchivedFile), `committed ${shortSha(outcome.commit)}`].join("; "));
265
- }
266
- void run();
267
- session.paneScroll = 0;
268
- session.requestRender();
269
- }, (err) => {
270
- session.flashError(err);
271
- session.requestRender();
272
- });
273
233
  return true;
274
234
  }
275
- if (key.ctrl && key.name === "o" && promptLineFree(ctx.es) && session.view === session.roleIds.length + 7) {
276
- // Ctrl+O on the Health pane toggles the problems-only view (mnemonic: **O**nly
277
- // problems) — the same warn/fail trim `tumwater doctor --problems` applies — so an
278
- // operator can hide the ok rows without leaving the surface. The pane repaints from the
279
- // cached report it already holds; before any checks resolve (the loading note is up)
280
- // the toggle only records the choice for the first paint, and the pane re-enters at its
281
- // head so the changed body starts at the top. Every other view keeps the History
282
- // Ctrl+O outcome cycle.
283
- healthProblems = !healthProblems;
284
- if (lastDoctorReport !== null)
285
- session.paneCache = (healthProblems ? renderDoctorProblems : renderDoctor)(lastDoctorReport);
286
- session.paneScroll = 0;
235
+ session.flashMessage("retiring every disabled role's leftovers …");
236
+ session.requestRender();
237
+ healthAction(session.retireLeftovers(session.root), (report) => {
238
+ const lines = leftoverRetireLines(report);
239
+ session.flashMessage(lines.length > 0 ? lines.join("; ") : "no leftover roles to retire");
240
+ });
241
+ return true;
242
+ };
243
+ const healthArchiveBacklog = (ctx) => {
244
+ if (!onHealthView(ctx, true))
245
+ return false;
246
+ // Ctrl+B on the Health pane, pressed twice, archives the finished entries still under
247
+ // PLANS.md's ## Done and BUGS.md's ## Fixed into PLANS-DONE.md and BUGS-FIXED.md and
248
+ // commits them on the base branch — the terminal twin of `tumwater archive` and the
249
+ // dashboard Health card's Archive backlog, all reached through the same archiveFinished-.
250
+ // OnMain. The first press only arms (beginKey disarms on any other key) and flashes what
251
+ // a second press does; the second flashes the same lines the CLI prints, joined with
252
+ // "; " (the nothing line verbatim when there is nothing to archive), then re-runs the
253
+ // checks so the `backlog archive` row reflects the move. A failure flashes instead of
254
+ // killing the TUI. Ctrl+B stays the prompt-line editor's line-kill (budget editor) while
255
+ // text is typed or an editor holds the line, and is unbound on every other view.
256
+ if (!session.armedArchiveBacklog) {
257
+ session.archiveBacklogArmed = true;
258
+ session.flashMessage("press Ctrl+B again to archive the finished entries under PLANS.md ## Done and BUGS.md ## Fixed — any other key keeps them");
287
259
  session.requestRender();
288
260
  return true;
289
261
  }
262
+ session.flashMessage("archiving finished backlog entries …");
263
+ session.requestRender();
264
+ healthAction(session.archiveBacklog(session.root), (outcome) => {
265
+ if (outcome.status === "nothing") {
266
+ session.flashMessage("nothing to archive: PLANS.md ## Done and BUGS.md ## Fixed hold no finished entries");
267
+ }
268
+ else if (outcome.status === "skipped") {
269
+ session.flashMessage(`nothing archived: ${outcome.reason}`);
270
+ }
271
+ else {
272
+ session.flashMessage([...outcome.moved.map(describeArchivedFile), `committed ${shortSha(outcome.commit)}`].join("; "));
273
+ }
274
+ });
275
+ return true;
276
+ };
277
+ const healthToggleProblems = (ctx) => {
278
+ if (!onHealthView(ctx, false))
279
+ return false;
280
+ // Ctrl+O on the Health pane toggles the problems-only view (mnemonic: **O**nly
281
+ // problems) — the same warn/fail trim `tumwater doctor --problems` applies — so an
282
+ // operator can hide the ok rows without leaving the surface. The pane repaints from the
283
+ // cached report it already holds; before any checks resolve (the loading note is up)
284
+ // the toggle only records the choice for the first paint, and the pane re-enters at its
285
+ // head so the changed body starts at the top. Every other view keeps the History
286
+ // Ctrl+O outcome cycle.
287
+ healthProblems = !healthProblems;
288
+ if (lastDoctorReport !== null)
289
+ session.paneCache = (healthProblems ? renderDoctorProblems : renderDoctor)(lastDoctorReport);
290
+ paneAtHead(session);
291
+ return true;
292
+ };
293
+ const handleKey = (key, ctx) => {
294
+ if (key.ctrl && key.name === "r")
295
+ return healthRefresh(ctx);
296
+ if (key.ctrl && key.name === "e")
297
+ return healthReclaim(ctx);
298
+ if (key.ctrl && key.name === "n")
299
+ return healthNotifyTest(ctx);
300
+ if (key.ctrl && key.name === "a")
301
+ return healthCommitBacklog(ctx);
302
+ if (key.ctrl && key.name === "k")
303
+ return healthKillOrphans(ctx);
304
+ if (key.ctrl && key.name === "s")
305
+ return healthDropBrokenPins(ctx);
306
+ if (key.ctrl && key.name === "u")
307
+ return healthResyncInstall(ctx);
308
+ if (key.ctrl && key.name === "l")
309
+ return healthRetireLeftovers(ctx);
310
+ if (key.ctrl && key.name === "b")
311
+ return healthArchiveBacklog(ctx);
312
+ if (key.ctrl && key.name === "o")
313
+ return healthToggleProblems(ctx);
290
314
  return false;
291
315
  };
292
316
  return {