mirror of
https://bitbucket.org/siakitem/my-pi.git
synced 2026-08-28 16:45:22 +00:00
104 lines
11 KiB
Markdown
104 lines
11 KiB
Markdown
---
|
||
issue: 573
|
||
issue_title: "[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 the `ask-user` direction 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 pure `resolveReviewShortcutDoublePress`; (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's `y`/`A`/`n`/`tab`) — avoids `tab` (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:`, not `feat!:`): the TUI interaction changes on upgrade, but the `PermissionPromptDecision` contract, gate outcomes, and config surface are additive/unchanged; the new toggle defaults on and is disableable.
|
||
- SDK verification: `ctx.ui.custom` renders inline by default and works only in TUI (`hasUI` is also true for RPC), so the gate is `ctx.mode === "tui"`, not `hasUI`. `config-modal.ts` is the in-package `ctx.ui.custom` precedent; `@earendil-works/pi-tui` is already a `devDependency`.
|
||
- `requestPermissionDecisionFromUi` is **retained** as the RPC fallback (lift-and-shift), so no consumer/doc breaks — only the injected seam re-points to a new `requestPermissionDecision` dispatcher.
|
||
- `doublePressToConfirm` threaded as a live **getter** (not an activation snapshot) so the `/permission-system` settings-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 `PermissionDecisionUi` with `custom` and put the dispatcher in `permission-dialog.ts`.
|
||
Instead I kept `PermissionDecisionUi` narrow (`select`/`input`) and added a separate `PermissionPromptUi = Pick<ExtensionUIContext, "select" | "input" | "custom">` plus the `requestPermissionDecision` dispatcher in `permission-prompt-component.ts`.
|
||
This avoids a `permission-dialog.ts` ↔ component import cycle and is more ISP-faithful; `permission-dialog.ts` was consequently **not** modified.
|
||
- **Tidy-first prep skipped:** the assessor's one recommended commit (a `makeStubUi` fixture) was predicated on widening the shared `PermissionDecisionUi` — the ISP deviation above made it low-value (only the 2 `local-user-authorizer.test.ts` sites broke, and they were rewritten in the wiring step anyway).
|
||
- **Config field forced call-site updates early:** making `doublePressToConfirm` a required field of `PermissionSystemExtensionConfig` broke three literal constructions (`config-modal.ts` `cloneDefaultConfig`, `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.
|
||
- **`matchesKey` works on raw strings** (verified via a throwaway probe): arrows `\u001b[A/B`, `\r` enter, `\u001b` escape, `\u007f` backspace all resolve, so the component maps keystrokes without a live terminal, and the pi-ask fake-`tui`/`plainTheme`/`done` harness tests the component end-to-end.
|
||
- **Pre-completion reviewer: WARN** — one non-blocking finding: the plan asked to bump the `Inline prompt component files` health-metric row `0 → 1`.
|
||
Deliberately **not** done: that column is the dated `Baseline (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 `reducePrompt` model (no SDK/TUI imports) and a thin `ctx.ui.custom` adapter made every decision path (double-press arming, reason validation, scope step, esc transitions) unit-testable without a live terminal.
|
||
A throwaway `matchesKey`-on-raw-strings probe confirmed the pi-ask fake-`tui`/`plainTheme`/`done` harness would work before committing to it.
|
||
- **The `ask-user` gate surfaced real scope.**
|
||
The raw third-party issue asked only for `y`/`n`/`tab`/reason keybinds; three planning `ask-user` rounds 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 in `permission-dialog.ts`" would have created a `permission-dialog.ts` ↔ component import cycle; catching it during TDD and refining to a narrow `PermissionDecisionUi` + a `Pick`-based `PermissionPromptUi` (ISP) left `permission-dialog.ts` untouched and shrank the diff.
|
||
- **The ship `UNSTABLE` guidance worked as written.** `release_pr_merge` refused with `merge_state: UNSTABLE`; the `statusCheckRollup` check was `IN_PROGRESS` (not the empty-rollup `GITHUB_TOKEN` case), so waiting two polls for it to finish and then retrying `release_pr_merge` was exactly the prompt's prescribed path — no premature `gh pr merge` fallback.
|
||
|
||
#### What caused friction (agent side)
|
||
|
||
- `missing-context` — the plan's Module-Level Changes instructed "update the health-metric row from `0` to `1`," but that column is the dated `Baseline (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) — a `README.md` `Edit` used the absolute path `/Users/chris/development/pi/pi-permission-system/README.md`, dropping the `pi-packages/packages` segment, and returned `ENOENT`.
|
||
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-user` rounds — 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 on `anthropic/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-hole` friction; the longest same-error sequence was 1 retry (the `README.md` path slip).
|
||
- **Feedback-loop gap analysis** — verification ran incrementally: each TDD step ran its affected test file (red→green), `pnpm run check` after every shared-interface/config change, and the full suite + root lint + `pnpm fallow dead-code` after 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
|
||
|
||
1. `.pi/skills/package-pi-permission-system/SKILL.md` — clarified that a dated `Baseline (<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`).
|
||
2. Closed #580 (orphaned mid-batch issue) with an implemented-in/released-in summary.
|
||
3. Filed #586 — the `/ship-issue` mid-batch-close gap that orphaned #580.
|
||
4. `packages/pi-permission-system/docs/retro/0573-inline-keybind-permission-dialog.md` — this Final Retrospective stage entry.
|