11 KiB
issue, issue_title
| issue | issue_title |
|---|---|
| 573 | [FEATURE REQUEST] Keybinds for approve/deny/deny with reason |
Retro: #573 — Inline keybind permission dialog
Stage: Planning (2026-07-13T00:00:00Z)
Session summary
Planned Phase 11 Step 4: an inline ctx.ui.custom<PermissionPromptDecision> permission dialog with y/s/n/r hotkeys, gated on ctx.mode === "tui" with the select/input flow preserved for RPC (the #519 constraint).
The design splits a pure decision model (permission-prompt-decision.ts) from a thin TUI component (permission-prompt-component.ts), adds a default-on doublePressToConfirm config toggle, and makes deny-with-reason a mandatory inline editor sub-step with back-navigation.
Committed the plan; no follow-up issues filed (deferred items are unfiled by design).
Observations
- This is a third-party issue (author
Hex4C≠gotgenes), but it was already scheduled in the operator's own roadmap (Phase 11 Step 4), so theask-userdirection gate largely confirmed the roadmap design. - The operator extended the raw request in two rounds of
ask-user: (1) double-press-to-confirm on all four hotkeys, a default-on config toggle, modeled on pi-ask's pureresolveReviewShortcutDoublePress; (2) deny-with-reason reason is mandatory (empty rejected),esc= back to the decision list. - Keybinding scheme resolved to the roadmap's
y/s/n/r(over the issue'sy/A/n/tab) — avoidstab(a navigation key) and uses mnemonic lowercase. - Tension noted and accepted: #573's motivation was fewer keypresses, but the operator chose double-press default-on for safety; the operator's call overrides the original motivation.
- Enter semantics decided by the planner (not re-asked): enter confirms the highlighted option in a single press; the double-press toggle governs only the letter-hotkey fast path. Navigate-then-enter is already two deliberate keystrokes.
- Classified non-breaking (
feat:, notfeat!:): the TUI interaction changes on upgrade, but thePermissionPromptDecisioncontract, gate outcomes, and config surface are additive/unchanged; the new toggle defaults on and is disableable. - SDK verification:
ctx.ui.customrenders inline by default and works only in TUI (hasUIis also true for RPC), so the gate isctx.mode === "tui", nothasUI.config-modal.tsis the in-packagectx.ui.customprecedent;@earendil-works/pi-tuiis already adevDependency. requestPermissionDecisionFromUiis retained as the RPC fallback (lift-and-shift), so no consumer/doc breaks — only the injected seam re-points to a newrequestPermissionDecisiondispatcher.doublePressToConfirmthreaded as a live getter (not an activation snapshot) so the/permission-systemsettings-modal toggle takes effect on the next prompt.
Stage: Implementation — TDD (2026-07-13T20:20:00Z)
Session summary
Implemented the inline keybind permission dialog across 5 TDD cycles: a pure decision model (permission-prompt-decision.ts), the doublePressToConfirm config toggle, the inline ctx.ui.custom component (permission-prompt-component.ts), the mode-dispatch wiring, and the architecture-doc completion.
Test count grew from 2418 to 2455 (+37); full suite, pnpm run check, root lint, and pnpm fallow dead-code all green.
Observations
- Design deviation (ISP + cycle avoidance): the plan said to widen
PermissionDecisionUiwithcustomand put the dispatcher inpermission-dialog.ts. Instead I keptPermissionDecisionUinarrow (select/input) and added a separatePermissionPromptUi = Pick<ExtensionUIContext, "select" | "input" | "custom">plus therequestPermissionDecisiondispatcher inpermission-prompt-component.ts. This avoids apermission-dialog.ts↔ component import cycle and is more ISP-faithful;permission-dialog.tswas consequently not modified. - Tidy-first prep skipped: the assessor's one recommended commit (a
makeStubUifixture) was predicated on widening the sharedPermissionDecisionUi— the ISP deviation above made it low-value (only the 2local-user-authorizer.test.tssites broke, and they were rewritten in the wiring step anyway). - Config field forced call-site updates early: making
doublePressToConfirma required field ofPermissionSystemExtensionConfigbroke three literal constructions (config-modal.tscloneDefaultConfig,config-modal.test.ts,config-reporter.test.ts), fixed in the same step-2 commit per the required-field rule. - Options are always
[y, s, n, r]: the plan's speculative "s absent" edge case does not occur — the fallback dialog always offers the session option, so the model mirrors that. matchesKeyworks on raw strings (verified via a throwaway probe): arrows\u001b[A/B,\renter,\u001bescape,\u007fbackspace all resolve, so the component maps keystrokes without a live terminal, and the pi-ask fake-tui/plainTheme/doneharness tests the component end-to-end.- Pre-completion reviewer: WARN — one non-blocking finding: the plan asked to bump the
Inline prompt component fileshealth-metric row0 → 1. Deliberately not done: that column is the datedBaseline (2026-07-12)snapshot, and the already-landed Phase 11 steps 1–3 left their achieved rows' baselines untouched too (the table is recomputed at phase close, not per-step). Editing only this row would make the baseline column internally inconsistent; the✅step heading + Mermaid node are the completion markers.
Stage: Final Retrospective (2026-07-14T01:30:00Z)
Session summary
One continuous session carried #573 from /plan-issue through /tdd-plan, /ship-issue, and this retro: an inline keybind permission dialog for TUI sessions, released as pi-permission-system 20.7.0.
Five TDD cycles landed with zero rework commits and no test failures (+37 tests, 2418 → 2455); the whole plan→ship arc was clean.
Observations
What went well
- Test-Driven Design paid off.
Splitting the branch-heavy interaction logic into a pure
reducePromptmodel (no SDK/TUI imports) and a thinctx.ui.customadapter made every decision path (double-press arming, reason validation, scope step, esc transitions) unit-testable without a live terminal. A throwawaymatchesKey-on-raw-strings probe confirmed the pi-ask fake-tui/plainTheme/doneharness would work before committing to it. - The
ask-usergate surfaced real scope. The raw third-party issue asked only fory/n/tab/reason keybinds; three planningask-userrounds drew out the operator's double-press-to-confirm affordance (default-on toggle) and the mandatory-reason rule — substantial behavior the issue never stated. The operator explicitly appended "I also want to improve the flow" to an answer, which the interactive flow invited. - An implementation-time design refinement improved on the plan.
The plan's "widen
PermissionDecisionUi+ dispatcher inpermission-dialog.ts" would have created apermission-dialog.ts↔ component import cycle; catching it during TDD and refining to a narrowPermissionDecisionUi+ aPick-basedPermissionPromptUi(ISP) leftpermission-dialog.tsuntouched and shrank the diff. - The ship
UNSTABLEguidance worked as written.release_pr_mergerefused withmerge_state: UNSTABLE; thestatusCheckRollupcheck wasIN_PROGRESS(not the empty-rollupGITHUB_TOKENcase), so waiting two polls for it to finish and then retryingrelease_pr_mergewas exactly the prompt's prescribed path — no prematuregh pr mergefallback.
What caused friction (agent side)
missing-context— the plan's Module-Level Changes instructed "update the health-metric row from0to1," but that column is the datedBaseline (2026-07-12)snapshot, recomputed at phase close, not a per-step live value (the already-landed Phase 11 Steps 1–3 left their achieved rows' baselines untouched). Following the instruction would have made the baseline column internally inconsistent. Impact: one pre-completion-reviewer WARN and a judgment call to decline the edit; no rework, but the plan (self-authored) committed to a doc edit without checking the table's own convention.other(path slip) — aREADME.mdEditused the absolute path/Users/chris/development/pi/pi-permission-system/README.md, dropping thepi-packages/packagessegment, and returnedENOENT. Impact: one wasted tool call, self-corrected immediately with the relative path.
What caused friction (user side)
- None.
The operator's extended requirements emerged naturally through the planning
ask-userrounds — the right place for a third-party issue where the operator's own preferences (double-press, mandatory reason) are not in the issue body.
Diagnostic details
- Model-performance correlation — both subagent dispatches (
tidy-first-assessor,pre-completion-reviewer) ran onanthropic/claude-sonnet-5, appropriate for their judgment-heavy work (preparatory-refactor assessment, quality review); no reasoning-weak-on-judgment or high-cost-on-mechanical mismatch. - Escalation-delay tracking — no
rabbit-holefriction; the longest same-error sequence was 1 retry (theREADME.mdpath slip). - Feedback-loop gap analysis — verification ran incrementally: each TDD step ran its affected test file (red→green),
pnpm run checkafter every shared-interface/config change, and the full suite + root lint +pnpm fallow dead-codeafter the last step. No end-only verification gap.
Follow-up: orphaned mid-batch issue #580
During this retro the operator noticed #580 (Phase 11 Step 2, shellTools config model) was still open despite being released in pi-permission-system-v20.5.0.
It was not #573's responsibility to close it — the two issues are unrelated and #580's commits predate #573's v20.6.0 ship baseline.
Root cause: #580 was a mid-batch member of the "shell-tool-aliases" batch (tail #574); /ship-issue step 4b couples the issue close to the release defer decision ("leave the issue open and skip steps 5–6"), and the batch-tail (#574) ship's stacked-issue check only scans <pkg-tag>..HEAD, which excludes a sibling already released below the tag.
So the issue orphaned.
Closed #580 manually with a summary; filed #586 to fix the ship-flow gap (two candidate fixes: decouple close from release in step 4b, or have the batch-tail ship enumerate and close its batch siblings).
Changes made
.pi/skills/package-pi-permission-system/SKILL.md— clarified that a datedBaseline (<date>)health-metric column is a fixed phase-open snapshot recomputed at phase close, not a per-step value (resolves the pre-completion WARN's root cause;Refs #573).- Closed #580 (orphaned mid-batch issue) with an implemented-in/released-in summary.
- Filed #586 — the
/ship-issuemid-batch-close gap that orphaned #580. packages/pi-permission-system/docs/retro/0573-inline-keybind-permission-dialog.md— this Final Retrospective stage entry.