Skip to content

Commit bd26bfa

Browse files
committed
fix(app): address coderabbit review findings
1 parent c47fbeb commit bd26bfa

25 files changed

Lines changed: 1149 additions & 168 deletions

packages/api/src/services/terminal-sessions.ts

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -765,8 +765,51 @@ const writePtyInput = (pty: PtyBridge | null, data: string): void => {
765765

766766
const shellQuote = (value: string): string => `'${value.replace(/'/gu, "'\\''")}'`
767767

768+
// CHANGE: Predicate for when tmux should forward right-click pane events.
769+
// WHY: Mouse-aware apps and copy/view mode still need pane mouse events, while tmux menus must stay disabled.
770+
// QUOTE(TZ): issue #340 right-click must not open the default tmux menu in browser terminals.
771+
// REF: PR #342 tmux right-click handling.
772+
// SOURCE: n/a
773+
// FORMAT THEOREM: mouse-aware-or-copy-mode => predicate evaluates truthy in tmux.
774+
// PURITY: CORE
775+
// EFFECT: none
776+
// INVARIANT: The predicate contains only tmux format language and no shell interpolation.
777+
// COMPLEXITY: O(1) time/O(1) space.
778+
/**
779+
* Tmux format predicate used by right-click pane bindings.
780+
*
781+
* @returns A tmux format expression, not a shell command.
782+
* @pure true
783+
* @effect none
784+
* @invariant Expression is constant and contains no user-controlled input.
785+
* @precondition tmux understands mouse_any_flag and pane mode format variables.
786+
* @postcondition The value is safe to embed after shellQuote.
787+
* @complexity O(1) time/O(1) space.
788+
* @throws Never
789+
*/
768790
const tmuxRightClickForwardPredicate =
769791
"#{||:#{mouse_any_flag},#{&&:#{pane_in_mode},#{?#{m/r:(copy|view)-mode,#{pane_mode}},0,1}}}"
792+
// CHANGE: Pane right-click bindings that are overridden at tmux startup.
793+
// WHY: These cover down/drag/up/end and Meta-modified events that previously reached display-menu.
794+
// QUOTE(TZ): issue #340 right-click must not open the default tmux menu in browser terminals.
795+
// REF: PR #342 tmux right-click handling.
796+
// SOURCE: n/a
797+
// FORMAT THEOREM: every binding in the array is mapped to renderTmuxPaneRightClickBinding.
798+
// PURITY: CORE
799+
// EFFECT: none
800+
// INVARIANT: Each entry is a static tmux root-table mouse binding name.
801+
// COMPLEXITY: O(1) time/O(1) space.
802+
/**
803+
* Tmux pane right-click binding names that should conditionally forward mouse events.
804+
*
805+
* @pure true
806+
* @effect none
807+
* @invariant The array contains only static tmux binding identifiers.
808+
* @precondition tmux root key table supports these binding names.
809+
* @postcondition Consumers can map each entry to a shell-safe bind-key command.
810+
* @complexity O(1) time/O(1) space.
811+
* @throws Never
812+
*/
770813
const tmuxRightClickPaneBindings: ReadonlyArray<string> = [
771814
"MouseDown3Pane",
772815
"MouseDrag3Pane",
@@ -777,6 +820,27 @@ const tmuxRightClickPaneBindings: ReadonlyArray<string> = [
777820
"M-MouseDragEnd3Pane",
778821
"M-MouseUp3Pane"
779822
]
823+
// CHANGE: Non-pane right-click bindings that are suppressed at tmux startup.
824+
// WHY: Status and border right-clicks are the tmux menu entry points that cannot be forwarded to pane apps.
825+
// QUOTE(TZ): issue #340 right-click must not open the default tmux menu in browser terminals.
826+
// REF: PR #342 tmux right-click handling.
827+
// SOURCE: n/a
828+
// FORMAT THEOREM: every binding in the array is mapped to renderTmuxRightClickSuppressBinding.
829+
// PURITY: CORE
830+
// EFFECT: none
831+
// INVARIANT: Each entry is a static tmux root-table mouse binding name.
832+
// COMPLEXITY: O(1) time/O(1) space.
833+
/**
834+
* Tmux status/border right-click binding names that should be unbound.
835+
*
836+
* @pure true
837+
* @effect none
838+
* @invariant The array contains only static tmux binding identifiers.
839+
* @precondition tmux root key table supports these binding names.
840+
* @postcondition Consumers can map each entry to a shell-safe unbind-key command.
841+
* @complexity O(1) time/O(1) space.
842+
* @throws Never
843+
*/
780844
const tmuxRightClickSuppressBindings: ReadonlyArray<string> = [
781845
"MouseDown3Status",
782846
"MouseDown3StatusLeft",
@@ -788,14 +852,82 @@ const tmuxRightClickSuppressBindings: ReadonlyArray<string> = [
788852
"M-MouseDown3Border"
789853
]
790854

855+
// CHANGE: Render one tmux bind-key command for a right-click pane event.
856+
// WHY: Pane events must reach mouse-aware programs without allowing tmux display-menu.
857+
// QUOTE(TZ): issue #340 right-click must not open the default tmux menu in browser terminals.
858+
// REF: PR #342 tmux right-click handling.
859+
// SOURCE: n/a
860+
// FORMAT THEOREM: static binding => shellQuote(protected fragments) in result.
861+
// PURITY: CORE
862+
// EFFECT: none
863+
// INVARIANT: Dynamic shell fragments are emitted through shellQuote.
864+
// COMPLEXITY: O(1) time/O(1) space.
865+
/**
866+
* Builds a tmux root-table command for a pane right-click binding.
867+
*
868+
* @param binding - Static tmux mouse binding name.
869+
* @returns Shell command that binds the event to conditional pane forwarding.
870+
* @pure true
871+
* @effect none
872+
* @invariant Shell-interpreted tmux format/action fragments are quoted.
873+
* @precondition binding is one of tmuxRightClickPaneBindings.
874+
* @postcondition The command exits successfully even when tmux rejects a binding.
875+
* @complexity O(1) time/O(1) space.
876+
* @throws Never
877+
*/
791878
const renderTmuxPaneRightClickBinding = (binding: string): string =>
792879
`tmux bind-key -T root ${binding} if-shell -F -t = ${shellQuote(tmuxRightClickForwardPredicate)} ${
793880
shellQuote("select-pane -t = ; send-keys -M")
794881
} >/dev/null 2>&1 || true`
795882

883+
// CHANGE: Render one tmux unbind-key command for a suppressed right-click event.
884+
// WHY: Non-pane right-click targets are tmux UI affordances and should not open display-menu.
885+
// QUOTE(TZ): issue #340 right-click must not open the default tmux menu in browser terminals.
886+
// REF: PR #342 tmux right-click handling.
887+
// SOURCE: n/a
888+
// FORMAT THEOREM: static binding => deterministic unbind command.
889+
// PURITY: CORE
890+
// EFFECT: none
891+
// INVARIANT: Result contains no user-controlled input.
892+
// COMPLEXITY: O(1) time/O(1) space.
893+
/**
894+
* Builds a tmux root-table command that suppresses a non-pane right-click binding.
895+
*
896+
* @param binding - Static tmux mouse binding name.
897+
* @returns Shell command that unbinds the event and tolerates unsupported bindings.
898+
* @pure true
899+
* @effect none
900+
* @invariant The returned command contains only static text plus binding.
901+
* @precondition binding is one of tmuxRightClickSuppressBindings.
902+
* @postcondition The command exits successfully even when the binding is absent.
903+
* @complexity O(1) time/O(1) space.
904+
* @throws Never
905+
*/
796906
const renderTmuxRightClickSuppressBinding = (binding: string): string =>
797907
`tmux unbind-key -T root ${binding} >/dev/null 2>&1 || true`
798908

909+
// CHANGE: Aggregate all tmux right-click startup commands.
910+
// WHY: Terminal session startup needs one ordered command list for pane forwarding and UI suppression.
911+
// QUOTE(TZ): PR #342 preserves right-click copy while tmux mouse tracking is active.
912+
// REF: PR #342 tmux right-click handling.
913+
// SOURCE: n/a
914+
// FORMAT THEOREM: result length = paneBindings length + suppressBindings length.
915+
// PURITY: CORE
916+
// EFFECT: none
917+
// INVARIANT: Pane commands precede suppress commands.
918+
// COMPLEXITY: O(n) time/O(n) space where n is the total binding count.
919+
/**
920+
* Renders the complete tmux right-click binding setup command list.
921+
*
922+
* @returns Readonly array of shell commands for tmux startup.
923+
* @pure true
924+
* @effect none
925+
* @invariant Pane forwarding commands are emitted before suppressing status/border commands.
926+
* @precondition Binding arrays contain static tmux binding identifiers.
927+
* @postcondition The result contains one command per configured binding.
928+
* @complexity O(n) time/O(n) space where n is total binding count.
929+
* @throws Never
930+
*/
799931
const renderTmuxRightClickBindingCommands = (): ReadonlyArray<string> => [
800932
...tmuxRightClickPaneBindings.map(renderTmuxPaneRightClickBinding),
801933
...tmuxRightClickSuppressBindings.map(renderTmuxRightClickSuppressBinding)

packages/app/src/docker-git/menu-create-advance.ts

Lines changed: 120 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,23 @@ import { applyCreateBufferToValues } from "./menu-create-step-apply.js"
1919
import { resolveCreateDisplaySteps, resolveCreateFlowSteps } from "./menu-create-steps.js"
2020
import type { CreateInputs, CreateStep } from "./menu-types.js"
2121

22+
/**
23+
* Creates the initial repo-url prompt view for the create flow.
24+
*
25+
* @pure true
26+
* @invariant step = 0 and values are empty
27+
* @complexity O(1)
28+
*/
29+
// CHANGE: expose a deterministic initial create-flow view constructor
30+
// WHY: CLI and browser callers need the same pure starting state
31+
// QUOTE(ТЗ): "fix CodeRabbit review comments"
32+
// REF: issue-339
33+
// SOURCE: n/a
34+
// FORMAT THEOREM: forall b: initial(b).step = 0
35+
// PURITY: CORE
36+
// EFFECT: n/a
37+
// INVARIANT: initial values contain no committed create inputs
38+
// COMPLEXITY: O(1)
2239
export const createInitialFlowView = (buffer = ""): CreateModeFlowView => ({
2340
mode: "create",
2441
step: 0,
@@ -37,6 +54,23 @@ const resolveDisplayFlowStep = (view: CreateFlowView): number => {
3754
return clampCreateSettingsStep(displayStep === -1 ? view.step : displayStep, displaySteps.length - 1)
3855
}
3956

57+
/**
58+
* Converts a create-flow view into the browser display-settings projection.
59+
*
60+
* @pure true
61+
* @invariant values are preserved exactly
62+
* @complexity O(s) where s = number of create steps
63+
*/
64+
// CHANGE: map create-mode progress onto browser display settings
65+
// WHY: display mode keeps all rows visible while preserving committed values
66+
// QUOTE(ТЗ): "fix CodeRabbit review comments"
67+
// REF: issue-339
68+
// SOURCE: n/a
69+
// FORMAT THEOREM: forall v: display(v).values = v.values
70+
// PURITY: CORE
71+
// EFFECT: n/a
72+
// INVARIANT: display step is clamped to the settings range
73+
// COMPLEXITY: O(s) where s = number of create steps
4074
export const createDisplayFlowView = (view: CreateFlowView): DisplayModeFlowView => ({
4175
mode: "display",
4276
step: resolveDisplayFlowStep(view),
@@ -130,6 +164,23 @@ const withActiveCreateDisplayContext = (
130164
return active === null ? null : onActive(active)
131165
}
132166

167+
/**
168+
* Applies the current browser display-settings row without moving selection.
169+
*
170+
* @pure true
171+
* @invariant display mode remains display mode on Continue
172+
* @complexity O(k) where k = number of stored create inputs
173+
*/
174+
// CHANGE: apply browser display settings through the shared pure step applicator
175+
// WHY: display mode must preserve row position while committing one decoded value
176+
// QUOTE(ТЗ): "fix CodeRabbit review comments"
177+
// REF: issue-339
178+
// SOURCE: n/a
179+
// FORMAT THEOREM: active(view) -> result in {Continue, Error}
180+
// PURITY: CORE
181+
// EFFECT: n/a
182+
// INVARIANT: inactive rows return null
183+
// COMPLEXITY: O(k) where k = number of stored create inputs
133184
export const applyCreateDisplaySettingsStep = (
134185
contextOrCwd: string | CreateFlowContext,
135186
view: DisplayModeFlowView
@@ -140,6 +191,23 @@ export const applyCreateDisplaySettingsStep = (
140191
(nextValues) => continueCreateDisplayFlow(view, nextValues)
141192
))
142193

194+
/**
195+
* Applies the current browser display-settings row and advances one row.
196+
*
197+
* @pure true
198+
* @invariant successful application clears the buffer
199+
* @complexity O(k + s) where s = number of display steps
200+
*/
201+
// CHANGE: compose display setting application with wrapped display navigation
202+
// WHY: Enter in browser settings should commit the row and move to the next editable row
203+
// QUOTE(ТЗ): "fix CodeRabbit review comments"
204+
// REF: issue-339
205+
// SOURCE: n/a
206+
// FORMAT THEOREM: Continue(v) -> step(next(v)) = wrappedSuccessor(v.step)
207+
// PURITY: CORE
208+
// EFFECT: n/a
209+
// INVARIANT: errors do not advance selection
210+
// COMPLEXITY: O(k + s) where s = number of display steps
143211
export const advanceCreateDisplaySettingsStep = (
144212
contextOrCwd: string | CreateFlowContext,
145213
view: DisplayModeFlowView
@@ -153,6 +221,23 @@ export const advanceCreateDisplaySettingsStep = (
153221
return movedView === null ? applied : { ...applied, view: movedView }
154222
}
155223

224+
/**
225+
* Completes browser display settings, applying a non-empty active buffer first.
226+
*
227+
* @pure true
228+
* @invariant completion resolves total CreateInputs
229+
* @complexity O(k) where k = number of stored create inputs
230+
*/
231+
// CHANGE: finish browser settings with optional final-row validation
232+
// WHY: a typed buffer should not be discarded when the user presses Done
233+
// QUOTE(ТЗ): "fix CodeRabbit review comments"
234+
// REF: issue-339
235+
// SOURCE: n/a
236+
// FORMAT THEOREM: complete(view) -> Complete(resolve(values')) or Error
237+
// PURITY: CORE
238+
// EFFECT: n/a
239+
// INVARIANT: invalid active buffers return typed parse errors
240+
// COMPLEXITY: O(k) where k = number of stored create inputs
156241
export const completeCreateDisplaySettingsFlow = (
157242
contextOrCwd: string | CreateFlowContext,
158243
view: DisplayModeFlowView
@@ -179,8 +264,25 @@ const resolveNextCreateFlowStep = (
179264
): number =>
180265
currentStep === "repoUrl"
181266
? firstCreateSettingsStepIndex
182-
: clampCreateSettingsStep(currentStepIndex, nextSteps.length - 1)
267+
: clampCreateSettingsStep(currentStepIndex + 1, nextSteps.length - 1)
183268

269+
/**
270+
* Advances create mode by applying the active prompt buffer.
271+
*
272+
* @pure true
273+
* @invariant non-repo steps advance to the next remaining settings index when continuing
274+
* @complexity O(k + s) where s = number of remaining create steps
275+
*/
276+
// CHANGE: advance normal create-flow settings after committing the active prompt
277+
// WHY: applying a non-repo step must move forward instead of reselecting the same index
278+
// QUOTE(ТЗ): "after applying a non-repoUrl step it advances to currentStepIndex + 1"
279+
// REF: issue-339
280+
// SOURCE: n/a
281+
// FORMAT THEOREM: step != repoUrl -> nextStep = clamp(stepIndex + 1)
282+
// PURITY: CORE
283+
// EFFECT: n/a
284+
// INVARIANT: next step is always within the current settings range when continuing
285+
// COMPLEXITY: O(k + s) where s = number of remaining create steps
184286
export const advanceCreateFlow = (
185287
contextOrCwd: string | CreateFlowContext,
186288
view: CreateModeFlowView,
@@ -209,6 +311,23 @@ export const advanceCreateFlow = (
209311
)
210312
}
211313

314+
/**
315+
* Dispatches an advance result to imperative TUI handlers.
316+
*
317+
* @pure false
318+
* @effect AdvanceCreateFlowHandlers
319+
* @complexity O(1)
320+
*/
321+
// CHANGE: keep create-flow result handling at the shell boundary
322+
// WHY: pure transition results are interpreted by caller-provided side-effect handlers
323+
// QUOTE(ТЗ): "fix CodeRabbit review comments"
324+
// REF: issue-339
325+
// SOURCE: n/a
326+
// FORMAT THEOREM: forall r: exactly one matching handler is invoked, except null
327+
// PURITY: SHELL
328+
// EFFECT: AdvanceCreateFlowHandlers
329+
// INVARIANT: null results invoke no handler
330+
// COMPLEXITY: O(1)
212331
export const handleAdvanceCreateFlowResult = (
213332
next: AdvanceCreateFlowResult | null,
214333
handlers: AdvanceCreateFlowHandlers

0 commit comments

Comments
 (0)