cursedbelt-core 1.0.0

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.
Files changed (307) hide show
  1. package/LICENSE +15 -0
  2. package/dist/client/wsWire.d.ts +49 -0
  3. package/dist/client/wsWire.js +57 -0
  4. package/dist/config/appConfig.d.ts +361 -0
  5. package/dist/config/appConfig.js +308 -0
  6. package/dist/core/activity/index.d.ts +8 -0
  7. package/dist/core/activity/index.js +7 -0
  8. package/dist/core/activity/model.d.ts +115 -0
  9. package/dist/core/activity/model.js +63 -0
  10. package/dist/core/analytics/index.d.ts +18 -0
  11. package/dist/core/analytics/index.js +18 -0
  12. package/dist/core/analytics/range.d.ts +186 -0
  13. package/dist/core/analytics/range.js +271 -0
  14. package/dist/core/analytics/schedule.d.ts +301 -0
  15. package/dist/core/analytics/schedule.js +372 -0
  16. package/dist/core/category/index.d.ts +3 -0
  17. package/dist/core/category/index.js +6 -0
  18. package/dist/core/category/memoryStore.d.ts +21 -0
  19. package/dist/core/category/memoryStore.js +97 -0
  20. package/dist/core/category/tree.d.ts +26 -0
  21. package/dist/core/category/tree.js +113 -0
  22. package/dist/core/category/types.d.ts +48 -0
  23. package/dist/core/category/types.js +8 -0
  24. package/dist/core/color.d.ts +14 -0
  25. package/dist/core/color.js +32 -0
  26. package/dist/core/colorScheme/colorSchemeStore.d.ts +70 -0
  27. package/dist/core/colorScheme/colorSchemeStore.js +123 -0
  28. package/dist/core/colorScheme/index.d.ts +7 -0
  29. package/dist/core/colorScheme/index.js +7 -0
  30. package/dist/core/domainOwners.d.ts +19 -0
  31. package/dist/core/domainOwners.js +15 -0
  32. package/dist/core/download.d.ts +14 -0
  33. package/dist/core/download.js +37 -0
  34. package/dist/core/events/ccEventBus.d.ts +44 -0
  35. package/dist/core/events/ccEventBus.js +70 -0
  36. package/dist/core/file-tree/fileTreeModel.d.ts +276 -0
  37. package/dist/core/file-tree/fileTreeModel.js +312 -0
  38. package/dist/core/folder-tree/actions.d.ts +84 -0
  39. package/dist/core/folder-tree/actions.js +119 -0
  40. package/dist/core/folder-tree/index.d.ts +3 -0
  41. package/dist/core/folder-tree/index.js +20 -0
  42. package/dist/core/folder-tree/path.d.ts +76 -0
  43. package/dist/core/folder-tree/path.js +146 -0
  44. package/dist/core/folder-tree/tree.d.ts +86 -0
  45. package/dist/core/folder-tree/tree.js +184 -0
  46. package/dist/core/layout/index.d.ts +1 -0
  47. package/dist/core/layout/index.js +7 -0
  48. package/dist/core/master-lock/index.d.ts +14 -0
  49. package/dist/core/master-lock/index.js +14 -0
  50. package/dist/core/master-lock/kdf.d.ts +63 -0
  51. package/dist/core/master-lock/kdf.js +117 -0
  52. package/dist/core/master-lock/policy.d.ts +32 -0
  53. package/dist/core/master-lock/policy.js +52 -0
  54. package/dist/core/master-lock/presence.d.ts +52 -0
  55. package/dist/core/master-lock/presence.js +61 -0
  56. package/dist/core/master-lock/wire.d.ts +139 -0
  57. package/dist/core/master-lock/wire.js +81 -0
  58. package/dist/core/media/bgRemoval.d.ts +27 -0
  59. package/dist/core/media/bgRemoval.js +101 -0
  60. package/dist/core/media/ffmpeg.d.ts +57 -0
  61. package/dist/core/media/ffmpeg.js +126 -0
  62. package/dist/core/media/format.d.ts +20 -0
  63. package/dist/core/media/format.js +54 -0
  64. package/dist/core/media/index.d.ts +12 -0
  65. package/dist/core/media/index.js +18 -0
  66. package/dist/core/media/mediaItem.d.ts +15 -0
  67. package/dist/core/media/mediaItem.js +34 -0
  68. package/dist/core/media/renderPlan.d.ts +63 -0
  69. package/dist/core/media/renderPlan.js +232 -0
  70. package/dist/core/media/segment.d.ts +79 -0
  71. package/dist/core/media/segment.js +247 -0
  72. package/dist/core/media/size.d.ts +1 -0
  73. package/dist/core/media/size.js +3 -0
  74. package/dist/core/media/trim.d.ts +21 -0
  75. package/dist/core/media/trim.js +40 -0
  76. package/dist/core/media/types.d.ts +148 -0
  77. package/dist/core/media/types.js +11 -0
  78. package/dist/core/media/uploadLimits.d.ts +7 -0
  79. package/dist/core/media/uploadLimits.js +16 -0
  80. package/dist/core/media/validate.d.ts +9 -0
  81. package/dist/core/media/validate.js +121 -0
  82. package/dist/core/media/videoCuts.d.ts +152 -0
  83. package/dist/core/media/videoCuts.js +284 -0
  84. package/dist/core/navigation/breadcrumbCompat.d.ts +24 -0
  85. package/dist/core/navigation/breadcrumbCompat.js +21 -0
  86. package/dist/core/navigation/coverageTest.d.ts +24 -0
  87. package/dist/core/navigation/coverageTest.js +48 -0
  88. package/dist/core/navigation/index.d.ts +6 -0
  89. package/dist/core/navigation/index.js +10 -0
  90. package/dist/core/navigation/migrateNavPreferences.d.ts +55 -0
  91. package/dist/core/navigation/migrateNavPreferences.js +63 -0
  92. package/dist/core/navigation/navigationModel.d.ts +165 -0
  93. package/dist/core/navigation/navigationModel.js +14 -0
  94. package/dist/core/navigation/navigationSelectors.d.ts +63 -0
  95. package/dist/core/navigation/navigationSelectors.js +167 -0
  96. package/dist/core/navigation/searchCatalogue.d.ts +23 -0
  97. package/dist/core/navigation/searchCatalogue.js +76 -0
  98. package/dist/core/net/reconnectSchedule.d.ts +46 -0
  99. package/dist/core/net/reconnectSchedule.js +38 -0
  100. package/dist/core/palette/ShortcutRegistry.d.ts +33 -0
  101. package/dist/core/palette/ShortcutRegistry.js +52 -0
  102. package/dist/core/palette/fuzzyRank.d.ts +18 -0
  103. package/dist/core/palette/fuzzyRank.js +73 -0
  104. package/dist/core/palette/hotkeys.d.ts +28 -0
  105. package/dist/core/palette/hotkeys.js +118 -0
  106. package/dist/core/palette/index.d.ts +3 -0
  107. package/dist/core/palette/index.js +8 -0
  108. package/dist/core/schema/projection.d.ts +84 -0
  109. package/dist/core/schema/projection.js +184 -0
  110. package/dist/core/session/claims.d.ts +145 -0
  111. package/dist/core/session/claims.js +157 -0
  112. package/dist/core/sharing/index.d.ts +5 -0
  113. package/dist/core/sharing/index.js +4 -0
  114. package/dist/core/sharing/model.d.ts +195 -0
  115. package/dist/core/sharing/model.js +150 -0
  116. package/dist/core/site/siteCopy.d.ts +92 -0
  117. package/dist/core/site/siteCopy.js +91 -0
  118. package/dist/core/slots.d.ts +22 -0
  119. package/dist/core/slots.js +31 -0
  120. package/dist/core/styles/constructableSheet.d.ts +51 -0
  121. package/dist/core/styles/constructableSheet.js +80 -0
  122. package/dist/core/tokens/registry.d.ts +119 -0
  123. package/dist/core/tokens/registry.js +447 -0
  124. package/dist/core/uiScale/index.d.ts +8 -0
  125. package/dist/core/uiScale/index.js +8 -0
  126. package/dist/core/uiScale/scale.d.ts +36 -0
  127. package/dist/core/uiScale/scale.js +47 -0
  128. package/dist/core/uiScale/uiScaleStore.d.ts +47 -0
  129. package/dist/core/uiScale/uiScaleStore.js +63 -0
  130. package/dist/core/wire/__fixtures__/rowsMarshalWorker.d.ts +1 -0
  131. package/dist/core/wire/__fixtures__/rowsMarshalWorker.js +23 -0
  132. package/dist/core/wire/index.d.ts +17 -0
  133. package/dist/core/wire/index.js +17 -0
  134. package/dist/core/wire/ingress.d.ts +44 -0
  135. package/dist/core/wire/ingress.js +134 -0
  136. package/dist/core/wire/marshalWorker.d.ts +70 -0
  137. package/dist/core/wire/marshalWorker.js +273 -0
  138. package/dist/core/wire/marshalling.d.ts +122 -0
  139. package/dist/core/wire/marshalling.js +126 -0
  140. package/dist/core/wire/wireHub.d.ts +80 -0
  141. package/dist/core/wire/wireHub.js +116 -0
  142. package/dist/ctgr/base64url.d.ts +8 -0
  143. package/dist/ctgr/base64url.js +39 -0
  144. package/dist/ctgr/chunkAccumulator.d.ts +214 -0
  145. package/dist/ctgr/chunkAccumulator.js +307 -0
  146. package/dist/ctgr/chunkStore.d.ts +24 -0
  147. package/dist/ctgr/chunkStore.js +125 -0
  148. package/dist/ctgr/codec.d.ts +44 -0
  149. package/dist/ctgr/codec.js +153 -0
  150. package/dist/ctgr/compress.d.ts +2 -0
  151. package/dist/ctgr/compress.js +20 -0
  152. package/dist/ctgr/crc32.d.ts +2 -0
  153. package/dist/ctgr/crc32.js +23 -0
  154. package/dist/ctgr/digest.d.ts +1 -0
  155. package/dist/ctgr/digest.js +15 -0
  156. package/dist/ctgr/index.d.ts +9 -0
  157. package/dist/ctgr/index.js +17 -0
  158. package/dist/ctgr/types.d.ts +64 -0
  159. package/dist/ctgr/types.js +21 -0
  160. package/dist/ctgr/v0compat.d.ts +27 -0
  161. package/dist/ctgr/v0compat.js +128 -0
  162. package/dist/index.d.ts +15 -0
  163. package/dist/index.js +17 -0
  164. package/dist/shared/errors.d.ts +101 -0
  165. package/dist/shared/errors.js +99 -0
  166. package/dist/testing/bytes.d.ts +32 -0
  167. package/dist/testing/bytes.js +33 -0
  168. package/dist/testing/domInspect.d.ts +59 -0
  169. package/dist/testing/domInspect.js +115 -0
  170. package/dist/testing/fakeFetch.d.ts +23 -0
  171. package/dist/testing/fakeFetch.js +21 -0
  172. package/dist/testing/index.d.ts +3 -0
  173. package/dist/testing/index.js +11 -0
  174. package/package.json +373 -0
  175. package/src/client/wsWire.ts +83 -0
  176. package/src/config/appConfig.spec.ts +158 -0
  177. package/src/config/appConfig.ts +372 -0
  178. package/src/core/activity/index.ts +21 -0
  179. package/src/core/activity/model.ts +160 -0
  180. package/src/core/analytics/index.ts +18 -0
  181. package/src/core/analytics/range.spec.ts +280 -0
  182. package/src/core/analytics/range.ts +383 -0
  183. package/src/core/analytics/schedule.spec.ts +311 -0
  184. package/src/core/analytics/schedule.ts +624 -0
  185. package/src/core/category/category.spec.ts +144 -0
  186. package/src/core/category/index.ts +19 -0
  187. package/src/core/category/memoryStore.ts +104 -0
  188. package/src/core/category/tree.ts +118 -0
  189. package/src/core/category/types.ts +59 -0
  190. package/src/core/color.spec.ts +35 -0
  191. package/src/core/color.ts +35 -0
  192. package/src/core/colorScheme/colorScheme.spec.ts +127 -0
  193. package/src/core/colorScheme/colorSchemeStore.ts +184 -0
  194. package/src/core/colorScheme/index.ts +23 -0
  195. package/src/core/domainOwners.ts +32 -0
  196. package/src/core/download.spec.ts +38 -0
  197. package/src/core/download.ts +35 -0
  198. package/src/core/events/ccEventBus.spec.ts +90 -0
  199. package/src/core/events/ccEventBus.ts +120 -0
  200. package/src/core/file-tree/fileTreeModel.spec.ts +462 -0
  201. package/src/core/file-tree/fileTreeModel.ts +511 -0
  202. package/src/core/folder-tree/actions.spec.ts +103 -0
  203. package/src/core/folder-tree/actions.ts +153 -0
  204. package/src/core/folder-tree/index.ts +46 -0
  205. package/src/core/folder-tree/path.spec.ts +113 -0
  206. package/src/core/folder-tree/path.ts +148 -0
  207. package/src/core/folder-tree/tree.spec.ts +157 -0
  208. package/src/core/folder-tree/tree.ts +244 -0
  209. package/src/core/layout/index.ts +7 -0
  210. package/src/core/master-lock/index.ts +50 -0
  211. package/src/core/master-lock/kdf.spec.ts +95 -0
  212. package/src/core/master-lock/kdf.ts +145 -0
  213. package/src/core/master-lock/policy.ts +55 -0
  214. package/src/core/master-lock/presence.spec.ts +73 -0
  215. package/src/core/master-lock/presence.ts +71 -0
  216. package/src/core/master-lock/wire.ts +155 -0
  217. package/src/core/media/bgRemoval.spec.ts +120 -0
  218. package/src/core/media/bgRemoval.ts +123 -0
  219. package/src/core/media/ffmpeg.spec.ts +135 -0
  220. package/src/core/media/ffmpeg.ts +162 -0
  221. package/src/core/media/format.spec.ts +41 -0
  222. package/src/core/media/format.ts +58 -0
  223. package/src/core/media/index.ts +105 -0
  224. package/src/core/media/mediaItem.spec.ts +46 -0
  225. package/src/core/media/mediaItem.ts +40 -0
  226. package/src/core/media/renderPlan.spec.ts +140 -0
  227. package/src/core/media/renderPlan.ts +318 -0
  228. package/src/core/media/segment.spec.ts +159 -0
  229. package/src/core/media/segment.ts +352 -0
  230. package/src/core/media/size.spec.ts +19 -0
  231. package/src/core/media/size.ts +3 -0
  232. package/src/core/media/trim.spec.ts +75 -0
  233. package/src/core/media/trim.ts +60 -0
  234. package/src/core/media/types.ts +147 -0
  235. package/src/core/media/uploadLimits.ts +18 -0
  236. package/src/core/media/validate.spec.ts +106 -0
  237. package/src/core/media/validate.ts +131 -0
  238. package/src/core/media/videoCuts.spec.ts +327 -0
  239. package/src/core/media/videoCuts.ts +355 -0
  240. package/src/core/navigation/breadcrumbCompat.ts +44 -0
  241. package/src/core/navigation/coverageTest.ts +54 -0
  242. package/src/core/navigation/index.ts +50 -0
  243. package/src/core/navigation/migrateNavPreferences.ts +119 -0
  244. package/src/core/navigation/navigation.spec.ts +388 -0
  245. package/src/core/navigation/navigationModel.ts +185 -0
  246. package/src/core/navigation/navigationSelectors.ts +206 -0
  247. package/src/core/navigation/searchCatalogue.ts +89 -0
  248. package/src/core/net/reconnectSchedule.spec.ts +49 -0
  249. package/src/core/net/reconnectSchedule.ts +78 -0
  250. package/src/core/palette/ShortcutRegistry.ts +69 -0
  251. package/src/core/palette/fuzzyRank.ts +78 -0
  252. package/src/core/palette/hotkeys.ts +132 -0
  253. package/src/core/palette/index.ts +22 -0
  254. package/src/core/palette/palette.spec.ts +160 -0
  255. package/src/core/schema/projection.spec.ts +122 -0
  256. package/src/core/schema/projection.ts +225 -0
  257. package/src/core/session/claims.ts +181 -0
  258. package/src/core/sharing/index.ts +33 -0
  259. package/src/core/sharing/model.spec.ts +104 -0
  260. package/src/core/sharing/model.ts +296 -0
  261. package/src/core/site/siteCopy.ts +115 -0
  262. package/src/core/slots.ts +38 -0
  263. package/src/core/styles/constructableSheet.ts +86 -0
  264. package/src/core/tokens/registry.spec.ts +160 -0
  265. package/src/core/tokens/registry.ts +536 -0
  266. package/src/core/uiScale/index.ts +32 -0
  267. package/src/core/uiScale/scale.ts +63 -0
  268. package/src/core/uiScale/uiScale.spec.ts +77 -0
  269. package/src/core/uiScale/uiScaleStore.ts +100 -0
  270. package/src/core/wire/__fixtures__/rowsMarshalWorker.ts +24 -0
  271. package/src/core/wire/index.ts +56 -0
  272. package/src/core/wire/ingress.spec.ts +276 -0
  273. package/src/core/wire/ingress.ts +182 -0
  274. package/src/core/wire/marshalWorker.spec.ts +196 -0
  275. package/src/core/wire/marshalWorker.ts +361 -0
  276. package/src/core/wire/marshalling.spec.ts +139 -0
  277. package/src/core/wire/marshalling.ts +238 -0
  278. package/src/core/wire/wireHub.spec.ts +137 -0
  279. package/src/core/wire/wireHub.ts +213 -0
  280. package/src/ctgr/base64url.ts +44 -0
  281. package/src/ctgr/chunkAccumulator.ts +403 -0
  282. package/src/ctgr/chunkStore.spec.ts +101 -0
  283. package/src/ctgr/chunkStore.ts +168 -0
  284. package/src/ctgr/codec.spec.ts +208 -0
  285. package/src/ctgr/codec.ts +202 -0
  286. package/src/ctgr/compress.ts +21 -0
  287. package/src/ctgr/crc32.ts +23 -0
  288. package/src/ctgr/digest.ts +14 -0
  289. package/src/ctgr/index.ts +39 -0
  290. package/src/ctgr/types.ts +80 -0
  291. package/src/ctgr/v0compat.spec.ts +73 -0
  292. package/src/ctgr/v0compat.ts +156 -0
  293. package/src/declaredDepsAreImported.spec.ts +87 -0
  294. package/src/index.ts +25 -0
  295. package/src/leafSubpathsImportNothing.spec.ts +287 -0
  296. package/src/lockfileIsTracked.spec.ts +50 -0
  297. package/src/namedSubpathsResolve.spec.ts +122 -0
  298. package/src/noPathDeps.spec.ts +24 -0
  299. package/src/publishShape.spec.ts +89 -0
  300. package/src/shared/errors.ts +153 -0
  301. package/src/skippedTestsAreAnswered.spec.ts +221 -0
  302. package/src/testTmpRoot.spec.ts +102 -0
  303. package/src/testing/bytes.ts +38 -0
  304. package/src/testing/domInspect.spec.ts +79 -0
  305. package/src/testing/domInspect.ts +127 -0
  306. package/src/testing/fakeFetch.ts +26 -0
  307. package/src/testing/index.ts +11 -0
