tumwater 0.4.0 → 0.4.2
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.
- package/README.md +4 -6
- package/dist/build-info.json +2 -2
- package/dist/src/agent/agent-backend.js +5 -2
- package/dist/src/agent/backend-traits.js +21 -0
- package/dist/src/agent/claude/claude-backend.js +1 -1
- package/dist/src/agent/claude/claude-stream.js +181 -182
- package/dist/src/agent/claude/{write-guard-hook.js → claude-write-guard-hook.js} +1 -1
- package/dist/src/agent/claude/claude-write-guard.js +5 -4
- package/dist/src/agent/codex/codex-backend.js +1 -1
- package/dist/src/agent/codex/codex-rollout.js +48 -45
- package/dist/src/agent/codex/codex-stream.js +269 -215
- package/dist/src/agent/codex/{write-guard-hook.js → codex-write-guard-hook.js} +2 -1
- package/dist/src/agent/codex/codex-write-guard.js +3 -3
- package/dist/src/agent/image-input.js +18 -87
- package/dist/src/agent/notes-shim.js +2 -2
- package/dist/src/agent/opencode/opencode-backend.js +2 -2
- package/dist/src/agent/opencode/opencode-catalog.js +2 -2
- package/dist/src/agent/opencode/opencode-config.js +2 -2
- package/dist/src/agent/opencode/opencode-stream.js +101 -92
- package/dist/src/agent/run-agent.js +76 -382
- package/dist/src/agent/run-settle.js +25 -15
- package/dist/src/agent/run-spawn.js +226 -0
- package/dist/src/agent/run-stream.js +185 -0
- package/dist/src/agent/session-ledger.js +16 -0
- package/dist/src/agent/write-guard.js +5 -3
- package/dist/src/backlog/backlog-archive.js +86 -170
- package/dist/src/backlog/backlog-cli-view.js +89 -70
- package/dist/src/backlog/backlog-eligibility.js +51 -59
- package/dist/src/backlog/backlog-file-bugs.js +133 -0
- package/dist/src/backlog/backlog-index-data.js +159 -0
- package/dist/src/backlog/backlog-index.js +7 -5
- package/dist/src/backlog/backlog-md.js +39 -28
- package/dist/src/backlog/backlog-release.js +21 -36
- package/dist/src/backlog/backlog-render.js +1 -1
- package/dist/src/backlog/backlog-structure.js +14 -145
- package/dist/src/backlog/backlog-write.js +31 -18
- package/dist/src/backlog/backlog.js +14 -4
- package/dist/src/backlog/question-answer.js +33 -15
- package/dist/src/baseline/main-baseline.js +71 -26
- package/dist/src/baseline/main-guardrails.js +76 -15
- package/dist/src/baseline/main-red.js +45 -94
- package/dist/src/baseline/main-watch.js +9 -3
- package/dist/src/brief/brief.js +40 -0
- package/dist/src/budget/budget-fallback-view.js +50 -8
- package/dist/src/budget/budget-wiring.js +15 -11
- package/dist/src/budget/budget.js +3 -2
- package/dist/src/build/build-check-events.js +4 -2
- package/dist/src/build/build-check-report.js +2 -1
- package/dist/src/build/build-check-scoped.js +70 -41
- package/dist/src/change/change-data.js +4 -2
- package/dist/src/cli/cli-args.js +9 -0
- package/dist/src/cli/cli-command-args.js +199 -98
- package/dist/src/cli/cli-flag-specs.js +6 -4
- package/dist/src/cli/cli-marker-commands.js +2 -2
- package/dist/src/cli/cli-query-commands.js +41 -6
- package/dist/src/cli/cli-review-commands.js +2 -1
- package/dist/src/cli/cli-role-command.js +46 -92
- package/dist/src/cli/cli-start.js +123 -47
- package/dist/src/cli/follow-view.js +3 -1
- package/dist/src/cli/help.js +19 -12
- package/dist/src/cli/log-commands.js +128 -107
- package/dist/src/cli/question-commands.js +82 -63
- package/dist/src/cli.js +396 -343
- package/dist/src/collections.js +46 -9
- package/dist/src/config/config-fallbacks.js +0 -4
- package/dist/src/config/config-live.js +179 -147
- package/dist/src/config/config-request.js +176 -51
- package/dist/src/config/config-validation-roles.js +271 -0
- package/dist/src/config/config-validation.js +10 -205
- package/dist/src/config/config-views.js +2 -7
- package/dist/src/config/config-write.js +205 -175
- package/dist/src/config/config.js +2 -1
- package/dist/src/doctor/doctor-backlog-bugs.js +24 -122
- package/dist/src/doctor/doctor-backlog-plans.js +24 -25
- package/dist/src/doctor/doctor-backlog.js +34 -42
- package/dist/src/doctor/doctor-check-names.js +4 -2
- package/dist/src/doctor/doctor-checks.js +16 -4
- package/dist/src/doctor/doctor-claude-checks.js +2 -2
- package/dist/src/doctor/doctor-codex-checks.js +9 -20
- package/dist/src/doctor/doctor-contention-checks.js +52 -19
- package/dist/src/doctor/doctor-holds.js +22 -20
- package/dist/src/doctor/doctor-model-checks.js +8 -184
- package/dist/src/doctor/doctor-notify.js +2 -5
- package/dist/src/doctor/doctor-opencode-checks.js +90 -41
- package/dist/src/doctor/doctor-principles.js +26 -0
- package/dist/src/doctor/doctor-project-checks.js +69 -19
- package/dist/src/doctor/doctor-role-notes.js +33 -0
- package/dist/src/doctor/doctor.js +13 -6
- package/dist/src/doctor/probe.js +1 -1
- package/dist/src/events/event-format.js +307 -162
- package/dist/src/events/event-labels.js +0 -1
- package/dist/src/events/event-legacy.js +1 -1
- package/dist/src/events/event-window.js +1 -1
- package/dist/src/events/events.js +15 -2
- package/dist/src/events/notify.js +1 -1
- package/dist/src/failure/failure-cluster.js +4 -3
- package/dist/src/failure/failure-data.js +126 -94
- package/dist/src/failure/failure-render.js +58 -17
- package/dist/src/failure/failure-state-change.js +308 -27
- package/dist/src/failure/time-spend.js +139 -106
- package/dist/src/files/files.js +7 -0
- package/dist/src/files/json-object.js +15 -2
- package/dist/src/gates/budget-gates.js +90 -68
- package/dist/src/gates/gate-polls.js +92 -61
- package/dist/src/gates/role-cap-gates.js +1 -1
- package/dist/src/gates/startup-gate.js +28 -28
- package/dist/src/gates/streak-gate.js +1 -1
- package/dist/src/git/git.js +1 -1
- package/dist/src/git/slot-salvage.js +9 -9
- package/dist/src/git/slots-state.js +12 -3
- package/dist/src/git/worktree-pool-startup.js +127 -0
- package/dist/src/git/worktree-pool.js +1 -120
- package/dist/src/gui/gui-admin-endpoints.js +3 -4
- package/dist/src/gui/gui-args.js +1 -1
- package/dist/src/gui/gui-backlog-commands.js +20 -11
- package/dist/src/gui/gui-backlog-endpoints.js +2 -8
- package/dist/src/gui/gui-config-commands.js +51 -76
- package/dist/src/gui/gui-endpoint-commands.js +4 -2
- package/dist/src/gui/gui-endpoint-common.js +4 -3
- package/dist/src/gui/gui-history-endpoints.js +36 -27
- package/dist/src/gui/gui-operator-commands.js +43 -17
- package/dist/src/gui/gui-prompt-commands.js +27 -21
- package/dist/src/gui/gui-quarantine-commands.js +30 -0
- package/dist/src/gui/gui-report-endpoints.js +13 -9
- package/dist/src/gui/gui-server.js +5 -3
- package/dist/src/gui/gui-team-commands.js +1 -1
- package/dist/src/history/history-data.js +7 -1
- package/dist/src/history/history-render.js +15 -10
- package/dist/src/history/history.js +103 -68
- package/dist/src/inbox/inbox-submit.js +1 -5
- package/dist/src/inbox/pending-prompt.js +3 -2
- package/dist/src/inbox/prompt-commands.js +194 -161
- package/dist/src/init/init.js +104 -25
- package/dist/src/merge/merge-core.js +1 -1
- package/dist/src/merge/merge-drain.js +1 -1
- package/dist/src/merge/merge-git.js +5 -5
- package/dist/src/merge/{merge-post-review.js → merge-post-review-drain.js} +1 -1
- package/dist/src/merge/merge-queue.js +2 -1
- package/dist/src/merge/merge-to-main.js +1 -1
- package/dist/src/merge/merge-verify.js +3 -2
- package/dist/src/operator/operator-commands.js +84 -52
- package/dist/src/operator/operator-intent.js +26 -3
- package/dist/src/operator/retire.js +2 -1
- package/dist/src/operator/revert.js +18 -12
- package/dist/src/orchestrator/orchestrator-poll.js +2 -2
- package/dist/src/orchestrator/orchestrator.js +1 -1
- package/dist/src/orchestrator/retention.js +6 -5
- package/dist/src/paths.js +10 -10
- package/dist/src/pi/pi-bin.js +2 -2
- package/dist/src/pi/pi-models.js +9 -0
- package/dist/src/pi/pi-stream.js +63 -41
- package/dist/src/pi/pi-watchdogs.js +84 -49
- package/dist/src/pi-extension/context-shake.js +55 -31
- package/dist/src/pi-extension/full-output.js +2 -3
- package/dist/src/process/process.js +24 -1
- package/dist/src/process/run-marker.js +2 -2
- package/dist/src/process/supervisor.js +24 -5
- package/dist/src/prompt/gate-notes.js +41 -16
- package/dist/src/prompt/notes-backlog.js +22 -29
- package/dist/src/prompt/principles.js +11 -0
- package/dist/src/prompt/prompt-followup.js +4 -12
- package/dist/src/prompt/prompt.js +15 -12
- package/dist/src/redeploy/redeploy-probes.js +11 -1
- package/dist/src/redeploy/redeployer.js +54 -7
- package/dist/src/redeploy/self-reload.js +7 -2
- package/dist/src/report/report-data.js +224 -320
- package/dist/src/report/report-fold.js +23 -231
- package/dist/src/report/report-main-red.js +70 -3
- package/dist/src/report/report-render.js +181 -48
- package/dist/src/report/report-since-data.js +218 -0
- package/dist/src/report/report-usage-fold.js +288 -0
- package/dist/src/report/report.js +2 -1
- package/dist/src/review/known-flakes.js +22 -3
- package/dist/src/review/post-merge-review-accept.js +3 -2
- package/dist/src/review/post-merge-review.js +16 -14
- package/dist/src/review/quarantine-render.js +29 -11
- package/dist/src/review/review-precheck.js +2 -1
- package/dist/src/roles/role-catalog.js +30 -29
- package/dist/src/roles/role-guidance.js +26 -43
- package/dist/src/roles/role-render.js +19 -0
- package/dist/src/roles/role-view.js +19 -5
- package/dist/src/roles/roles.js +2 -22
- package/dist/src/run/ci-parity.js +5 -6
- package/dist/src/run/feedback-filings.js +10 -1
- package/dist/src/run/qa-coverage.js +4 -4
- package/dist/src/run/run-apply.js +14 -2
- package/dist/src/run/run-detail-data.js +50 -10
- package/dist/src/run/run-detail-render.js +32 -1
- package/dist/src/run/run-detail.js +44 -22
- package/dist/src/run/run-dismiss.js +43 -0
- package/dist/src/run/run-finalize.js +75 -46
- package/dist/src/run/run-prompt.js +271 -187
- package/dist/src/run/run-resume.js +32 -31
- package/dist/src/run/run-stage.js +116 -64
- package/dist/src/run/run-timing.js +18 -4
- package/dist/src/run/run-verdict.js +119 -83
- package/dist/src/run/stage-backlog-checks.js +4 -31
- package/dist/src/run/stage-check.js +4 -12
- package/dist/src/run/stage-checks-shared.js +15 -0
- package/dist/src/run/stage-name-checks.js +3 -13
- package/dist/src/run/stage-path-checks.js +26 -29
- package/dist/src/runner/leftover.js +13 -4
- package/dist/src/runner/revision.js +2 -2
- package/dist/src/runner/runner-fallback.js +1 -1
- package/dist/src/runner/runner-outcome.js +0 -5
- package/dist/src/runner/runner-pi.js +72 -49
- package/dist/src/runner/runner-run.js +19 -102
- package/dist/src/runner/runner-state-load.js +0 -10
- package/dist/src/runner/runner.js +1 -32
- package/dist/src/scheduling/claims.js +4 -11
- package/dist/src/status/status-data.js +140 -91
- package/dist/src/status/status-polls.js +41 -5
- package/dist/src/status/status-role-notes.js +53 -0
- package/dist/src/status/status-roles.js +10 -37
- package/dist/src/text/datetime.js +8 -1
- package/dist/src/text/format.js +13 -1
- package/dist/src/text/phrases.js +16 -2
- package/dist/src/text/text.js +12 -0
- package/dist/src/ui/alert-feeds.js +115 -0
- package/dist/src/ui/badges.js +3 -4
- package/dist/src/ui/gui/gui-client-boot.js +78 -57
- package/dist/src/ui/gui/gui-client-drawer-config.js +8 -3
- package/dist/src/ui/gui/gui-client-drawer.js +18 -2
- package/dist/src/ui/gui/gui-client-feed.js +3 -8
- package/dist/src/ui/gui/gui-client-history.js +16 -16
- package/dist/src/ui/gui/gui-client-model.js +9 -9
- package/dist/src/ui/gui/gui-client-operator.js +6 -10
- package/dist/src/ui/gui/gui-client-pending.js +4 -4
- package/dist/src/ui/gui/gui-client-report-chart.js +111 -10
- package/dist/src/ui/gui/gui-client-report.js +70 -11
- package/dist/src/ui/gui/gui-client-roles.js +52 -12
- package/dist/src/ui/gui/gui-client-settings.js +31 -26
- package/dist/src/ui/gui/gui-client-team.js +28 -2
- package/dist/src/ui/gui/gui-client.js +33 -4
- package/dist/src/ui/gui/gui-icons.js +7 -1
- package/dist/src/ui/progress-data.js +2 -2
- package/dist/src/ui/run-progress-model.js +2 -2
- package/dist/src/ui/status-model.js +26 -16
- package/dist/src/ui/status-payload.js +149 -326
- package/dist/src/ui/status-render.js +131 -80
- package/dist/src/ui/team-alert-backlog.js +236 -0
- package/dist/src/ui/team-alert-shared.js +92 -0
- package/dist/src/ui/team-alert-status.js +326 -0
- package/dist/src/ui/team-alert-tail.js +238 -0
- package/dist/src/ui/team-alerts.js +16 -984
- package/dist/src/ui/transcript-tail.js +137 -94
- package/dist/src/ui/tui/tui-backlog.js +1 -1
- package/dist/src/ui/tui/tui-editor-answer.js +54 -44
- package/dist/src/ui/tui/tui-editor-attach.js +74 -63
- package/dist/src/ui/tui/tui-editor-backends.js +62 -56
- package/dist/src/ui/tui/tui-editor-brief.js +64 -54
- package/dist/src/ui/tui/tui-editor-budget.js +106 -89
- package/dist/src/ui/tui/tui-editor-config.js +2 -4
- package/dist/src/ui/tui/tui-editor-ctx.js +12 -1
- package/dist/src/ui/tui/tui-editor-entry.js +171 -153
- package/dist/src/ui/tui/tui-editor-instances.js +2 -1
- package/dist/src/ui/tui/tui-editor-instructions.js +50 -107
- package/dist/src/ui/tui/tui-editor-interval.js +39 -112
- package/dist/src/ui/tui/tui-editor-notes.js +28 -84
- package/dist/src/ui/tui/tui-editor-pause.js +210 -183
- package/dist/src/ui/tui/tui-editor-prompt.js +46 -35
- package/dist/src/ui/tui/tui-editor-queued.js +74 -64
- package/dist/src/ui/tui/tui-editor-quiet.js +122 -108
- package/dist/src/ui/tui/tui-editor-release.js +85 -0
- package/dist/src/ui/tui/tui-editor-retire.js +57 -46
- package/dist/src/ui/tui/tui-editor-role-enabled.js +90 -80
- package/dist/src/ui/tui/tui-editor-role-file.js +87 -0
- package/dist/src/ui/tui/tui-editor-role-key.js +50 -42
- package/dist/src/ui/tui/tui-editor-search.js +70 -58
- package/dist/src/ui/tui/tui-editor-session.js +3 -0
- package/dist/src/ui/tui/tui-editors.js +4 -1
- package/dist/src/ui/tui/tui-frame.js +7 -3
- package/dist/src/ui/tui/tui-history.js +12 -10
- package/dist/src/ui/tui/tui-keys.js +6 -1
- package/dist/src/ui/tui/tui-pane-activity.js +62 -50
- package/dist/src/ui/tui/tui-pane-backlog.js +4 -4
- package/dist/src/ui/tui/tui-pane-ctx.js +1 -1
- package/dist/src/ui/tui/tui-pane-health.js +45 -26
- package/dist/src/ui/tui/tui-pane-history.js +296 -264
- package/dist/src/ui/tui/tui-pane-report.js +74 -57
- package/dist/src/ui/tui/tui-pane-session.js +6 -2
- package/dist/src/ui/tui/tui-pane-transcript.js +158 -131
- package/dist/src/ui/tui/tui-pane.js +201 -174
- package/dist/src/ui/tui/tui-panes.js +211 -148
- package/dist/src/ui/tui/tui.js +4 -4
- package/dist/src/verdict/fix-claim.js +18 -6
- package/dist/src/verdict/refusal.js +32 -7
- package/package.json +1 -1
- package/dist/src/doctor/image-probe.js +0 -257
- /package/dist/src/gui/{http-body.js → gui-http-body.js} +0 -0
- /package/dist/src/merge/{backlog-conflicts.js → merge-backlog-conflicts.js} +0 -0
|
@@ -17,77 +17,87 @@ export function createQueuedEditor(session) {
|
|
|
17
17
|
session.held.queued = null;
|
|
18
18
|
restoreDraft();
|
|
19
19
|
};
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
20
|
+
// Ctrl+E opens the Queued pane's in-place editor on the selected row (mnemonic:
|
|
21
|
+
// **E**dit) through the same readQueuedPromptBody/editQueuedFile pair the dashboard's
|
|
22
|
+
// Edit uses: the body is pre-filled marker- and attachment-stripped, and Enter writes
|
|
23
|
+
// it back to the same queue file, so its position, enqueue stamp, deferral, and
|
|
24
|
+
// attached images survive. A row that vanished between selection and this press (the
|
|
25
|
+
// role dequeued it, or another operator cancelled it) flashes the gone notice and
|
|
26
|
+
// refreshes the list. A no-selection press (list mode) falls through to the report
|
|
27
|
+
// window below, exactly as before.
|
|
28
|
+
const queuedOpenEdit = (ctx) => {
|
|
29
|
+
if (session.anyEditorHeld() ||
|
|
30
|
+
ctx.view !== ctx.roleIds.length + 6 ||
|
|
31
|
+
ctx.selectedQueued === null) {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
try {
|
|
35
|
+
const position = Math.min(ctx.selectedQueued, ctx.queuedRows.length - 1);
|
|
36
|
+
const row = ctx.queuedRows[position];
|
|
37
|
+
if (row !== undefined) {
|
|
38
|
+
const body = readQueuedPromptBody(session.root, row.role, row.file);
|
|
39
|
+
if (body === null) {
|
|
40
|
+
session.flashMessage(`inbox message #${row.position} for ${row.role} is gone`);
|
|
41
|
+
session.refreshQueued();
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
// Mid-recall, the line holds a history entry while the real draft sits in the
|
|
45
|
+
// recall state: settle first, so the queued editor saves (and later restores)
|
|
46
|
+
// the draft the operator was writing.
|
|
47
|
+
restoreDraft = session.takeDraft();
|
|
48
|
+
queuedEditFor = { role: row.role, file: row.file };
|
|
49
|
+
session.held.queued = queuedEditFor;
|
|
50
|
+
session.input = body;
|
|
51
|
+
session.cursor = body.length;
|
|
52
|
+
session.flashMessage(`editing inbox message #${row.position} for ${row.role} — Enter to save, Esc to cancel`);
|
|
52
53
|
}
|
|
53
54
|
}
|
|
54
|
-
catch (err) {
|
|
55
|
-
session.flashError(err);
|
|
56
|
-
}
|
|
57
|
-
return true;
|
|
58
55
|
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
exitQueuedEdit();
|
|
62
|
-
return true;
|
|
56
|
+
catch (err) {
|
|
57
|
+
session.flashError(err);
|
|
63
58
|
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
59
|
+
return true;
|
|
60
|
+
};
|
|
61
|
+
// Esc cancels the Queued pane's in-place editor, restoring the previous prompt text.
|
|
62
|
+
const cancelQueuedEdit = () => {
|
|
63
|
+
exitQueuedEdit();
|
|
64
|
+
return true;
|
|
65
|
+
};
|
|
66
|
+
// Enter saves the queued editor's line through the same file-addressed
|
|
67
|
+
// editQueuedFile pair the dashboard's Edit uses: the write targets the queue file
|
|
68
|
+
// the row named, so a concurrent dequeue cannot land it on the wrong prompt. An
|
|
69
|
+
// invalid (whitespace-only) line flashes and STAYS in the editor; a gone outcome
|
|
70
|
+
// is the concurrent-dequeue race, flashed honestly and returned to the list; a
|
|
71
|
+
// throw keeps the text in the editor for a retry.
|
|
72
|
+
const commitQueuedEdit = () => {
|
|
73
|
+
const parsed = parseRolePromptInput(session.input);
|
|
74
|
+
if (!parsed.ok) {
|
|
75
|
+
session.flashMessage(parsed.error);
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
// The dispatch guards on `queuedEditFor`, so it is never null when this arm runs.
|
|
79
|
+
const target = queuedEditFor;
|
|
80
|
+
try {
|
|
81
|
+
const outcome = editQueuedFile(session.root, target.role, target.file, parsed.value);
|
|
82
|
+
exitQueuedEdit();
|
|
83
|
+
session.refreshQueued();
|
|
84
|
+
session.flashMessage(outcome.status === "edited"
|
|
85
|
+
? `edited inbox message for ${target.role}`
|
|
86
|
+
: "the inbox message was already gone");
|
|
74
87
|
}
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
try {
|
|
78
|
-
const outcome = editQueuedFile(session.root, target.role, target.file, parsed.value);
|
|
79
|
-
exitQueuedEdit();
|
|
80
|
-
session.refreshQueued();
|
|
81
|
-
session.flashMessage(outcome.status === "edited"
|
|
82
|
-
? `edited inbox message for ${target.role}`
|
|
83
|
-
: "the inbox message was already gone");
|
|
84
|
-
}
|
|
85
|
-
catch (err) {
|
|
86
|
-
session.flashError(err);
|
|
87
|
-
}
|
|
88
|
+
catch (err) {
|
|
89
|
+
session.flashError(err);
|
|
88
90
|
}
|
|
89
|
-
return true;
|
|
90
91
|
}
|
|
92
|
+
return true;
|
|
93
|
+
};
|
|
94
|
+
const handleKey = (_str, key, ctx) => {
|
|
95
|
+
if (key.ctrl && key.name === "e")
|
|
96
|
+
return queuedOpenEdit(ctx);
|
|
97
|
+
if (key.name === "escape" && queuedEditFor)
|
|
98
|
+
return cancelQueuedEdit();
|
|
99
|
+
if (key.name === "return" && queuedEditFor)
|
|
100
|
+
return commitQueuedEdit();
|
|
91
101
|
return false;
|
|
92
102
|
};
|
|
93
103
|
return { handleKey, exit: exitQueuedEdit };
|
|
@@ -2,6 +2,7 @@ import { setConfigKey, unsetConfigKey } from "../../config/config-write.js";
|
|
|
2
2
|
import { loadConfigSafe } from "../../config/config.js";
|
|
3
3
|
import { DIRECTOR_ROLE } from "../../roles/roles.js";
|
|
4
4
|
import { baseRoleOf } from "../../roles/runner-ids.js";
|
|
5
|
+
import { viewedRole } from "./tui-editor-ctx.js";
|
|
5
6
|
/** The Ctrl+O per-role / Ctrl+S team-wide quiet-hours editor on the shared prompt line, the
|
|
6
7
|
* terminal twin of the dashboard's `Quiet hours` rows and `tumwater role <id> --quiet-hours`
|
|
7
8
|
* / `tumwater config set quietHours`. It owns its mode flags, its saved-draft pair, and the
|
|
@@ -34,123 +35,136 @@ export function createQuietEditor(session) {
|
|
|
34
35
|
restoreDraft();
|
|
35
36
|
lockedRole = null;
|
|
36
37
|
};
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
// pre-fill from the file's own entry — one fresh read per open is the smallest honest
|
|
66
|
-
// source; a broken config degrades to no pre-fill and the write reports the error.
|
|
67
|
-
lockedRole = role;
|
|
68
|
-
const existing = loadConfigSafe(session.root).config?.quietHoursPerRole?.[baseRoleOf(role)] ?? "";
|
|
69
|
-
restoreDraft = session.takeDraft();
|
|
70
|
-
quietMode = true;
|
|
71
|
-
quietTeam = false;
|
|
72
|
-
session.held.quiet = true;
|
|
73
|
-
session.held.quietTeam = false;
|
|
74
|
-
session.input = existing;
|
|
75
|
-
session.cursor = session.input.length;
|
|
76
|
-
session.flashMessage(`edit ${role}'s quiet hours (e.g. 23:00-07:00, empty clears): Enter to save, Esc to cancel`);
|
|
38
|
+
// The shared way both open paths put the editor on the line: take the prompt draft to
|
|
39
|
+
// restore on exit, raise the mode flags and the held registry for the chosen scope, pre-fill
|
|
40
|
+
// the prompt text (cursor at its end) and flash the guidance line.
|
|
41
|
+
const openQuietEditor = (team, existing, message) => {
|
|
42
|
+
restoreDraft = session.takeDraft();
|
|
43
|
+
quietMode = true;
|
|
44
|
+
quietTeam = team;
|
|
45
|
+
session.held.quiet = true;
|
|
46
|
+
session.held.quietTeam = team;
|
|
47
|
+
session.input = existing;
|
|
48
|
+
session.cursor = session.input.length;
|
|
49
|
+
session.flashMessage(message);
|
|
50
|
+
};
|
|
51
|
+
// The Ctrl+O arm: edits the viewed role's own quiet-hours window (mnemonic: the silent *o*ur
|
|
52
|
+
// hours) on its Transcript view. Every non-transcript view keeps Ctrl+O — the guard lets the
|
|
53
|
+
// key fall through to the pane arms (Activity's event-type cycle, History's outcome cycle,
|
|
54
|
+
// Health's problems-only toggle) unless the quiet editor already holds the line. While a
|
|
55
|
+
// scope is open, Ctrl+O toggles only the per-role scope out; in the team scope it is consumed
|
|
56
|
+
// and does nothing. Entering pre-fills the window from `quietHoursPerRole.<base>` (empty when
|
|
57
|
+
// unset — empty means "off" on save) and saves the prompt text; leaving restores it. The
|
|
58
|
+
// director is exempt — its quietHoursPerRole.director entry is inert (the gate and doctor
|
|
59
|
+
// both exempt it).
|
|
60
|
+
const roleQuietKey = (ctx) => {
|
|
61
|
+
if (quietMode) {
|
|
62
|
+
// Consumed either way: in the per-role scope it toggles the editor out, in the team
|
|
63
|
+
// scope Ctrl+O leaves the team editor open.
|
|
64
|
+
if (!quietTeam)
|
|
65
|
+
exitQuietMode();
|
|
77
66
|
return true;
|
|
78
67
|
}
|
|
79
|
-
if (
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
// in the per-role scope it is consumed and does nothing (so it never opens the interval
|
|
87
|
-
// editor over the quiet editor). Entering pre-fills the window from the FILE's top-level
|
|
88
|
-
// `quietHours` (empty when unset — empty means "off" on save) and saves the prompt text;
|
|
89
|
-
// leaving restores it. The team window owns no role, so lockedRole stays null.
|
|
90
|
-
if (quietMode) {
|
|
91
|
-
// In the team scope Ctrl+S toggles the editor out. In the per-role scope it must not
|
|
92
|
-
// touch the per-role editor, but it also must not silently vanish: fall through so the
|
|
93
|
-
// interval editor (the other Ctrl+S arm, which runs after this one) flashes the way out,
|
|
94
|
-
// naming the per-role quiet editor as the holder.
|
|
95
|
-
if (!quietTeam)
|
|
96
|
-
return false;
|
|
97
|
-
exitQuietMode();
|
|
98
|
-
return true;
|
|
99
|
-
}
|
|
100
|
-
if (ctx.view !== 0)
|
|
101
|
-
return false;
|
|
102
|
-
if (session.editorEntryDenied("quiet"))
|
|
103
|
-
return true;
|
|
104
|
-
const existing = loadConfigSafe(session.root).config?.quietHours ?? "";
|
|
105
|
-
restoreDraft = session.takeDraft();
|
|
106
|
-
quietMode = true;
|
|
107
|
-
quietTeam = true;
|
|
108
|
-
session.held.quiet = true;
|
|
109
|
-
session.held.quietTeam = true;
|
|
110
|
-
session.input = existing;
|
|
111
|
-
session.cursor = session.input.length;
|
|
112
|
-
session.flashMessage("edit the team's quiet hours (e.g. 23:00-07:00, empty clears): Enter to save, Esc to cancel");
|
|
68
|
+
if (!(ctx.view >= 1 && ctx.view <= ctx.roleIds.length))
|
|
69
|
+
return false;
|
|
70
|
+
const role = viewedRole(ctx);
|
|
71
|
+
if (session.editorEntryDenied("quiet"))
|
|
72
|
+
return true;
|
|
73
|
+
if (role === DIRECTOR_ROLE) {
|
|
74
|
+
session.flashMessage("the director has no quiet hours");
|
|
113
75
|
return true;
|
|
114
76
|
}
|
|
115
|
-
|
|
116
|
-
|
|
77
|
+
// Pin the role first so the write below is safe against a mid-edit roster shift, then
|
|
78
|
+
// pre-fill from the file's own entry — one fresh read per open is the smallest honest
|
|
79
|
+
// source; a broken config degrades to no pre-fill and the write reports the error.
|
|
80
|
+
lockedRole = role;
|
|
81
|
+
const existing = loadConfigSafe(session.root).config?.quietHoursPerRole?.[baseRoleOf(role)] ?? "";
|
|
82
|
+
openQuietEditor(false, existing, `edit ${role}'s quiet hours (e.g. 23:00-07:00, empty clears): Enter to save, Esc to cancel`);
|
|
83
|
+
return true;
|
|
84
|
+
};
|
|
85
|
+
// The Ctrl+S arm: edits the team-wide quiet-hours window on the Activity view (view 0) — the
|
|
86
|
+
// terminal twin of the dashboard Settings `quietHours` row and `tumwater config set
|
|
87
|
+
// quietHours`, so an operator can put the whole team to sleep for a nightly window. On every
|
|
88
|
+
// non-Activity view Ctrl+S keeps its other meaning — the guard lets the key fall through to
|
|
89
|
+
// the interval editor (a role's Transcript) and the pane arms (History's trailing-window
|
|
90
|
+
// cycle). While a scope is open, Ctrl+S toggles only the team scope out; in the per-role
|
|
91
|
+
// scope it is consumed and does nothing (so it never opens the interval editor over the
|
|
92
|
+
// quiet editor). Entering pre-fills the window from the FILE's top-level `quietHours` (empty
|
|
93
|
+
// when unset — empty means "off" on save) and saves the prompt text; leaving restores it.
|
|
94
|
+
// The team window owns no role, so lockedRole stays null.
|
|
95
|
+
const teamQuietKey = (ctx) => {
|
|
96
|
+
if (quietMode) {
|
|
97
|
+
// In the team scope Ctrl+S toggles the editor out. In the per-role scope it must not
|
|
98
|
+
// touch the per-role editor, but it also must not silently vanish: fall through so the
|
|
99
|
+
// interval editor (the other Ctrl+S arm, which runs after this one) flashes the way out,
|
|
100
|
+
// naming the per-role quiet editor as the holder.
|
|
101
|
+
if (!quietTeam)
|
|
102
|
+
return false;
|
|
117
103
|
exitQuietMode();
|
|
118
104
|
return true;
|
|
119
105
|
}
|
|
120
|
-
if (
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
else {
|
|
150
|
-
session.flashMessage(result.error); // broken config or write failure — stay in edit mode
|
|
151
|
-
}
|
|
106
|
+
if (ctx.view !== 0)
|
|
107
|
+
return false;
|
|
108
|
+
if (session.editorEntryDenied("quiet"))
|
|
109
|
+
return true;
|
|
110
|
+
const existing = loadConfigSafe(session.root).config?.quietHours ?? "";
|
|
111
|
+
openQuietEditor(true, existing, "edit the team's quiet hours (e.g. 23:00-07:00, empty clears): Enter to save, Esc to cancel");
|
|
112
|
+
return true;
|
|
113
|
+
};
|
|
114
|
+
// The Esc arm: cancels quiet-edit mode, restoring the previous prompt text.
|
|
115
|
+
const cancelQuietEdit = () => {
|
|
116
|
+
exitQuietMode();
|
|
117
|
+
return true;
|
|
118
|
+
};
|
|
119
|
+
// The Enter arm: commits the window through the shared setter family the CLI's `--quiet-hours`
|
|
120
|
+
// and `tumwater config set quietHours` / the dashboard rows use: an empty or whitespace-only
|
|
121
|
+
// line clears the key (`unsetConfigKey`), otherwise `setConfigKey` writes the dotted
|
|
122
|
+
// `quietHoursPerRole.<base>` entry (per-role scope) or the top-level `quietHours` (team scope)
|
|
123
|
+
// as a JSON string. checkQuietHours (naming the dotted key) rejects a malformed per-role
|
|
124
|
+
// window and validateConfig guards the team write; both write tumwater.json atomically and the
|
|
125
|
+
// running orchestrator picks the change up on its next poll. An invalid window flashes and
|
|
126
|
+
// STAYS in edit mode so the operator can fix it; success restores the saved prompt text.
|
|
127
|
+
const commitQuietEntry = () => {
|
|
128
|
+
const cfgKey = quietTeam ? "quietHours" : `quietHoursPerRole.${baseRoleOf(lockedRole)}`;
|
|
129
|
+
const text = session.input.trim();
|
|
130
|
+
const result = text === ""
|
|
131
|
+
? unsetConfigKey(session.root, cfgKey)
|
|
132
|
+
: setConfigKey(session.root, cfgKey, JSON.stringify(text));
|
|
133
|
+
if (result.ok) {
|
|
134
|
+
flashQuietOutcome(result, text);
|
|
152
135
|
return true;
|
|
153
136
|
}
|
|
137
|
+
session.flashMessage(result.error); // broken config or write failure — stay in edit mode
|
|
138
|
+
return true;
|
|
139
|
+
};
|
|
140
|
+
// The one success flash, shared by both scopes: reads the scope and role before exiting so
|
|
141
|
+
// the message still names the window that was just written, computes the no-op case (nothing
|
|
142
|
+
// was set and an empty line was cleared, or the value was already in place) and flashes the
|
|
143
|
+
// matching line. Both setConfigKey's and unsetConfigKey's success members carry `oldValue`, so
|
|
144
|
+
// the shared structural type accepts either branch's `result`.
|
|
145
|
+
const flashQuietOutcome = (result, text) => {
|
|
146
|
+
const team = quietTeam;
|
|
147
|
+
const role = lockedRole;
|
|
148
|
+
exitQuietMode();
|
|
149
|
+
const scope = team ? "team" : `${role}`;
|
|
150
|
+
const noop = text === "" ? result.oldValue === undefined : "unchanged" in result;
|
|
151
|
+
session.flashMessage(noop
|
|
152
|
+
? text === ""
|
|
153
|
+
? `${scope}: quiet hours is not set in tumwater.json, so nothing was cleared`
|
|
154
|
+
: `${scope}: quiet hours is already ${text}, so tumwater.json was not written`
|
|
155
|
+
: text === ""
|
|
156
|
+
? `${scope}: quiet hours cleared`
|
|
157
|
+
: `${scope}: quiet hours set to ${text}`);
|
|
158
|
+
};
|
|
159
|
+
const handleKey = (_str, key, ctx) => {
|
|
160
|
+
if (key.ctrl && key.name === "o")
|
|
161
|
+
return roleQuietKey(ctx);
|
|
162
|
+
if (key.ctrl && key.name === "s")
|
|
163
|
+
return teamQuietKey(ctx);
|
|
164
|
+
if (key.name === "escape" && quietMode)
|
|
165
|
+
return cancelQuietEdit();
|
|
166
|
+
if (key.name === "return" && quietMode)
|
|
167
|
+
return commitQuietEntry();
|
|
154
168
|
return false;
|
|
155
169
|
};
|
|
156
170
|
return {
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { releaseQuarantinedTest } from "../../review/known-flakes.js";
|
|
2
|
+
/** The Failures view's Ctrl+X release editor on the shared prompt line, the terminal twin of
|
|
3
|
+
* `tumwater quarantine --release <n>` and the dashboard Failures card's Release button. Like
|
|
4
|
+
* the retire editor it owns its `releaseMode` flag, its saved-draft pair, and the handleKey
|
|
5
|
+
* arms for its key (the toggle with the `heldEditorFlash(entering)` guard clause — the shared
|
|
6
|
+
* guard priority lives in the session module — plus Esc and Enter). Guarded to the Failures
|
|
7
|
+
* view (`ctx.view === roleIds.length + 3`), so every other view's Ctrl+X arm (Transcript clears
|
|
8
|
+
* notes, Backlog/Inbox/Pending cancel, History model filter, Activity backends) keeps the key.
|
|
9
|
+
* Enter runs the same `releaseQuarantinedTest(session.root, test)` core the CLI and the
|
|
10
|
+
* dashboard button call, with a typed test path as its confirmation (no two-press arm): the
|
|
11
|
+
* exact path the Failures list shows, Enter to release, Esc to cancel. A success removes the
|
|
12
|
+
* entry from `.tumwater/state/quarantine.json`, exits the editor, flashes the outcome, and
|
|
13
|
+
* repaints the Failures body; a name not in the list flashes `not in the flake quarantine` and
|
|
14
|
+
* STAYS open so the operator can fix the name; an empty name flashes and stays. */
|
|
15
|
+
export function createQuarantineReleaseEditor(session) {
|
|
16
|
+
let releaseMode = false;
|
|
17
|
+
let restoreDraft = () => { };
|
|
18
|
+
// Leave release-edit mode (Esc, Enter's success path, or Ctrl+T): restore the saved prompt
|
|
19
|
+
// text.
|
|
20
|
+
const exitReleaseMode = () => {
|
|
21
|
+
if (!releaseMode)
|
|
22
|
+
return;
|
|
23
|
+
releaseMode = false;
|
|
24
|
+
session.held.release = false;
|
|
25
|
+
restoreDraft();
|
|
26
|
+
};
|
|
27
|
+
// Open release-edit mode on the Failures view's prompt line (Ctrl+X). The Failures index
|
|
28
|
+
// guard keeps the same key's other arms (Transcript notes-clear, Backlog cancel, and the
|
|
29
|
+
// like) on their own views; a guard miss returns false so the key falls through. The line
|
|
30
|
+
// edits one quarantined test path, typed exactly as the Failures list shows it; Enter
|
|
31
|
+
// releases that test through the same releaseQuarantinedTest core the CLI and the dashboard
|
|
32
|
+
// button use. Entering saves the prompt draft and blanks the line; leaving restores it.
|
|
33
|
+
const openReleaseEdit = (ctx) => {
|
|
34
|
+
if (ctx.view !== ctx.roleIds.length + 3 || releaseMode)
|
|
35
|
+
return false;
|
|
36
|
+
if (session.editorEntryDenied("release"))
|
|
37
|
+
return true;
|
|
38
|
+
// Mid-recall, hand the arrows' saved draft back before the release editor takes the line —
|
|
39
|
+
// the same settle the retire and role editors do — so leaving restores the draft, not a
|
|
40
|
+
// history entry, with no stale recall state riding along.
|
|
41
|
+
restoreDraft = session.takeDraft();
|
|
42
|
+
releaseMode = true;
|
|
43
|
+
session.held.release = true;
|
|
44
|
+
session.input = "";
|
|
45
|
+
session.cursor = 0;
|
|
46
|
+
session.flashMessage("release a quarantined test: type its test path as the Failures list shows it, Enter to release, Esc to cancel");
|
|
47
|
+
return true;
|
|
48
|
+
};
|
|
49
|
+
// Esc cancels release-edit mode, restoring the previous prompt text and removing nothing.
|
|
50
|
+
const cancelReleaseEdit = () => {
|
|
51
|
+
exitReleaseMode();
|
|
52
|
+
session.flashMessage("release cancelled");
|
|
53
|
+
return true;
|
|
54
|
+
};
|
|
55
|
+
// Enter releases the typed test path through releaseQuarantinedTest — the same core, and
|
|
56
|
+
// the same single-press confirmation, the CLI's `tumwater quarantine --release <n>` and
|
|
57
|
+
// the dashboard Failures card's Release button use — then flashes the shared outcome
|
|
58
|
+
// wording and repaints the Failures body so the released line is gone. An empty name, or
|
|
59
|
+
// one not in the quarantine, flashes and STAYS in the editor so the operator can fix it.
|
|
60
|
+
const submitReleaseEdit = () => {
|
|
61
|
+
const test = session.input.trim();
|
|
62
|
+
if (test === "") {
|
|
63
|
+
session.flashMessage("a test name is required");
|
|
64
|
+
return true;
|
|
65
|
+
}
|
|
66
|
+
if (!releaseQuarantinedTest(session.root, test)) {
|
|
67
|
+
session.flashMessage(`not in the flake quarantine: ${test}`);
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
exitReleaseMode();
|
|
71
|
+
session.flashMessage(`released quarantined test ${test} — the gate no longer excuses it`);
|
|
72
|
+
session.refreshReport();
|
|
73
|
+
return true;
|
|
74
|
+
};
|
|
75
|
+
const handleKey = (_str, key, ctx) => {
|
|
76
|
+
if (key.ctrl && key.name === "x")
|
|
77
|
+
return openReleaseEdit(ctx);
|
|
78
|
+
if (key.name === "escape" && releaseMode)
|
|
79
|
+
return cancelReleaseEdit();
|
|
80
|
+
if (key.name === "return" && releaseMode)
|
|
81
|
+
return submitReleaseEdit();
|
|
82
|
+
return false;
|
|
83
|
+
};
|
|
84
|
+
return { handleKey, exit: exitReleaseMode };
|
|
85
|
+
}
|
|
@@ -20,55 +20,66 @@ export function createRetireEditor(session) {
|
|
|
20
20
|
session.held.retire = false;
|
|
21
21
|
restoreDraft();
|
|
22
22
|
};
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
restoreDraft = session.takeDraft();
|
|
37
|
-
retireMode = true;
|
|
38
|
-
session.held.retire = true;
|
|
39
|
-
session.input = "";
|
|
40
|
-
session.cursor = 0;
|
|
41
|
-
session.flashMessage("retire a disabled role's leftovers: type the role id, Enter to retire, Esc to cancel");
|
|
23
|
+
// Ctrl+X toggles retire-edit mode on the Health view's prompt line (mnemonic: **x**
|
|
24
|
+
// remove — the same key the Transcript notes-clear, Backlog cancel, and Inbox cancel
|
|
25
|
+
// arms use on their own views, guarded away here by the Health index). The line edits one
|
|
26
|
+
// role id; Enter retires that role's leftovers through the same retireRole core the CLI
|
|
27
|
+
// and the dashboard button use. Entering saves the prompt draft and blanks the line;
|
|
28
|
+
// leaving restores it. The guard returns false on a miss so the key falls through to the
|
|
29
|
+
// panes' own Ctrl+X arms on their views.
|
|
30
|
+
const openRetireEdit = (ctx) => {
|
|
31
|
+
if (ctx.view !== ctx.roleIds.length + 7)
|
|
32
|
+
return false;
|
|
33
|
+
if (retireMode)
|
|
34
|
+
return false;
|
|
35
|
+
if (session.editorEntryDenied("retire"))
|
|
42
36
|
return true;
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
37
|
+
// Mid-recall, hand the arrows' saved draft back before the retire editor takes the line —
|
|
38
|
+
// the same settle the role editors do — so leaving restores the draft, not a
|
|
39
|
+
// history entry, with no stale recall state riding along.
|
|
40
|
+
restoreDraft = session.takeDraft();
|
|
41
|
+
retireMode = true;
|
|
42
|
+
session.held.retire = true;
|
|
43
|
+
session.input = "";
|
|
44
|
+
session.cursor = 0;
|
|
45
|
+
session.flashMessage("retire a disabled role's leftovers: type the role id, Enter to retire, Esc to cancel");
|
|
46
|
+
return true;
|
|
47
|
+
};
|
|
48
|
+
// Esc cancels retire-edit mode, restoring the previous prompt text and removing nothing.
|
|
49
|
+
const cancelRetireEdit = () => {
|
|
50
|
+
exitRetireMode();
|
|
51
|
+
session.flashMessage("retire cancelled");
|
|
52
|
+
return true;
|
|
53
|
+
};
|
|
54
|
+
// Enter retires the typed role id through retireRole — the same core, and the same
|
|
55
|
+
// never-force policy, the CLI's `tumwater retire --role <id>` and the dashboard's Retire
|
|
56
|
+
// button use — then flashes the dashboard's own outcome wording. An empty id flashes and
|
|
57
|
+
// STAYS in the editor so the operator can type one; a safety-rail refusal throws (the
|
|
58
|
+
// role still enabled, unmerged commits, a dirty or unusable worktree, a run in progress)
|
|
59
|
+
// and the cursor stays so the operator can fix the id or reach for the CLI's --force.
|
|
60
|
+
const submitRetireEdit = () => {
|
|
61
|
+
const role = session.input.trim();
|
|
62
|
+
if (role === "") {
|
|
63
|
+
session.flashMessage("a role id is required");
|
|
70
64
|
return true;
|
|
71
65
|
}
|
|
66
|
+
void retireRole(session.root, role, {}).then(({ removed }) => {
|
|
67
|
+
exitRetireMode();
|
|
68
|
+
session.flashMessage(removed.length === 0
|
|
69
|
+
? `nothing to remove for role ${role}`
|
|
70
|
+
: removed.map((a) => `removed ${artifactPhrase(a, role)}`).join("; "));
|
|
71
|
+
}, (err) => {
|
|
72
|
+
session.flashError(err);
|
|
73
|
+
});
|
|
74
|
+
return true;
|
|
75
|
+
};
|
|
76
|
+
const handleKey = (_str, key, ctx) => {
|
|
77
|
+
if (key.ctrl && key.name === "x")
|
|
78
|
+
return openRetireEdit(ctx);
|
|
79
|
+
if (key.name === "escape" && retireMode)
|
|
80
|
+
return cancelRetireEdit();
|
|
81
|
+
if (key.name === "return" && retireMode)
|
|
82
|
+
return submitRetireEdit();
|
|
72
83
|
return false;
|
|
73
84
|
};
|
|
74
85
|
return { handleKey, exit: exitRetireMode };
|