cursedbelt-core 2.0.0 → 2.1.1

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 (155) hide show
  1. package/dist/core/activity/index.d.ts +2 -2
  2. package/dist/core/activity/index.js +1 -1
  3. package/dist/core/activity/model.d.ts +2 -2
  4. package/dist/core/analytics/index.d.ts +2 -2
  5. package/dist/core/analytics/index.js +2 -2
  6. package/dist/core/category/index.d.ts +3 -3
  7. package/dist/core/category/index.js +2 -2
  8. package/dist/core/category/memoryStore.d.ts +1 -1
  9. package/dist/core/category/memoryStore.js +1 -1
  10. package/dist/core/category/tree.d.ts +1 -1
  11. package/dist/core/colorScheme/index.d.ts +1 -1
  12. package/dist/core/colorScheme/index.js +1 -1
  13. package/dist/core/duplicates/index.d.ts +115 -0
  14. package/dist/core/duplicates/index.js +177 -0
  15. package/dist/core/folder-tree/actions.d.ts +1 -1
  16. package/dist/core/folder-tree/actions.js +1 -1
  17. package/dist/core/folder-tree/index.d.ts +3 -3
  18. package/dist/core/folder-tree/index.js +3 -3
  19. package/dist/core/folder-tree/tree.js +1 -1
  20. package/dist/core/master-lock/index.d.ts +4 -4
  21. package/dist/core/master-lock/index.js +4 -4
  22. package/dist/core/master-lock/wire.d.ts +1 -1
  23. package/dist/core/media/ffmpeg.d.ts +1 -1
  24. package/dist/core/media/format.d.ts +1 -1
  25. package/dist/core/media/index.d.ts +12 -12
  26. package/dist/core/media/index.js +11 -11
  27. package/dist/core/media/mediaItem.d.ts +1 -1
  28. package/dist/core/media/renderPlan.d.ts +1 -1
  29. package/dist/core/media/renderPlan.js +1 -1
  30. package/dist/core/media/validate.d.ts +1 -1
  31. package/dist/core/media/validate.js +1 -1
  32. package/dist/core/navigation/coverageTest.d.ts +1 -1
  33. package/dist/core/navigation/coverageTest.js +1 -1
  34. package/dist/core/navigation/index.d.ts +6 -6
  35. package/dist/core/navigation/index.js +6 -6
  36. package/dist/core/navigation/migrateNavPreferences.d.ts +1 -1
  37. package/dist/core/navigation/navigationSelectors.d.ts +1 -1
  38. package/dist/core/navigation/navigationSelectors.js +1 -1
  39. package/dist/core/navigation/searchCatalogue.d.ts +1 -1
  40. package/dist/core/palette/fuzzyRank.d.ts +1 -1
  41. package/dist/core/palette/index.d.ts +3 -3
  42. package/dist/core/palette/index.js +3 -3
  43. package/dist/core/sharing/index.d.ts +2 -2
  44. package/dist/core/sharing/index.js +1 -1
  45. package/dist/core/tokens/registry.d.ts +2 -2
  46. package/dist/core/tokens/registry.js +1 -1
  47. package/dist/core/uiScale/index.d.ts +2 -2
  48. package/dist/core/uiScale/index.js +2 -2
  49. package/dist/core/uiScale/uiScaleStore.js +1 -1
  50. package/dist/core/wire/__fixtures__/rowsMarshalWorker.js +1 -1
  51. package/dist/core/wire/index.d.ts +4 -4
  52. package/dist/core/wire/index.js +4 -4
  53. package/dist/core/wire/ingress.d.ts +1 -1
  54. package/dist/core/wire/ingress.js +3 -3
  55. package/dist/core/wire/marshalWorker.d.ts +1 -1
  56. package/dist/core/wire/marshalWorker.js +2 -2
  57. package/dist/core/wire/marshalling.js +1 -1
  58. package/dist/ctgr/chunkAccumulator.d.ts +1 -1
  59. package/dist/ctgr/chunkAccumulator.js +2 -2
  60. package/dist/ctgr/chunkStore.d.ts +1 -1
  61. package/dist/ctgr/chunkStore.js +2 -2
  62. package/dist/ctgr/codec.d.ts +1 -1
  63. package/dist/ctgr/codec.js +5 -5
  64. package/dist/ctgr/index.d.ts +9 -9
  65. package/dist/ctgr/index.js +9 -9
  66. package/dist/ctgr/v0compat.d.ts +1 -1
  67. package/dist/ctgr/v0compat.js +2 -2
  68. package/dist/index.d.ts +2 -2
  69. package/dist/index.js +2 -2
  70. package/dist/testing/index.d.ts +3 -3
  71. package/dist/testing/index.js +3 -3
  72. package/package.json +10 -4
  73. package/src/config/appConfig.spec.ts +1 -1
  74. package/src/core/activity/index.ts +2 -2
  75. package/src/core/activity/model.ts +2 -2
  76. package/src/core/analytics/index.ts +2 -2
  77. package/src/core/analytics/range.spec.ts +1 -1
  78. package/src/core/analytics/schedule.spec.ts +1 -1
  79. package/src/core/category/category.spec.ts +1 -1
  80. package/src/core/category/index.ts +3 -3
  81. package/src/core/category/memoryStore.ts +2 -2
  82. package/src/core/category/tree.ts +1 -1
  83. package/src/core/color.spec.ts +1 -1
  84. package/src/core/colorScheme/colorScheme.spec.ts +1 -1
  85. package/src/core/colorScheme/index.ts +1 -1
  86. package/src/core/download.spec.ts +1 -1
  87. package/src/core/duplicates/index.spec.ts +116 -0
  88. package/src/core/duplicates/index.ts +203 -0
  89. package/src/core/events/ccEventBus.spec.ts +1 -1
  90. package/src/core/file-tree/fileTreeModel.spec.ts +1 -1
  91. package/src/core/folder-tree/actions.spec.ts +2 -2
  92. package/src/core/folder-tree/actions.ts +2 -2
  93. package/src/core/folder-tree/index.ts +3 -3
  94. package/src/core/folder-tree/path.spec.ts +1 -1
  95. package/src/core/folder-tree/tree.spec.ts +1 -1
  96. package/src/core/folder-tree/tree.ts +1 -1
  97. package/src/core/master-lock/index.ts +4 -4
  98. package/src/core/master-lock/kdf.spec.ts +2 -2
  99. package/src/core/master-lock/presence.spec.ts +1 -1
  100. package/src/core/master-lock/wire.spec.ts +1 -1
  101. package/src/core/master-lock/wire.ts +1 -1
  102. package/src/core/media/bgRemoval.spec.ts +1 -1
  103. package/src/core/media/ffmpeg.spec.ts +2 -2
  104. package/src/core/media/ffmpeg.ts +1 -1
  105. package/src/core/media/format.spec.ts +1 -1
  106. package/src/core/media/format.ts +1 -1
  107. package/src/core/media/index.ts +12 -12
  108. package/src/core/media/mediaItem.spec.ts +2 -2
  109. package/src/core/media/mediaItem.ts +1 -1
  110. package/src/core/media/renderPlan.spec.ts +1 -1
  111. package/src/core/media/renderPlan.ts +2 -2
  112. package/src/core/media/segment.spec.ts +1 -1
  113. package/src/core/media/size.spec.ts +1 -1
  114. package/src/core/media/trim.spec.ts +1 -1
  115. package/src/core/media/validate.spec.ts +2 -2
  116. package/src/core/media/validate.ts +2 -2
  117. package/src/core/media/videoCuts.spec.ts +1 -1
  118. package/src/core/navigation/coverageTest.ts +2 -2
  119. package/src/core/navigation/index.ts +6 -6
  120. package/src/core/navigation/migrateNavPreferences.ts +1 -1
  121. package/src/core/navigation/navigation.spec.ts +1 -1
  122. package/src/core/navigation/navigationSelectors.ts +1 -1
  123. package/src/core/navigation/searchCatalogue.ts +1 -1
  124. package/src/core/net/reconnectSchedule.spec.ts +1 -1
  125. package/src/core/palette/fuzzyRank.ts +1 -1
  126. package/src/core/palette/index.ts +3 -3
  127. package/src/core/palette/palette.spec.ts +4 -4
  128. package/src/core/schema/projection.spec.ts +1 -1
  129. package/src/core/sharing/index.ts +2 -2
  130. package/src/core/sharing/model.spec.ts +1 -1
  131. package/src/core/tokens/registry.spec.ts +1 -1
  132. package/src/core/tokens/registry.ts +2 -2
  133. package/src/core/uiScale/index.ts +2 -2
  134. package/src/core/uiScale/uiScale.spec.ts +1 -1
  135. package/src/core/uiScale/uiScaleStore.ts +1 -1
  136. package/src/core/wire/__fixtures__/rowsMarshalWorker.ts +1 -1
  137. package/src/core/wire/index.ts +4 -4
  138. package/src/core/wire/ingress.spec.ts +2 -2
  139. package/src/core/wire/ingress.ts +3 -3
  140. package/src/core/wire/marshalWorker.spec.ts +2 -2
  141. package/src/core/wire/marshalWorker.ts +2 -2
  142. package/src/core/wire/marshalling.spec.ts +1 -1
  143. package/src/core/wire/marshalling.ts +1 -1
  144. package/src/core/wire/wireHub.spec.ts +1 -1
  145. package/src/ctgr/chunkAccumulator.ts +3 -3
  146. package/src/ctgr/chunkStore.spec.ts +2 -2
  147. package/src/ctgr/chunkStore.ts +3 -3
  148. package/src/ctgr/codec.spec.ts +4 -4
  149. package/src/ctgr/codec.ts +5 -5
  150. package/src/ctgr/index.ts +9 -9
  151. package/src/ctgr/v0compat.spec.ts +2 -2
  152. package/src/ctgr/v0compat.ts +3 -3
  153. package/src/index.ts +2 -2
  154. package/src/testTmpRoot.spec.ts +1 -1
  155. package/src/testing/index.ts +3 -3