@@ -0,0 +1,33 @@
1
+ /** A registered keyboard shortcut — its binding plus how it reads in the palette. */
2
+ export interface ShortcutEntry {
3
+ /** Short name of the thing it acts on (e.g. `'Board'`). */
4
+ label: string;
5
+ /** Binding in tinykeys format (`'$mod+k'`, `'alt+b'`) — also the registry key. */
6
+ keys: string;
7
+ /** What the shortcut does, shown as the palette row's description (e.g. `'Open the board'`). */
8
+ action: string;
9
+ }
10
+ type Listener = () => void;
11
+ /**
12
+ * Keyed by `keys`, so registering the same binding twice replaces it (last write wins
13
+ * — no duplicate rows) and a feature can re-register on every mount idempotently.
14
+ */
15
+ export declare class ShortcutRegistry {
16
+ private entries;
17
+ private listeners;
18
+ private snapshot;
19
+ /** Add or replace the shortcut for `entry.keys`. */
20
+ register(entry: ShortcutEntry): void;
21
+ /** Remove the shortcut bound to `keys` (no-op if absent). */
22
+ unregister(keys: string): void;
23
+ /** Every registered shortcut, in insertion order. Stable until the next mutation. */
24
+ getAll(): ShortcutEntry[];
25
+ /** Subscribe to registry changes (returns an unsubscribe). Powers the live palette list. */
26
+ subscribe(listener: Listener): () => void;
27
+ /** Drop every shortcut (mainly for tests / teardown). */
28
+ clear(): void;
29
+ private commit;
30
+ }
31
+ /** The app-wide shortcut registry singleton. */
32
+ export declare const shortcutRegistry: ShortcutRegistry;
33
+ export {};
@@ -0,0 +1,52 @@
1
+ // A tiny global registry of the app's keyboard shortcuts — the single source of truth
2
+ // that BOTH the keybinding layer (`useHotkeys`) and the command palette read from. An
3
+ // app registers a shortcut once; the palette can then list "Board → ⌥B" in its
4
+ // cold-state reference without the binding and its documentation drifting apart.
5
+ //
6
+ // Register in a mount effect (never at module load) so the palette's list reflects the
7
+ // shortcuts actually wired up for the current screen; the registry notifies subscribers
8
+ // on every change so a mounted palette stays live as shortcuts come and go.
9
+ /**
10
+ * Keyed by `keys`, so registering the same binding twice replaces it (last write wins
11
+ * — no duplicate rows) and a feature can re-register on every mount idempotently.
12
+ */
13
+ export class ShortcutRegistry {
14
+ entries = new Map();
15
+ listeners = new Set();
16
+ // A frozen snapshot, rebuilt only on mutation, so `getAll()` is referentially stable
17
+ // between changes — required for `useSyncExternalStore` to not loop.
18
+ snapshot = [];
19
+ /** Add or replace the shortcut for `entry.keys`. */
20
+ register(entry) {
21
+ this.entries.set(entry.keys, entry);
22
+ this.commit();
23
+ }
24
+ /** Remove the shortcut bound to `keys` (no-op if absent). */
25
+ unregister(keys) {
26
+ if (this.entries.delete(keys))
27
+ this.commit();
28
+ }
29
+ /** Every registered shortcut, in insertion order. Stable until the next mutation. */
30
+ getAll() {
31
+ return this.snapshot;
32
+ }
33
+ /** Subscribe to registry changes (returns an unsubscribe). Powers the live palette list. */
34
+ subscribe(listener) {
35
+ this.listeners.add(listener);
36
+ return () => this.listeners.delete(listener);
37
+ }
38
+ /** Drop every shortcut (mainly for tests / teardown). */
39
+ clear() {
40
+ if (this.entries.size === 0)
41
+ return;
42
+ this.entries.clear();
43
+ this.commit();
44
+ }
45
+ commit() {
46
+ this.snapshot = [...this.entries.values()];
47
+ for (const listener of this.listeners)
48
+ listener();
49
+ }
50
+ }
51
+ /** The app-wide shortcut registry singleton. */
52
+ export const shortcutRegistry = new ShortcutRegistry();
@@ -0,0 +1,18 @@
1
+ import type { NavItem } from '../navigation';
2
+ /**
3
+ * The initials of a multi-word label (`'Query Builder'` → `'qb'`). Single-word labels
4
+ * return `''` (their prefix already matches via the label itself). Powers initialism
5
+ * matching ("sh" → "System Health") under the strict contiguous matcher.
6
+ */
7
+ export declare function searchInitials(label: string): string;
8
+ /**
9
+ * Rank `items` against `query`, best match first; non-matches dropped. Generic over the
10
+ * item type (so callers can pass plain {@link NavItem}s *or* richer catalogue entries
11
+ * that carry domain/group tags for grouping — the extra fields ride straight through).
12
+ * An empty/blank query returns `[]` (the palette shows its cold state instead).
13
+ *
14
+ * Returns *all* matches in rank order; callers cap the count for display (the palette
15
+ * shows 30). uFuzzy's contiguous scorer means "frac" ranks "Fractal" above "Format
16
+ * Lab", and the initials augmentation means "qb" surfaces "Query Builder".
17
+ */
18
+ export declare function fuzzyRankItems<T extends NavItem>(query: string, items: T[]): T[];
@@ -0,0 +1,73 @@
1
+ // The command-palette ranking layer — uFuzzy over the nav catalogue. This is the
2
+ // *upgrade* HeaderSearch's `rankCatalogue` (a substring scorer) cannot match: uFuzzy
3
+ // scores by contiguous-match proximity, so abbreviated/typeahead input ranks the
4
+ // right page first. Pure + synchronous + React-free (uFuzzy is regex-based, no DOM),
5
+ // so it runs in the client, a server test, or the kitchen-sink demo identically.
6
+ //
7
+ // Why `intraMode: 1` (SingleError, contiguous) over `0` (MultiInsert, scattered):
8
+ // scattered mode treats every needle char as "appears somewhere later", which is far
9
+ // too permissive for short nav labels (every query matches half the menu). Contiguous
10
+ // mode keeps results tight — the documented tradeoff is lower typo tolerance, which is
11
+ // the right call for a fixed, known set of navigation labels.
12
+ //
13
+ // The catch: contiguous mode means a bare initialism ("qb") would NOT reach "Query
14
+ // Builder" (the chars aren't adjacent). So each haystack string is augmented with the
15
+ // label's **initials** as an explicit token ("Query Builder" → "query builder qb") —
16
+ // now "qb" matches that token contiguously, giving free initialism support without
17
+ // loosening the matcher. Keywords (synonyms / old names / abbreviations) are appended
18
+ // too, so "mandelbrot" still finds "Fractal".
19
+ import uFuzzy from '@leeoniya/ufuzzy';
20
+ // One singleton instance — uFuzzy compiles regexes from its options on construction,
21
+ // so re-creating it per query would be wasteful. `intraMode: 1` = contiguous match.
22
+ const uf = new uFuzzy({ intraMode: 1 });
23
+ /** Split a label into words on whitespace and the separators nav labels actually use. */
24
+ const WORD_SPLIT = /[\s/&·,_-]+/;
25
+ /**
26
+ * The initials of a multi-word label (`'Query Builder'` → `'qb'`). Single-word labels
27
+ * return `''` (their prefix already matches via the label itself). Powers initialism
28
+ * matching ("sh" → "System Health") under the strict contiguous matcher.
29
+ */
30
+ export function searchInitials(label) {
31
+ const words = label.split(WORD_SPLIT).filter(Boolean);
32
+ if (words.length < 2)
33
+ return '';
34
+ // Lower-cased for a predictable token; uFuzzy matches case-insensitively anyway.
35
+ return words.map((w) => (w[0] ?? '').toLowerCase()).join('');
36
+ }
37
+ /** Build the per-item haystack string: label + its initials + any keywords, space-joined. */
38
+ function haystackFor(item) {
39
+ return [item.label, searchInitials(item.label), ...(item.keywords ?? [])]
40
+ .filter(Boolean)
41
+ .join(' ');
42
+ }
43
+ /**
44
+ * Rank `items` against `query`, best match first; non-matches dropped. Generic over the
45
+ * item type (so callers can pass plain {@link NavItem}s *or* richer catalogue entries
46
+ * that carry domain/group tags for grouping — the extra fields ride straight through).
47
+ * An empty/blank query returns `[]` (the palette shows its cold state instead).
48
+ *
49
+ * Returns *all* matches in rank order; callers cap the count for display (the palette
50
+ * shows 30). uFuzzy's contiguous scorer means "frac" ranks "Fractal" above "Format
51
+ * Lab", and the initials augmentation means "qb" surfaces "Query Builder".
52
+ */
53
+ export function fuzzyRankItems(query, items) {
54
+ const q = query.trim();
55
+ if (!q || items.length === 0)
56
+ return [];
57
+ const haystack = items.map(haystackFor);
58
+ const [idxs, , order] = uf.search(haystack, q);
59
+ // `idxs` is null when nothing matched and an empty array when filtered to zero; both
60
+ // mean "no results". `order` (the ranked iteration order into `idxs`) is present
61
+ // whenever there are matches under uFuzzy's info threshold (1e3 — far above any nav
62
+ // set); fall back to raw `idxs` order if it's ever absent.
63
+ if (!idxs || idxs.length === 0)
64
+ return [];
65
+ const ranked = order ? order.map((o) => idxs[o]) : idxs;
66
+ const out = [];
67
+ for (const haystackIdx of ranked) {
68
+ const item = items[haystackIdx];
69
+ if (item)
70
+ out.push(item);
71
+ }
72
+ return out;
73
+ }
@@ -0,0 +1,28 @@
1
+ /** The four modifier flags a binding can require. */
2
+ interface HotkeyMods {
3
+ meta: boolean;
4
+ ctrl: boolean;
5
+ alt: boolean;
6
+ shift: boolean;
7
+ }
8
+ /** A parsed binding: the required modifier set + the final (lower-cased) key. */
9
+ export interface ParsedHotkey extends HotkeyMods {
10
+ key: string;
11
+ }
12
+ /**
13
+ * Is this an Apple platform (macOS / iOS)? Decides whether `$mod` means ⌘ (Meta) or
14
+ * Ctrl, and which glyphs the badge draws. Guarded for non-DOM (SSR / unit) hosts —
15
+ * returns `false` when there's no `navigator`.
16
+ */
17
+ export declare function isApplePlatform(): boolean;
18
+ /** Split a `'+'`-separated binding into its required modifiers + final key. */
19
+ export declare function parseHotkey(binding: string, isApple?: boolean): ParsedHotkey;
20
+ /** Does a keyboard event satisfy `binding` — exact modifier set + the key? */
21
+ export declare function matchHotkey(e: KeyboardEvent, binding: string, isApple?: boolean): boolean;
22
+ /**
23
+ * Render a binding as a human badge for the platform: macOS draws glyphs with no
24
+ * separator (`⌘K`, `⌥B`, `⌃⇧P`); elsewhere words joined with a thin space (`Ctrl K`,
25
+ * `Alt B`). The palette input badge + the per-row shortcut labels both use this.
26
+ */
27
+ export declare function formatHotkey(binding: string, isApple?: boolean): string;
28
+ export {};
@@ -0,0 +1,118 @@
1
+ // Platform-portable keyboard-shortcut parsing, matching, and display formatting.
2
+ // React-free (pure functions + DOM event/`navigator` reads, no React) so the
3
+ // ShortcutRegistry, the `useHotkeys` hook, and the palette badge all share one
4
+ // source of truth for "what does `$mod+k` mean and how is it drawn?".
5
+ //
6
+ // Binding syntax is the tinykeys format — `'+'`-separated modifiers then a key:
7
+ // `$mod+k` → ⌘K on macOS, Ctrl K elsewhere (the portable command modifier)
8
+ // `alt+b` → ⌥B on macOS, Alt B elsewhere
9
+ // `shift+?` → ⇧?
10
+ // `$mod` is the whole point: one binding, correct on every platform.
11
+ /**
12
+ * Is this an Apple platform (macOS / iOS)? Decides whether `$mod` means ⌘ (Meta) or
13
+ * Ctrl, and which glyphs the badge draws. Guarded for non-DOM (SSR / unit) hosts —
14
+ * returns `false` when there's no `navigator`.
15
+ */
16
+ export function isApplePlatform() {
17
+ if (typeof navigator === 'undefined')
18
+ return false;
19
+ // `navigator.platform` is the most reliable signal but is being deprecated, so fall
20
+ // back to the UA string. Covers Mac, iPhone, iPad, iPod (incl. iPadOS desktop-UA).
21
+ const probe = `${navigator.platform ?? ''} ${navigator.userAgent ?? ''}`;
22
+ return /mac|iphone|ipad|ipod/i.test(probe);
23
+ }
24
+ /** Split a `'+'`-separated binding into its required modifiers + final key. */
25
+ export function parseHotkey(binding, isApple = isApplePlatform()) {
26
+ const parts = binding
27
+ .toLowerCase()
28
+ .split('+')
29
+ .map((p) => p.trim())
30
+ .filter(Boolean);
31
+ const key = parts.pop() ?? '';
32
+ const tokens = new Set(parts);
33
+ const mod = tokens.has('$mod');
34
+ return {
35
+ key,
36
+ meta: tokens.has('meta') || tokens.has('cmd') || tokens.has('command') || (mod && isApple),
37
+ ctrl: tokens.has('ctrl') || tokens.has('control') || (mod && !isApple),
38
+ alt: tokens.has('alt') || tokens.has('option') || tokens.has('opt'),
39
+ shift: tokens.has('shift'),
40
+ };
41
+ }
42
+ /** Does a keyboard event satisfy `binding` — exact modifier set + the key? */
43
+ export function matchHotkey(e, binding, isApple = isApplePlatform()) {
44
+ const want = parseHotkey(binding, isApple);
45
+ // Exact modifier match (not "at least") so `$mod+k` never fires on ⌘⇧K.
46
+ if (e.metaKey !== want.meta)
47
+ return false;
48
+ if (e.ctrlKey !== want.ctrl)
49
+ return false;
50
+ if (e.altKey !== want.alt)
51
+ return false;
52
+ if (e.shiftKey !== want.shift)
53
+ return false;
54
+ return eventMatchesKey(e, want.key);
55
+ }
56
+ /**
57
+ * Match the produced `key`, falling back to the physical `code` — because on macOS a
58
+ * held Option mutates `event.key` (⌥B yields "∫"), so an `alt+b` binding must match on
59
+ * `KeyB` too. Letters + digits are covered (the practical shortcut surface).
60
+ */
61
+ function eventMatchesKey(e, key) {
62
+ if (!key)
63
+ return false;
64
+ if (e.key.toLowerCase() === key)
65
+ return true;
66
+ if (key.length === 1 && key >= 'a' && key <= 'z' && e.code === `Key${key.toUpperCase()}`) {
67
+ return true;
68
+ }
69
+ if (key.length === 1 && key >= '0' && key <= '9' && e.code === `Digit${key}`)
70
+ return true;
71
+ return false;
72
+ }
73
+ const MAC_GLYPH = {
74
+ meta: '⌘',
75
+ ctrl: '⌃',
76
+ alt: '⌥',
77
+ shift: '⇧',
78
+ };
79
+ const PC_WORD = {
80
+ meta: 'Win',
81
+ ctrl: 'Ctrl',
82
+ alt: 'Alt',
83
+ shift: 'Shift',
84
+ };
85
+ /** Pretty-print a key token for display: a letter/digit upper-cases, named keys title-case. */
86
+ function displayKey(key) {
87
+ if (key.length === 1)
88
+ return key.toUpperCase();
89
+ const named = {
90
+ arrowup: '↑',
91
+ arrowdown: '↓',
92
+ arrowleft: '←',
93
+ arrowright: '→',
94
+ enter: '↵',
95
+ escape: 'Esc',
96
+ backspace: '⌫',
97
+ delete: 'Del',
98
+ ' ': 'Space',
99
+ space: 'Space',
100
+ };
101
+ return named[key] ?? key.charAt(0).toUpperCase() + key.slice(1);
102
+ }
103
+ /**
104
+ * Render a binding as a human badge for the platform: macOS draws glyphs with no
105
+ * separator (`⌘K`, `⌥B`, `⌃⇧P`); elsewhere words joined with a thin space (`Ctrl K`,
106
+ * `Alt B`). The palette input badge + the per-row shortcut labels both use this.
107
+ */
108
+ export function formatHotkey(binding, isApple = isApplePlatform()) {
109
+ const p = parseHotkey(binding, isApple);
110
+ const order = isApple
111
+ ? ['ctrl', 'alt', 'shift', 'meta'] // Apple convention: ⌃⌥⇧⌘ then key
112
+ : ['ctrl', 'meta', 'alt', 'shift'];
113
+ const mods = order.filter((m) => p[m]).map((m) => (isApple ? MAC_GLYPH[m] : PC_WORD[m]));
114
+ const key = displayKey(p.key);
115
+ if (isApple)
116
+ return [...mods, key].join('');
117
+ return [...mods, key].join(' ');
118
+ }
@@ -0,0 +1,3 @@
1
+ export { fuzzyRankItems, searchInitials, } from './fuzzyRank';
2
+ export { formatHotkey, isApplePlatform, matchHotkey, type ParsedHotkey, parseHotkey, } from './hotkeys';
3
+ export { type ShortcutEntry, ShortcutRegistry, shortcutRegistry, } from './ShortcutRegistry';
@@ -0,0 +1,8 @@
1
+ // `cursedbelt-core/palette` — the React-free command-palette core: the uFuzzy ranking
2
+ // layer, the shortcut registry, and the portable hotkey utilities. No React, no
3
+ // browser dependency beyond optional `navigator`/event reads — usable in a startup
4
+ // module, a server test, or the React layer alike. The `CommandPalette` component +
5
+ // its hooks live in `cursedbelt/react` and build on exactly this.
6
+ export { fuzzyRankItems, searchInitials, } from './fuzzyRank';
7
+ export { formatHotkey, isApplePlatform, matchHotkey, parseHotkey, } from './hotkeys';
8
+ export { ShortcutRegistry, shortcutRegistry, } from './ShortcutRegistry';
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Dynamic Schema Projection — the Virtual Schema Adapter (02 §7.2).
3
+ *
4
+ * User-defined structure (imported CSVs, external tables, custom columns)
5
+ * projects through `l_columns` typed metadata (01 §2.1). Components read a
6
+ * unified layout object and **no user action ever generates DDL**.
7
+ *
8
+ * That last rule is the whole design. The obvious implementation — `ALTER TABLE`
9
+ * per user column — makes every import a schema migration: it needs a write lock
10
+ * on a live DB, it cannot be rolled back cleanly mid-import, and a tenant with
11
+ * 300 custom columns produces a 300-column table that no index helps. Projecting
12
+ * instead means the physical schema is fixed (`l_values` with one typed column
13
+ * per storage class) and the *logical* schema is data.
14
+ *
15
+ * Typed value columns also fix a real SQLite failure: a single `value TEXT`
16
+ * column holding mixed storage classes sorts `10` before `9`, and a date next to
17
+ * a number compares by its type tag first. Landing each value in the column
18
+ * matching its declared type keeps `ORDER BY` single-storage-class correct.
19
+ */
20
+ /** The column types `l_columns.col_type` admits (01 §2.1). */
21
+ export declare const PROJECTED_COLUMN_TYPES: readonly ["text", "number", "bool", "date", "select", "json"];
22
+ export type ProjectedColumnType = (typeof PROJECTED_COLUMN_TYPES)[number];
23
+ /** One user-defined column's metadata — a row of `l_columns`. */
24
+ export interface ProjectedColumn {
25
+ id: string;
26
+ name: string;
27
+ colType: ProjectedColumnType;
28
+ position: number;
29
+ /** Type-specific options — `select` choices, number precision, date format. */
30
+ options?: Record<string, unknown>;
31
+ }
32
+ /** The physical `l_values` slots. Exactly one is non-null per (row, column). */
33
+ export interface ProjectedValueRow {
34
+ v_text: string | null;
35
+ v_num: number | null;
36
+ v_bool: number | null;
37
+ v_date: string | null;
38
+ v_json: string | null;
39
+ }
40
+ /** Which physical column a logical type lands in. */
41
+ export declare const STORAGE_COLUMN: Readonly<Record<ProjectedColumnType, keyof ProjectedValueRow>>;
42
+ /** What a component sees — never the physical row. */
43
+ export interface ProjectedField {
44
+ columnId: string;
45
+ name: string;
46
+ type: ProjectedColumnType;
47
+ value: string | number | boolean | null;
48
+ /** Parsed `json` payload; undefined for every other type. */
49
+ json?: unknown;
50
+ }
51
+ export interface ProjectionDiagnostic {
52
+ code: 'projection-lossy-cast';
53
+ columnId: string;
54
+ message: string;
55
+ }
56
+ /**
57
+ * Project a stored row into the unified layout object components read.
58
+ *
59
+ * Reads only the slot the column's declared type owns. Deliberately: a row whose
60
+ * `v_text` still holds a value from before a column's type was changed must not
61
+ * resurface — the declared type is the truth, and a stale slot is stale data.
62
+ */
63
+ export declare function projectColumn(column: ProjectedColumn, row: ProjectedValueRow | null): ProjectedField;
64
+ export interface CastResult {
65
+ row: ProjectedValueRow;
66
+ diagnostics: readonly ProjectionDiagnostic[];
67
+ }
68
+ /**
69
+ * Cast an incoming value into the slot its column's type owns.
70
+ *
71
+ * A value that cannot be cast is preserved as `json` with a WARNING
72
+ * `projection-lossy-cast` rather than being dropped. Dropping is the behavior
73
+ * that loses a user's imported data silently; keeping it means the cell is
74
+ * visibly wrong and recoverable.
75
+ */
76
+ export declare function castValue(column: ProjectedColumn, value: unknown): CastResult;
77
+ /**
78
+ * Map an external field type (CSV sniff, sheet cell type, API schema) onto a
79
+ * projected column type. Anything unrecognized becomes `json` — the lossless
80
+ * default, per 02 §7.2.
81
+ */
82
+ export declare function mapExternalType(externalType: string): ProjectedColumnType;
83
+ /** Project a whole row: every declared column, in `position` order. */
84
+ export declare function projectRow(columns: readonly ProjectedColumn[], values: ReadonlyMap<string, ProjectedValueRow>): readonly ProjectedField[];
@@ -0,0 +1,184 @@
1
+ /**
2
+ * Dynamic Schema Projection — the Virtual Schema Adapter (02 §7.2).
3
+ *
4
+ * User-defined structure (imported CSVs, external tables, custom columns)
5
+ * projects through `l_columns` typed metadata (01 §2.1). Components read a
6
+ * unified layout object and **no user action ever generates DDL**.
7
+ *
8
+ * That last rule is the whole design. The obvious implementation — `ALTER TABLE`
9
+ * per user column — makes every import a schema migration: it needs a write lock
10
+ * on a live DB, it cannot be rolled back cleanly mid-import, and a tenant with
11
+ * 300 custom columns produces a 300-column table that no index helps. Projecting
12
+ * instead means the physical schema is fixed (`l_values` with one typed column
13
+ * per storage class) and the *logical* schema is data.
14
+ *
15
+ * Typed value columns also fix a real SQLite failure: a single `value TEXT`
16
+ * column holding mixed storage classes sorts `10` before `9`, and a date next to
17
+ * a number compares by its type tag first. Landing each value in the column
18
+ * matching its declared type keeps `ORDER BY` single-storage-class correct.
19
+ */
20
+ /** The column types `l_columns.col_type` admits (01 §2.1). */
21
+ export const PROJECTED_COLUMN_TYPES = ['text', 'number', 'bool', 'date', 'select', 'json'];
22
+ /** Which physical column a logical type lands in. */
23
+ export const STORAGE_COLUMN = {
24
+ text: 'v_text',
25
+ number: 'v_num',
26
+ bool: 'v_bool',
27
+ date: 'v_date',
28
+ // A `select` is a constrained string, so it sorts and indexes as text.
29
+ select: 'v_text',
30
+ json: 'v_json',
31
+ };
32
+ /**
33
+ * Project a stored row into the unified layout object components read.
34
+ *
35
+ * Reads only the slot the column's declared type owns. Deliberately: a row whose
36
+ * `v_text` still holds a value from before a column's type was changed must not
37
+ * resurface — the declared type is the truth, and a stale slot is stale data.
38
+ */
39
+ export function projectColumn(column, row) {
40
+ const base = { columnId: column.id, name: column.name, type: column.colType };
41
+ if (row === null)
42
+ return { ...base, value: null };
43
+ switch (column.colType) {
44
+ case 'number':
45
+ return { ...base, value: row.v_num };
46
+ case 'bool':
47
+ // SQLite has no boolean storage class; 0/1 becomes a real boolean here so
48
+ // components never write `=== 1`.
49
+ return { ...base, value: row.v_bool === null ? null : row.v_bool !== 0 };
50
+ case 'date':
51
+ return { ...base, value: row.v_date };
52
+ case 'json': {
53
+ if (row.v_json === null)
54
+ return { ...base, value: null };
55
+ try {
56
+ return { ...base, value: row.v_json, json: JSON.parse(row.v_json) };
57
+ }
58
+ catch {
59
+ // Malformed JSON surfaces as raw text rather than throwing — one bad
60
+ // cell must not blank an entire table render.
61
+ return { ...base, value: row.v_json };
62
+ }
63
+ }
64
+ default:
65
+ return { ...base, value: row.v_text };
66
+ }
67
+ }
68
+ const EMPTY_ROW = { v_text: null, v_num: null, v_bool: null, v_date: null, v_json: null };
69
+ /**
70
+ * Cast an incoming value into the slot its column's type owns.
71
+ *
72
+ * A value that cannot be cast is preserved as `json` with a WARNING
73
+ * `projection-lossy-cast` rather than being dropped. Dropping is the behavior
74
+ * that loses a user's imported data silently; keeping it means the cell is
75
+ * visibly wrong and recoverable.
76
+ */
77
+ export function castValue(column, value) {
78
+ const row = { ...EMPTY_ROW };
79
+ const diagnostics = [];
80
+ if (value === null || value === undefined || value === '')
81
+ return { row, diagnostics };
82
+ const lossy = (message) => {
83
+ diagnostics.push({ code: 'projection-lossy-cast', columnId: column.id, message });
84
+ row.v_json = JSON.stringify(value);
85
+ };
86
+ switch (column.colType) {
87
+ case 'number': {
88
+ const numeric = typeof value === 'number' ? value : Number(String(value).replace(/[,\s]/g, ''));
89
+ if (Number.isFinite(numeric))
90
+ row.v_num = numeric;
91
+ else
92
+ lossy(`'${String(value)}' is not a number; stored as json.`);
93
+ break;
94
+ }
95
+ case 'bool': {
96
+ const text = String(value).trim().toLowerCase();
97
+ if (typeof value === 'boolean')
98
+ row.v_bool = value ? 1 : 0;
99
+ else if (['true', 'yes', 'y', '1'].includes(text))
100
+ row.v_bool = 1;
101
+ else if (['false', 'no', 'n', '0'].includes(text))
102
+ row.v_bool = 0;
103
+ else
104
+ lossy(`'${String(value)}' is not a boolean; stored as json.`);
105
+ break;
106
+ }
107
+ case 'date': {
108
+ const iso = toIsoDate(value);
109
+ if (iso !== null)
110
+ row.v_date = iso;
111
+ else
112
+ lossy(`'${String(value)}' is not a recognizable date; stored as json.`);
113
+ break;
114
+ }
115
+ case 'select': {
116
+ const choices = Array.isArray(column.options?.choices) ? column.options.choices : null;
117
+ const text = String(value);
118
+ if (choices === null || choices.map(String).includes(text))
119
+ row.v_text = text;
120
+ else
121
+ lossy(`'${text}' is not one of the declared choices; stored as json.`);
122
+ break;
123
+ }
124
+ case 'json':
125
+ row.v_json = typeof value === 'string' ? value : JSON.stringify(value);
126
+ break;
127
+ default:
128
+ row.v_text = typeof value === 'string' ? value : String(value);
129
+ }
130
+ return { row, diagnostics };
131
+ }
132
+ /**
133
+ * ISO-8601 date, or null. Anchored to UTC so the same CSV imported in two
134
+ * timezones produces the same stored day — the off-by-one-day class of import
135
+ * bug.
136
+ */
137
+ function toIsoDate(value) {
138
+ if (value instanceof Date)
139
+ return Number.isNaN(value.getTime()) ? null : value.toISOString();
140
+ const text = String(value).trim();
141
+ if (/^\d{4}-\d{2}-\d{2}$/.test(text))
142
+ return `${text}T00:00:00.000Z`;
143
+ const parsed = Date.parse(text);
144
+ return Number.isNaN(parsed) ? null : new Date(parsed).toISOString();
145
+ }
146
+ /**
147
+ * Map an external field type (CSV sniff, sheet cell type, API schema) onto a
148
+ * projected column type. Anything unrecognized becomes `json` — the lossless
149
+ * default, per 02 §7.2.
150
+ */
151
+ export function mapExternalType(externalType) {
152
+ switch (externalType.toLowerCase()) {
153
+ case 'string':
154
+ case 'text':
155
+ case 'varchar':
156
+ return 'text';
157
+ case 'number':
158
+ case 'numeric':
159
+ case 'int':
160
+ case 'integer':
161
+ case 'float':
162
+ case 'double':
163
+ case 'decimal':
164
+ return 'number';
165
+ case 'bool':
166
+ case 'boolean':
167
+ return 'bool';
168
+ case 'date':
169
+ case 'datetime':
170
+ case 'timestamp':
171
+ return 'date';
172
+ case 'enum':
173
+ case 'select':
174
+ return 'select';
175
+ default:
176
+ return 'json';
177
+ }
178
+ }
179
+ /** Project a whole row: every declared column, in `position` order. */
180
+ export function projectRow(columns, values) {
181
+ return [...columns]
182
+ .sort((a, b) => a.position - b.position)
183
+ .map((column) => projectColumn(column, values.get(column.id) ?? null));
184
+ }