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.
- package/README.md +6 -6
- package/dist/build-info.json +2 -2
- package/dist/src/agent/agent-backend.js +6 -6
- package/dist/src/agent/{run-agent.js → agent-run.js} +27 -13
- package/dist/src/agent/{run-settle.js → agent-settle.js} +48 -24
- package/dist/src/agent/{run-spawn.js → agent-spawn.js} +21 -12
- package/dist/src/agent/{run-stream.js → agent-stream.js} +4 -4
- package/dist/src/agent/backend-traits.js +2 -2
- package/dist/src/agent/claude/claude-account.js +6 -27
- package/dist/src/agent/claude/claude-backend.js +3 -3
- package/dist/src/agent/claude/claude-env.js +27 -31
- package/dist/src/agent/claude/claude-stream.js +111 -54
- package/dist/src/agent/codex/codex-account.js +8 -0
- package/dist/src/agent/codex/codex-args.js +5 -5
- package/dist/src/agent/codex/codex-backend.js +2 -2
- package/dist/src/agent/codex/codex-env.js +28 -15
- package/dist/src/agent/codex/codex-hook-trust.js +39 -20
- package/dist/src/agent/codex/codex-rollout.js +93 -56
- package/dist/src/agent/codex/codex-stream.js +1 -1
- package/dist/src/agent/env-scrub.js +18 -0
- package/dist/src/agent/image-input.js +68 -11
- package/dist/src/agent/notes-shim.js +2 -2
- package/dist/src/agent/opencode/opencode-backend.js +26 -24
- package/dist/src/agent/opencode/opencode-catalog.js +85 -36
- package/dist/src/agent/opencode/opencode-config.js +32 -14
- package/dist/src/agent/opencode/opencode-env.js +12 -13
- package/dist/src/agent/opencode/opencode-stream.js +47 -25
- package/dist/src/agent/session-ledger.js +23 -27
- package/dist/src/agent/subscription-account.js +2 -83
- package/dist/src/backlog/backlog-archive.js +3 -4
- package/dist/src/backlog/backlog-cli-view.js +81 -41
- package/dist/src/backlog/backlog-cli.js +57 -26
- package/dist/src/backlog/backlog-eligibility.js +56 -122
- package/dist/src/backlog/backlog-md.js +5 -5
- package/dist/src/backlog/backlog-parts.js +65 -27
- package/dist/src/backlog/{question-answer.js → backlog-question-answer.js} +1 -1
- package/dist/src/backlog/backlog-release.js +12 -12
- package/dist/src/backlog/backlog-structure.js +114 -61
- package/dist/src/backlog/backlog.js +3 -2
- package/dist/src/baseline/main-baseline.js +148 -84
- package/dist/src/baseline/main-guardrails.js +2 -2
- package/dist/src/baseline/main-watch.js +84 -50
- package/dist/src/brief/brief.js +26 -13
- package/dist/src/budget/budget-fallback-view.js +100 -243
- package/dist/src/budget/budget-wiring.js +29 -65
- package/dist/src/budget/budget.js +4 -5
- package/dist/src/budget/fallback-breaker.js +2 -2
- package/dist/src/build/build-check-detect.js +43 -14
- package/dist/src/build/build-check-events.js +2 -2
- package/dist/src/build/build-check-report.js +10 -0
- package/dist/src/build/build-check-scoped.js +40 -18
- package/dist/src/build/build-check.js +13 -5
- package/dist/src/build/build-stage.js +6 -4
- package/dist/src/change/change-data.js +1 -4
- package/dist/src/cli/cli-command-args.js +89 -52
- package/dist/src/cli/cli-completion-command.js +75 -0
- package/dist/src/cli/{config-commands.js → cli-config-commands.js} +82 -71
- package/dist/src/cli/cli-doctor-command.js +103 -0
- package/dist/src/cli/cli-flag-specs.js +42 -22
- package/dist/src/cli/cli-init.js +36 -14
- package/dist/src/cli/{log-commands.js → cli-log-commands.js} +43 -23
- package/dist/src/cli/{notify-command.js → cli-notify-command.js} +2 -2
- package/dist/src/cli/cli-query-commands.js +2 -2
- package/dist/src/cli/{question-commands.js → cli-question-commands.js} +86 -60
- package/dist/src/cli/cli-repo-gate.js +2 -2
- package/dist/src/cli/cli-role-command.js +2 -41
- package/dist/src/cli/cli-start-summary.js +4 -3
- package/dist/src/cli/cli-start-supervisor.js +190 -0
- package/dist/src/cli/cli-start.js +94 -244
- package/dist/src/cli/completion.js +85 -49
- package/dist/src/cli/follow-view.js +20 -2
- package/dist/src/cli/help.js +6 -13
- package/dist/src/cli.js +70 -176
- package/dist/src/collections.js +39 -27
- package/dist/src/concurrency/check-slots.js +70 -0
- package/dist/src/concurrency/lock.js +23 -1
- package/dist/src/concurrency/run-permit.js +49 -0
- package/dist/src/concurrency/semaphore.js +46 -250
- package/dist/src/config/config-editable-keys.js +4 -7
- package/dist/src/config/config-fallbacks.js +81 -101
- package/dist/src/config/config-field-checks.js +66 -37
- package/dist/src/config/config-keys.js +66 -40
- package/dist/src/config/config-live.js +4 -28
- package/dist/src/config/{model-selector.js → config-model-selector.js} +4 -4
- package/dist/src/config/config-request.js +163 -102
- package/dist/src/config/config-schema.js +10 -12
- package/dist/src/config/config-selector-checks.js +1 -1
- package/dist/src/config/config-validation-agents.js +2 -2
- package/dist/src/config/config-validation-roles.js +8 -72
- package/dist/src/config/config-validation.js +4 -39
- package/dist/src/config/config-views.js +26 -50
- package/dist/src/config/config-write.js +128 -102
- package/dist/src/config/config.js +5 -14
- package/dist/src/doctor/doctor-backlog-bugs.js +7 -6
- package/dist/src/doctor/doctor-backlog-plans.js +53 -120
- package/dist/src/doctor/doctor-backlog.js +16 -14
- package/dist/src/doctor/doctor-check-names.js +3 -6
- package/dist/src/doctor/doctor-checks.js +8 -8
- package/dist/src/doctor/doctor-claude-checks.js +70 -36
- package/dist/src/doctor/doctor-codex-checks.js +6 -6
- package/dist/src/doctor/doctor-contention-checks.js +57 -117
- package/dist/src/doctor/doctor-fallback-checks.js +48 -26
- package/dist/src/doctor/doctor-holds.js +8 -47
- package/dist/src/doctor/doctor-install.js +1 -1
- package/dist/src/doctor/doctor-launch-services.js +1 -1
- package/dist/src/doctor/doctor-model-checks.js +178 -94
- package/dist/src/doctor/doctor-notify.js +2 -2
- package/dist/src/doctor/doctor-opencode-checks.js +5 -4
- package/dist/src/doctor/doctor-orphans.js +122 -21
- package/dist/src/doctor/{outcome.js → doctor-outcome.js} +7 -0
- package/dist/src/doctor/doctor-pi-checks.js +2 -2
- package/dist/src/doctor/doctor-principles.js +3 -3
- package/dist/src/doctor/doctor-project-checks.js +24 -11
- package/dist/src/doctor/doctor-role-notes.js +1 -1
- package/dist/src/doctor/doctor.js +8 -11
- package/dist/src/events/event-format.js +44 -64
- package/dist/src/events/event-labels.js +1 -5
- package/dist/src/events/event-legacy.js +3 -5
- package/dist/src/events/{notify.js → event-notify.js} +6 -6
- package/dist/src/events/event-read.js +1 -1
- package/dist/src/events/event-window.js +1 -1
- package/dist/src/events/events.js +8 -0
- package/dist/src/failure/failure-cluster.js +9 -18
- package/dist/src/failure/failure-data.js +12 -8
- package/dist/src/failure/failure-render.js +46 -36
- package/dist/src/failure/failure-state-change.js +54 -120
- package/dist/src/failure/{time-spend.js → failure-time-spend.js} +117 -84
- package/dist/src/files/files.js +30 -0
- package/dist/src/files/json-object.js +8 -8
- package/dist/src/files/stat-cache.js +12 -8
- package/dist/src/gates/budget-gates.js +85 -84
- package/dist/src/gates/gate-polls.js +32 -74
- package/dist/src/gates/pause-gates.js +16 -2
- package/dist/src/gates/readiness.js +1 -1
- package/dist/src/gates/role-cap-gates.js +44 -35
- package/dist/src/gates/{startup-gate.js → startup-gates.js} +9 -8
- package/dist/src/gates/{streak-gate.js → streak-gates.js} +75 -52
- package/dist/src/git/git-diff.js +6 -2
- package/dist/src/git/git.js +75 -45
- package/dist/src/git/worktree-pool.js +3 -6
- package/dist/src/gui/gui-admin-endpoints.js +3 -3
- package/dist/src/gui/gui-args.js +12 -0
- package/dist/src/gui/gui-backlog-commands.js +4 -4
- package/dist/src/gui/gui-backlog-endpoints.js +61 -41
- package/dist/src/gui/gui-command.js +43 -24
- package/dist/src/gui/gui-config-commands.js +2 -64
- package/dist/src/gui/gui-endpoint-commands.js +1 -1
- package/dist/src/gui/gui-endpoint-common.js +19 -1
- package/dist/src/gui/gui-history-endpoints.js +100 -52
- package/dist/src/gui/gui-operator-commands.js +43 -48
- package/dist/src/gui/gui-prompt-commands.js +25 -25
- package/dist/src/gui/gui-report-endpoints.js +1 -1
- package/dist/src/gui/gui-server.js +1 -2
- package/dist/src/gui/gui-team-commands.js +2 -1
- package/dist/src/history/history-data.js +71 -44
- package/dist/src/history/history-queued.js +49 -27
- package/dist/src/history/history.js +54 -36
- package/dist/src/inbox/prompt-commands.js +59 -42
- package/dist/src/init/init.js +88 -40
- package/dist/src/merge/merge-backlog-conflicts.js +1 -1
- package/dist/src/merge/merge-check-failures.js +12 -9
- package/dist/src/merge/merge-core.js +7 -32
- package/dist/src/merge/merge-drain.js +11 -13
- package/dist/src/merge/merge-pipeline.js +6 -7
- package/dist/src/merge/merge-post-review-drain.js +11 -14
- package/dist/src/merge/merge-queue-render.js +3 -3
- package/dist/src/merge/merge-requeue.js +1 -1
- package/dist/src/merge/merge-slot.js +0 -8
- package/dist/src/merge/merge-stack.js +17 -18
- package/dist/src/merge/merge-to-main.js +13 -58
- package/dist/src/merge/merge-verify.js +12 -21
- package/dist/src/notes-cli.js +52 -42
- package/dist/src/operator/operator-commands.js +80 -47
- package/dist/src/operator/operator-intent.js +3 -3
- package/dist/src/orchestrator/orchestrator-launch.js +31 -63
- package/dist/src/orchestrator/orchestrator-poll.js +44 -69
- package/dist/src/orchestrator/orchestrator-scheduling.js +5 -9
- package/dist/src/orchestrator/orchestrator.js +8 -21
- package/dist/src/orchestrator/scheduling-admit.js +101 -68
- package/dist/src/orchestrator/scheduling-scope.js +18 -27
- package/dist/src/orchestrator/scheduling-skip.js +10 -14
- package/dist/src/orchestrator/startup-migrations.js +1 -1
- package/dist/src/paths.js +10 -9
- package/dist/src/pi/pi-args.js +5 -2
- package/dist/src/pi/pi-backend.js +2 -2
- package/dist/src/pi/pi-bin.js +3 -3
- package/dist/src/pi/pi-event-line.js +28 -1
- package/dist/src/pi/pi-listed-models.js +13 -0
- package/dist/src/pi/pi-models.js +43 -29
- package/dist/src/pi/pi-settings.js +43 -20
- package/dist/src/pi/pi-stream.js +41 -8
- package/dist/src/pi/pi-watchdogs.js +43 -15
- package/dist/src/pi/pi.js +2 -2
- package/dist/src/pi-extension/context-shake.js +32 -20
- package/dist/src/pi-extension/kill-guard.js +69 -0
- package/dist/src/process/process-group.js +51 -26
- package/dist/src/process/process.js +20 -12
- package/dist/src/process/run-marker.js +33 -15
- package/dist/src/process/supervisor.js +40 -28
- package/dist/src/prompt/dialect.js +1 -1
- package/dist/src/prompt/gate-notes.js +2 -2
- package/dist/src/prompt/gate-prompts.js +1 -44
- package/dist/src/prompt/notes-backlog.js +66 -37
- package/dist/src/prompt/principles.js +6 -4
- package/dist/src/prompt/prompt-followup.js +12 -4
- package/dist/src/prompt/prompt.js +88 -38
- package/dist/src/redeploy/redeployer.js +21 -1
- package/dist/src/report/report-data.js +53 -37
- package/dist/src/report/report-fold.js +16 -9
- package/dist/src/report/report-render.js +21 -194
- package/dist/src/report/report-since-data.js +4 -3
- package/dist/src/report/report-since-render.js +173 -0
- package/dist/src/report/report-usage-fold.js +8 -8
- package/dist/src/report/report.js +46 -22
- package/dist/src/review/diff-rules.js +35 -21
- package/dist/src/review/post-merge-review-render.js +9 -3
- package/dist/src/review/post-merge-review.js +48 -41
- package/dist/src/review/review-precheck.js +14 -4
- package/dist/src/review/review.js +48 -170
- package/dist/src/review/secret-leaks.js +26 -16
- package/dist/src/roles/legacy-role-migration.js +6 -3
- package/dist/src/roles/role-catalog.js +11 -3
- package/dist/src/roles/role-guidance.js +16 -12
- package/dist/src/roles/role-render.js +7 -46
- package/dist/src/roles/role-view.js +10 -33
- package/dist/src/run/acceptance-greps.js +88 -45
- package/dist/src/run/ci-parity.js +19 -8
- package/dist/src/run/run-apply.js +12 -15
- package/dist/src/run/run-detail-data.js +121 -68
- package/dist/src/run/run-detail-render.js +92 -35
- package/dist/src/run/run-detail.js +72 -41
- package/dist/src/run/run-prompt.js +63 -50
- package/dist/src/run/run-resume.js +76 -29
- package/dist/src/run/run-stage.js +141 -90
- package/dist/src/run/run-timing.js +8 -2
- package/dist/src/run/run-usage.js +31 -1
- package/dist/src/run/run-verdict.js +128 -50
- package/dist/src/run/stage-check.js +60 -50
- package/dist/src/run/stage-checks-shared.js +28 -0
- package/dist/src/run/stage-name-checks.js +111 -92
- package/dist/src/run/stage-path-checks.js +69 -42
- package/dist/src/run/stage-prose-checks.js +55 -40
- package/dist/src/runner/{leftover.js → runner-leftover.js} +9 -26
- package/dist/src/runner/runner-outcome.js +7 -2
- package/dist/src/runner/runner-pi.js +9 -22
- package/dist/src/runner/runner-recovery.js +5 -13
- package/dist/src/runner/{revision.js → runner-revision.js} +1 -2
- package/dist/src/runner/runner-run.js +103 -50
- package/dist/src/runner/runner-state-load.js +18 -28
- package/dist/src/runner/runner-state.js +0 -14
- package/dist/src/runner/runner.js +41 -25
- package/dist/src/scheduling/backoff.js +8 -3
- package/dist/src/scheduling/claim-entries.js +2 -1
- package/dist/src/scheduling/claims.js +0 -4
- package/dist/src/scheduling/scheduling.js +53 -66
- package/dist/src/status/status-budget.js +4 -43
- package/dist/src/status/status-data.js +35 -39
- package/dist/src/status/status-role-notes.js +2 -2
- package/dist/src/status/status-roles.js +9 -18
- package/dist/src/team/{error-storm.js → team-error-storm.js} +1 -1
- package/dist/src/team/{failure-spread.js → team-failure-spread.js} +5 -5
- package/dist/src/team/team-hold.js +105 -70
- package/dist/src/team/{orchestrator-info.js → team-orchestrator-info.js} +55 -36
- package/dist/src/team/team-polls.js +53 -201
- package/dist/src/team/{reclaim.js → team-reclaim.js} +61 -31
- package/dist/src/team/team-state.js +4 -13
- package/dist/src/text/datetime.js +16 -7
- package/dist/src/text/markdown.js +1 -1
- package/dist/src/text/phrases.js +29 -15
- package/dist/src/text/suggest.js +1 -1
- package/dist/src/text/text.js +6 -5
- package/dist/src/ui/alert-feeds.js +46 -111
- package/dist/src/ui/badges.js +17 -88
- package/dist/src/ui/gui/gui-client-boot.js +2 -8
- package/dist/src/ui/gui/gui-client-composer.js +1 -1
- package/dist/src/ui/gui/gui-client-drawer-config.js +8 -33
- package/dist/src/ui/gui/gui-client-drawer.js +1 -1
- package/dist/src/ui/gui/gui-client-feed.js +1 -1
- package/dist/src/ui/gui/gui-client-history.js +28 -14
- package/dist/src/ui/gui/gui-client-model.js +0 -12
- package/dist/src/ui/gui/gui-client-operator.js +7 -5
- package/dist/src/ui/gui/gui-client-pending.js +7 -6
- package/dist/src/ui/gui/gui-client-report-chart.js +5 -5
- package/dist/src/ui/gui/gui-client-report.js +1 -1
- package/dist/src/ui/gui/gui-client-roles.js +0 -1
- package/dist/src/ui/gui/gui-client-settings.js +43 -84
- package/dist/src/ui/gui/gui-client-team.js +12 -6
- package/dist/src/ui/gui/gui-client.js +7 -1
- package/dist/src/ui/progress-data.js +107 -44
- package/dist/src/ui/run-progress-model.js +20 -4
- package/dist/src/ui/status-model.js +2 -24
- package/dist/src/ui/status-payload.js +11 -30
- package/dist/src/ui/status-render.js +13 -24
- package/dist/src/ui/team-alert-backlog.js +14 -204
- package/dist/src/ui/team-alert-shared.js +6 -29
- package/dist/src/ui/team-alert-status.js +9 -170
- package/dist/src/ui/team-alert-tail.js +19 -215
- package/dist/src/ui/team-alerts.js +8 -15
- package/dist/src/ui/transcript-run.js +27 -16
- package/dist/src/ui/tui/tui-backlog.js +2 -2
- package/dist/src/ui/tui/tui-editor-answer.js +12 -10
- package/dist/src/ui/tui/tui-editor-attach.js +4 -3
- package/dist/src/ui/tui/tui-editor-backends.js +4 -3
- package/dist/src/ui/tui/tui-editor-brief.js +6 -9
- package/dist/src/ui/tui/tui-editor-budget.js +4 -3
- package/dist/src/ui/tui/tui-editor-config.js +80 -68
- package/dist/src/ui/tui/tui-editor-entry.js +12 -20
- package/dist/src/ui/tui/tui-editor-pause.js +51 -57
- package/dist/src/ui/tui/tui-editor-prompt.js +9 -13
- package/dist/src/ui/tui/tui-editor-queued.js +11 -14
- package/dist/src/ui/tui/tui-editor-quiet.js +4 -3
- package/dist/src/ui/tui/tui-editor-release.js +4 -3
- package/dist/src/ui/tui/tui-editor-retire.js +7 -7
- package/dist/src/ui/tui/tui-editor-role-enabled.js +4 -3
- package/dist/src/ui/tui/tui-editor-role-file.js +4 -4
- package/dist/src/ui/tui/tui-editor-role-key.js +4 -3
- package/dist/src/ui/tui/tui-editor-role-model.js +97 -15
- package/dist/src/ui/tui/tui-editor-search.js +4 -3
- package/dist/src/ui/tui/tui-editor-session.js +17 -4
- package/dist/src/ui/tui/tui-editors.js +5 -10
- package/dist/src/ui/tui/tui-frame.js +182 -134
- package/dist/src/ui/tui/tui-guard.js +20 -0
- package/dist/src/ui/tui/tui-input.js +167 -98
- package/dist/src/ui/tui/tui-keymap.js +51 -29
- package/dist/src/ui/tui/tui-keys.js +1 -1
- package/dist/src/ui/tui/tui-pane-activity.js +5 -4
- package/dist/src/ui/tui/tui-pane-backlog.js +74 -36
- package/dist/src/ui/tui/tui-pane-ctx.js +1 -1
- package/dist/src/ui/tui/tui-pane-health.js +252 -230
- package/dist/src/ui/tui/tui-pane-history.js +67 -74
- package/dist/src/ui/tui/tui-pane-pending.js +288 -247
- package/dist/src/ui/tui/tui-pane-queued.js +25 -27
- package/dist/src/ui/tui/tui-pane-report.js +2 -1
- package/dist/src/ui/tui/tui-pane-session.js +15 -10
- package/dist/src/ui/tui/tui-pane-transcript.js +19 -31
- package/dist/src/ui/tui/tui-pane.js +14 -11
- package/dist/src/ui/tui/tui-panes.js +38 -33
- package/dist/src/ui/tui/tui.js +4 -7
- package/dist/src/verdict/fix-claim.js +107 -56
- package/dist/src/verdict/refusal.js +1 -1
- package/dist/src/verdict/reply-contract.js +3 -4
- package/package.json +5 -5
- package/dist/src/concurrency/provider-lease.js +0 -105
- package/dist/src/review/review-followup.js +0 -87
- package/dist/src/review/review-run.js +0 -395
- package/dist/src/runner/model-fallback.js +0 -76
- package/dist/src/runner/runner-fallback.js +0 -66
- package/dist/src/ui/tui/tui-editor-claude-reserve.js +0 -24
- package/dist/src/ui/tui/tui-editor-provider-caps.js +0 -27
- /package/dist/src/cli/{brief-command.js → cli-brief-command.js} +0 -0
- /package/dist/src/doctor/{probe.js → doctor-probe.js} +0 -0
- /package/dist/src/gates/{disk-gate.js → disk-gates.js} +0 -0
- /package/dist/src/operator/{retire.js → operator-retire.js} +0 -0
- /package/dist/src/operator/{revert.js → operator-revert.js} +0 -0
|
@@ -3,7 +3,7 @@ import { renderMergeQueue, revisionNote, retryNote, scopeMergeQueue } from "../.
|
|
|
3
3
|
import { cancelQueuedMerge } from "../../merge/merge-cancel.js";
|
|
4
4
|
import { moveQueuedMergeToTop } from "../../merge/merge-top.js";
|
|
5
5
|
import { mergeChanges } from "../../merge/merge-slot.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 { renderChangeOverlaps, renderRoleChanges, renderTeamChange } from "../../change/change-render.js";
|
|
9
9
|
import { scopeChangeOverlaps } from "../../change/change-data.js";
|
|
@@ -15,7 +15,9 @@ import { renderPostReviews, postReviewStatusLabel, scopePostReviews, POST_REVIEW
|
|
|
15
15
|
import { acceptPostReview } from "../../review/post-merge-review-accept.js";
|
|
16
16
|
import { squash } from "../../text/text.js";
|
|
17
17
|
import { promptLineFree } from "./tui-pane-ctx.js";
|
|
18
|
+
import { paneAtHead } from "./tui-pane-session.js";
|
|
18
19
|
import { arrowDir } from "./tui-keymap.js";
|
|
20
|
+
import { distinctOrdered } from "../../collections.js";
|
|
19
21
|
import { moveEntrySelection } from "./tui-backlog.js";
|
|
20
22
|
/** The overlapping-files block under the roster: empty for a no-base view (the roster above
|
|
21
23
|
* already says the main branch is missing), else the CLI's renderChangeOverlaps under a
|
|
@@ -37,32 +39,19 @@ export function pendingOverlapsText(view, scope) {
|
|
|
37
39
|
export function overlapHolders(view) {
|
|
38
40
|
if (view.state === "no-base")
|
|
39
41
|
return [];
|
|
40
|
-
|
|
41
|
-
for (const o of view.overlaps)
|
|
42
|
-
for (const r of o.roles)
|
|
43
|
-
if (!seen.includes(r))
|
|
44
|
-
seen.push(r);
|
|
45
|
-
return seen;
|
|
42
|
+
return distinctOrdered(view.overlaps.flatMap((o) => o.roles));
|
|
46
43
|
}
|
|
47
44
|
/** The distinct roles named across the merge queue, in first-seen (queue) order; empty for an
|
|
48
45
|
* empty queue. The queue's own roles lead the pane's Ctrl+G scope cycle, before the overlap
|
|
49
46
|
* holders — the queue scope is the `tumwater queue --role <id>` twin. */
|
|
50
47
|
export function mergeQueueRoles(view) {
|
|
51
|
-
|
|
52
|
-
for (const e of view.entries)
|
|
53
|
-
if (!seen.includes(e.role))
|
|
54
|
-
seen.push(e.role);
|
|
55
|
-
return seen;
|
|
48
|
+
return distinctOrdered(view.entries.map((e) => e.role));
|
|
56
49
|
}
|
|
57
50
|
/** The distinct roles named across the post-merge review queue, in first-seen (FIFO) order;
|
|
58
51
|
* empty for an empty queue. The review block's contribution to the pane's Ctrl+G scope cycle,
|
|
59
52
|
* after the queue's roles — the review scope is the `tumwater reviews --role <id>` twin. */
|
|
60
53
|
export function postReviewRoles(entries) {
|
|
61
|
-
|
|
62
|
-
for (const e of entries)
|
|
63
|
-
if (!seen.includes(e.role))
|
|
64
|
-
seen.push(e.role);
|
|
65
|
-
return seen;
|
|
54
|
+
return distinctOrdered(entries.map((e) => e.role));
|
|
66
55
|
}
|
|
67
56
|
/** The heading over one overlap's drill-down: its place in the list and the path. */
|
|
68
57
|
export function overlapDrillHeading(index, total, path) {
|
|
@@ -274,6 +263,19 @@ export function createPendingPane(session) {
|
|
|
274
263
|
}
|
|
275
264
|
}
|
|
276
265
|
};
|
|
266
|
+
// Re-render the whole queue body from scratch after a filter (Ctrl+G role scope, Ctrl+O
|
|
267
|
+
// review state) cycled: rebuild the rows off the current filter, drop any overlap drill,
|
|
268
|
+
// reset the composed body and the pane cache to the new content, and repaint from the top.
|
|
269
|
+
// The shared tail of the two filter-cycle arms, so both filters repaint identically.
|
|
270
|
+
const repaintQueueBody = () => {
|
|
271
|
+
rebuildQueueRows();
|
|
272
|
+
overlapIndex = -1;
|
|
273
|
+
++drillGen;
|
|
274
|
+
head = queueAndReviews() + rosterText;
|
|
275
|
+
compose();
|
|
276
|
+
session.paneCache = base;
|
|
277
|
+
paneAtHead(session);
|
|
278
|
+
};
|
|
277
279
|
const refresh = () => {
|
|
278
280
|
// Generation guard: each refresh bumps the counter, and a resolution paints only if it
|
|
279
281
|
// is the latest request's — Ctrl+R held twice, or a refresh raced with a re-entry,
|
|
@@ -307,275 +309,314 @@ export function createPendingPane(session) {
|
|
|
307
309
|
session.requestRender();
|
|
308
310
|
});
|
|
309
311
|
};
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
312
|
+
// The shared Pending-pane key-arm guard: the prompt line is free (an editor would keep the
|
|
313
|
+
// key for its own view's meaning) and the Pending view (roleIds.length + 5) is current. True
|
|
314
|
+
// means the arm may run.
|
|
315
|
+
const onPendingView = (ctx) => promptLineFree(ctx.es) && session.view === session.roleIds.length + 5;
|
|
316
|
+
// Ctrl+R on the Pending pane refreshes in place — the terminal twin of the dashboard
|
|
317
|
+
// Pending view's Refresh button: re-read the durable merge queue and restart the
|
|
318
|
+
// change-roster collector, painting what the queue holds now. The pane re-enters at
|
|
319
|
+
// its head so the changed body starts at the top; the roster's git read is a fresh
|
|
320
|
+
// collector run, and a run already in flight is superseded by the new read.
|
|
321
|
+
const pendingRefresh = (ctx) => {
|
|
322
|
+
if (!onPendingView(ctx))
|
|
323
|
+
return false;
|
|
324
|
+
refresh();
|
|
325
|
+
paneAtHead(session);
|
|
326
|
+
return true;
|
|
327
|
+
};
|
|
328
|
+
// Ctrl+G on the Pending pane cycles roleScope through each role the merge queue, the
|
|
329
|
+
// post-merge reviews and the overlapping files name and back to all (the `queue --role <id>`,
|
|
330
|
+
// `reviews --role <id>` and `diff --overlaps --role <id>` twins, one scope covering all three
|
|
331
|
+
// surfaces); with nothing to scope it resets to null and flashes the empty message. Rebounds
|
|
332
|
+
// the queue and review rows off the new scope and repaints from the top.
|
|
333
|
+
const pendingScopeCycle = (ctx) => {
|
|
334
|
+
if (!onPendingView(ctx))
|
|
335
|
+
return false;
|
|
336
|
+
const queueRoles = lastQueue ? mergeQueueRoles(lastQueue) : [];
|
|
337
|
+
const reviewRoles = postReviewRoles(lastReviews);
|
|
338
|
+
const holders = lastOverlaps?.state === "ready" ? overlapHolders(lastOverlaps) : [];
|
|
339
|
+
const roles = [
|
|
340
|
+
...queueRoles,
|
|
341
|
+
...reviewRoles.filter((r) => !queueRoles.includes(r)),
|
|
342
|
+
...holders.filter((h) => !queueRoles.includes(h) && !reviewRoles.includes(h)),
|
|
343
|
+
];
|
|
344
|
+
if (roles.length === 0) {
|
|
345
|
+
roleScope = null;
|
|
343
346
|
rebuildQueueRows();
|
|
344
|
-
overlapIndex = -1;
|
|
345
|
-
++drillGen;
|
|
346
347
|
head = queueAndReviews() + rosterText;
|
|
347
348
|
compose();
|
|
348
349
|
session.paneCache = base;
|
|
349
|
-
session.
|
|
350
|
-
session.requestRender();
|
|
350
|
+
session.flashMessage("no queued merges, post-merge reviews or overlapping files to scope");
|
|
351
351
|
return true;
|
|
352
352
|
}
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
353
|
+
const slots = [null, ...roles];
|
|
354
|
+
const at = roleScope === null ? 0 : slots.indexOf(roleScope);
|
|
355
|
+
roleScope = slots[(at + 1) % slots.length] ?? null;
|
|
356
|
+
repaintQueueBody();
|
|
357
|
+
return true;
|
|
358
|
+
};
|
|
359
|
+
// Ctrl+O on the Pending pane cycles the post-merge review state filter through all,
|
|
360
|
+
// waiting, failing and rejected and back — the `tumwater reviews --status` twin and the
|
|
361
|
+
// dashboard Post-merge reviews State picker, meaning the full state vocabulary even when
|
|
362
|
+
// the queue holds none of a state (the renderer's `no post-merge reviews matching
|
|
363
|
+
// --status …` line is the empty-selection state). Each kept review keeps its whole-queue
|
|
364
|
+
// position, so Ctrl+A still accepts the right entry under any filter. The pane's own arm
|
|
365
|
+
// is routed ahead of History's in tui-panes.ts, so it never advances the History outcome
|
|
366
|
+
// filter; the Ctrl+G role scope composes with this filter in the block's header.
|
|
367
|
+
const pendingReviewStateCycle = (ctx) => {
|
|
368
|
+
if (!onPendingView(ctx))
|
|
369
|
+
return false;
|
|
370
|
+
const slots = [null, ...POST_REVIEW_STATES];
|
|
371
|
+
const at = reviewState === null ? 0 : slots.indexOf(reviewState);
|
|
372
|
+
reviewState = slots[(at + 1) % slots.length] ?? null;
|
|
373
|
+
repaintQueueBody();
|
|
374
|
+
return true;
|
|
375
|
+
};
|
|
376
|
+
// Ctrl+N on the Pending pane steps through the overlapping files, painting each holder's
|
|
377
|
+
// change limited to that file (the `tumwater diff --role a,b --path <path>` view), scoped
|
|
378
|
+
// by the Ctrl+G role selection; an exhausted list returns to the composed base. Each step
|
|
379
|
+
// re-runs the collector guarded against a stale resolution (drill/refresh generation and
|
|
380
|
+
// the view still being current).
|
|
381
|
+
const pendingOverlapDrill = (ctx) => {
|
|
382
|
+
if (!onPendingView(ctx))
|
|
383
|
+
return false;
|
|
384
|
+
const overlaps = lastOverlaps && roleScope !== null ? scopeChangeOverlaps(lastOverlaps, [roleScope]) : lastOverlaps;
|
|
385
|
+
if (overlaps?.state !== "ready" || overlaps.overlaps.length === 0) {
|
|
386
|
+
session.flashMessage("no overlapping files to step through");
|
|
387
|
+
return true;
|
|
388
|
+
}
|
|
389
|
+
if (++overlapIndex >= overlaps.overlaps.length) {
|
|
366
390
|
overlapIndex = -1;
|
|
367
391
|
++drillGen;
|
|
368
|
-
head = queueAndReviews() + rosterText;
|
|
369
|
-
compose();
|
|
370
392
|
session.paneCache = base;
|
|
371
|
-
session
|
|
372
|
-
session.requestRender();
|
|
393
|
+
paneAtHead(session);
|
|
373
394
|
return true;
|
|
374
395
|
}
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
session.requestRender();
|
|
387
|
-
return true;
|
|
388
|
-
}
|
|
389
|
-
const { path, roles } = overlaps.overlaps[overlapIndex];
|
|
390
|
-
const heading = overlapDrillHeading(overlapIndex, overlaps.overlaps.length, path);
|
|
391
|
-
session.paneCache = heading + "\n\nloading…";
|
|
392
|
-
session.paneScroll = 0;
|
|
396
|
+
const { path, roles } = overlaps.overlaps[overlapIndex];
|
|
397
|
+
const heading = overlapDrillHeading(overlapIndex, overlaps.overlaps.length, path);
|
|
398
|
+
session.paneCache = heading + "\n\nloading…";
|
|
399
|
+
paneAtHead(session);
|
|
400
|
+
const gen = ++drillGen;
|
|
401
|
+
const refresh0 = refreshGen;
|
|
402
|
+
const current = () => gen === drillGen && refresh0 === refreshGen && session.view === session.roleIds.length + 5;
|
|
403
|
+
void Promise.all(roles.map((r) => session.collectChange(session.root, r, { path }))).then((views) => {
|
|
404
|
+
if (!current())
|
|
405
|
+
return;
|
|
406
|
+
session.paneCache = heading + "\n\n" + renderRoleChanges({ mainBranch: overlaps.mainBranch, roles: views });
|
|
393
407
|
session.requestRender();
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
if (!current())
|
|
399
|
-
return;
|
|
400
|
-
session.paneCache = heading + "\n\n" + renderRoleChanges({ mainBranch: overlaps.mainBranch, roles: views });
|
|
401
|
-
session.requestRender();
|
|
402
|
-
}, (err) => {
|
|
403
|
-
if (!current())
|
|
404
|
-
return;
|
|
405
|
-
session.flashError(err);
|
|
406
|
-
session.requestRender();
|
|
407
|
-
});
|
|
408
|
-
return true;
|
|
409
|
-
}
|
|
410
|
-
const arrow = arrowDir(key.name);
|
|
411
|
-
if (session.view === session.roleIds.length + 5 && arrow !== null && promptLineFree(ctx.es)) {
|
|
412
|
-
// In the Pending pane, up/down select a queued merge row in full (wrapping, the backlog
|
|
413
|
-
// pane's rule) instead of editing the prompt; an empty queue has nothing to select, so
|
|
414
|
-
// moveEntrySelection clears the selection. The selected detail shares paneScroll with the
|
|
415
|
-
// list, so a newly opened row must start at its head, or it renders scrolled into the
|
|
416
|
-
// middle of the previous row's body.
|
|
417
|
-
selectedQueue = moveEntrySelection(queueRows.length, selectedQueue, arrow);
|
|
418
|
-
const selRow = selectedQueue === null ? undefined : queueRows[selectedQueue];
|
|
419
|
-
queueDetail = selRow ? rowDetail(selRow) : "";
|
|
420
|
-
session.paneScroll = 0;
|
|
408
|
+
}, (err) => {
|
|
409
|
+
if (!current())
|
|
410
|
+
return;
|
|
411
|
+
session.flashError(err);
|
|
421
412
|
session.requestRender();
|
|
413
|
+
});
|
|
414
|
+
return true;
|
|
415
|
+
};
|
|
416
|
+
// Up/down in the Pending pane select a queued merge row in full (wrapping, the backlog
|
|
417
|
+
// pane's rule) instead of editing the prompt; an empty queue has nothing to select, so
|
|
418
|
+
// moveEntrySelection clears the selection. The selected detail shares paneScroll with the
|
|
419
|
+
// list, so a newly opened row must start at its head, or it renders scrolled into the
|
|
420
|
+
// middle of the previous row's body.
|
|
421
|
+
const pendingSelectRow = (arrow, ctx) => {
|
|
422
|
+
if (!onPendingView(ctx))
|
|
423
|
+
return false;
|
|
424
|
+
selectedQueue = moveEntrySelection(queueRows.length, selectedQueue, arrow);
|
|
425
|
+
const selRow = selectedQueue === null ? undefined : queueRows[selectedQueue];
|
|
426
|
+
queueDetail = selRow ? rowDetail(selRow) : "";
|
|
427
|
+
paneAtHead(session);
|
|
428
|
+
return true;
|
|
429
|
+
};
|
|
430
|
+
// Esc on the Pending pane returns it to list mode; the cached list is untouched (nothing
|
|
431
|
+
// was cancelled), so the pane shows the same renderMergeQueue it entered with. The shared
|
|
432
|
+
// paneScroll resets with the body switch, like a selection move.
|
|
433
|
+
const pendingClearSelection = (ctx) => {
|
|
434
|
+
if (!onPendingView(ctx) || selectedQueue === null)
|
|
435
|
+
return false;
|
|
436
|
+
selectedQueue = null;
|
|
437
|
+
queueDetail = "";
|
|
438
|
+
paneAtHead(session);
|
|
439
|
+
return true;
|
|
440
|
+
};
|
|
441
|
+
// Ctrl+X on the Pending pane cancels the selected queued merge (the terminal twin of
|
|
442
|
+
// `tumwater queue --cancel <n>`): a review row names its own action, the first press on a
|
|
443
|
+
// merge arms it (any other key keeps it), and the second press re-reads the queue, guards
|
|
444
|
+
// the in-flight and shifted-away races, then removes the entry through the CLI's own core.
|
|
445
|
+
const pendingCancelMerge = (ctx) => {
|
|
446
|
+
if (!onPendingView(ctx))
|
|
447
|
+
return false;
|
|
448
|
+
const row = selectedRow();
|
|
449
|
+
if (row === null)
|
|
422
450
|
return true;
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
// Esc on the Pending pane returns it to list mode; the cached list is untouched (nothing
|
|
426
|
-
// was cancelled), so the pane shows the same renderMergeQueue it entered with. The shared
|
|
427
|
-
// paneScroll resets with the body switch, like a selection move.
|
|
428
|
-
selectedQueue = null;
|
|
429
|
-
queueDetail = "";
|
|
430
|
-
session.paneScroll = 0;
|
|
451
|
+
if (row.kind === "review") {
|
|
452
|
+
session.flashMessage("that row is a post-merge review — Ctrl+A accepts it");
|
|
431
453
|
session.requestRender();
|
|
432
454
|
return true;
|
|
433
455
|
}
|
|
434
|
-
if (
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
session.flashMessage("that row is a post-merge review — Ctrl+A accepts it");
|
|
440
|
-
session.requestRender();
|
|
441
|
-
return true;
|
|
442
|
-
}
|
|
443
|
-
if (session.armedQueue?.sha === row.entry.sha && session.armedQueue?.position === row.position) {
|
|
444
|
-
// Second press on the same entry: re-read the queue, guard the in-flight race, then
|
|
445
|
-
// remove it through the CLI's own core (`tumwater queue --cancel <n>`), so the pane,
|
|
446
|
-
// the CLI, and the dashboard cancel cannot drift.
|
|
447
|
-
const confirm = async () => {
|
|
448
|
-
if (queueShiftedAway(row))
|
|
449
|
-
return;
|
|
450
|
-
if (guardMergeInFlight(row))
|
|
451
|
-
return;
|
|
452
|
-
const outcome = await cancelQueuedMerge(session.root, row.position);
|
|
453
|
-
if (outcome.status === "cancelled") {
|
|
454
|
-
selectedQueue = null;
|
|
455
|
-
queueDetail = "";
|
|
456
|
-
session.flashMessage(`cancelled queue entry ${row.position}: ${roleLabel(outcome.role)} ${shortSha(outcome.sha)} — ${outcome.summary}`);
|
|
457
|
-
refresh();
|
|
458
|
-
session.requestRender();
|
|
459
|
-
}
|
|
460
|
-
else {
|
|
461
|
-
selectedQueue = null;
|
|
462
|
-
queueDetail = "";
|
|
463
|
-
session.flashMessage("that merge already left the queue");
|
|
464
|
-
refresh();
|
|
465
|
-
session.requestRender();
|
|
466
|
-
}
|
|
467
|
-
};
|
|
468
|
-
void confirm();
|
|
469
|
-
}
|
|
470
|
-
else {
|
|
471
|
-
session.queueArmed = { position: row.position, sha: row.entry.sha };
|
|
472
|
-
session.flashMessage(`press Ctrl+X again to cancel merge #${row.position} — any other key keeps it`);
|
|
473
|
-
session.requestRender();
|
|
474
|
-
}
|
|
475
|
-
return true;
|
|
476
|
-
}
|
|
477
|
-
if (key.ctrl && key.name === "a" && promptLineFree(ctx.es) && session.view === session.roleIds.length + 5) {
|
|
478
|
-
// Ctrl+A on the Pending pane accepts the selected post-merge review unreviewed (the
|
|
479
|
-
// terminal twin of `tumwater reviews --accept <n>`), once selected and twice pressed
|
|
480
|
-
// like the queue's Ctrl+X cancel. A merge row names its own action instead; an unarmed
|
|
481
|
-
// press sets reviewArmed, and the second press re-reads the queue and settles the entry
|
|
482
|
-
// through the CLI's own core, so the pane and the CLI cannot drift.
|
|
483
|
-
const review = selectedRow();
|
|
484
|
-
if (review === null)
|
|
485
|
-
return true;
|
|
486
|
-
if (review.kind === "merge") {
|
|
487
|
-
session.flashMessage("that row is a queued merge — Ctrl+X cancels it");
|
|
488
|
-
session.requestRender();
|
|
489
|
-
return true;
|
|
490
|
-
}
|
|
491
|
-
if (session.armedReview?.position === review.position && session.armedReview?.commit === review.entry.commit) {
|
|
492
|
-
const confirm = async () => {
|
|
493
|
-
const entries = readPostReviews(session.root);
|
|
494
|
-
if (entries[review.position - 1]?.commit !== review.entry.commit) {
|
|
495
|
-
session.flashMessage("the review queue changed — try again");
|
|
496
|
-
refresh();
|
|
497
|
-
session.requestRender();
|
|
498
|
-
return;
|
|
499
|
-
}
|
|
500
|
-
const outcome = acceptPostReview(session.root, review.position);
|
|
501
|
-
if (outcome.status === "accepted") {
|
|
502
|
-
selectedQueue = null;
|
|
503
|
-
queueDetail = "";
|
|
504
|
-
session.flashMessage(`accepted post-merge review of ${shortSha(outcome.commit)} unreviewed — ${outcome.summary}`);
|
|
505
|
-
refresh();
|
|
506
|
-
session.requestRender();
|
|
507
|
-
}
|
|
508
|
-
else if (outcome.status === "settled") {
|
|
509
|
-
session.flashMessage(`review #${review.position} already merged its fix on main — nothing to accept`);
|
|
510
|
-
refresh();
|
|
511
|
-
session.requestRender();
|
|
512
|
-
}
|
|
513
|
-
else {
|
|
514
|
-
session.flashMessage("the review queue changed — try again");
|
|
515
|
-
refresh();
|
|
516
|
-
session.requestRender();
|
|
517
|
-
}
|
|
518
|
-
};
|
|
519
|
-
void confirm();
|
|
520
|
-
}
|
|
521
|
-
else {
|
|
522
|
-
session.reviewArmed = { position: review.position, commit: review.entry.commit };
|
|
523
|
-
session.flashMessage(`press Ctrl+A again to accept review #${review.position} — any other key keeps it`);
|
|
524
|
-
session.requestRender();
|
|
525
|
-
}
|
|
526
|
-
return true;
|
|
527
|
-
}
|
|
528
|
-
if (key.ctrl && key.name === "v" && promptLineFree(ctx.es) && session.view === session.roleIds.length + 5) {
|
|
529
|
-
// Move the selected merge to the head of the queue — the pane's twin of `tumwater
|
|
530
|
-
// queue --top <n>`, through the same core the CLI and the dashboard use, so the three
|
|
531
|
-
// cannot drift on the rename or the already-first/out-of-range outcomes. A single
|
|
532
|
-
// keypress is enough because the move is reversible and non-destructive (unlike
|
|
533
|
-
// Ctrl+X's two-step cancel arm).
|
|
534
|
-
const row = selectedRow();
|
|
535
|
-
if (row === null)
|
|
536
|
-
return true;
|
|
537
|
-
if (row.kind === "review") {
|
|
538
|
-
session.flashMessage("that row is a post-merge review — Ctrl+A accepts it");
|
|
539
|
-
session.requestRender();
|
|
540
|
-
return true;
|
|
541
|
-
}
|
|
542
|
-
const top = async () => {
|
|
543
|
-
if (row.position === 1) {
|
|
544
|
-
session.flashMessage("merge #1 is already at the front");
|
|
545
|
-
session.requestRender();
|
|
546
|
-
return;
|
|
547
|
-
}
|
|
456
|
+
if (session.armedQueue?.sha === row.entry.sha && session.armedQueue?.position === row.position) {
|
|
457
|
+
// Second press on the same entry: re-read the queue, guard the in-flight race, then
|
|
458
|
+
// remove it through the CLI's own core (`tumwater queue --cancel <n>`), so the pane,
|
|
459
|
+
// the CLI, and the dashboard cancel cannot drift.
|
|
460
|
+
const confirm = async () => {
|
|
548
461
|
if (queueShiftedAway(row))
|
|
549
462
|
return;
|
|
550
463
|
if (guardMergeInFlight(row))
|
|
551
464
|
return;
|
|
552
|
-
const outcome =
|
|
553
|
-
if (outcome.status === "
|
|
465
|
+
const outcome = await cancelQueuedMerge(session.root, row.position);
|
|
466
|
+
if (outcome.status === "cancelled") {
|
|
554
467
|
selectedQueue = null;
|
|
555
468
|
queueDetail = "";
|
|
556
|
-
session.flashMessage(`
|
|
469
|
+
session.flashMessage(`cancelled queue entry ${row.position}: ${roleLabel(outcome.role)} ${shortSha(outcome.sha)} — ${outcome.summary}`);
|
|
470
|
+
refresh();
|
|
471
|
+
session.requestRender();
|
|
472
|
+
}
|
|
473
|
+
else {
|
|
474
|
+
selectedQueue = null;
|
|
475
|
+
queueDetail = "";
|
|
476
|
+
session.flashMessage("that merge already left the queue");
|
|
477
|
+
refresh();
|
|
478
|
+
session.requestRender();
|
|
479
|
+
}
|
|
480
|
+
};
|
|
481
|
+
void confirm();
|
|
482
|
+
}
|
|
483
|
+
else {
|
|
484
|
+
session.queueArmed = { position: row.position, sha: row.entry.sha };
|
|
485
|
+
session.flashMessage(`press Ctrl+X again to cancel merge #${row.position} — any other key keeps it`);
|
|
486
|
+
session.requestRender();
|
|
487
|
+
}
|
|
488
|
+
return true;
|
|
489
|
+
};
|
|
490
|
+
// Ctrl+A on the Pending pane accepts the selected post-merge review unreviewed (the
|
|
491
|
+
// terminal twin of `tumwater reviews --accept <n>`), once selected and twice pressed
|
|
492
|
+
// like the queue's Ctrl+X cancel. A merge row names its own action instead; an unarmed
|
|
493
|
+
// press sets reviewArmed, and the second press re-reads the queue and settles the entry
|
|
494
|
+
// through the CLI's own core, so the pane and the CLI cannot drift.
|
|
495
|
+
const pendingAcceptReview = (ctx) => {
|
|
496
|
+
if (!onPendingView(ctx))
|
|
497
|
+
return false;
|
|
498
|
+
const review = selectedRow();
|
|
499
|
+
if (review === null)
|
|
500
|
+
return true;
|
|
501
|
+
if (review.kind === "merge") {
|
|
502
|
+
session.flashMessage("that row is a queued merge — Ctrl+X cancels it");
|
|
503
|
+
session.requestRender();
|
|
504
|
+
return true;
|
|
505
|
+
}
|
|
506
|
+
if (session.armedReview?.position === review.position && session.armedReview?.commit === review.entry.commit) {
|
|
507
|
+
const confirm = async () => {
|
|
508
|
+
const entries = readPostReviews(session.root);
|
|
509
|
+
if (entries[review.position - 1]?.commit !== review.entry.commit) {
|
|
510
|
+
session.flashMessage("the review queue changed — try again");
|
|
557
511
|
refresh();
|
|
558
512
|
session.requestRender();
|
|
513
|
+
return;
|
|
559
514
|
}
|
|
560
|
-
|
|
561
|
-
|
|
515
|
+
const outcome = acceptPostReview(session.root, review.position);
|
|
516
|
+
if (outcome.status === "accepted") {
|
|
517
|
+
selectedQueue = null;
|
|
518
|
+
queueDetail = "";
|
|
519
|
+
session.flashMessage(`accepted post-merge review of ${shortSha(outcome.commit)} unreviewed — ${outcome.summary}`);
|
|
562
520
|
refresh();
|
|
563
521
|
session.requestRender();
|
|
564
522
|
}
|
|
565
|
-
else if (outcome.status === "
|
|
566
|
-
session.flashMessage(
|
|
523
|
+
else if (outcome.status === "settled") {
|
|
524
|
+
session.flashMessage(`review #${review.position} already merged its fix on main — nothing to accept`);
|
|
567
525
|
refresh();
|
|
568
526
|
session.requestRender();
|
|
569
527
|
}
|
|
570
528
|
else {
|
|
571
|
-
session.flashMessage("
|
|
529
|
+
session.flashMessage("the review queue changed — try again");
|
|
572
530
|
refresh();
|
|
573
531
|
session.requestRender();
|
|
574
532
|
}
|
|
575
533
|
};
|
|
576
|
-
void
|
|
534
|
+
void confirm();
|
|
535
|
+
}
|
|
536
|
+
else {
|
|
537
|
+
session.reviewArmed = { position: review.position, commit: review.entry.commit };
|
|
538
|
+
session.flashMessage(`press Ctrl+A again to accept review #${review.position} — any other key keeps it`);
|
|
539
|
+
session.requestRender();
|
|
540
|
+
}
|
|
541
|
+
return true;
|
|
542
|
+
};
|
|
543
|
+
// Ctrl+V moves the selected merge to the head of the queue — the pane's twin of `tumwater
|
|
544
|
+
// queue --top <n>`, through the same core the CLI and the dashboard use, so the three
|
|
545
|
+
// cannot drift on the rename or the already-first/out-of-range outcomes. A single
|
|
546
|
+
// keypress is enough because the move is reversible and non-destructive (unlike
|
|
547
|
+
// Ctrl+X's two-step cancel arm); a review row names its own action instead.
|
|
548
|
+
const pendingMoveToTop = (ctx) => {
|
|
549
|
+
if (!onPendingView(ctx))
|
|
550
|
+
return false;
|
|
551
|
+
const row = selectedRow();
|
|
552
|
+
if (row === null)
|
|
553
|
+
return true;
|
|
554
|
+
if (row.kind === "review") {
|
|
555
|
+
session.flashMessage("that row is a post-merge review — Ctrl+A accepts it");
|
|
556
|
+
session.requestRender();
|
|
577
557
|
return true;
|
|
578
558
|
}
|
|
559
|
+
const top = async () => {
|
|
560
|
+
if (row.position === 1) {
|
|
561
|
+
session.flashMessage(`merge #${row.position} is already at the front`);
|
|
562
|
+
session.requestRender();
|
|
563
|
+
return;
|
|
564
|
+
}
|
|
565
|
+
if (queueShiftedAway(row))
|
|
566
|
+
return;
|
|
567
|
+
if (guardMergeInFlight(row))
|
|
568
|
+
return;
|
|
569
|
+
const outcome = moveQueuedMergeToTop(session.root, row.position);
|
|
570
|
+
if (outcome.status === "topped") {
|
|
571
|
+
selectedQueue = null;
|
|
572
|
+
queueDetail = "";
|
|
573
|
+
session.flashMessage(`moved merge #${row.position} to the front: ${roleLabel(outcome.role)} ${shortSha(outcome.sha)} — ${outcome.summary}`);
|
|
574
|
+
refresh();
|
|
575
|
+
session.requestRender();
|
|
576
|
+
}
|
|
577
|
+
else if (outcome.status === "already-first") {
|
|
578
|
+
session.flashMessage(`merge #${row.position} is already at the front`);
|
|
579
|
+
refresh();
|
|
580
|
+
session.requestRender();
|
|
581
|
+
}
|
|
582
|
+
else if (outcome.status === "out-of-range") {
|
|
583
|
+
session.flashMessage("the merge queue changed — try again");
|
|
584
|
+
refresh();
|
|
585
|
+
session.requestRender();
|
|
586
|
+
}
|
|
587
|
+
else {
|
|
588
|
+
session.flashMessage("that merge already left the queue");
|
|
589
|
+
refresh();
|
|
590
|
+
session.requestRender();
|
|
591
|
+
}
|
|
592
|
+
};
|
|
593
|
+
void top();
|
|
594
|
+
return true;
|
|
595
|
+
};
|
|
596
|
+
const ctrlRoutes = {
|
|
597
|
+
r: pendingRefresh,
|
|
598
|
+
g: pendingScopeCycle,
|
|
599
|
+
o: pendingReviewStateCycle,
|
|
600
|
+
n: pendingOverlapDrill,
|
|
601
|
+
x: pendingCancelMerge,
|
|
602
|
+
a: pendingAcceptReview,
|
|
603
|
+
v: pendingMoveToTop,
|
|
604
|
+
};
|
|
605
|
+
const ctrlRoute = (key, ctx) => {
|
|
606
|
+
if (!key.ctrl || key.name === undefined)
|
|
607
|
+
return null;
|
|
608
|
+
const route = ctrlRoutes[key.name];
|
|
609
|
+
return route === undefined ? null : route(ctx);
|
|
610
|
+
};
|
|
611
|
+
const handleKey = (key, ctx) => {
|
|
612
|
+
const ctrl = ctrlRoute(key, ctx);
|
|
613
|
+
if (ctrl !== null)
|
|
614
|
+
return ctrl;
|
|
615
|
+
const arrow = arrowDir(key.name);
|
|
616
|
+
if (arrow !== null)
|
|
617
|
+
return pendingSelectRow(arrow, ctx);
|
|
618
|
+
if (key.name === "escape")
|
|
619
|
+
return pendingClearSelection(ctx);
|
|
579
620
|
return false;
|
|
580
621
|
};
|
|
581
622
|
return {
|