@@ -5,7 +5,7 @@
5
5
  // model, a registered route is automatically discoverable — and an unregistered one
6
6
  // can never silently bury. Framework-agnostic (throws a plain Error; works under
7
7
  // bun:test, vitest, jest) so the same factory drops into any app's spec file.
8
- import { deriveAllPaths, normalizeNavPath } from './navigationSelectors';
8
+ import { deriveAllPaths, normalizeNavPath } from './navigationSelectors.js';
9
9
  /**
10
10
  * Build the coverage assertion for `model` against `allRoutePaths` (the app's full
11
11
  * route list, e.g. derived from `routes.tsx`). The returned `() => void` throws —
@@ -1,6 +1,6 @@
1
- export { type BreadcrumbItem, type BreadcrumbNode, buildBreadcrumbTrail, } from './breadcrumbCompat';
2
- export { createCoverageTest, findCoverageGaps, } from './coverageTest';
3
- export { CURRENT_NAV_PREFS_VERSION, type LegacyNavPrefs, type MigrateNavPrefsOptions, migrateNavPreferences, type NavPreferences, type NavTilePrefs, } from './migrateNavPreferences';
4
- export { allowAllGate, type GateFn, type NavDomain, type NavGate, type NavGroup, type NavHostLimit, type NavItem, type NavigationModel, } from './navigationModel';
5
- export { deriveAllPaths, deriveBreadcrumbTrail, deriveDomain, deriveDomainGroups, deriveDomainItems, deriveSearchCatalogue, deriveSideNavDomains, type NavCrumb, normalizeNavPath, type SearchCatalogueEntry, } from './navigationSelectors';
6
- export { type CatalogueHit, type CatalogueResultGroup, groupCatalogueHits, rankCatalogue, } from './searchCatalogue';
1
+ export { type BreadcrumbItem, type BreadcrumbNode, buildBreadcrumbTrail, } from './breadcrumbCompat.js';
2
+ export { createCoverageTest, findCoverageGaps, } from './coverageTest.js';
3
+ export { CURRENT_NAV_PREFS_VERSION, type LegacyNavPrefs, type MigrateNavPrefsOptions, migrateNavPreferences, type NavPreferences, type NavTilePrefs, } from './migrateNavPreferences.js';
4
+ export { allowAllGate, type GateFn, type NavDomain, type NavGate, type NavGroup, type NavHostLimit, type NavItem, type NavigationModel, } from './navigationModel.js';
5
+ export { deriveAllPaths, deriveBreadcrumbTrail, deriveDomain, deriveDomainGroups, deriveDomainItems, deriveSearchCatalogue, deriveSideNavDomains, type NavCrumb, normalizeNavPath, type SearchCatalogueEntry, } from './navigationSelectors.js';
6
+ export { type CatalogueHit, type CatalogueResultGroup, groupCatalogueHits, rankCatalogue, } from './searchCatalogue.js';
@@ -2,9 +2,9 @@
2
2
  // routing-free: the single source of truth (the IA tree) plus every derivation off
3
3
  // it (side nav, hub tiles, breadcrumbs, search catalogue, coverage test, prefs
4
4
  // migration). The React components in `cursedbelt/react` consume exactly this.
5
- export { buildBreadcrumbTrail, } from './breadcrumbCompat';
6
- export { createCoverageTest, findCoverageGaps, } from './coverageTest';
7
- export { CURRENT_NAV_PREFS_VERSION, migrateNavPreferences, } from './migrateNavPreferences';
8
- export { allowAllGate, } from './navigationModel';
9
- export { deriveAllPaths, deriveBreadcrumbTrail, deriveDomain, deriveDomainGroups, deriveDomainItems, deriveSearchCatalogue, deriveSideNavDomains, normalizeNavPath, } from './navigationSelectors';
10
- export { groupCatalogueHits, rankCatalogue, } from './searchCatalogue';
5
+ export { buildBreadcrumbTrail, } from './breadcrumbCompat.js';
6
+ export { createCoverageTest, findCoverageGaps, } from './coverageTest.js';
7
+ export { CURRENT_NAV_PREFS_VERSION, migrateNavPreferences, } from './migrateNavPreferences.js';
8
+ export { allowAllGate, } from './navigationModel.js';
9
+ export { deriveAllPaths, deriveBreadcrumbTrail, deriveDomain, deriveDomainGroups, deriveDomainItems, deriveSearchCatalogue, deriveSideNavDomains, normalizeNavPath, } from './navigationSelectors.js';
10
+ export { groupCatalogueHits, rankCatalogue, } from './searchCatalogue.js';
@@ -1,4 +1,4 @@
1
- import type { NavigationModel } from './navigationModel';
1
+ import type { NavigationModel } from './navigationModel.js';
2
2
  /** Bump when the migration logic or the prefs shape changes. */
