| 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 | 97 | |
| 98 | 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 | 108 | See: |
| 103 | 109 |
apps/server/src/keybindings.test.ts-2| ... | ... | @@ -30,9 +30,7 @@ describe("server keybindings", () => { |
| 30 | 30 | }); |
| 31 | 31 | |
| 32 | 32 | assert.deepEqual(compiled, { |
| 33 | key: "mod+d", | |
| 34 | 33 | command: "terminal.split", |
| 35 | when: "terminalOpen && !terminalFocus", | |
| 36 | 34 | shortcut: { |
| 37 | 35 | key: "d", |
| 38 | 36 | metaKey: false, |
apps/server/src/keybindings.ts-3| ... | ... | @@ -216,16 +216,13 @@ export function compileResolvedKeybindingRule( |
| 216 | 216 | const whenAst = parseKeybindingWhenExpression(when); |
| 217 | 217 | if (!whenAst) return null; |
| 218 | 218 | return { |
| 219 | key, | |
| 220 | 219 | command: rule.command, |
| 221 | when, | |
| 222 | 220 | shortcut, |
| 223 | 221 | whenAst, |
| 224 | 222 | }; |
| 225 | 223 | } |
| 226 | 224 | |
| 227 | 225 | return { |
| 228 | key, | |
| 229 | 226 | command: rule.command, |
| 230 | 227 | shortcut, |
| 231 | 228 | }; |
apps/web/src/keybindings.test.ts-2| ... | ... | @@ -125,7 +125,6 @@ function compile(bindings: TestBinding[]): ResolvedKeybindingsConfig { |
| 125 | 125 | } |
| 126 | 126 | if (binding.whenAst) { |
| 127 | 127 | resolved.push({ |
| 128 | key: binding.key.trim(), | |
| 129 | 128 | command: binding.command, |
| 130 | 129 | shortcut: parsedShortcut, |
| 131 | 130 | whenAst: binding.whenAst, |
| ... | ... | @@ -133,7 +132,6 @@ function compile(bindings: TestBinding[]): ResolvedKeybindingsConfig { |
| 133 | 132 | continue; |
| 134 | 133 | } |
| 135 | 134 | resolved.push({ |
| 136 | key: binding.key.trim(), | |
| 137 | 135 | command: binding.command, |
| 138 | 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 | 37 | |
| 38 | 38 | it("parses resolved keybinding rules", () => { |
| 39 | 39 | const parsed = resolvedKeybindingRuleSchema.parse({ |
| 40 | key: "mod+d", | |
| 41 | 40 | command: "terminal.split", |
| 42 | when: "terminalOpen && !terminalFocus", | |
| 43 | 41 | shortcut: { |
| 44 | 42 | key: "d", |
| 45 | 43 | metaKey: false, |
packages/contracts/src/keybindings.ts-2| ... | ... | @@ -58,9 +58,7 @@ export const keybindingWhenNodeSchema: z.ZodType<KeybindingWhenNode> = z.lazy(() |
| 58 | 58 | ); |
| 59 | 59 | |
| 60 | 60 | export const resolvedKeybindingRuleSchema = z.object({ |
| 61 | key: keybindingValueSchema, | |
| 62 | 61 | command: keybindingCommandSchema, |
| 63 | when: keybindingWhenSchema.optional(), | |
| 64 | 62 | shortcut: keybindingShortcutSchema, |
| 65 | 63 | whenAst: keybindingWhenNodeSchema.optional(), |
| 66 | 64 | }); |