| author | |
| committer | |
| log | 3db020a683398511a169c411513a945a1cd2f384 |
| tree | bad857551683630ee9baeab075a30757af72c260 |
| parent | 20d3ad5d50ba9afb0df99bad1f71dddc762d2032 |
- 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 tests8 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 |
| 97 | 97 | ||
| 98 | ## Server-Resolved Payload | 98 | ## Server-Resolved Payload |
| 99 | 99 | ||
| 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 | |||
| 102 | Resolved rule payload fields: | ||
| 103 | |||
| 104 | - `command` | ||
| 105 | - `shortcut` | ||
| 106 | - `whenAst` (optional) | ||
| 101 | 107 | ||
| 102 | See: | 108 | See: |
| 103 | 109 |
apps/server/src/keybindings.test.ts-2| ... | @@ -30,9 +30,7 @@ describe("server keybindings", () => { | ... | @@ -30,9 +30,7 @@ describe("server keybindings", () => { |
| 30 | }); | 30 | }); |
| 31 | 31 | ||
| 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 | } |
| 226 | 224 | ||
| 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 @@ | ||
| 1 | import { assert, describe, it } from "vitest"; | ||
| 2 | import { LruCache } from "./lru"; | ||
| 3 | |||
| 4 | describe("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 @@ | ||
| 1 | export 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", () => { |
| 37 | 37 | ||
| 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 | ); |
| 59 | 59 | ||
| 60 | export const resolvedKeybindingRuleSchema = z.object({ | 60 | export 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 | }); |