3
3
  export declare const CURRENT_NAV_PREFS_VERSION = 1;
4
4
  /** Per-hub tile customization (keyed by item path/id within a domain). */
@@ -1,4 +1,4 @@
1
- import { type GateFn, type NavDomain, type NavGroup, type NavItem, type NavigationModel } from './navigationModel';
1
+ import { type GateFn, type NavDomain, type NavGroup, type NavItem, type NavigationModel } from './navigationModel.js';
2
2
  /**
3
3
  * Normalize a path for route *matching* — drop the query string, the hash (the
4
4
  * `#tab=…` in-page target), and a trailing slash. `'/'` stays `'/'`. Used so a tab
@@ -3,7 +3,7 @@
3
3
  // model exclusively through these — there is no second list to keep in sync. They
4
4
  // import nothing from React, so they run identically in the client, in a server
5
5
  // test, and in the CI coverage check.
6
- import { allowAllGate, } from './navigationModel';
6
+ import { allowAllGate, } from './navigationModel.js';
7
7
  /**
8
8
  * Normalize a path for route *matching* — drop the query string, the hash (the
9
9
  * `#tab=…` in-page target), and a trailing slash. `'/'` stays `'/'`. Used so a tab
@@ -1,4 +1,4 @@
1
- import type { SearchCatalogueEntry } from './navigationSelectors';
1
+ import type { SearchCatalogueEntry } from './navigationSelectors.js';
2
2
  /** A query hit with its computed relevance score (higher = better). */
