modern-cmdk 1.1.0 → 1.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,16 +1,34 @@
1
+ //#region src/core/utils/string-wellformed.ts
2
+ /**
3
+ * Regex matching lone surrogates (unpaired high or low surrogates).
4
+ * A lone high surrogate (U+D800–U+DBFF) not followed by a low surrogate,
5
+ * or a lone low surrogate (U+DC00–U+DFFF) not preceded by a high surrogate.
6
+ */
7
+ const LONE_SURROGATE_RE = /[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/g;
8
+ /** Unicode replacement character used to replace lone surrogates. */
9
+ const REPLACEMENT_CHAR = "�";
10
+ /**
11
+ * Ensures a string is well-formed Unicode by replacing lone surrogates
12
+ * with the Unicode replacement character (U+FFFD).
13
+ * Cross-browser replacement for `str.isWellFormed() ? str : str.toWellFormed()`.
14
+ */
15
+ function ensureWellFormed(str) {
16
+ return str.replace(LONE_SURROGATE_RE, REPLACEMENT_CHAR);
17
+ }
18
+ //#endregion
1
19
  //#region src/core/frecency/idb-storage.ts
2
20
  /** Serialize a FrecencyRecord to a JSON-safe format */
3
21
  function serializeRecord(record) {
4
22
  return {
5
23
  frequency: record.frequency,
6
- lastUsedNs: record.lastUsed.epochNanoseconds.toString()
24
+ lastUsedMs: record.lastUsed
7
25
  };
8
26
  }
9
27
  /** Deserialize a FrecencyRecord from its JSON-safe form */
10
28
  function deserializeRecord(serialized) {
11
29
  return {
12
30
  frequency: serialized.frequency,
13
- lastUsed: Temporal.Instant.fromEpochNanoseconds(BigInt(serialized.lastUsedNs))
31
+ lastUsed: serialized.lastUsedMs
14
32
  };
15
33
  }
16
34
  /** Serialize FrecencyData for IndexedDB storage — uses Iterator Helpers */
@@ -21,9 +39,9 @@ function serializeData(data) {
21
39
  function deserializeData(serialized) {
22
40
  return { records: new Map(serialized.records.values().map(([id, record]) => [id, deserializeRecord(record)])) };
23
41
  }
24
- /** Build the namespaced IDB key — String.toWellFormed() (ES2026) ensures valid Unicode in key */
42
+ /** Build the namespaced IDB key — ensures valid Unicode in key */
25
43
  function storageKey(namespace) {
26
- return `frecency:${namespace.isWellFormed() ? namespace : namespace.toWellFormed()}`;
44
+ return `frecency:${ensureWellFormed(namespace)}`;
27
45
  }
28
46
  let idbModule;
29
47
  /**
@@ -103,13 +121,13 @@ var IdbFrecencyStorage = class {
103
121
  };
104
122
  //#endregion
105
123
  //#region src/core/types.ts
106
- /** Create a branded {@link ItemId} from a plain string. Ensures well-formed Unicode (ES2026). */
124
+ /** Create a branded {@link ItemId} from a plain string. Ensures well-formed Unicode. */
107
125
  function itemId(id) {
108
- return id.isWellFormed() ? id : id.toWellFormed();
126
+ return ensureWellFormed(id);
109
127
  }
110
- /** Create a branded {@link GroupId} from a plain string. Ensures well-formed Unicode (ES2026). */
128
+ /** Create a branded {@link GroupId} from a plain string. Ensures well-formed Unicode. */
111
129
  function groupId(id) {
112
- return id.isWellFormed() ? id : id.toWellFormed();
130
+ return ensureWellFormed(id);
113
131
  }
114
132
  /** Default frecency decay configuration */
115
133
  const DEFAULT_FRECENCY_DECAY = {
@@ -137,7 +155,7 @@ function createInitialState(options) {
137
155
  page: "root",
138
156
  pageStack: [],
139
157
  open: options?.open ?? false,
140
- lastUpdated: Temporal.Now.instant()
158
+ lastUpdated: Date.now()
141
159
  };
142
160
  }
143
161
  //#endregion
@@ -176,11 +194,11 @@ const BUCKET_HOURS = {
176
194
  };
177
195
  /**
178
196
  * Computes a frecency bonus for an item based on usage frequency and recency.
179
- * Uses Temporal.Duration bucket boundaries (hour/day/week/month) with
197
+ * Uses hour/day/week/month bucket boundaries with
180
198
  * configurable exponential decay weights per time bucket.
181
199
  */
182
- function computeFrecencyBonus(history, now = Temporal.Now.instant(), config = DEFAULT_FRECENCY_DECAY) {
183
- const hours = now.since(history.lastUsed).total("hours");
200
+ function computeFrecencyBonus(history, now = Date.now(), config = DEFAULT_FRECENCY_DECAY) {
201
+ const hours = (now - history.lastUsed) / 36e5;
184
202
  const { hourWeight = 4, dayWeight = 2, weekWeight = 1.5, monthWeight = 1, olderWeight = .5 } = config;
185
203
  const recencyWeight = hours < BUCKET_HOURS.hour ? hourWeight : hours < BUCKET_HOURS.day ? dayWeight : hours < BUCKET_HOURS.week ? weekWeight : hours < BUCKET_HOURS.month ? monthWeight : olderWeight;
186
204
  return history.frequency * recencyWeight;
@@ -188,7 +206,7 @@ function computeFrecencyBonus(history, now = Temporal.Now.instant(), config = DE
188
206
  /**
189
207
  * Stateful frecency engine that tracks item selection history and provides
190
208
  * ranking boosts for search results. Supports pluggable storage backends
191
- * and automatic flush on dispose. Uses Temporal.Now.instant() for timestamps.
209
+ * and automatic flush on dispose. Uses Date.now() for timestamps.
192
210
  */
193
211
  var FrecencyEngine = class {
194
212
  #storage;
@@ -208,7 +226,7 @@ var FrecencyEngine = class {
208
226
  }
209
227
  /** Record a selection event for an item */
210
228
  recordSelection(itemId) {
211
- const now = Temporal.Now.instant();
229
+ const now = Date.now();
212
230
  const updated = {
213
231
  frequency: (this.#data.records.get(itemId)?.frequency ?? 0) + 1,
214
232
  lastUsed: now
@@ -223,7 +241,7 @@ var FrecencyEngine = class {
223
241
  return computeFrecencyBonus(record, now, this.#config);
224
242
  }
225
243
  /** Get all frecency bonuses as a map — for...of for hot ranking path (no closure overhead) */
226
- getAllBonuses(now = Temporal.Now.instant()) {
244
+ getAllBonuses(now = Date.now()) {
227
245
  if (this.#data.records.size === 0) return EMPTY_BONUS_MAP;
228
246
  const bonuses = /* @__PURE__ */ new Map();
229
247
  for (const [id, record] of this.#data.records) bonuses.set(id, computeFrecencyBonus(record, now, this.#config));
@@ -259,13 +277,29 @@ var FrecencyEngine = class {
259
277
  this.#storage[Symbol.dispose]();
260
278
  }
261
279
  [Symbol.dispose]() {
262
- if (this.#dirty) Promise.try(() => this.#storage.save(this.#namespace, this.#data)).catch((err) => {
280
+ if (this.#dirty) Promise.resolve().then(() => this.#storage.save(this.#namespace, this.#data)).catch((err) => {
263
281
  console.warn("[modern-cmdk] FrecencyEngine dispose flush failed:", err);
264
282
  });
265
283
  this.#storage[Symbol.dispose]();
266
284
  }
267
285
  };
268
286
  //#endregion
287
+ //#region src/core/utils/group-by.ts
288
+ /**
289
+ * Groups items from an iterable into a Map keyed by the result of `keyFn`.
290
+ * Cross-browser replacement for `Map.groupBy()`.
291
+ */
292
+ function mapGroupBy(items, keyFn) {
293
+ const map = /* @__PURE__ */ new Map();
294
+ for (const item of items) {
295
+ const key = keyFn(item);
296
+ const group = map.get(key);
297
+ if (group) group.push(item);
298
+ else map.set(key, [item]);
299
+ }
300
+ return map;
301
+ }
302
+ //#endregion
269
303
  //#region src/core/keyboard/matcher.ts
270
304
  /**
271
305
  * Tests whether a KeyboardEvent exactly matches a ParsedShortcut,
@@ -288,7 +322,7 @@ function findMatchingShortcut(event, shortcuts) {
288
322
  * Map.groupBy returns a Map directly — no Object.entries conversion needed.
289
323
  */
290
324
  function detectConflicts(shortcuts) {
291
- const grouped = Map.groupBy(shortcuts, (s) => s.normalized);
325
+ const grouped = mapGroupBy(shortcuts, (s) => s.normalized);
292
326
  return new Map(grouped.entries().filter(([, group]) => group.length > 1));
293
327
  }
294
328
  //#endregion
@@ -309,10 +343,10 @@ const MODIFIER_MAP = {
309
343
  };
310
344
  /**
311
345
  * Parse a shortcut string like "Mod+Shift+K" into a structured ParsedShortcut.
312
- * Uses String.isWellFormed() (ES2026) to ensure valid Unicode input.
346
+ * Ensures valid Unicode input via ensureWellFormed helper.
313
347
  */
314
348
  function parseShortcut(shortcut) {
315
- const safeShortcut = shortcut.isWellFormed() ? shortcut : shortcut.toWellFormed();
349
+ const safeShortcut = ensureWellFormed(shortcut);
316
350
  const parts = safeShortcut.split("+").map((p) => p.trim().toLowerCase());
317
351
  let meta = false;
318
352
  let ctrl = false;
@@ -440,10 +474,54 @@ var KeyboardShortcutRegistry = class {
440
474
  }
441
475
  };
442
476
  //#endregion
477
+ //#region src/core/utils/set-ops.ts
478
+ /** Returns a new Set containing elements present in both `a` and `b`. */
479
+ function setIntersection(a, b) {
480
+ const result = /* @__PURE__ */ new Set();
481
+ const [smaller, larger] = a.size <= b.size ? [a, b] : [b, a];
482
+ for (const item of smaller) if (larger.has(item)) result.add(item);
483
+ return result;
484
+ }
485
+ /** Returns a new Set containing elements in `a` but not in `b`. */
486
+ function setDifference(a, b) {
487
+ const result = /* @__PURE__ */ new Set();
488
+ for (const item of a) if (!b.has(item)) result.add(item);
489
+ return result;
490
+ }
491
+ /** Returns a new Set containing all elements from both `a` and `b`. */
492
+ function setUnion(a, b) {
493
+ const result = new Set(a);
494
+ for (const item of b) result.add(item);
495
+ return result;
496
+ }
497
+ /** Returns `true` if every element in `a` is also in `b`. */
498
+ function setIsSubsetOf(a, b) {
499
+ if (a.size > b.size) return false;
500
+ for (const item of a) if (!b.has(item)) return false;
501
+ return true;
502
+ }
503
+ /** Returns `true` if every element in `b` is also in `a`. */
504
+ function setIsSupersetOf(a, b) {
505
+ return setIsSubsetOf(b, a);
506
+ }
507
+ /** Returns `true` if `a` and `b` share no elements. */
508
+ function setIsDisjointFrom(a, b) {
509
+ const [smaller, larger] = a.size <= b.size ? [a, b] : [b, a];
510
+ for (const item of smaller) if (larger.has(item)) return false;
511
+ return true;
512
+ }
513
+ /** Returns a new Set containing elements in either `a` or `b` but not both. */
514
+ function setSymmetricDifference(a, b) {
515
+ const result = new Set(a);
516
+ for (const item of b) if (result.has(item)) result.delete(item);
517
+ else result.add(item);
518
+ return result;
519
+ }
520
+ //#endregion
443
521
  //#region src/core/registry.ts
444
522
  /**
445
523
  * Manages registration and lookup of command items and groups.
446
- * Uses ES2026 Set methods (union/intersection/difference) for efficient ID operations.
524
+ * Uses set operation helpers (union/intersection/difference) for efficient ID operations.
447
525
  * All registration methods return Disposable for automatic cleanup via `using`.
448
526
  */
449
527
  var CommandRegistry = class {
@@ -495,10 +573,10 @@ var CommandRegistry = class {
495
573
  if (idx !== -1) this.#itemOrder = this.#itemOrder.toSpliced(idx, 1);
496
574
  this.#invalidateCache();
497
575
  }
498
- /** Bulk unregister using Set.difference (ES2026) — for...of for hot path */
576
+ /** Bulk unregister — for...of for hot path */
499
577
  unregisterItems(ids) {
500
578
  for (const id of ids) this.#items.delete(id);
501
- this.#itemOrderSet = this.#itemOrderSet.difference(ids);
579
+ this.#itemOrderSet = setDifference(this.#itemOrderSet, ids);
502
580
  this.#itemOrder = this.#itemOrder.filter((id) => !ids.has(id));
503
581
  this.#invalidateCache();
504
582
  }
@@ -536,10 +614,9 @@ var CommandRegistry = class {
536
614
  this.#cachedGroups = this.#groups.values().toArray().toSorted((a, b) => (a.priority ?? 0) - (b.priority ?? 0));
537
615
  return this.#cachedGroups;
538
616
  }
539
- /** Get items grouped by groupId using Map.groupBy (ES2026) — direct Map, no Object.entries conversion */
617
+ /** Get items grouped by groupId — direct Map, no Object.entries conversion */
540
618
  getGroupedItems() {
541
- const items = this.getItems();
542
- const grouped = Map.groupBy(items, (item) => item.groupId ?? "__ungrouped");
619
+ const grouped = mapGroupBy(this.getItems(), (item) => item.groupId ?? "__ungrouped");
543
620
  const result = /* @__PURE__ */ new Map();
544
621
  for (const group of this.getGroups()) {
545
622
  const groupItems = grouped.get(group.id);
@@ -553,33 +630,33 @@ var CommandRegistry = class {
553
630
  getItemIds() {
554
631
  return this.#itemOrderSet;
555
632
  }
556
- /** Find intersection of registered items with a candidate set (ES2026 Set.intersection) */
633
+ /** Find intersection of registered items with a candidate set */
557
634
  intersectWith(candidateIds) {
558
- return this.#itemOrderSet.intersection(candidateIds);
635
+ return setIntersection(this.#itemOrderSet, candidateIds);
559
636
  }
560
- /** Find items registered but not in a given set (ES2026 Set.difference) */
637
+ /** Find items registered but not in a given set */
561
638
  differenceFrom(otherIds) {
562
- return this.#itemOrderSet.difference(otherIds);
639
+ return setDifference(this.#itemOrderSet, otherIds);
563
640
  }
564
- /** Union of registered IDs with another set (ES2026 Set.union) */
641
+ /** Union of registered IDs with another set */
565
642
  unionWith(otherIds) {
566
- return this.#itemOrderSet.union(otherIds);
643
+ return setUnion(this.#itemOrderSet, otherIds);
567
644
  }
568
- /** Check if registered items are a subset of the given set (ES2026 Set.isSubsetOf) */
645
+ /** Check if registered items are a subset of the given set */
569
646
  isSubsetOf(otherIds) {
570
- return this.#itemOrderSet.isSubsetOf(otherIds);
647
+ return setIsSubsetOf(this.#itemOrderSet, otherIds);
571
648
  }
572
- /** Check if registered items are a superset of the given set (ES2026 Set.isSupersetOf) */
649
+ /** Check if registered items are a superset of the given set */
573
650
  isSupersetOf(otherIds) {
574
- return this.#itemOrderSet.isSupersetOf(otherIds);
651
+ return setIsSupersetOf(this.#itemOrderSet, otherIds);
575
652
  }
576
- /** Check if registered items share no IDs with the given set (ES2026 Set.isDisjointFrom) */
653
+ /** Check if registered items share no IDs with the given set */
577
654
  isDisjointFrom(otherIds) {
578
- return this.#itemOrderSet.isDisjointFrom(otherIds);
655
+ return setIsDisjointFrom(this.#itemOrderSet, otherIds);
579
656
  }
580
- /** Symmetric difference between registered IDs and another set (ES2026 Set.symmetricDifference) */
657
+ /** Symmetric difference between registered IDs and another set */
581
658
  symmetricDifferenceWith(otherIds) {
582
- return this.#itemOrderSet.symmetricDifference(otherIds);
659
+ return setSymmetricDifference(this.#itemOrderSet, otherIds);
583
660
  }
584
661
  /** Total number of registered items */
585
662
  get size() {
@@ -825,7 +902,7 @@ function createSearchEngine(options) {
825
902
  },
826
903
  remove(ids) {
827
904
  for (const id of ids) indexedEntries.delete(id);
828
- previousResults = previousResults.difference(ids);
905
+ previousResults = setDifference(previousResults, ids);
829
906
  },
830
907
  clear() {
831
908
  indexedEntries.clear();
@@ -1009,7 +1086,7 @@ function createCommandMachine(options = {}) {
1009
1086
  }
1010
1087
  filteredIdSet = new Set(filteredIds);
1011
1088
  filteredIdIndex = new Map(filteredIds.entries().map(([i, id]) => [id, i]));
1012
- const groupedIds = Map.groupBy(filteredIds, (id) => {
1089
+ const groupedIds = mapGroupBy(filteredIds, (id) => {
1013
1090
  return registry.getItem(id)?.groupId ?? "__ungrouped";
1014
1091
  });
1015
1092
  const activeId = state.activeId && filteredIdSet.has(state.activeId) ? state.activeId : filteredIds[0] ?? null;
@@ -1020,7 +1097,7 @@ function createCommandMachine(options = {}) {
1020
1097
  groupedIds,
1021
1098
  filteredCount: filteredIds.length,
1022
1099
  activeId,
1023
- lastUpdated: Temporal.Now.instant()
1100
+ lastUpdated: Date.now()
1024
1101
  });
1025
1102
  }
1026
1103
  function navigate(direction) {
@@ -1032,7 +1109,7 @@ function createCommandMachine(options = {}) {
1032
1109
  setState({
1033
1110
  ...state,
1034
1111
  activeId: newActiveId,
1035
- lastUpdated: Temporal.Now.instant()
1112
+ lastUpdated: Date.now()
1036
1113
  });
1037
1114
  options.onActiveChange?.(newActiveId);
1038
1115
  }
@@ -1044,7 +1121,7 @@ function createCommandMachine(options = {}) {
1044
1121
  setState({
1045
1122
  ...state,
1046
1123
  activeId: newActiveId,
1047
- lastUpdated: Temporal.Now.instant()
1124
+ lastUpdated: Date.now()
1048
1125
  });
1049
1126
  options.onActiveChange?.(newActiveId);
1050
1127
  }
@@ -1062,7 +1139,7 @@ function createCommandMachine(options = {}) {
1062
1139
  setState({
1063
1140
  ...state,
1064
1141
  activeId: newActiveId,
1065
- lastUpdated: Temporal.Now.instant()
1142
+ lastUpdated: Date.now()
1066
1143
  });
1067
1144
  options.onActiveChange?.(newActiveId);
1068
1145
  }
@@ -1088,7 +1165,7 @@ function createCommandMachine(options = {}) {
1088
1165
  setState({
1089
1166
  ...state,
1090
1167
  activeId: event.id,
1091
- lastUpdated: Temporal.Now.instant()
1168
+ lastUpdated: Date.now()
1092
1169
  });
1093
1170
  options.onActiveChange?.(event.id);
1094
1171
  }
@@ -1101,7 +1178,7 @@ function createCommandMachine(options = {}) {
1101
1178
  ...state,
1102
1179
  page: event.page,
1103
1180
  pageStack: [...state.pageStack, state.page],
1104
- lastUpdated: Temporal.Now.instant()
1181
+ lastUpdated: Date.now()
1105
1182
  });
1106
1183
  break;
1107
1184
  case "PAGE_POP": {
@@ -1110,7 +1187,7 @@ function createCommandMachine(options = {}) {
1110
1187
  ...state,
1111
1188
  page: prevPage,
1112
1189
  pageStack: state.pageStack.toSpliced(-1, 1),
1113
- lastUpdated: Temporal.Now.instant()
1190
+ lastUpdated: Date.now()
1114
1191
  });
1115
1192
  break;
1116
1193
  }
@@ -1118,7 +1195,7 @@ function createCommandMachine(options = {}) {
1118
1195
  setState({
1119
1196
  ...state,
1120
1197
  open: true,
1121
- lastUpdated: Temporal.Now.instant()
1198
+ lastUpdated: Date.now()
1122
1199
  });
1123
1200
  emitter.emit("openChange", true);
1124
1201
  options.onOpenChange?.(true);
@@ -1127,7 +1204,7 @@ function createCommandMachine(options = {}) {
1127
1204
  setState({
1128
1205
  ...state,
1129
1206
  open: false,
1130
- lastUpdated: Temporal.Now.instant()
1207
+ lastUpdated: Date.now()
1131
1208
  });
1132
1209
  emitter.emit("openChange", false);
1133
1210
  options.onOpenChange?.(false);
@@ -1137,7 +1214,7 @@ function createCommandMachine(options = {}) {
1137
1214
  setState({
1138
1215
  ...state,
1139
1216
  open: newOpen,
1140
- lastUpdated: Temporal.Now.instant()
1217
+ lastUpdated: Date.now()
1141
1218
  });
1142
1219
  emitter.emit("openChange", newOpen);
1143
1220
  options.onOpenChange?.(newOpen);
@@ -1213,26 +1290,30 @@ function createCommandMachine(options = {}) {
1213
1290
  //#endregion
1214
1291
  //#region src/core/search/fuzzy-scorer.ts
1215
1292
  /**
1216
- * Async-safe fuzzy scorer — wraps scoring in Promise.try (ES2026) for error resilience.
1217
- * Falls back gracefully on error via Promise.try's synchronous-throw capture.
1293
+ * Async-safe fuzzy scorer — wraps scoring in Promise for error resilience.
1294
+ * Falls back gracefully on error via synchronous-throw capture.
1218
1295
  */
1219
1296
  function scoreItemAsync(query, item) {
1220
- return Promise.try(() => scoreItem(query, item));
1297
+ return new Promise((resolve) => {
1298
+ resolve(scoreItem(query, item));
1299
+ });
1221
1300
  }
1222
1301
  /**
1223
- * Batch score items synchronously inside a single Promise.try (ES2026).
1302
+ * Batch score items synchronously inside a single Promise.
1224
1303
  * Eliminates per-item microtask overhead from async generators — runs the
1225
1304
  * entire scoring pipeline in one synchronous pass, wrapped for error safety.
1226
1305
  */
1227
1306
  function batchScoreItems(query, items) {
1228
- return Promise.try(() => items.values().map((item) => scoreItem(query, item)).filter((r) => r != null).toArray().toSorted((a, b) => b.score - a.score));
1307
+ return new Promise((resolve) => {
1308
+ resolve(items.values().map((item) => scoreItem(query, item)).filter((r) => r != null).toArray().toSorted((a, b) => b.score - a.score));
1309
+ });
1229
1310
  }
1230
1311
  //#endregion
1231
1312
  //#region src/core/telemetry.ts
1232
1313
  /**
1233
1314
  * Creates a telemetry middleware that measures command palette interactions.
1234
- * Uses Temporal.Now.instant() (ES2026) for nanosecond-precision timestamps
1235
- * and Temporal.Instant.since() for accurate duration computation.
1315
+ * Uses Date.now() for millisecond-precision timestamps
1316
+ * and subtraction for duration computation.
1236
1317
  *
1237
1318
  * @example
1238
1319
  * ```ts
@@ -1246,19 +1327,19 @@ function createTelemetryMiddleware(hooks) {
1246
1327
  let openTimestamp = null;
1247
1328
  return {
1248
1329
  onOpen() {
1249
- openTimestamp = Temporal.Now.instant();
1330
+ openTimestamp = Date.now();
1250
1331
  hooks.onPaletteOpen?.();
1251
1332
  },
1252
1333
  onClose() {
1253
1334
  if (openTimestamp != null) {
1254
- const elapsed = Temporal.Now.instant().since(openTimestamp);
1255
- hooks.onPaletteClose?.(elapsed.total("milliseconds"));
1335
+ const durationMs = Date.now() - openTimestamp;
1336
+ hooks.onPaletteClose?.(durationMs);
1256
1337
  openTimestamp = null;
1257
1338
  }
1258
1339
  },
1259
1340
  onSearch(query, resultCount, startTime) {
1260
- const elapsed = Temporal.Now.instant().since(startTime);
1261
- hooks.onSearchComplete?.(query, resultCount, elapsed.total("milliseconds"));
1341
+ const durationMs = Date.now() - startTime;
1342
+ hooks.onSearchComplete?.(query, resultCount, durationMs);
1262
1343
  },
1263
1344
  onSelect(itemId, query, position) {
1264
1345
  hooks.onItemSelected?.(itemId, query, position);
@@ -123,8 +123,8 @@ interface CommandState {
123
123
  readonly pageStack: readonly string[];
124
124
  /** Whether the command palette dialog is open */
125
125
  readonly open: boolean;
126
- /** Temporal.Instant of the last state mutation (ES2026) */
127
- readonly lastUpdated: Temporal.Instant;
126
+ /** Epoch-millisecond timestamp of the last state mutation */
127
+ readonly lastUpdated: number;
128
128
  }
129
129
  /** All possible events the state machine accepts */
130
130
  type CommandEvent = {
@@ -169,7 +169,8 @@ type CommandEvent = {
169
169
  /** Frecency record for a single item */
170
170
  interface FrecencyRecord {
171
171
  readonly frequency: number;
172
- readonly lastUsed: Temporal.Instant;
172
+ /** Epoch-millisecond timestamp of last usage */
173
+ readonly lastUsed: number;
173
174
  }
174
175
  /** Complete frecency data for persistence */
175
176
  interface FrecencyData {
@@ -274,7 +275,7 @@ declare class KeyboardShortcutRegistry implements Disposable {
274
275
  //#region src/core/registry.d.ts
275
276
  /**
276
277
  * Manages registration and lookup of command items and groups.
277
- * Uses ES2026 Set methods (union/intersection/difference) for efficient ID operations.
278
+ * Uses set operation helpers (union/intersection/difference) for efficient ID operations.
278
279
  * All registration methods return Disposable for automatic cleanup via `using`.
279
280
  */
280
281
  declare class CommandRegistry implements Disposable {
@@ -285,7 +286,7 @@ declare class CommandRegistry implements Disposable {
285
286
  registerItems(items: readonly CommandItem$1[]): Disposable;
286
287
  /** Unregister a single item — O(1) Map/Set delete, indexOf+toSpliced for order */
287
288
  unregisterItem(id: ItemId): void;
288
- /** Bulk unregister using Set.difference (ES2026) — for...of for hot path */
289
+ /** Bulk unregister — for...of for hot path */
289
290
  unregisterItems(ids: ReadonlySet<ItemId>): void;
290
291
  /** Register a group */
291
292
  registerGroup(group: CommandGroup$1): Disposable;
@@ -299,23 +300,23 @@ declare class CommandRegistry implements Disposable {
299
300
  getItems(): readonly CommandItem$1[];
300
301
  /** Get all registered groups sorted by priority — cached between group mutations */
301
302
  getGroups(): readonly CommandGroup$1[];
302
- /** Get items grouped by groupId using Map.groupBy (ES2026) — direct Map, no Object.entries conversion */
303
+ /** Get items grouped by groupId — direct Map, no Object.entries conversion */
303
304
  getGroupedItems(): ReadonlyMap<GroupId, readonly CommandItem$1[]>;
304
305
  /** Get item IDs as a Set — returns internal set (O(1), no allocation) */
305
306
  getItemIds(): ReadonlySet<ItemId>;
306
- /** Find intersection of registered items with a candidate set (ES2026 Set.intersection) */
307
+ /** Find intersection of registered items with a candidate set */
307
308
  intersectWith(candidateIds: ReadonlySet<ItemId>): ReadonlySet<ItemId>;
308
- /** Find items registered but not in a given set (ES2026 Set.difference) */
309
+ /** Find items registered but not in a given set */
309
310
  differenceFrom(otherIds: ReadonlySet<ItemId>): ReadonlySet<ItemId>;
310
- /** Union of registered IDs with another set (ES2026 Set.union) */
311
+ /** Union of registered IDs with another set */
311
312
  unionWith(otherIds: ReadonlySet<ItemId>): ReadonlySet<ItemId>;
312
- /** Check if registered items are a subset of the given set (ES2026 Set.isSubsetOf) */
313
+ /** Check if registered items are a subset of the given set */
313
314
  isSubsetOf(otherIds: ReadonlySet<ItemId>): boolean;
314
- /** Check if registered items are a superset of the given set (ES2026 Set.isSupersetOf) */
315
+ /** Check if registered items are a superset of the given set */
315
316
  isSupersetOf(otherIds: ReadonlySet<ItemId>): boolean;
316
- /** Check if registered items share no IDs with the given set (ES2026 Set.isDisjointFrom) */
317
+ /** Check if registered items share no IDs with the given set */
317
318
  isDisjointFrom(otherIds: ReadonlySet<ItemId>): boolean;
318
- /** Symmetric difference between registered IDs and another set (ES2026 Set.symmetricDifference) */
319
+ /** Symmetric difference between registered IDs and another set */
319
320
  symmetricDifferenceWith(otherIds: ReadonlySet<ItemId>): ReadonlySet<ItemId>;
320
321
  /** Total number of registered items */
321
322
  get size(): number;
@@ -593,6 +594,10 @@ interface CommandContextValue {
593
594
  readonly setOptimisticActiveId: (id: ItemId | null) => void;
594
595
  /** O(1) membership check for filtered items — avoids O(n) Array.includes per item */
595
596
  readonly filteredIdSet: ReadonlySet<ItemId>;
597
+ /** IDs currently visible in virtual viewport + overscan. null = not virtualizing (render all filtered). */
598
+ readonly visibleIdSet: ReadonlySet<ItemId> | null;
599
+ /** Map of visible item IDs to their translateY pixel offsets. null = not virtualizing. */
600
+ readonly virtualPositionMap: ReadonlyMap<ItemId, number> | null;
596
601
  readonly rootId: CommandRootId;
597
602
  readonly listId: string;
598
603
  readonly inputId: string;
@@ -613,6 +618,10 @@ interface CommandStateContextValue {
613
618
  readonly state: CommandState;
614
619
  readonly isPending: boolean;
615
620
  readonly filteredIdSet: ReadonlySet<ItemId>;
621
+ /** IDs currently visible in virtual viewport + overscan. null = not virtualizing (render all filtered). */
622
+ readonly visibleIdSet: ReadonlySet<ItemId> | null;
623
+ /** Map of visible item IDs to their translateY pixel offsets. null = not virtualizing. */
624
+ readonly virtualPositionMap: ReadonlyMap<ItemId, number> | null;
616
625
  }
617
626
  declare const CommandStableContext: Context<CommandStableContextValue | null>;
618
627
  declare const CommandStateContext: Context<CommandStateContextValue | null>;