feat(pulls,conversations,hotkeys): add shortcuts to edit PR dialogs
Brought to you by:
thebguy
Originally created by: theBGuy
Originally owned by: theBGuy
Add keyboard shortcuts to create and edit pull request dialogs so users can submit forms and trigger AI-generated titles and descriptions without leaving the keyboard. The shortcuts respect configured bindings and remain contained within the open dialog.
Ctrl/Cmd+Enter submission handling to src/features/conversations/EditTitleBodyDialog.tsx for editing pull request and issue titles and descriptions.src/features/conversations/EditTitleBodyDialog.tsx.useEffectiveBindings and eventToBinding in src/features/conversations/EditTitleBodyDialog.tsx so customized generate bindings work while preventing shortcuts from leaking to global actions.src/features/conversations/EditTitleBodyDialog.tsx while generation is running or disabled.runGenerate handlers in src/features/pulls/LocalPrView.tsx and src/features/pulls/RemotePrView.tsx.EditTitleBodyDialog from src/features/pulls/LocalPrView.tsx and src/features/pulls/RemotePrView.tsx.generate-commit-message binding in the Generate button hints in src/features/pulls/LocalPrView.tsx and src/features/pulls/RemotePrView.tsx.src/features/pulls/LocalPrView.tsx and remote PR generation based on the PR's cached diff in src/features/pulls/RemotePrView.tsx.src/features/help/content.ts to cover both create and edit dialogs.src/lib/hotkeys/registry.ts.changelog.d/added-create-pr-submit-shortcut.md.
Originally posted by: cloudflare-workers-and-pages[bot]
Deploying gitdesktop with
Cloudflare Pages
eec286eView logs
Originally posted by: theBGuy
🤖 GitDesktop AI security audit ·
opus· automatedNo security issues found in these changes. The diff is entirely client-side React keyboard-handling and refactoring (hoisting an existing AI-generate call into a named function, reusing hotkey bindings, adding submit/generate chords to edit dialogs). No new untrusted-data sink is introduced: the PR diff/commit content passed to AI generation was already flowing there before this change and is the product working as designed, and there is no
dangerouslySetInnerHTML, dynamic code execution, or new network/host-controlled input.Posted by GitDesktop — AI output, verify before acting on it.
Originally posted by: theBGuy
🧭 Context & deliberate calls (for reviewers — the design decisions behind this diff, so rounds can focus on the genuinely new):
This brings the shared
EditTitleBodyDialogto chord parity with the create dialogs from [#89] (same DialogContent-onKeyDown pattern, three review rounds + live validation there).DialogContent(the Popup), not the<form>— the X close button renders as a form sibling inside the Popup, so a form-level handler misses a chord pressed with focus on the X, which would then leak to the globalcommit/generate-commit-messageactions behind the dialog. The unconditionalpreventDefaulton mod+enter is what contains the chord (same rationale as the [#89] create dialogs).onGenerateprop. The issue views pass nothing, so the chord falls through untouched there — and the help copy deliberately says "PR dialog" for the generate claim.generate-commit-message's effective binding (useEffectiveBindings), so a Settings → Keyboard rebinding drives both the chord and the button hint; explicitly-unbound (null) turns the chord off. Same mechanism as the create dialogs.CreateLocalPrDialog).generateDisabledmirrors the Generate button's disabled state — LocalPrView passesahead.length === 0; RemotePrView's button has no disabled state, so it passes nothing (deliberate asymmetry, not an omission).prForGennarrowing alias in both views: extracting the button's onClick into a hoistedrunGenerate()loses the!prguard's type narrowing (hoisted function bodies don't inherit it), so aconst prForGen = pralias at the narrowing point is the minimal fix that keeps one shared function for button + chord. tsc-verified.titlehint uses the sameSUBMIT_HINT = formatBinding("mod+enter")mechanism as the create dialogs' SubmitButtons; the hint doesn't show while the button is disabled — same accepted tradeoff as [#89].eventToBinding, shipped in [#89]).BUILT_IN_KEYSmod+enter copy, the help-guide sentence, and the existing unreleased changelog fragment were extended in place; README/site deliberately untouched (shortcut-level detail — the existing capability lines cover it).Verification:
pnpm buildgreen (tsc -b+ vite), scoped biome check clean, and live-validated in the dev app on a scratch local PR: mod+g fires the edit dialog's Generate from any field, a repeat mod+g during generation neither cancels nor restarts it, and mod+enter submits and persists. The submit-while-generating swallow arm is static-verified (identical to the create dialogs' gate that [#89] validated live).Posted by GitDesktop — automated agent comment, verify before acting on it.
Related
Tickets:
#89Originally posted by: theBGuy
🤖 GitDesktop AI review ·
opus· automatedThis PR adds
Ctrl/Cmd+Entersubmit and a rebinding-awaregenerate-commit-messagechord to the shared Edit title/description dialog, and extracts the two PR views' Generate onClick bodies into reusablerunGeneratehandlers. Overall it's sound and closely mirrors the already-shippedCreateLocalPrDialogpattern (chord captured onDialogContentso the X button and every field are covered, unconditionalpreventDefaultfor containment, binding read viauseEffectiveBindings). Nothing blocking.Consistency
src/features/conversations/EditTitleBodyDialog.tsx, theSubmitButton(line ~202). The keyboard path correctly refuses to submit while a generation is in flight (if (!generating) form.handleSubmit()), but the Save button itself is left enabled — unlikeCreateLocalPrDialog, whose submit isdisabled={generating}. Concrete case: in the Edit PR dialog with AI on, click Generate; while the title/body are still streaming, the Save button is clickable, so the user can persist a half-streamed title/body — the exact thing the chord guard and the create dialog both prevent. This is a real button-vs-keyboard divergence. Fix: pass the generating flag through, e.g.<form.SubmitButton disabled={generating} title={SUBMIT_HINT}>— safe for the issue views too, wheregeneratingisundefined(→ not disabled).Nits
src/features/pulls/LocalPrView.tsx/RemotePrView.tsx. Each view recomputesgenerateBinding = useEffectiveBindings().get("generate-commit-message")purely to build the button-hint suffix, whileEditTitleBodyDialogindependently does the same lookup to drive the chord. It's harmless and each use is legitimate, but the parent-side copy exists only for the hint string; a small shared helper (or passing the formatted hint down) would avoid the third duplicated lookup of the same binding. Optional.One thing I checked that is not a problem: calling
useEffectiveBindings()inside thewithFormrenderfunction is fine —GeneralSectionand other sections already call hooks (useSelector) the same way, sowithFormrenders the render prop as a real component. Also confirmed the description editor (markdown-editor.tsx) doesn'tstopPropagationonmod+Enter/mod+g, so the chord still bubbles toDialogContentfrom the body field (it only interceptsmod+b/i/k).Posted by GitDesktop — AI output, verify before acting on it.
Originally posted by: theBGuy
📋 Round-1 dispositions (AI review; Copilot's two threads answered inline):
should-fix (Save enabled while generating) — ACCEPTED, applied, in the next push. Verified: both create dialogs gate their SubmitButton (
CreateLocalPrDialogdisabled={generating},CreatePrDialogdisabled={generating || …}) and the Edit dialog's Save didn't — a real mouse-vs-keyboard divergence, and worse than it looks: RemotePrView cancels the in-flight generation on dialog close, so a mid-stream Save would persist the half-streamed text and abort the generation. Fix is exactly the suggested<form.SubmitButton disabled={generating} title={SUBMIT_HINT}>plus an invariant comment at the site ("the mouse path must match the chord path's!generatinggate"); issue views unaffected (generatingundefined → falsy, as you noted).pnpm buildgreen + scoped biome clean after the change.nit (third duplicated binding lookup) — DECLINED as the optional it was offered as, with reasoning. The parent-side lookup mirrors the create dialogs' own pattern (each computes its hint locally,
CreateLocalPrDialog~149–156), and the dialog can't supply the hint to the button: the Generate button is composed by the views insidebodyActions(a plain ReactNode), so injecting a dialog-computed hint would mean convertingbodyActionsto a render prop — API churn larger than the duplication it removes.useEffectiveBindingsis a cheap subscription; three self-contained use-sites beat one threaded prop here. Happy to revisit if the dialog ever grows its own Generate surface.Also appreciated the two negative checks (hooks-in-
withFormlegitimacy;markdown-editoronly interceptingmod+b/i/kso the chords bubble) — both match the build-phase reading.Posted by GitDesktop — automated agent comment, verify before acting on it.
Originally posted by: theBGuy
🤖 GitDesktop AI review ·
opus· automatedI verified the change against the actual code:
pris narrowed by an earlyif (!pr) return(LocalPrView line 194; RemotePrView line 736,pr = details.dataat 593) beforeconst prForGen = pr, so the hoistedrunGeneratebodies read a non-optional value — no null-safety gap.useEffectiveBindings()is called before the early returns in both views, so hook order is preserved. The chord handler andeventToBinding/formatBindingusage are correct.This PR brings the shared
EditTitleBodyDialogtomod+enter-submit and rebinding-aware generate-chord parity with the create dialogs, and hoists the two PR views' GenerateonClickbodies into reusablerunGeneratehandlers. It's sound and nothing is blocking.Resolved since last review
EditTitleBodyDialog.tsxline 205 now renders<form.SubmitButton disabled={generating} title={SUBMIT_HINT}>. The mouse path now matches the chord path's!generatinggate, and issue views (generatingundefined → falsy) are unaffected. Verified in the current diff.Triage of other reviewers
registry.ts:694"which dialogs is ambiguous" note is addressed in this diff — the line now reads "a PR or issue create/edit dialog," and the changelog/help copy name the same scope. Not an open issue.BUILT_IN_KEYSand the help-guide sentence) were both updated here. Not a blocker.Nits
LocalPrView.tsx(155–159) /RemotePrView.tsx(403–407) vsEditTitleBodyDialog.tsx(121–122):generate-commit-messageis looked up viauseEffectiveBindings()in three places (each view for the hint string, the dialog for the chord). Each use is legitimate and the author already declined consolidating this with sound reasoning (the hint can't be threaded throughbodyActionswithout turning it into a render prop). Optional, no defect — noted only for completeness.The refactor is behavior-preserving for the mouse paths (the extracted
runGeneratebodies are verbatim moves), and the new keyboard paths mirror the reviewed-and-shipped create-dialog pattern. Good to merge.Posted by GitDesktop — AI output, verify before acting on it.
Ticket changed by: theBGuy