3
3
  export interface CatalogueHit extends SearchCatalogueEntry {
4
4
  score: number;
@@ -1,4 +1,4 @@
1
- import type { NavItem } from '../navigation';
1
+ import type { NavItem } from '../navigation/index.js';
2
2
  /**
3
3
  * The initials of a multi-word label (`'Query Builder'` → `'qb'`). Single-word labels
4
4
  * return `''` (their prefix already matches via the label itself). Powers initialism
@@ -1,3 +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';
1
+ export { fuzzyRankItems, searchInitials, } from './fuzzyRank.js';
2
+ export { formatHotkey, isApplePlatform, matchHotkey, type ParsedHotkey, parseHotkey, } from './hotkeys.js';
3
+ export { type ShortcutEntry, ShortcutRegistry, shortcutRegistry, } from './ShortcutRegistry.js';
@@ -3,6 +3,6 @@
3
3
  // browser dependency beyond optional `navigator`/event reads — usable in a startup
4
4
  // module, a server test, or the React layer alike. The `CommandPalette` component +
5
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';
6
+ export { fuzzyRankItems, searchInitials, } from './fuzzyRank.js';
7
+ export { formatHotkey, isApplePlatform, matchHotkey, parseHotkey, } from './hotkeys.js';
8
+ export { ShortcutRegistry, shortcutRegistry, } from './ShortcutRegistry.js';
@@ -1,5 +1,5 @@
1
1
  /** The fleet's sharing/access vocabulary — see `./model.ts` for the whole
2
2
  * contract. Storage lives in `cursedbelt-server/sharing`, the UI in
3
3
  * `cursedbelt/react/sharing`; both speak these words. */
4
- export { accessMeets, describeAccess, describeShareEvent, isShareAccess, isShareLevel, isShareSubjectType, levelsMeeting, normalizeSubject, normalizeSubjectId, resolveAccess, resourceKey, sameResource, sameSubject, SHARE_ACTIONS, strongerAccess, } from "./model";
5
- export type { ShareAccess, ShareAction, ShareEvent, ShareGrant, ShareGroup, ShareGroupMember, ShareLevel, ShareResource, ShareSubject, ShareSubjectType, ShareViewer, } from "./model";
4
+ export { accessMeets, describeAccess, describeShareEvent, isShareAccess, isShareLevel, isShareSubjectType, levelsMeeting, normalizeSubject, normalizeSubjectId, resolveAccess, resourceKey, sameResource, sameSubject, SHARE_ACTIONS, strongerAccess, } from "./model.js";
5
+ export type { ShareAccess, ShareAction, ShareEvent, ShareGrant, ShareGroup, ShareGroupMember, ShareLevel, ShareResource, ShareSubject, ShareSubjectType, ShareViewer, } from "./model.js";
@@ -1,4 +1,4 @@
1
1
  /** The fleet's sharing/access vocabulary — see `./model.ts` for the whole
2
2
  * contract. Storage lives in `cursedbelt-server/sharing`, the UI in
3
3
  * `cursedbelt/react/sharing`; both speak these words. */
4
- export { accessMeets, describeAccess, describeShareEvent, isShareAccess, isShareLevel, isShareSubjectType, levelsMeeting, normalizeSubject, normalizeSubjectId, resolveAccess, resourceKey, sameResource, sameSubject, SHARE_ACTIONS, strongerAccess, } from "./model";
4
+ export { accessMeets, describeAccess, describeShareEvent, isShareAccess, isShareLevel, isShareSubjectType, levelsMeeting, normalizeSubject, normalizeSubjectId, resolveAccess, resourceKey, sameResource, sameSubject, SHARE_ACTIONS, strongerAccess, } from "./model.js";
@@ -50,8 +50,8 @@ export interface TokenDef<S extends z.ZodType = z.ZodType> {
50
50
  * They used to be a second, divergent declaration of the same names — see the
51
51
  * unification note on `CCWireMarshaller` (05 §B1).
52
52
  */
53
- import type { CCWireMarshaller } from '../wire/marshalling';
54
- export { type CCWireMarshaller, MARSHALLER_WORKER_OFFLOAD_BYTES, requiresWorkerOffload, } from '../wire/marshalling';
53
+ import type { CCWireMarshaller } from '../wire/marshalling.js';
54
+ export { type CCWireMarshaller, MARSHALLER_WORKER_OFFLOAD_BYTES, requiresWorkerOffload, } from '../wire/marshalling.js';
55
55
  export type TokenRegistryIssueCode = 'token-collision' | 'token-namespace-violation' | 'subtype-inversion' | 'token-supertype-missing' | 'token-subtype-cycle' | 'token-sample-invalid';
56
56
  export interface TokenRegistryIssue {
57
57
  code: TokenRegistryIssueCode;
@@ -19,7 +19,7 @@
19
19
  * (the core would stop compiling the moment the plugin is deleted).
20
20
  */
21
21
  import { z } from 'zod';
22
- export { MARSHALLER_WORKER_OFFLOAD_BYTES, requiresWorkerOffload, } from '../wire/marshalling';
22
+ export { MARSHALLER_WORKER_OFFLOAD_BYTES, requiresWorkerOffload, } from '../wire/marshalling.js';
23
23
  const TOKEN_ID_RE = /^[a-z][a-z0-9-]*:[a-z0-9-]+$/;
24
24
  const EXTENSION_ID_RE = /^x-[a-z0-9-]+:[a-z0-9-]+$/;
25
25
  export const isExtensionTokenId = (id) => id.startsWith('x-');
@@ -4,5 +4,5 @@
4
4
  * the saved size on app boot (before React mounts → no flash); the React control +
5
5
  * hook live in `cursedbelt/react`.
6
6
  */
7
- export { clampUiScale, DEFAULT_UI_SCALE, MAX_UI_SCALE, MIN_UI_SCALE, nearestUiScaleLevel, stepUiScale, UI_SCALE_LEVELS, UI_SCALE_STEP, type UiScaleLevel, type UiScaleLevelId, uiScaleToFontSize, } from './scale';
8
- export { applyUiScaleToDocument, createUiScaleStore, getUiScale, initUiScale, setUiScale, subscribeUiScale, UI_SCALE_STORAGE_KEY, type UiScaleStorage, type UiScaleStore, type UiScaleStoreOptions, uiScaleStore, } from './uiScaleStore';
7
+ export { clampUiScale, DEFAULT_UI_SCALE, MAX_UI_SCALE, MIN_UI_SCALE, nearestUiScaleLevel, stepUiScale, UI_SCALE_LEVELS, UI_SCALE_STEP, type UiScaleLevel, type UiScaleLevelId, uiScaleToFontSize, } from './scale.js';
8
+ export { applyUiScaleToDocument, createUiScaleStore, getUiScale, initUiScale, setUiScale, subscribeUiScale, UI_SCALE_STORAGE_KEY, type UiScaleStorage, type UiScaleStore, type UiScaleStoreOptions, uiScaleStore, } from './uiScaleStore.js';
@@ -4,5 +4,5 @@
4
4
  * the saved size on app boot (before React mounts → no flash); the React control +
5
5
  * hook live in `cursedbelt/react`.
6
6
  */
7
- export { clampUiScale, DEFAULT_UI_SCALE, MAX_UI_SCALE, MIN_UI_SCALE, nearestUiScaleLevel, stepUiScale, UI_SCALE_LEVELS, UI_SCALE_STEP, uiScaleToFontSize, } from './scale';
8
- export { applyUiScaleToDocument, createUiScaleStore, getUiScale, initUiScale, setUiScale, subscribeUiScale, UI_SCALE_STORAGE_KEY, uiScaleStore, } from './uiScaleStore';
7
+ export { clampUiScale, DEFAULT_UI_SCALE, MAX_UI_SCALE, MIN_UI_SCALE, nearestUiScaleLevel, stepUiScale, UI_SCALE_LEVELS, UI_SCALE_STEP, uiScaleToFontSize, } from './scale.js';
8
+ export { applyUiScaleToDocument, createUiScaleStore, getUiScale, initUiScale, setUiScale, subscribeUiScale, UI_SCALE_STORAGE_KEY, uiScaleStore, } from './uiScaleStore.js';
@@ -8,7 +8,7 @@
8
8
  * real store with fakes; the exported default singleton binds them to
9
9
  * `localStorage` + the `<html>` element.
10
10
  */
11
- import { clampUiScale, DEFAULT_UI_SCALE, stepUiScale, uiScaleToFontSize } from './scale';
11
+ import { clampUiScale, DEFAULT_UI_SCALE, stepUiScale, uiScaleToFontSize } from './scale.js';
12
12
  export const UI_SCALE_STORAGE_KEY = 'cwip.uiScale';
13
13
  export function createUiScaleStore(options = {}) {
14
14
  const { storage = null, apply, key = UI_SCALE_STORAGE_KEY } = options;
@@ -8,7 +8,7 @@
8
8
  * and the worker imports it.
9
9
  */
10
10
  import { z } from 'zod';
11
- import { serveMarshalWorker } from '../marshalWorker';
11
+ import { serveMarshalWorker } from '../marshalWorker.js';
12
12
  const schema = z.object({ rows: z.array(z.object({ id: z.string() })) });
13
13
  await serveMarshalWorker({
14
14
  id: 'rows',
@@ -11,7 +11,7 @@
11
11
  * `wireHub` is re-exported from `cursedbelt-cc/client` as well; that entry is where
12
12
  * a shell host looks, this one is where the wire tier lives.
13
13
  */
14
- export { type CCWireMarshaller, createMarshallerRegistry, MARSHALLER_PAYLOAD_TOO_LARGE, MARSHALLER_SCHEMA_VIOLATION, MARSHALLER_VALIDATION_FAILED, MARSHALLER_WORKER_OFFLOAD_BYTES, type MarshallerRegistry, type MarshalOptions, type MarshalWorker, marshalInbound, payloadBytes, requiresWorkerOffload, } from './marshalling';
15
- export { createMarshalWorkerClient, MARSHAL_WORKER_DEFAULT_TIMEOUT_MS, MARSHALLER_WORKER_FAILED, MARSHALLER_WORKER_TIMEOUT, type MarshalWorkerClient, type MarshalWorkerClientOptions, type MarshalWorkerRequest, type MarshalWorkerResponse, serveMarshalWorker, } from './marshalWorker';
16
- export { createMarshalledIngress, INGRESS_TOKEN_UNKNOWN, type MarshalledIngress, type MarshalledIngressOptions, } from './ingress';
17
- export { createInstanceRuntime, createWireHub, type InstanceRuntime, type InstanceRuntimeOptions, type WireEnvelope, type WireHub, type WireHubOptions, } from './wireHub';
14
+ export { type CCWireMarshaller, createMarshallerRegistry, MARSHALLER_PAYLOAD_TOO_LARGE, MARSHALLER_SCHEMA_VIOLATION, MARSHALLER_VALIDATION_FAILED, MARSHALLER_WORKER_OFFLOAD_BYTES, type MarshallerRegistry, type MarshalOptions, type MarshalWorker, marshalInbound, payloadBytes, requiresWorkerOffload, } from './marshalling.js';
15
+ export { createMarshalWorkerClient, MARSHAL_WORKER_DEFAULT_TIMEOUT_MS, MARSHALLER_WORKER_FAILED, MARSHALLER_WORKER_TIMEOUT, type MarshalWorkerClient, type MarshalWorkerClientOptions, type MarshalWorkerRequest, type MarshalWorkerResponse, serveMarshalWorker, } from './marshalWorker.js';
16
+ export { createMarshalledIngress, INGRESS_TOKEN_UNKNOWN, type MarshalledIngress, type MarshalledIngressOptions, } from './ingress.js';
17
+ export { createInstanceRuntime, createWireHub, type InstanceRuntime, type InstanceRuntimeOptions, type WireEnvelope, type WireHub, type WireHubOptions, } from './wireHub.js';
@@ -11,7 +11,7 @@
11
11
  * `wireHub` is re-exported from `cursedbelt-cc/client` as well; that entry is where
12
12
  * a shell host looks, this one is where the wire tier lives.
13
13
  */
14
- export { createMarshallerRegistry, MARSHALLER_PAYLOAD_TOO_LARGE, MARSHALLER_SCHEMA_VIOLATION, MARSHALLER_VALIDATION_FAILED, MARSHALLER_WORKER_OFFLOAD_BYTES, marshalInbound, payloadBytes, requiresWorkerOffload, } from './marshalling';
15
- export { createMarshalWorkerClient, MARSHAL_WORKER_DEFAULT_TIMEOUT_MS, MARSHALLER_WORKER_FAILED, MARSHALLER_WORKER_TIMEOUT, serveMarshalWorker, } from './marshalWorker';
16
- export { createMarshalledIngress, INGRESS_TOKEN_UNKNOWN, } from './ingress';
17
- export { createInstanceRuntime, createWireHub, } from './wireHub';
14
+ export { createMarshallerRegistry, MARSHALLER_PAYLOAD_TOO_LARGE, MARSHALLER_SCHEMA_VIOLATION, MARSHALLER_VALIDATION_FAILED, MARSHALLER_WORKER_OFFLOAD_BYTES, marshalInbound, payloadBytes, requiresWorkerOffload, } from './marshalling.js';
15
+ export { createMarshalWorkerClient, MARSHAL_WORKER_DEFAULT_TIMEOUT_MS, MARSHALLER_WORKER_FAILED, MARSHALLER_WORKER_TIMEOUT, serveMarshalWorker, } from './marshalWorker.js';
16
+ export { createMarshalledIngress, INGRESS_TOKEN_UNKNOWN, } from './ingress.js';
17
+ export { createInstanceRuntime, createWireHub, } from './wireHub.js';
@@ -1,4 +1,4 @@
1
- import { type MarshallerRegistry } from './marshalling';
1
+ import { type MarshallerRegistry } from './marshalling.js';
2
2
  /** No marshaller is registered for the token an ingress was asked to feed. */
3
3
  export declare const INGRESS_TOKEN_UNKNOWN = "INGRESS_TOKEN_UNKNOWN";
4
4
  export interface MarshalledIngressOptions {
@@ -50,9 +50,9 @@
50
50
  * A CSV import is the same three lines with a different marshaller and a
51
51
  * different service call. Neither needs to know that a worker exists.
52
52
  */
53
- import { AppError } from '../../shared/errors';
54
- import { createMarshalWorkerClient } from './marshalWorker';
55
- import { marshalInbound, } from './marshalling';
53
+ import { AppError } from '../../shared/errors.js';
54
+ import { createMarshalWorkerClient } from './marshalWorker.js';
55
+ import { marshalInbound, } from './marshalling.js';
56
56
  /** No marshaller is registered for the token an ingress was asked to feed. */
57
57
  export const INGRESS_TOKEN_UNKNOWN = 'INGRESS_TOKEN_UNKNOWN';
58
58
  /**
@@ -1,4 +1,4 @@
1
- import { type CCWireMarshaller, MARSHALLER_SCHEMA_VIOLATION, MARSHALLER_VALIDATION_FAILED, type MarshalWorker } from './marshalling';
1
+ import { type CCWireMarshaller, MARSHALLER_SCHEMA_VIOLATION, MARSHALLER_VALIDATION_FAILED, type MarshalWorker } from './marshalling.js';
2
2
  export declare const MARSHALLER_WORKER_FAILED = "MARSHALLER_WORKER_FAILED";
3
3
  export declare const MARSHALLER_WORKER_TIMEOUT = "MARSHALLER_WORKER_TIMEOUT";
4
4
  /** How long one payload may occupy the worker before the host gives up. */
@@ -33,8 +33,8 @@
33
33
  * inside the server branch — a static import would put a Node builtin in the
34
34
  * module graph of every client bundle that touches wire code.
35
35
  */
36
- import { AppError } from '../../shared/errors';
37
- import { MARSHALLER_SCHEMA_VIOLATION, MARSHALLER_VALIDATION_FAILED, } from './marshalling';
36
+ import { AppError } from '../../shared/errors.js';
37
+ import { MARSHALLER_SCHEMA_VIOLATION, MARSHALLER_VALIDATION_FAILED, } from './marshalling.js';
38
38
  export const MARSHALLER_WORKER_FAILED = 'MARSHALLER_WORKER_FAILED';
39
39
  export const MARSHALLER_WORKER_TIMEOUT = 'MARSHALLER_WORKER_TIMEOUT';
40
40
  /** How long one payload may occupy the worker before the host gives up. */
@@ -1,4 +1,4 @@
1
- import { AppError } from '../../shared/errors';
1
+ import { AppError } from '../../shared/errors.js';
2
2
  /** Above this, `validate` + `transform` are offloaded to a Worker (02 §7.1). */
3
3
  export const MARSHALLER_WORKER_OFFLOAD_BYTES = 512 * 1024;
4
4
  /**
@@ -61,7 +61,7 @@
61
61
  * - Inconsistency errors (e.g., second chunk claims different total count) delete the entry immediately; recovery requires retransmitting all chunks from the client
62
62
  * - The service itself is a singleton; do NOT create multiple instances per server or request thread
63
63
  */
64
- import type { CtgrDecoded } from './types';
64
+ import type { CtgrDecoded } from './types.js';
65
65
  /**
66
66
  * Configuration for ChunkAccumulator resource limits and timing behavior.
67
67
  *
@@ -61,8 +61,8 @@
61
61
  * - Inconsistency errors (e.g., second chunk claims different total count) delete the entry immediately; recovery requires retransmitting all chunks from the client
62
62
  * - The service itself is a singleton; do NOT create multiple instances per server or request thread
63
63
  */
64
- import { decodePayload, parseChunk } from './codec';
65
- import { sha256Hex } from './digest';
64
+ import { decodePayload, parseChunk } from './codec.js';
65
+ import { sha256Hex } from './digest.js';
66
66
  /**
67
67
  * ChunkAccumulator — The hardened service for multi-chunk payload assembly.
68
68
  *
@@ -1,4 +1,4 @@
1
- import type { CtgrDecoded } from './types';
1
+ import type { CtgrDecoded } from './types.js';
2
2
  export interface ChunkStoreOpts {
3
3
  /** Max concurrent in-flight (incomplete) payloads. Default 200. */
4
4
  maxInFlight?: number;
@@ -9,8 +9,8 @@
9
9
  * It NEVER throws: a malformed/corrupt/oversized payload returns an `Error` the
10
10
  * middleware maps to a 400; an intermediate chunk returns `false` (→ 202).
11
11
  */
12
- import { decodePayload, parseChunk } from './codec';
13
- import { sha256Hex } from './digest';
12
+ import { decodePayload, parseChunk } from './codec.js';
13
+ import { sha256Hex } from './digest.js';
14
14
  export class ChunkStore {
15
15
  entries = new Map();
16
16
  maxInFlight;
@@ -1,4 +1,4 @@
1
- import { type CtgrDecoded, type CtgrMethod, type EncodedRequest, type EncodeOpts, type UploadedFile } from './types';
1
+ import { type CtgrDecoded, type CtgrMethod, type EncodedRequest, type EncodeOpts, type UploadedFile } from './types.js';
2
2
  /**
3
3
  * Encode a mutating request into one or more `/ctgr<XX>` GET-twin chunks. Small
4
4
  * bodies become a single chunk; large bodies gzip (when over the threshold) and split
@@ -11,11 +11,11 @@
11
11
  * double-base64'd and whose swap/reverse was trivially-reversible obfuscation, not
12
12
  * protection — the real protection (getOnly JWT claim + CSRF guard) is unchanged.
13
13
  */
14
- import { fromBase64, fromBase64url, toBase64url } from './base64url';
15
- import { gunzip, gzip } from './compress';
16
- import { crc32 } from './crc32';
17
- import { sha256Hex } from './digest';
18
- import { ABBREV_TO_METHOD, CTGR_WIRE_VERSION, METHOD_TO_ABBREV, } from './types';
14
+ import { fromBase64, fromBase64url, toBase64url } from './base64url.js';
15
+ import { gunzip, gzip } from './compress.js';
16
+ import { crc32 } from './crc32.js';
17
+ import { sha256Hex } from './digest.js';
18
+ import { ABBREV_TO_METHOD, CTGR_WIRE_VERSION, METHOD_TO_ABBREV, } from './types.js';
19
19
  const DEFAULT_CHUNK_SIZE = 1800;
20
20
  const DEFAULT_GZIP_THRESHOLD = 1024;
21
21
  const textEncoder = new TextEncoder();
@@ -1,9 +1,9 @@
1
- export { fromBase64, fromBase64url, toBase64, toBase64url } from './base64url';
2
- export { ChunkAccumulator, type ChunkAccumulatorConfig, } from './chunkAccumulator';
3
- export { ChunkStore, type ChunkStoreOpts } from './chunkStore';
4
- export { decodePayload, decodeRequest, encodeRequest, extractFiles, type ParsedChunk, parseChunk, } from './codec';
5
- export { gunzip, gzip } from './compress';
6
- export { crc32 } from './crc32';
7
- export { sha256Hex } from './digest';
8
- export { ABBREV_TO_METHOD, CTGR_WIRE_VERSION, type CtgrAbbrev, type CtgrDecoded, type CtgrMethod, type EncodedChunk, type EncodedRequest, type EncodeOpts, METHOD_TO_ABBREV, type SerializedFile, type UploadedFile, } from './types';
9
- export { decodeV0Payload, V0ChunkStore, type V0ChunkStoreOpts } from './v0compat';
1
+ export { fromBase64, fromBase64url, toBase64, toBase64url } from './base64url.js';
2
+ export { ChunkAccumulator, type ChunkAccumulatorConfig, } from './chunkAccumulator.js';
3
+ export { ChunkStore, type ChunkStoreOpts } from './chunkStore.js';
4
+ export { decodePayload, decodeRequest, encodeRequest, extractFiles, type ParsedChunk, parseChunk, } from './codec.js';
5
+ export { gunzip, gzip } from './compress.js';
6
+ export { crc32 } from './crc32.js';
7
+ export { sha256Hex } from './digest.js';
8
+ export { ABBREV_TO_METHOD, CTGR_WIRE_VERSION, type CtgrAbbrev, type CtgrDecoded, type CtgrMethod, type EncodedChunk, type EncodedRequest, type EncodeOpts, METHOD_TO_ABBREV, type SerializedFile, type UploadedFile, } from './types.js';
9
+ export { decodeV0Payload, V0ChunkStore, type V0ChunkStoreOpts } from './v0compat.js';
@@ -6,12 +6,12 @@
6
6
  * The ChunkAccumulator is the primary abstraction for multi-chunk payload assembly;
7
7
  * ChunkStore is kept for backward compatibility (they are identical implementations).
8
8
  */
9
- export { fromBase64, fromBase64url, toBase64, toBase64url } from './base64url';
10
- export { ChunkAccumulator, } from './chunkAccumulator';
11
- export { ChunkStore } from './chunkStore';
12
- export { decodePayload, decodeRequest, encodeRequest, extractFiles, parseChunk, } from './codec';
13
- export { gunzip, gzip } from './compress';
14
- export { crc32 } from './crc32';
15
- export { sha256Hex } from './digest';
16
- export { ABBREV_TO_METHOD, CTGR_WIRE_VERSION, METHOD_TO_ABBREV, } from './types';
17
- export { decodeV0Payload, V0ChunkStore } from './v0compat';
9
+ export { fromBase64, fromBase64url, toBase64, toBase64url } from './base64url.js';
10
+ export { ChunkAccumulator, } from './chunkAccumulator.js';
11
+ export { ChunkStore } from './chunkStore.js';
12
+ export { decodePayload, decodeRequest, encodeRequest, extractFiles, parseChunk, } from './codec.js';
13
+ export { gunzip, gzip } from './compress.js';
14
+ export { crc32 } from './crc32.js';
15
+ export { sha256Hex } from './digest.js';
16
+ export { ABBREV_TO_METHOD, CTGR_WIRE_VERSION, METHOD_TO_ABBREV, } from './types.js';
17
+ export { decodeV0Payload, V0ChunkStore } from './v0compat.js';
@@ -1,4 +1,4 @@
1
- import type { CtgrDecoded, CtgrMethod } from './types';
1
+ import type { CtgrDecoded, CtgrMethod } from './types.js';
2
2
  /** Reverse the v0 client pipeline (`encode→reverse→swap→encode→swap`) and parse JSON. */
3
3
  export declare function decodeV0Payload(encoded: string): unknown;
4
4
  export interface V0ChunkStoreOpts {
@@ -9,8 +9,8 @@
9
9
  * heuristic, since v1 always sets `_cv=1`. v0 carried the method only in the path
10
10
  * (`/ctgr<XX>`), so the middleware passes the path-derived method in.
11
11
  */
12
- import { fromBase64 } from './base64url';
13
- import { extractFiles } from './codec';
12
+ import { fromBase64 } from './base64url.js';
13
+ import { extractFiles } from './codec.js';
14
14
  const decodeBase64Utf8 = (str) => new TextDecoder().decode(fromBase64(str));
15
15
  const reverseString = (str) => str.split('').reverse().join('');
16
16
  const swapEnds = (str) => {
package/dist/index.d.ts CHANGED
@@ -11,5 +11,5 @@
11
11
  * drag 47 dependencies into every consumer.
12
12
  */
13
13
  export declare const VERSION = "1.0.0";
14
- export { contrastText } from './core/color';
15
- export { isSiteCopyKey, SITE_COPY_BAKED_ONLY, SITE_COPY_DEFAULTS, SITE_COPY_KEYS, SITE_COPY_MAX_VALUE_LENGTH, type SiteCopyKey, } from './core/site/siteCopy';
14
+ export { contrastText } from './core/color.js';
15
+ export { isSiteCopyKey, SITE_COPY_BAKED_ONLY, SITE_COPY_DEFAULTS, SITE_COPY_KEYS, SITE_COPY_MAX_VALUE_LENGTH, type SiteCopyKey, } from './core/site/siteCopy.js';
package/dist/index.js CHANGED
@@ -13,5 +13,5 @@
13
13
  export const VERSION = '1.0.0';
14
14
  // The two root-level conveniences the pre-split "." barrel actually served:
15
15
  // the WCAG readable-foreground picker and the baked marketing-copy contract.
16
- export { contrastText } from './core/color';
17
- export { isSiteCopyKey, SITE_COPY_BAKED_ONLY, SITE_COPY_DEFAULTS, SITE_COPY_KEYS, SITE_COPY_MAX_VALUE_LENGTH, } from './core/site/siteCopy';
16
+ export { contrastText } from './core/color.js';
17
+ export { isSiteCopyKey, SITE_COPY_BAKED_ONLY, SITE_COPY_DEFAULTS, SITE_COPY_KEYS, SITE_COPY_MAX_VALUE_LENGTH, } from './core/site/siteCopy.js';
@@ -1,3 +1,3 @@
1
- export { installCheapDomInspect } from "./domInspect";
2
- export { asBody, bytesResponse, bytesStream } from "./bytes";
3
- export { type FetchImpl, fakeFetch } from "./fakeFetch";
1
+ export { installCheapDomInspect } from "./domInspect.js";
2
+ export { asBody, bytesResponse, bytesStream } from "./bytes.js";
3
+ export { type FetchImpl, fakeFetch } from "./fakeFetch.js";
@@ -6,6 +6,6 @@
6
6
  // can `import { installCheapDomInspect } from "cursedbelt-core/testing"` instead of
7
7
  // keeping a ~60-line hand-port of domInspect.ts — the file already lived here,
8
8
  // it just had no export.
9
- export { installCheapDomInspect } from "./domInspect";
10
- export { asBody, bytesResponse, bytesStream } from "./bytes";
11
- export { fakeFetch } from "./fakeFetch";
9
+ export { installCheapDomInspect } from "./domInspect.js";
10
+ export { asBody, bytesResponse, bytesStream } from "./bytes.js";
11
+ export { fakeFetch } from "./fakeFetch.js";
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "cursedbelt-core",
3
- "version": "2.0.0",
3
+ "version": "2.1.1",
4
4
  "license": "ISC",
5
5
  "type": "module",
6
- "description": "Framework-agnostic core of the cursedbelt split \u2014 isomorphic domain logic. cwip below it, cursedbelt-server and cursedbelt (react) above it.",
6
+ "description": "Framework-agnostic core of the cursedbelt split — isomorphic domain logic. cwip below it, cursedbelt-server and cursedbelt (react) above it.",
7
7
  "cursed": {
8
8
  "schemaVersion": 1,
9
9
  "app": {
@@ -20,7 +20,7 @@
20
20
  "typecheck": "tsc -p tsconfig.build.json --noEmit",
21
21
  "test": "NODE_ENV=development bun test src",
22
22
  "build": "bun run clean && tsc -p tsconfig.build.json",
23
- "paths": "bun ../../tools/check-paths.ts .",
23
+ "paths": "bun run scripts/paths.ts",
24
24
  "verify": "bun run paths && bun run typecheck && bun run build && bun run test",
25
25
  "prepublishOnly": "bun run verify"
26
26
  },
@@ -125,6 +125,12 @@
125
125
  "source": "./src/core/download.ts",
126
126
  "import": "./dist/core/download.js"
127
127
  },
128
+ "./duplicates": {
129
+ "types": "./dist/core/duplicates/index.d.ts",
130
+ "bun": "./src/core/duplicates/index.ts",
131
+ "source": "./src/core/duplicates/index.ts",
132
+ "import": "./dist/core/duplicates/index.js"
133
+ },
128
134
  "./errors": {
129
135
  "types": "./dist/shared/errors.d.ts",
130
136
  "bun": "./src/shared/errors.ts",
@@ -356,7 +362,7 @@
356
362
  },
357
363
  "dependencies": {
358
364
  "@leeoniya/ufuzzy": "^1.0.19",
359
- "cwip": "^4.1.0"
365
+ "cwip": "^4.6.0"
360
366
  },
361
367
  "peerDependencies": {
362
368
  "zod": "^4.4.0"
@@ -1,5 +1,5 @@
1
1
  import { describe, expect, it } from 'bun:test';
2
- import { AppConfigSchema, effectiveSignupMode, featureKeysOf, resolveInstanceFeatures, ROUTE_PATH_RE } from './appConfig';
2
+ import { AppConfigSchema, effectiveSignupMode, featureKeysOf, resolveInstanceFeatures, ROUTE_PATH_RE } from './appConfig.js';
3
3
 
4
4
  const base = (over: Record<string, unknown> = {}) => ({
5
5
  configVersion: 1,
@@ -12,10 +12,10 @@ export {
12
12
  describeActivityResource,
13
13
  isActivityAction,
14
14
  isActivityOutcome,
15
- } from "./model";
15
+ } from "./model.js";
16
16
  export type {
17
17
  ActivityEvent,
18
18
  ActivityEventInput,
19
19
  ActivityOutcome,
20
20
  ActivityResource,
21
- } from "./model";
21
+ } from "./model.js";
@@ -30,7 +30,7 @@
30
30
  * - **`id` is source-qualified** (`log:41`, `share:12`), because the stream is a
31
31
  * UNION over more than one table and two tables' autoincrements collide at 1.
32
32
  */
33
- import type { ShareEvent } from "../sharing/model";
33
+ import type { ShareEvent } from "../sharing/model.js";
34
34
 
35
35
  /** How it went. `denied` is its own outcome rather than an `error`: a refused
36
36
  * action is the single most interesting row in an audit trail, and one that
@@ -44,7 +44,7 @@ export const isActivityOutcome = (value: unknown): value is ActivityOutcome =>
44
44
 
45
45
  /**
46
46
  * What was acted on: `(app, kind, id)` — deliberately identical to
47
- * {@link import("../sharing/model").ShareResource}, so a resource can be handed
47
+ * {@link import("../sharing/model.js").ShareResource}, so a resource can be handed
48
48
  * from one primitive to the other without a translation layer.
49
49
  */
50
50
  export interface ActivityResource {
@@ -14,5 +14,5 @@
14
14
  *
15
15
  * The React furniture that renders it lives in `cursedbelt/react/analytics`.
16
16
  */
17
- export * from './range';
18
- export * from './schedule';
17
+ export * from './range.js';
18
+ export * from './schedule.js';
@@ -16,7 +16,7 @@ import {
16
16
  startOfDay,
17
17
  startOfHour,
18
18
  unitForSpan,
19
- } from './range';
19
+ } from './range.js';
20
20
 
21
21
  /** 2026-08-13 14:30 LOCAL — the tests are about local calendar behaviour. */
22
22
  const NOW = new Date(2026, 7, 13, 14, 30, 0).getTime();
@@ -10,7 +10,7 @@ import {
10
10
  simulateSchedule,
11
11
  stretchAt,
12
12
  sweepFleetSizes,
13
- } from './schedule';
13
+ } from './schedule.js';
14
14
 
15
15
  const MIN = 60_000;
16
16
  const item = (id: string, minutes: number, rest: Partial<ScheduleItem> = {}): ScheduleItem => ({
@@ -7,7 +7,7 @@ import {
7
7
  flattenCategoryForest,
8
8
  wouldCreateCycle,
9
9
  type Category,
10
- } from './index';
10
+ } from './index.js';
11
11
 
12
12
  const cat = (id: string, parentId: string | null, label: string, order = 0): Category => ({
13
13
  id,
@@ -8,12 +8,12 @@ export type {
8
8
  CategoryCreateInput,
9
9
  CategoryNode,
10
10
  CategoryStore,
11
- } from './types';
11
+ } from './types.js';
12
12
  export {
13
13
  buildCategoryForest,
14
14
  categoryPath,
15
15
  descendantCategoryIds,
16
16
  flattenCategoryForest,
17
17
  wouldCreateCycle,
18
- } from './tree';
19
- export { createMemoryCategoryStore } from './memoryStore';
18
+ } from './tree.js';
19
+ export { createMemoryCategoryStore } from './memoryStore.js';
@@ -1,5 +1,5 @@
1
- import type { Category, CategoryCreateInput, CategoryStore } from './types';
2
- import { wouldCreateCycle } from './tree';
1
+ import type { Category, CategoryCreateInput, CategoryStore } from './types.js';
2
+ import { wouldCreateCycle } from './tree.js';
3
3
 
4
4
  /**
5
5
  * Zero-config in-memory {@link CategoryStore}: a per-boot `Map` per tenant, so a
@@ -1,4 +1,4 @@
1
- import type { Category, CategoryNode } from './types';
1
+ import type { Category, CategoryNode } from './types.js';
2
2
 
3
3
  /** Sort siblings by explicit `order`, then label (locale-aware, stable). */
4
4
  function compareSiblings(a: Category, b: Category): number {
@@ -1,5 +1,5 @@
1
1
  import { describe, expect, it } from 'bun:test';
2
- import { contrastText } from './color';
2
+ import { contrastText } from './color.js';
3
3
 
4
4
  describe('contrastText', () => {
5
5
  it('returns white on dark backgrounds', () => {