authorgravatar for 88523594+RakshithBhat03@users.noreply.github.comRakshith Bhat <88523594+RakshithBhat03@users.noreply.github.com> 2026-09-02 09:44:32+05:30
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-09-02 00:14:32-04:00
loga1a2bb1cd9f000f53fbeae752ed5c38f152eadf2
treeb9d4d6292e09c7257c5dcf0dfa9d66db99022b9a
parente9db39ce05c706122bb747fd5e7e70e1aac78935
signature Signed by PGP key B5690EEEBB952194

fix(web): label keybinding condition removal actions (#8664)


3 files changed, 68 insertions(+), 30 deletions(-)

apps/web/src/components/settings/KeybindingsSettings.logic.test.ts+14
...@@ -12,6 +12,7 @@ import {...@@ -12,6 +12,7 @@ import {
12 shortcutToKeybindingInput,12 shortcutToKeybindingInput,
13 unknownWhenVariables,13 unknownWhenVariables,
14 whenAstToExpression,14 whenAstToExpression,
15 whenNodeRemoveLabel,
15} from "./KeybindingsSettings.logic";16} from "./KeybindingsSettings.logic";
1617
17describe("KeybindingsSettings.logic", () => {18describe("KeybindingsSettings.logic", () => {
...@@ -120,6 +121,19 @@ describe("KeybindingsSettings.logic", () => {...@@ -120,6 +121,19 @@ describe("KeybindingsSettings.logic", () => {
120 });121 });
121 });122 });
122123
124 it("describes the scope of each visual expression removal", () => {
125 const condition = { type: "identifier", name: "terminalFocus" } as const;
126 const negatedCondition = { type: "not", node: condition } as const;
127 const group = { type: "and", left: condition, right: negatedCondition } as const;
128 const negatedGroup = { type: "not", node: group } as const;
129
130 expect(whenNodeRemoveLabel(group, 0)).toBe("Clear all conditions");
131 expect(whenNodeRemoveLabel(condition, 1)).toBe("Remove condition");
132 expect(whenNodeRemoveLabel(negatedCondition, 1)).toBe("Remove condition");
133 expect(whenNodeRemoveLabel(group, 1)).toBe("Remove group and its conditions");
134 expect(whenNodeRemoveLabel(negatedGroup, 1)).toBe("Remove group and its conditions");
135 });
136
123 it("formats static and project script command labels", () => {137 it("formats static and project script command labels", () => {
124 expect(commandLabel("commandPalette.toggle")).toBe("Command Palette: Toggle");138 expect(commandLabel("commandPalette.toggle")).toBe("Command Palette: Toggle");
125 expect(commandLabel("themeEditor.toggle")).toBe("Theme Editor: Toggle");139 expect(commandLabel("themeEditor.toggle")).toBe("Theme Editor: Toggle");
apps/web/src/components/settings/KeybindingsSettings.logic.ts+8
...@@ -68,6 +68,14 @@ export function whenAstToExpression(node: KeybindingWhenNode | undefined): strin...@@ -68,6 +68,14 @@ export function whenAstToExpression(node: KeybindingWhenNode | undefined): strin
68 }68 }
69}69}
7070
71export function whenNodeRemoveLabel(node: KeybindingWhenNode, depth: number): string {
72 if (depth === 0) return "Clear all conditions";
73 if (node.type === "identifier" || (node.type === "not" && node.node.type === "identifier")) {
74 return "Remove condition";
75 }
76 return "Remove group and its conditions";
77}
78
71function wrapWhenExpression(node: KeybindingWhenNode): string {79function wrapWhenExpression(node: KeybindingWhenNode): string {
72 if (node.type === "identifier" || node.type === "not") return whenAstToExpression(node);80 if (node.type === "identifier" || node.type === "not") return whenAstToExpression(node);
73 return `(${whenAstToExpression(node)})`;81 return `(${whenAstToExpression(node)})`;
apps/web/src/components/settings/KeybindingsSettings.tsx+46-30
...@@ -67,6 +67,7 @@ import {...@@ -67,6 +67,7 @@ import {
67 type WhenVariableOption,67 type WhenVariableOption,
68 unknownWhenVariables,68 unknownWhenVariables,
69 whenAstToExpression,69 whenAstToExpression,
70 whenNodeRemoveLabel,
70} from "./KeybindingsSettings.logic";71} from "./KeybindingsSettings.logic";
71import { SettingsPageContainer, SettingsRow, SettingsSection } from "./settingsLayout";72import { SettingsPageContainer, SettingsRow, SettingsSection } from "./settingsLayout";
72import { searchableSetting } from "./settingsSearch";73import { searchableSetting } from "./settingsSearch";
...@@ -349,6 +350,37 @@ function WhenVariableSelect({...@@ -349,6 +350,37 @@ function WhenVariableSelect({
349 );350 );
350}351}
351352
353function WhenExpressionRemoveButton({
354 label,
355 className,
356 onRemove,
357}: {
358 label: string;
359 className?: string | undefined;
360 onRemove: () => void;
361}) {
362 return (
363 <Tooltip>
364 <TooltipTrigger
365 delay={200}
366 render={
367 <Button
368 type="button"
369 variant="ghost"
370 size="icon-sm"
371 className={cn("size-7", className)}
372 aria-label={label}
373 onClick={onRemove}
374 />
375 }
376 >
377 <MinusIcon aria-hidden className="size-3.5" />
378 </TooltipTrigger>
379 <TooltipPopup side="top">{label}</TooltipPopup>
380 </Tooltip>
381 );
382}
383
352function WhenExpressionNodeEditor({384function WhenExpressionNodeEditor({
353 node,385 node,
354 variables,386 variables,
...@@ -388,16 +420,10 @@ function WhenExpressionNodeEditor({...@@ -388,16 +420,10 @@ function WhenExpressionNodeEditor({
388 onChange={(value) => onChange(setConditionIdentifier(node, value))}420 onChange={(value) => onChange(setConditionIdentifier(node, value))}
389 />421 />
390 {onRemove ? (422 {onRemove ? (
391 <Button423 <WhenExpressionRemoveButton
392 type="button"424 label={whenNodeRemoveLabel(node, depth)}
393 variant="ghost"425 onRemove={onRemove}
394 size="icon-sm"426 />
395 className="size-7"
396 aria-label="Remove condition"
397 onClick={onRemove}
398 >
399 <MinusIcon className="size-3.5" />
400 </Button>
401 ) : null}427 ) : null}
402 </div>428 </div>
403 );429 );
...@@ -423,16 +449,11 @@ function WhenExpressionNodeEditor({...@@ -423,16 +449,11 @@ function WhenExpressionNodeEditor({
423 Not449 Not
424 </Toggle>450 </Toggle>
425 {onRemove ? (451 {onRemove ? (
426 <Button452 <WhenExpressionRemoveButton
427 type="button"453 label={whenNodeRemoveLabel(node, depth)}
428 variant="ghost"454 className="ml-auto"
429 size="icon-sm"455 onRemove={onRemove}
430 className="ml-auto size-7"456 />
431 aria-label="Remove negated group"
432 onClick={onRemove}
433 >
434 <MinusIcon className="size-3.5" />
435 </Button>
436 ) : null}457 ) : null}
437 </div>458 </div>
438 <div className="relative pl-4">459 <div className="relative pl-4">
...@@ -546,16 +567,11 @@ function WhenExpressionNodeEditor({...@@ -546,16 +567,11 @@ function WhenExpressionNodeEditor({
546 Group567 Group
547 </Button>568 </Button>
548 {onRemove ? (569 {onRemove ? (
549 <Button570 <WhenExpressionRemoveButton
550 type="button"571 label={whenNodeRemoveLabel(node, depth)}
551 variant="ghost"572 className="ml-auto"
552 size="icon-sm"573 onRemove={onRemove}
553 className="ml-auto size-7"574 />
554 aria-label="Remove group"
555 onClick={onRemove}
556 >
557 <MinusIcon className="size-3.5" />
558 </Button>
559 ) : null}575 ) : null}
560 </div>576 </div>
561 <div className="space-y-2">577 <div className="space-y-2">