authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-02-15 13:56:37-08:00
committergravatar for jmarminge@gmail.comJulius Marminge <jmarminge@gmail.com> 2026-02-15 15:51:05-08:00
log3db020a683398511a169c411513a945a1cd2f384
treebad857551683630ee9baeab075a30757af72c260
parent20d3ad5d50ba9afb0df99bad1f71dddc762d2032

Simplify resolved keybinding payload and remove unused LRU cache

- drop `key` and `when` from resolved keybinding rule schema/payload - update server/web/contracts tests and keybindings docs for `whenAst`-only resolution - delete unused web `LruCache` implementation and tests

8 files changed, 7 insertions(+), 83 deletions(-)

KEYBINDINGS.md+7-1
...@@ -97,7 +97,13 @@ If you define any rules for a command, the default rule for that command is remo...@@ -97,7 +97,13 @@ If you define any rules for a command, the default rule for that command is remo
9797
98## Server-Resolved Payload98## Server-Resolved Payload
9999
100`~/.t3/keybindings.json` is parsed and compiled on the server. The web client receives resolved rules (including parsed shortcut/when fields) and does not parse raw keybinding strings.100`~/.t3/keybindings.json` is parsed and compiled on the server. The web client receives resolved rules and does not parse raw keybinding strings.
101
102Resolved rule payload fields:
103
104- `command`
105- `shortcut`
106- `whenAst` (optional)
101107
102See:108See:
103109
apps/server/src/keybindings.test.ts-2
...@@ -30,9 +30,7 @@ describe("server keybindings", () => {...@@ -30,9 +30,7 @@ describe("server keybindings", () => {
30 });30 });
3131
32 assert.deepEqual(compiled, {32 assert.deepEqual(compiled, {
33 key: "mod+d",
34 command: "terminal.split",33 command: "terminal.split",
35 when: "terminalOpen && !terminalFocus",
36 shortcut: {34 shortcut: {
37 key: "d",35 key: "d",
38 metaKey: false,36 metaKey: false,
apps/server/src/keybindings.ts-3
...@@ -216,16 +216,13 @@ export function compileResolvedKeybindingRule(...@@ -216,16 +216,13 @@ export function compileResolvedKeybindingRule(
216 const whenAst = parseKeybindingWhenExpression(when);216 const whenAst = parseKeybindingWhenExpression(when);
217 if (!whenAst) return null;217 if (!whenAst) return null;
218 return {218 return {
219 key,
220 command: rule.command,219 command: rule.command,
221 when,
222 shortcut,220 shortcut,
223 whenAst,221 whenAst,
224 };222 };
225 }223 }
226224
227 return {225 return {
228 key,
229 command: rule.command,226 command: rule.command,
230 shortcut,227 shortcut,
231 };228 };
apps/web/src/keybindings.test.ts-2
...@@ -125,7 +125,6 @@ function compile(bindings: TestBinding[]): ResolvedKeybindingsConfig {...@@ -125,7 +125,6 @@ function compile(bindings: TestBinding[]): ResolvedKeybindingsConfig {
125 }125 }
126 if (binding.whenAst) {126 if (binding.whenAst) {
127 resolved.push({127 resolved.push({
128 key: binding.key.trim(),
129 command: binding.command,128 command: binding.command,
130 shortcut: parsedShortcut,129 shortcut: parsedShortcut,
131 whenAst: binding.whenAst,130 whenAst: binding.whenAst,
...@@ -133,7 +132,6 @@ function compile(bindings: TestBinding[]): ResolvedKeybindingsConfig {...@@ -133,7 +132,6 @@ function compile(bindings: TestBinding[]): ResolvedKeybindingsConfig {
133 continue;132 continue;
134 }133 }
135 resolved.push({134 resolved.push({
136 key: binding.key.trim(),
137 command: binding.command,135 command: binding.command,
138 shortcut: parsedShortcut,136 shortcut: parsedShortcut,
139 });137 });
apps/web/src/lib/lru.test.ts deleted-33
...@@ -1,33 +0,0 @@
1import { assert, describe, it } from "vitest";
2import { LruCache } from "./lru";
3
4describe("LruCache", () => {
5 it("evicts the least recently used entry", () => {
6 const cache = new LruCache<string, number>(2);
7 cache.set("a", 1);
8 cache.set("b", 2);
9 assert.strictEqual(cache.get("a"), 1);
10
11 cache.set("c", 3);
12
13 assert.strictEqual(cache.get("b"), undefined);
14 assert.strictEqual(cache.get("a"), 1);
15 assert.strictEqual(cache.get("c"), 3);
16 });
17
18 it("refreshes recency on update", () => {
19 const cache = new LruCache<string, number>(2);
20 cache.set("a", 1);
21 cache.set("b", 2);
22 cache.set("a", 10);
23 cache.set("c", 3);
24
25 assert.strictEqual(cache.get("b"), undefined);
26 assert.strictEqual(cache.get("a"), 10);
27 assert.strictEqual(cache.get("c"), 3);
28 });
29
30 it("rejects non-positive capacities", () => {
31 assert.throws(() => new LruCache<string, number>(0), "positive integer");
32 });
33});
apps/web/src/lib/lru.ts deleted-38
...@@ -1,38 +0,0 @@
1export class LruCache<K, V> {
2 private readonly entries = new Map<K, V>();
3
4 constructor(private readonly maxEntries: number) {
5 if (!Number.isInteger(maxEntries) || maxEntries <= 0) {
6 throw new Error("LruCache maxEntries must be a positive integer.");
7 }
8 }
9
10 get size(): number {
11 return this.entries.size;
12 }
13
14 get(key: K): V | undefined {
15 const value = this.entries.get(key);
16 if (value === undefined && !this.entries.has(key)) {
17 return undefined;
18 }
19
20 // Refresh recency on successful lookup.
21 this.entries.delete(key);
22 this.entries.set(key, value as V);
23 return value;
24 }
25
26 set(key: K, value: V): void {
27 if (this.entries.has(key)) {
28 this.entries.delete(key);
29 }
30 this.entries.set(key, value);
31
32 while (this.entries.size > this.maxEntries) {
33 const oldest = this.entries.keys().next().value as K | undefined;
34 if (oldest === undefined) break;
35 this.entries.delete(oldest);
36 }
37 }
38}
packages/contracts/src/keybindings.test.ts-2
...@@ -37,9 +37,7 @@ describe("keybindings contracts", () => {...@@ -37,9 +37,7 @@ describe("keybindings contracts", () => {
3737
38 it("parses resolved keybinding rules", () => {38 it("parses resolved keybinding rules", () => {
39 const parsed = resolvedKeybindingRuleSchema.parse({39 const parsed = resolvedKeybindingRuleSchema.parse({
40 key: "mod+d",
41 command: "terminal.split",40 command: "terminal.split",
42 when: "terminalOpen && !terminalFocus",
43 shortcut: {41 shortcut: {
44 key: "d",42 key: "d",
45 metaKey: false,43 metaKey: false,
packages/contracts/src/keybindings.ts-2
...@@ -58,9 +58,7 @@ export const keybindingWhenNodeSchema: z.ZodType<KeybindingWhenNode> = z.lazy(()...@@ -58,9 +58,7 @@ export const keybindingWhenNodeSchema: z.ZodType<KeybindingWhenNode> = z.lazy(()
58);58);
5959
60export const resolvedKeybindingRuleSchema = z.object({60export const resolvedKeybindingRuleSchema = z.object({
61 key: keybindingValueSchema,
62 command: keybindingCommandSchema,61 command: keybindingCommandSchema,
63 when: keybindingWhenSchema.optional(),
64 shortcut: keybindingShortcutSchema,62 shortcut: keybindingShortcutSchema,
65 whenAst: keybindingWhenNodeSchema.optional(),63 whenAst: keybindingWhenNodeSchema.optional(),
66});64});