@drawnagency/primitives 0.1.95 → 0.1.97

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 (86) hide show
  1. package/dist/{chunk-ZKGMLLUQ.js → chunk-5BFPJWIO.js} +5 -5
  2. package/dist/chunk-EUSWEVOA.js +29 -0
  3. package/dist/{chunk-CI624HRS.js → chunk-F2337B35.js} +1 -1
  4. package/dist/{chunk-QQBBYQYD.js → chunk-FOEZNEF5.js} +9 -9
  5. package/dist/chunk-HJSE6SK5.js +27 -0
  6. package/dist/{chunk-YUUUCDXN.js → chunk-II3X7GLJ.js} +1 -1
  7. package/dist/{chunk-DJAJRBPD.js → chunk-KPP2CFW6.js} +2 -2
  8. package/dist/{chunk-ABV7OZB3.js → chunk-QEHPGA5N.js} +26 -8
  9. package/dist/{chunk-SOILPZ7U.js → chunk-S4L3ZFKM.js} +1 -1
  10. package/dist/{chunk-RQPHPAT2.js → chunk-YDXBJR74.js} +2 -2
  11. package/dist/chunk-ZD6XGRQM.js +85 -0
  12. package/dist/collab/rebase.d.ts +20 -0
  13. package/dist/collab/rebase.d.ts.map +1 -1
  14. package/dist/collab/rebase.js +17 -0
  15. package/dist/components/editor/DropEdgeIndicator.d.ts +6 -1
  16. package/dist/components/editor/DropEdgeIndicator.d.ts.map +1 -1
  17. package/dist/components/editor/NavGroupRow.d.ts +21 -0
  18. package/dist/components/editor/NavGroupRow.d.ts.map +1 -0
  19. package/dist/components/editor/PagesModal.d.ts +7 -1
  20. package/dist/components/editor/PagesModal.d.ts.map +1 -1
  21. package/dist/components/editor/index.js +4 -4
  22. package/dist/components/primitives/index.js +7 -7
  23. package/dist/components/sections/brand-guide.js +6 -6
  24. package/dist/components/sections/register-schemas.js +10 -10
  25. package/dist/components/sections/register.js +10 -10
  26. package/dist/components/shared/Navigation.d.ts.map +1 -1
  27. package/dist/components/shell/EditorShell.d.ts.map +1 -1
  28. package/dist/hooks/useSortableNavRow.d.ts +27 -0
  29. package/dist/hooks/useSortableNavRow.d.ts.map +1 -0
  30. package/dist/index.js +60 -38
  31. package/dist/lib/dexie.d.ts.map +1 -1
  32. package/dist/lib/dexie.js +3 -1
  33. package/dist/lib/index-ops.d.ts +77 -0
  34. package/dist/lib/index-ops.d.ts.map +1 -0
  35. package/dist/lib/index-ops.js +223 -0
  36. package/dist/lib/index.d.ts +1 -0
  37. package/dist/lib/index.d.ts.map +1 -1
  38. package/dist/lib/index.js +17 -6
  39. package/dist/lib/loader.d.ts +2 -2
  40. package/dist/lib/loader.d.ts.map +1 -1
  41. package/dist/lib/nav-tree.d.ts +63 -0
  42. package/dist/lib/nav-tree.d.ts.map +1 -0
  43. package/dist/lib/nav.d.ts +13 -2
  44. package/dist/lib/nav.d.ts.map +1 -1
  45. package/dist/lib/pages.d.ts +0 -1
  46. package/dist/lib/pages.d.ts.map +1 -1
  47. package/dist/lib/reorder.d.ts +24 -0
  48. package/dist/lib/reorder.d.ts.map +1 -1
  49. package/dist/media/index.js +2 -2
  50. package/dist/schemas/index.d.ts +1 -0
  51. package/dist/schemas/index.d.ts.map +1 -1
  52. package/dist/schemas/index.js +8 -2
  53. package/dist/schemas/nav.d.ts +40 -0
  54. package/dist/schemas/nav.d.ts.map +1 -0
  55. package/dist/schemas/site-config.d.ts +11 -1
  56. package/dist/schemas/site-config.d.ts.map +1 -1
  57. package/dist/storage/index.d.ts +1 -1
  58. package/dist/storage/index.d.ts.map +1 -1
  59. package/dist/storage/index.js +9 -3
  60. package/dist/storage/types.d.ts +44 -0
  61. package/dist/storage/types.d.ts.map +1 -1
  62. package/package.json +5 -1
  63. package/src/collab/rebase.ts +35 -0
  64. package/src/components/editor/DropEdgeIndicator.tsx +15 -8
  65. package/src/components/editor/NavGroupRow.tsx +126 -0
  66. package/src/components/editor/PagesModal.tsx +150 -29
  67. package/src/components/shared/Navigation.tsx +51 -13
  68. package/src/components/shell/EditorShell.tsx +40 -10
  69. package/src/hooks/useSortableNavRow.ts +139 -0
  70. package/src/lib/dexie.ts +6 -1
  71. package/src/lib/index-ops.ts +309 -0
  72. package/src/lib/index.ts +1 -0
  73. package/src/lib/loader.ts +31 -4
  74. package/src/lib/nav-tree.ts +192 -0
  75. package/src/lib/nav.ts +43 -10
  76. package/src/lib/pages.ts +14 -11
  77. package/src/lib/reorder.ts +56 -0
  78. package/src/schemas/index.ts +1 -0
  79. package/src/schemas/nav.ts +36 -0
  80. package/src/schemas/site-config.ts +9 -1
  81. package/src/storage/index.ts +1 -1
  82. package/src/storage/types.ts +55 -0
  83. package/dist/chunk-VAOW4WXQ.js +0 -15
  84. package/dist/chunk-YBVK254P.js +0 -14
  85. /package/dist/{chunk-53DKVAB6.js → chunk-72CG6AA2.js} +0 -0
  86. /package/dist/{chunk-3HJDMFTB.js → chunk-XYCCXKVC.js} +0 -0
package/dist/lib/index.js CHANGED
@@ -18,7 +18,7 @@ import {
18
18
  safeRedirect,
19
19
  suggestAccessibleOn,
20
20
  wcagLevel
21
- } from "../chunk-ABV7OZB3.js";
21
+ } from "../chunk-QEHPGA5N.js";
22
22
  import {
23
23
  curatedIcons,
24
24
  getIcon
@@ -34,17 +34,22 @@ import {
34
34
  generateNavLinks,
35
35
  sanitizeHtml,
36
36
  toSectionId
37
- } from "../chunk-53DKVAB6.js";
37
+ } from "../chunk-72CG6AA2.js";
38
38
  import {
39
39
  cn
40
40
  } from "../chunk-7EOSDFS3.js";
41
- import "../chunk-S2L3BPLS.js";
42
41
  import {
43
- audiencePasses
44
- } from "../chunk-YBVK254P.js";
42
+ audiencePasses,
43
+ groupDisplayLabel,
44
+ groupOfPage,
45
+ navEntries,
46
+ pagesInNavOrder,
47
+ reconcileNav
48
+ } from "../chunk-ZD6XGRQM.js";
49
+ import "../chunk-S2L3BPLS.js";
45
50
  import "../chunk-5BM2Y3NJ.js";
51
+ import "../chunk-HJSE6SK5.js";
46
52
  import "../chunk-TZQPOR5A.js";
47
- import "../chunk-RHWWQV3J.js";
48
53
  import {
49
54
  clearRegistry,
50
55
  createRegistry,
@@ -56,6 +61,7 @@ import {
56
61
  registerSchema,
57
62
  registerSection
58
63
  } from "../chunk-VKAGMEKE.js";
64
+ import "../chunk-RHWWQV3J.js";
59
65
  import "../chunk-FM4M4Q2W.js";
60
66
  import "../chunk-NSCT3AMV.js";
61
67
  import {
@@ -88,12 +94,17 @@ export {
88
94
  getSchema,
89
95
  getSection,
90
96
  gridColsClass,
97
+ groupDisplayLabel,
98
+ groupOfPage,
91
99
  loadSiteContent,
92
100
  loadStaticSiteContent,
93
101
  mergeSiteContent,
94
102
  navChangeEvent,
103
+ navEntries,
95
104
  onPrimaryVarValue,
105
+ pagesInNavOrder,
96
106
  primaryVarValue,
107
+ reconcileNav,
97
108
  registerSchema,
98
109
  registerSection,
99
110
  relativeLuminance,
@@ -13,13 +13,13 @@ export interface SiteContent {
13
13
  * Validates each section file against its Zod schema.
14
14
  * Returns sections in the concatenated order of every page's order.
15
15
  */
16
- export declare function mergeSiteContent(index: SiteIndex, sectionFiles: Record<string, unknown>): SiteContent;
16
+ export declare function mergeSiteContent(index: SiteIndex, sectionFiles: Record<string, unknown>, navFile?: unknown): SiteContent;
17
17
  /**
18
18
  * Build a SiteContent from a Vite `import.meta.glob` result.
19
19
  * Callers glob the section JSON files at build time, pass the parsed index, and
20
20
  * receive sections merged across every page's order.
21
21
  */
22
- export declare function loadStaticSiteContent(index: SiteIndex, sectionGlob: Record<string, unknown>): SiteContent;
22
+ export declare function loadStaticSiteContent(index: SiteIndex, sectionGlob: Record<string, unknown>, navFile?: unknown): SiteContent;
23
23
  /**
24
24
  * Load site content from the filesystem.
25
25
  * For use in scripts and tests — Astro pages should use import.meta.glob instead.
@@ -1 +1 @@
1
- {"version":3,"file":"loader.d.ts","sourceRoot":"","sources":["../../src/lib/loader.ts"],"names":[],"mappings":"AAAA,OAAO,EAAoB,KAAK,OAAO,EAAE,MAAM,qBAAqB,CAAC;AACrE,OAAO,EAAe,KAAK,SAAS,EAAE,KAAK,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAKvF,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,EAAE,WAAW,CAAC;CACnB;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,aAAa,EAAE,CAAC;IAC1B,KAAK,EAAE,SAAS,CAAC;CAClB;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,SAAS,EAChB,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GACpC,WAAW,CA6Cb;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,SAAS,EAChB,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GACnC,WAAW,CAQb;AAED;;;GAGG;AACH,wBAAsB,eAAe,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAe9E"}
1
+ {"version":3,"file":"loader.d.ts","sourceRoot":"","sources":["../../src/lib/loader.ts"],"names":[],"mappings":"AAAA,OAAO,EAAoB,KAAK,OAAO,EAAE,MAAM,qBAAqB,CAAC;AACrE,OAAO,EAAe,KAAK,SAAS,EAAE,KAAK,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAMvF,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,EAAE,WAAW,CAAC;CACnB;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,aAAa,EAAE,CAAC;IAC1B,KAAK,EAAE,SAAS,CAAC;CAClB;AAiBD;;;;GAIG;AACH,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,SAAS,EAChB,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACrC,OAAO,CAAC,EAAE,OAAO,GAChB,WAAW,CA6Cb;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,SAAS,EAChB,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACpC,OAAO,CAAC,EAAE,OAAO,GAChB,WAAW,CAQb;AAED;;;GAGG;AACH,wBAAsB,eAAe,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAwB9E"}
@@ -0,0 +1,63 @@
1
+ import type { NavEntry, NavGroupEntry } from "../schemas/nav";
2
+ import type { Page, SiteIndex } from "../schemas/site-config";
3
+ /**
4
+ * Repair an authored nav against the current page set.
5
+ *
6
+ * Total, pure, and idempotent: every possible input maps to a valid nav in
7
+ * which each page appears exactly once. This is what lets the sidecar tolerate
8
+ * an older writer editing index.json without knowing about nav.json — the worst
9
+ * case is a page drifting to the bottom of the nav, never a hard failure. It is
10
+ * deliberately NOT a schema refinement: parse-rejecting content leaves viewer
11
+ * and editor both refusing to load, with no in-product repair path.
12
+ *
13
+ * "Total" is meant literally, so the shape guards below are load-bearing rather
14
+ * than paranoia: callers include agent-authored JSON and live editor state, and
15
+ * `pages` is not id-unique (IndexSchema checks slug uniqueness, not page ids).
16
+ * Every entry is cloned on the way out — the editor holds this in React state,
17
+ * where a returned-by-reference entry would let a later mutation silently edit
18
+ * prior state and defeat identity-based re-render checks.
19
+ */
20
+ export declare function reconcileNav(entries: NavEntry[], pages: Page[]): NavEntry[];
21
+ /**
22
+ * The ONLY sanctioned way to read the nav. Always reconciles, so callers never
23
+ * have to reason about whether index.nav is present, stale, or authored by an
24
+ * older writer. Cheap and idempotent — call it freely.
25
+ */
26
+ export declare function navEntries(index: SiteIndex): NavEntry[];
27
+ /** Pages in authored nav order. Use this anywhere user-visible sequence matters. */
28
+ export declare function pagesInNavOrder(index: SiteIndex): Page[];
29
+ export declare function groupOfPage(index: SiteIndex, pageId: string): NavGroupEntry | null;
30
+ /** Display fallback for blank group labels — mirrors pageDisplayTitle in lib/pages.ts. */
31
+ export declare function groupDisplayLabel(label: string): string;
32
+ /** A position in the nav: inside a group, or at the top level when groupId is null. */
33
+ export interface NavLocation {
34
+ groupId: string | null;
35
+ index: number;
36
+ }
37
+ export type NavRow = {
38
+ kind: "page";
39
+ pageId: string;
40
+ groupId: string | null;
41
+ index: number;
42
+ } | {
43
+ kind: "group";
44
+ groupId: string;
45
+ index: number;
46
+ } | {
47
+ kind: "empty-group";
48
+ groupId: string;
49
+ } | {
50
+ kind: "tail";
51
+ };
52
+ export declare function addGroup(index: SiteIndex, groupId: string): SiteIndex;
53
+ export declare function renameGroup(index: SiteIndex, groupId: string, label: string): SiteIndex;
54
+ export declare function setGroupStartCollapsed(index: SiteIndex, groupId: string, startCollapsed: boolean): SiteIndex;
55
+ /** Removes the group; its members become top-level entries at the group's slot. */
56
+ export declare function deleteGroup(index: SiteIndex, groupId: string): SiteIndex;
57
+ /**
58
+ * The single reorder+reparent primitive. `from` and `to` are both NavLocations,
59
+ * so moving within a container, between containers, and promoting/demoting a
60
+ * page are all the same operation.
61
+ */
62
+ export declare function moveNavEntry(index: SiteIndex, from: NavLocation, to: NavLocation): SiteIndex;
63
+ //# sourceMappingURL=nav-tree.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nav-tree.d.ts","sourceRoot":"","sources":["../../src/lib/nav-tree.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAC9D,OAAO,KAAK,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAE9D;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,QAAQ,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,QAAQ,EAAE,CA0C3E;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,SAAS,GAAG,QAAQ,EAAE,CAEvD;AAED,oFAAoF;AACpF,wBAAgB,eAAe,CAAC,KAAK,EAAE,SAAS,GAAG,IAAI,EAAE,CAexD;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,GAAG,aAAa,GAAG,IAAI,CAKlF;AAED,0FAA0F;AAC1F,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEvD;AAED,uFAAuF;AACvF,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,MAAM,MAAM,GACd;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GACvE;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GACjD;IAAE,IAAI,EAAE,aAAa,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAIxC;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAMrB,wBAAgB,QAAQ,CAAC,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,GAAG,SAAS,CAKrE;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,SAAS,CAIvF;AAED,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,OAAO,GAAG,SAAS,CAI5G;AAED,mFAAmF;AACnF,wBAAgB,WAAW,CAAC,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,GAAG,SAAS,CAUxE;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,SAAS,EAAE,IAAI,EAAE,WAAW,EAAE,EAAE,EAAE,WAAW,GAAG,SAAS,CAgC5F"}
package/dist/lib/nav.d.ts CHANGED
@@ -17,9 +17,20 @@ export interface PageNavItem {
17
17
  isActive: boolean;
18
18
  headings: NavItem[];
19
19
  }
20
- export interface SiteNav {
20
+ export interface GroupNavItem {
21
+ kind: "group";
22
+ id: string;
23
+ label: string;
24
+ startCollapsed: boolean;
21
25
  pages: PageNavItem[];
22
- /** Live pages with showInNav off — editor-only group, like the archive. */
26
+ }
27
+ export type SiteNavEntry = ({
28
+ kind: "page";
29
+ } & PageNavItem) | GroupNavItem;
30
+ export interface SiteNav {
31
+ /** The authored tree: loose pages and groups, in nav order. */
32
+ entries: SiteNavEntry[];
33
+ /** Live pages with showInNav off — editor-only flat bucket, like the archive. */
23
34
  hidden: PageNavItem[];
24
35
  archive: PageNavItem[];
25
36
  }
@@ -1 +1 @@
1
- {"version":3,"file":"nav.d.ts","sourceRoot":"","sources":["../../src/lib/nav.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAC9C,OAAO,EAAc,KAAK,eAAe,EAAE,MAAM,YAAY,CAAC;AAE9D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAExD,MAAM,WAAW,OAAO;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,OAAO,EAAE,CAAC;CACrB;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAchD;AAED,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,aAAa,EAAE,EACzB,QAAQ,CAAC,EAAE,eAAe,GACzB,OAAO,EAAE,CAqDX;AAED,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,OAAO,CAAC;IAClB,QAAQ,EAAE,OAAO,EAAE,CAAC;CACrB;AAED,MAAM,WAAW,OAAO;IACtB,KAAK,EAAE,WAAW,EAAE,CAAC;IACrB,2EAA2E;IAC3E,MAAM,EAAE,WAAW,EAAE,CAAC;IACtB,OAAO,EAAE,WAAW,EAAE,CAAC;CACxB;AAMD,wBAAgB,eAAe,CAAC,IAAI,EAAE;IACpC,KAAK,EAAE,SAAS,CAAC;IACjB,cAAc,EAAE,aAAa,EAAE,CAAC;IAChC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,IAAI,EAAE,QAAQ,GAAG,QAAQ,CAAC;IAC1B,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,QAAQ,CAAC,EAAE,eAAe,CAAC;CAC5B,GAAG,OAAO,CA4BV"}
1
+ {"version":3,"file":"nav.d.ts","sourceRoot":"","sources":["../../src/lib/nav.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAC9C,OAAO,EAAc,KAAK,eAAe,EAAE,MAAM,YAAY,CAAC;AAG9D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAExD,MAAM,WAAW,OAAO;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,OAAO,EAAE,CAAC;CACrB;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAchD;AAED,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,aAAa,EAAE,EACzB,QAAQ,CAAC,EAAE,eAAe,GACzB,OAAO,EAAE,CAqDX;AAED,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,OAAO,CAAC;IAClB,QAAQ,EAAE,OAAO,EAAE,CAAC;CACrB;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,OAAO,CAAC;IACd,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,cAAc,EAAE,OAAO,CAAC;IACxB,KAAK,EAAE,WAAW,EAAE,CAAC;CACtB;AAED,MAAM,MAAM,YAAY,GAAG,CAAC;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG,WAAW,CAAC,GAAG,YAAY,CAAC;AAE3E,MAAM,WAAW,OAAO;IACtB,+DAA+D;IAC/D,OAAO,EAAE,YAAY,EAAE,CAAC;IACxB,iFAAiF;IACjF,MAAM,EAAE,WAAW,EAAE,CAAC;IACtB,OAAO,EAAE,WAAW,EAAE,CAAC;CACxB;AAMD,wBAAgB,eAAe,CAAC,IAAI,EAAE;IACpC,KAAK,EAAE,SAAS,CAAC;IACjB,cAAc,EAAE,aAAa,EAAE,CAAC;IAChC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,IAAI,EAAE,QAAQ,GAAG,QAAQ,CAAC;IAC1B,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,QAAQ,CAAC,EAAE,eAAe,CAAC;CAC5B,GAAG,OAAO,CAiDV"}
@@ -13,7 +13,6 @@ export declare function homePage(index: SiteIndex): Page;
13
13
  export declare function pageBySlug(index: SiteIndex, slug: string): Page | undefined;
14
14
  export declare function audiencePasses(access: string[], audience: string | null): boolean;
15
15
  export declare function addPage(index: SiteIndex, id: string): SiteIndex;
16
- export declare function reorderPages(index: SiteIndex, fromIndex: number, toIndex: number): SiteIndex;
17
16
  export declare function setHomePage(index: SiteIndex, pageId: string): SiteIndex;
18
17
  export declare function setPageArchived(index: SiteIndex, pageId: string, archived: boolean): SiteIndex;
19
18
  export declare function setPageFields(index: SiteIndex, pageId: string, patch: Partial<Pick<Page, "title" | "slug" | "showInNav">>): SiteIndex;
@@ -1 +1 @@
1
- {"version":3,"file":"pages.d.ts","sourceRoot":"","sources":["../../src/lib/pages.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAK3E,OAAO,EAAyB,KAAK,eAAe,EAAE,MAAM,6BAA6B,CAAC;AAE1F;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEtD;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAErD;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,MAAM,CAQvE;AAED,wBAAgB,QAAQ,CAAC,KAAK,EAAE,SAAS,EAAE,EAAE,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAEvE;AAED,wBAAgB,QAAQ,CAAC,KAAK,EAAE,SAAS,GAAG,IAAI,CAE/C;AAED,wBAAgB,UAAU,CAAC,KAAK,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAI3E;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAIjF;AAMD,wBAAgB,OAAO,CAAC,KAAK,EAAE,SAAS,EAAE,EAAE,EAAE,MAAM,GAAG,SAAS,CAQ/D;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,SAAS,CAO5F;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,GAAG,SAAS,CAWvE;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,GAAG,SAAS,CAM9F;AAED,wBAAgB,aAAa,CAC3B,KAAK,EAAE,SAAS,EAChB,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,OAAO,CAAC,IAAI,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,GAAG,WAAW,CAAC,CAAC,GACzD,SAAS,CAEX;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,CAAC,eAAe,CAAC,GAAG,SAAS,CAgB5G;AAED,wBAAgB,eAAe,CAC7B,KAAK,EAAE,SAAS,EAChB,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,EAAE,GACf;IAAE,KAAK,EAAE,SAAS,CAAC;IAAC,eAAe,EAAE,MAAM,EAAE,CAAA;CAAE,CAkBjD;AAED,wBAAgB,UAAU,CACxB,KAAK,EAAE,SAAS,EAChB,MAAM,EAAE,MAAM,GACb;IAAE,KAAK,EAAE,SAAS,CAAC;IAAC,iBAAiB,EAAE,MAAM,EAAE,CAAA;CAAE,CAUnD;AAED,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,SAAS,EAChB,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,WAAW,EACjB,WAAW,EAAE,MAAM,GAClB,SAAS,CAQX;AAED,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,GAAG,SAAS,CAOrF;AAED,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,SAAS,EAChB,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,GACd,SAAS,CAWX;AAED,wBAAgB,WAAW,CACzB,KAAK,EAAE,SAAS,EAChB,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,KAAK,GAAG,QAAQ,GACzB,SAAS,CAuBX"}
1
+ {"version":3,"file":"pages.d.ts","sourceRoot":"","sources":["../../src/lib/pages.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAK3E,OAAO,EAAyB,KAAK,eAAe,EAAE,MAAM,6BAA6B,CAAC;AAG1F;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEtD;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAErD;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,MAAM,CAQvE;AAED,wBAAgB,QAAQ,CAAC,KAAK,EAAE,SAAS,EAAE,EAAE,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAEvE;AAED,wBAAgB,QAAQ,CAAC,KAAK,EAAE,SAAS,GAAG,IAAI,CAE/C;AAED,wBAAgB,UAAU,CAAC,KAAK,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAI3E;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAIjF;AAMD,wBAAgB,OAAO,CAAC,KAAK,EAAE,SAAS,EAAE,EAAE,EAAE,MAAM,GAAG,SAAS,CAY/D;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,GAAG,SAAS,CAWvE;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,GAAG,SAAS,CAM9F;AAED,wBAAgB,aAAa,CAC3B,KAAK,EAAE,SAAS,EAChB,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,OAAO,CAAC,IAAI,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,GAAG,WAAW,CAAC,CAAC,GACzD,SAAS,CAEX;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,CAAC,eAAe,CAAC,GAAG,SAAS,CAgB5G;AAED,wBAAgB,eAAe,CAC7B,KAAK,EAAE,SAAS,EAChB,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,EAAE,GACf;IAAE,KAAK,EAAE,SAAS,CAAC;IAAC,eAAe,EAAE,MAAM,EAAE,CAAA;CAAE,CAkBjD;AAED,wBAAgB,UAAU,CACxB,KAAK,EAAE,SAAS,EAChB,MAAM,EAAE,MAAM,GACb;IAAE,KAAK,EAAE,SAAS,CAAC;IAAC,iBAAiB,EAAE,MAAM,EAAE,CAAA;CAAE,CAiBnD;AAED,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,SAAS,EAChB,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,WAAW,EACjB,WAAW,EAAE,MAAM,GAClB,SAAS,CAQX;AAED,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,GAAG,SAAS,CAOrF;AAED,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,SAAS,EAChB,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,GACd,SAAS,CAWX;AAED,wBAAgB,WAAW,CACzB,KAAK,EAAE,SAAS,EAChB,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,KAAK,GAAG,QAAQ,GACzB,SAAS,CAuBX"}
@@ -1,3 +1,4 @@
1
+ import type { NavLocation, NavRow } from "./nav-tree";
1
2
  /**
2
3
  * Compute the destination index for a closest-edge drag-to-reorder drop.
3
4
  *
@@ -9,4 +10,27 @@
9
10
  * Returns the target index, or `null` when the drop would not move the row.
10
11
  */
11
12
  export declare function computeReorderTarget(fromIndex: number, overIndex: number, edge: "top" | "bottom" | null): number | null;
13
+ /**
14
+ * Resolve a closest-edge drop over a nav row into a destination NavLocation.
15
+ *
16
+ * With one level of nesting a target's own identity determines its container,
17
+ * so page drags need no depth inference. Group drags DO need remapping: a group
18
+ * can only live at the top level, so hovering a member row is reinterpreted as
19
+ * "next to that member's group" rather than rejected — otherwise every member
20
+ * row of every expanded group is an inert dead zone during a group drag.
21
+ *
22
+ * `overEntryIndex` is the row's index in the TOP-LEVEL entry list: its own index
23
+ * for a top-level page or group row, its enclosing group's index for a member
24
+ * row, and the total entry count for the tail row. Group-drag remapping targets
25
+ * it directly.
26
+ *
27
+ * Returns null when the drop would not move anything, or is not permitted.
28
+ */
29
+ export declare function computeNavDropTarget(args: {
30
+ from: NavLocation;
31
+ fromKind: "page" | "group";
32
+ over: NavRow;
33
+ overEntryIndex: number;
34
+ edge: "top" | "bottom" | null;
35
+ }): NavLocation | null;
12
36
  //# sourceMappingURL=reorder.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"reorder.d.ts","sourceRoot":"","sources":["../../src/lib/reorder.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,wBAAgB,oBAAoB,CAClC,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,KAAK,GAAG,QAAQ,GAAG,IAAI,GAC5B,MAAM,GAAG,IAAI,CAKf"}
1
+ {"version":3,"file":"reorder.d.ts","sourceRoot":"","sources":["../../src/lib/reorder.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAEtD;;;;;;;;;GASG;AACH,wBAAgB,oBAAoB,CAClC,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,KAAK,GAAG,QAAQ,GAAG,IAAI,GAC5B,MAAM,GAAG,IAAI,CAKf;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE;IACzC,IAAI,EAAE,WAAW,CAAC;IAClB,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,cAAc,EAAE,MAAM,CAAC;IACvB,IAAI,EAAE,KAAK,GAAG,QAAQ,GAAG,IAAI,CAAC;CAC/B,GAAG,WAAW,GAAG,IAAI,CA8BrB"}
@@ -18,8 +18,8 @@ import {
18
18
  resolveMedia,
19
19
  sanitizeMediaName,
20
20
  setMediaProvider
21
- } from "../chunk-SOILPZ7U.js";
22
- import "../chunk-YBVK254P.js";
21
+ } from "../chunk-S4L3ZFKM.js";
22
+ import "../chunk-ZD6XGRQM.js";
23
23
  import {
24
24
  ImageManifestSchema,
25
25
  MediaConfigSchema,
@@ -1,6 +1,7 @@
1
1
  export * from "./shared";
2
2
  export * from "./sections";
3
3
  export * from "./site-config";
4
+ export * from "./nav";
4
5
  export * from "./media";
5
6
  export * from "./audience";
6
7
  export * from "./media-grid-options";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/schemas/index.ts"],"names":[],"mappings":"AAAA,cAAc,UAAU,CAAC;AACzB,cAAc,YAAY,CAAC;AAC3B,cAAc,eAAe,CAAC;AAC9B,cAAc,SAAS,CAAC;AACxB,cAAc,YAAY,CAAC;AAC3B,cAAc,sBAAsB,CAAC;AACrC,cAAc,QAAQ,CAAC;AACvB,cAAc,QAAQ,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/schemas/index.ts"],"names":[],"mappings":"AAAA,cAAc,UAAU,CAAC;AACzB,cAAc,YAAY,CAAC;AAC3B,cAAc,eAAe,CAAC;AAC9B,cAAc,OAAO,CAAC;AACtB,cAAc,SAAS,CAAC;AACxB,cAAc,YAAY,CAAC;AAC3B,cAAc,sBAAsB,CAAC;AACrC,cAAc,QAAQ,CAAC;AACvB,cAAc,QAAQ,CAAC"}
@@ -3,7 +3,7 @@ import {
3
3
  AudienceNameSchema,
4
4
  MediaGridOptionsSchema,
5
5
  slugifyAudienceName
6
- } from "../chunk-3HJDMFTB.js";
6
+ } from "../chunk-XYCCXKVC.js";
7
7
  import {
8
8
  AudienceSchema,
9
9
  RoleSchema,
@@ -16,10 +16,15 @@ import {
16
16
  LinkValueSchema,
17
17
  isSafeHref
18
18
  } from "../chunk-PZJL2S3Z.js";
19
+ import {
20
+ NavEntrySchema,
21
+ NavFileSchema
22
+ } from "../chunk-HJSE6SK5.js";
19
23
  import {
20
24
  getSectionContentSchema,
21
25
  getSectionSchema
22
26
  } from "../chunk-TZQPOR5A.js";
27
+ import "../chunk-VKAGMEKE.js";
23
28
  import {
24
29
  ChatbotConfigSchema,
25
30
  ChatbotTaskSchema,
@@ -42,7 +47,6 @@ import {
42
47
  VariantSchema,
43
48
  normalizeSiteIndex
44
49
  } from "../chunk-RHWWQV3J.js";
45
- import "../chunk-VKAGMEKE.js";
46
50
  import "../chunk-FM4M4Q2W.js";
47
51
  import "../chunk-NSCT3AMV.js";
48
52
  import "../chunk-JSBRDJBE.js";
@@ -64,6 +68,8 @@ export {
64
68
  MediaGridOptionsSchema,
65
69
  MediaItemSchema,
66
70
  MediaReferenceSchema,
71
+ NavEntrySchema,
72
+ NavFileSchema,
67
73
  PageSchema,
68
74
  PageStatusSchema,
69
75
  RESERVED_SLUGS,
@@ -0,0 +1,40 @@
1
+ import { z } from "zod";
2
+ /**
3
+ * The site's authored navigation tree, persisted to `src/content/nav.json`.
4
+ *
5
+ * Deliberately NOT a key on index.json: `applyContentWrite` re-serializes the
6
+ * Zod-parsed index, so any writer running a package version that predates a new
7
+ * index key silently deletes it from disk. A sidecar is absent from an old
8
+ * writer's file list and survives untouched.
9
+ */
10
+ export declare const NavEntrySchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
11
+ kind: z.ZodLiteral<"page">;
12
+ pageId: z.ZodString;
13
+ }, z.core.$strip>, z.ZodObject<{
14
+ kind: z.ZodLiteral<"group">;
15
+ id: z.ZodString;
16
+ label: z.ZodDefault<z.ZodString>;
17
+ startCollapsed: z.ZodDefault<z.ZodBoolean>;
18
+ pageIds: z.ZodDefault<z.ZodArray<z.ZodString>>;
19
+ }, z.core.$strip>], "kind">;
20
+ export declare const NavFileSchema: z.ZodObject<{
21
+ entries: z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
22
+ kind: z.ZodLiteral<"page">;
23
+ pageId: z.ZodString;
24
+ }, z.core.$strip>, z.ZodObject<{
25
+ kind: z.ZodLiteral<"group">;
26
+ id: z.ZodString;
27
+ label: z.ZodDefault<z.ZodString>;
28
+ startCollapsed: z.ZodDefault<z.ZodBoolean>;
29
+ pageIds: z.ZodDefault<z.ZodArray<z.ZodString>>;
30
+ }, z.core.$strip>], "kind">>;
31
+ }, z.core.$strip>;
32
+ export type NavEntry = z.infer<typeof NavEntrySchema>;
33
+ export type NavPageEntry = Extract<NavEntry, {
34
+ kind: "page";
35
+ }>;
36
+ export type NavGroupEntry = Extract<NavEntry, {
37
+ kind: "group";
38
+ }>;
39
+ export type NavFile = z.infer<typeof NavFileSchema>;
40
+ //# sourceMappingURL=nav.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nav.d.ts","sourceRoot":"","sources":["../../src/schemas/nav.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;;;;;;GAOG;AACH,eAAO,MAAM,cAAc;;;;;;;;;2BAgBzB,CAAC;AAEH,eAAO,MAAM,aAAa;;;;;;;;;;;iBAExB,CAAC;AAEH,MAAM,MAAM,QAAQ,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC;AACtD,MAAM,MAAM,YAAY,GAAG,OAAO,CAAC,QAAQ,EAAE;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAC/D,MAAM,MAAM,aAAa,GAAG,OAAO,CAAC,QAAQ,EAAE;IAAE,IAAI,EAAE,OAAO,CAAA;CAAE,CAAC,CAAC;AACjE,MAAM,MAAM,OAAO,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,aAAa,CAAC,CAAC"}
@@ -1,4 +1,5 @@
1
1
  import { z } from "zod";
2
+ import type { NavEntry } from "./nav";
2
3
  export declare const StatusSchema: z.ZodPipe<z.ZodEnum<{
3
4
  draft: "draft";
4
5
  live: "live";
@@ -417,7 +418,16 @@ export declare const IndexSchema: z.ZodPipe<z.ZodPipe<z.ZodObject<{
417
418
  }, z.core.$strip>>;
418
419
  lastModified: z.ZodOptional<z.ZodNullable<z.ZodString>>;
419
420
  }, z.core.$strip>>;
420
- export type SiteIndex = z.infer<typeof CanonicalIndexSchema>;
421
+ /**
422
+ * `nav` is a RUNTIME field only: it is loaded from the src/content/nav.json
423
+ * sidecar and attached by the loader, never parsed from or written to
424
+ * index.json. It is optional because most SiteIndex values (tests, legacy
425
+ * content, partial writes) have no nav — reads go through navEntries(), which
426
+ * reconciles a missing nav into a flat one.
427
+ */
428
+ export type SiteIndex = z.infer<typeof CanonicalIndexSchema> & {
429
+ nav?: NavEntry[];
430
+ };
421
431
  /** Parse + normalize any raw index (legacy or canonical) to the canonical shape. */
422
432
  export declare function normalizeSiteIndex(raw: unknown): SiteIndex;
423
433
  export declare const ChatbotTaskSchema: z.ZodObject<{
@@ -1 +1 @@
1
- {"version":3,"file":"site-config.d.ts","sourceRoot":"","sources":["../../src/schemas/site-config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AASxB,eAAO,MAAM,YAAY;;;;;+FAExB,CAAC;AAEF,eAAO,MAAM,iBAAiB;;;;;;;;;iBAI5B,CAAC;AAEH,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAQ5D,eAAO,MAAM,cAAc,8EAA+E,CAAC;AAW3G,eAAO,MAAM,gBAAgB;;;EAA+B,CAAC;AAE7D,eAAO,MAAM,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAUrB,CAAC;AAEH,MAAM,MAAM,IAAI,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,UAAU,CAAC,CAAC;AA0C9C,QAAA,MAAM,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAuEtB,CAAC;AAEL,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBAAyE,CAAC;AAElG,MAAM,MAAM,SAAS,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAE7D,oFAAoF;AACpF,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,OAAO,GAAG,SAAS,CAE1D;AAMD,eAAO,MAAM,iBAAiB;;;;;iBAM5B,CAAC;AACH,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAE5D,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;iBAiC5B,CAAC;AACL,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAEhE,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAmE3B,CAAC;AAEH,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC"}
1
+ {"version":3,"file":"site-config.d.ts","sourceRoot":"","sources":["../../src/schemas/site-config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAQxB,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAEtC,eAAO,MAAM,YAAY;;;;;+FAExB,CAAC;AAEF,eAAO,MAAM,iBAAiB;;;;;;;;;iBAI5B,CAAC;AAEH,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAQ5D,eAAO,MAAM,cAAc,8EAA+E,CAAC;AAW3G,eAAO,MAAM,gBAAgB;;;EAA+B,CAAC;AAE7D,eAAO,MAAM,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAUrB,CAAC;AAEH,MAAM,MAAM,IAAI,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,UAAU,CAAC,CAAC;AA0C9C,QAAA,MAAM,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAuEtB,CAAC;AAEL,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBAAyE,CAAC;AAElG;;;;;;GAMG;AACH,MAAM,MAAM,SAAS,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,GAAG;IAAE,GAAG,CAAC,EAAE,QAAQ,EAAE,CAAA;CAAE,CAAC;AAEpF,oFAAoF;AACpF,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,OAAO,GAAG,SAAS,CAE1D;AAMD,eAAO,MAAM,iBAAiB;;;;;iBAM5B,CAAC;AACH,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAE5D,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;iBAiC5B,CAAC;AACL,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAEhE,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAmE3B,CAAC;AAEH,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC"}
@@ -1,3 +1,3 @@
1
1
  export type { StorageProvider, FileWrite, AssetStore } from "./types";
2
- export { StorageConflictError, ASSET_CACHE_CONTROL } from "./types";
2
+ export { StorageConflictError, ASSET_CACHE_CONTROL, IDEMPOTENCY_TRAILER, IDEMPOTENCY_KEY_RE, withIdempotencyTrailer } from "./types";
3
3
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/storage/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,eAAe,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACtE,OAAO,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,MAAM,SAAS,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/storage/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,eAAe,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACtE,OAAO,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,sBAAsB,EAAE,MAAM,SAAS,CAAC"}
@@ -1,9 +1,15 @@
1
1
  import {
2
2
  ASSET_CACHE_CONTROL,
3
- StorageConflictError
4
- } from "../chunk-VAOW4WXQ.js";
3
+ IDEMPOTENCY_KEY_RE,
4
+ IDEMPOTENCY_TRAILER,
5
+ StorageConflictError,
6
+ withIdempotencyTrailer
7
+ } from "../chunk-EUSWEVOA.js";
5
8
  import "../chunk-JSBRDJBE.js";
6
9
  export {
7
10
  ASSET_CACHE_CONTROL,
8
- StorageConflictError
11
+ IDEMPOTENCY_KEY_RE,
12
+ IDEMPOTENCY_TRAILER,
13
+ StorageConflictError,
14
+ withIdempotencyTrailer
9
15
  };
@@ -24,7 +24,51 @@ export interface StorageProvider {
24
24
  listDirectory(path: string, options?: {
25
25
  draft?: boolean;
26
26
  }): Promise<string[]>;
27
+ /**
28
+ * Find a recent commit on the draft branch whose message carries the given
29
+ * `Idempotency-Key:` trailer, returning its sha (null if not found).
30
+ *
31
+ * This exists to make a retried write LEGIBLE, not to make it safe — safety is
32
+ * already `writeFiles`' baseVersion check, which a retry cannot get past once
33
+ * the original landed (the head moved, so the retry's baseVersion is stale).
34
+ * What the check cannot do is tell "your write failed" from "your write
35
+ * succeeded and you didn't hear back", and it reports both as a conflict. A
36
+ * caller that tagged its write can ask that question here.
37
+ *
38
+ * OPTIONAL: providers that cannot search history omit it, and callers fall
39
+ * back to surfacing the plain conflict.
40
+ *
41
+ * Best-effort by contract. It searches a BOUNDED window, so an old key reads
42
+ * as absent, and an implementation may answer "not found" when the history
43
+ * read itself fails — failing closed keeps the conflict rather than inventing
44
+ * a replay. So `null` means "could not confirm", NOT "your write did not
45
+ * land": a caller must not tell the user the earlier attempt failed.
46
+ */
47
+ findCommitByIdempotencyKey?(key: string, options?: {
48
+ limit?: number;
49
+ }): Promise<string | null>;
27
50
  }
51
+ /**
52
+ * Commit-message trailer carrying a write's idempotency key. The commit itself is
53
+ * the store — no table, and no TTL to tune: keys live exactly as long as the
54
+ * draft branch, and `promoteDraft` deletes it, which is the correct lifetime
55
+ * (after a publish there is no draft, so an old write must not replay).
56
+ */
57
+ export declare const IDEMPOTENCY_TRAILER = "Idempotency-Key";
58
+ /**
59
+ * Keys are interpolated into a commit message, so the charset is enforced HERE,
60
+ * at the interpolation point, and not only by the caller that happens to have a
61
+ * schema. A key containing a newline would let a caller append arbitrary further
62
+ * trailer lines to the commit.
63
+ */
64
+ export declare const IDEMPOTENCY_KEY_RE: RegExp;
65
+ /**
66
+ * Build a commit message carrying `key` as a trailer, or return `subject`
67
+ * unchanged when no key is given. Throws on a malformed key: every caller
68
+ * reaches this through a write path that should have validated first, so an
69
+ * invalid key here is a bug, not user input to be tolerated.
70
+ */
71
+ export declare function withIdempotencyTrailer(subject: string, key?: string): string;
28
72
  export type FileWrite = {
29
73
  path: string;
30
74
  content: string | Uint8Array | null;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/storage/types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,qBAAa,oBAAqB,SAAQ,KAAK;aACjB,cAAc,EAAE,MAAM,GAAG,IAAI;gBAA7B,cAAc,EAAE,MAAM,GAAG,IAAI;CAI1D;AAED,MAAM,WAAW,eAAe;IAC9B,UAAU,CACR,KAAK,EAAE,SAAS,EAAE,EAClB,OAAO,EAAE,MAAM,EAIf,OAAO,CAAC,EAAE;QAAE,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,GACxC,OAAO,CAAC;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAEhC,QAAQ,CACN,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,GAC5B,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAAC;IAE9B,YAAY,IAAI,OAAO,CAAC;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAE7C,UAAU,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;IAE9B,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;CAC/E;AAED,MAAM,MAAM,SAAS,GAAG;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CAAC;IACpC,QAAQ,CAAC,EAAE,OAAO,GAAG,QAAQ,CAAC;CAC/B,CAAC;AAEF;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,mBAAmB,gCAAgC,CAAC;AAEjE;;;;;;GAMG;AACH,MAAM,WAAW,UAAU;IACzB,6DAA6D;IAC7D,eAAe,CACb,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE;QAAE,WAAW,EAAE,MAAM,CAAA;KAAE,GAC5B,OAAO,CAAC;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAEnE,oEAAoE;IACpE,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IAEnC,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEvC,sDAAsD;IACtD,IAAI,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC,CAAC;CACpF"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/storage/types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,qBAAa,oBAAqB,SAAQ,KAAK;aACjB,cAAc,EAAE,MAAM,GAAG,IAAI;gBAA7B,cAAc,EAAE,MAAM,GAAG,IAAI;CAI1D;AAED,MAAM,WAAW,eAAe;IAC9B,UAAU,CACR,KAAK,EAAE,SAAS,EAAE,EAClB,OAAO,EAAE,MAAM,EAIf,OAAO,CAAC,EAAE;QAAE,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,GACxC,OAAO,CAAC;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAEhC,QAAQ,CACN,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,GAC5B,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAAC;IAE9B,YAAY,IAAI,OAAO,CAAC;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAE7C,UAAU,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;IAE9B,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IAE9E;;;;;;;;;;;;;;;;;;;OAmBG;IACH,0BAA0B,CAAC,CACzB,GAAG,EAAE,MAAM,EACX,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,GAC3B,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;CAC3B;AAED;;;;;GAKG;AACH,eAAO,MAAM,mBAAmB,oBAAoB,CAAC;AAErD;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB,QAA6B,CAAC;AAE7D;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,CAM5E;AAED,MAAM,MAAM,SAAS,GAAG;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CAAC;IACpC,QAAQ,CAAC,EAAE,OAAO,GAAG,QAAQ,CAAC;CAC/B,CAAC;AAEF;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,mBAAmB,gCAAgC,CAAC;AAEjE;;;;;;GAMG;AACH,MAAM,WAAW,UAAU;IACzB,6DAA6D;IAC7D,eAAe,CACb,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE;QAAE,WAAW,EAAE,MAAM,CAAA;KAAE,GAC5B,OAAO,CAAC;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAEnE,oEAAoE;IACpE,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IAEnC,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEvC,sDAAsD;IACtD,IAAI,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC,CAAC;CACpF"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@drawnagency/primitives",
3
- "version": "0.1.95",
3
+ "version": "0.1.97",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  "./package.json": "./package.json",
@@ -60,6 +60,10 @@
60
60
  "types": "./dist/lib/registry.d.ts",
61
61
  "default": "./dist/lib/registry.js"
62
62
  },
63
+ "./lib/index-ops": {
64
+ "types": "./dist/lib/index-ops.d.ts",
65
+ "default": "./dist/lib/index-ops.js"
66
+ },
63
67
  "./lib/media-folders": {
64
68
  "types": "./dist/lib/media-folders.d.ts",
65
69
  "default": "./dist/lib/media-folders.js"
@@ -3,6 +3,8 @@
3
3
  // path and the live save-sync path (both in core); no I/O here.
4
4
  import type { LoadedSection } from "../lib/loader";
5
5
  import type { SiteIndex } from "../schemas/site-config";
6
+ import { IndexSchema } from "../schemas/site-config";
7
+ import { NavFileSchema } from "../schemas/nav";
6
8
 
7
9
  export interface RebaseArgs {
8
10
  localSections: LoadedSection[];
@@ -79,6 +81,39 @@ export function reconcileRemoteIndex(remoteIndex: SiteIndex, localIndex: SiteInd
79
81
  };
80
82
  }
81
83
 
84
+ /** Parse a peer's raw live-index broadcast and fold it under ours — the whole
85
+ * `onRemoteIndexUpdate` transform, kept here rather than in the handler so the
86
+ * nav-preservation step below cannot be lost to a future refactor of the shell.
87
+ * Returns null when the payload is not a valid index (caller ignores the
88
+ * message).
89
+ *
90
+ * The `nav` lift is load-bearing and mirrors content-ops' write path.
91
+ * `IndexSchema` STRIPS unknown keys — that strip is what guarantees nav never
92
+ * reaches index.json — so parsing a peer's index without lifting nav off first
93
+ * yields a nav-less index. Installing that flattens the local sidebar, and
94
+ * since every structural mutation materializes a full nav from `navEntries()`,
95
+ * the next local page/group edit would save a flat nav.json and delete every
96
+ * group on the site. Silent, and reachable from an ordinary two-editor session.
97
+ *
98
+ * Nav entries address PAGES, which `reconcileRemoteIndex`'s section-id prune
99
+ * never touches, so the peer's nav transfers whole; readers go through
100
+ * `navEntries()` → `reconcileNav`, which repairs any drift against the
101
+ * resulting page set. A peer that sends no nav (an older client) leaves ours
102
+ * intact rather than flattening it. */
103
+ export function applyRemoteIndexUpdate(rawRemoteIndex: unknown, localIndex: SiteIndex): SiteIndex | null {
104
+ const rawNav = (rawRemoteIndex as { nav?: unknown } | null | undefined)?.nav;
105
+ const parsed = IndexSchema.safeParse(rawRemoteIndex);
106
+ if (!parsed.success) return null;
107
+
108
+ // safeParse, not parse: this arrives off the network, and a throw inside a
109
+ // collab callback would take down the session rather than one bad message.
110
+ const parsedNav = rawNav === undefined ? null : NavFileSchema.safeParse({ entries: rawNav });
111
+ const nav = parsedNav?.success ? parsedNav.data.entries : localIndex.nav;
112
+
113
+ const reconciled = reconcileRemoteIndex(parsed.data, localIndex);
114
+ return nav !== undefined ? { ...reconciled, nav } : reconciled;
115
+ }
116
+
82
117
  export function rebaseContent(args: RebaseArgs): RebaseResult {
83
118
  const localById = new Map(args.localSections.map((s) => [s.section.id, s]));
84
119
  const remoteIds = new Set(args.remoteSections.map((s) => s.section.id));
@@ -1,13 +1,20 @@
1
1
  /**
2
2
  * The horizontal insertion line shown at the top or bottom edge of a row during
3
3
  * a closest-edge drag-to-reorder, paired with `useSortableRow`'s `closestEdge`.
4
+ *
5
+ * `inset` starts the line at the nested-row indent, which is the ONLY thing
6
+ * distinguishing "drop inside this group" from "drop before this group" — and
7
+ * a group's last member slot from the top-level slot that follows it.
4
8
  */
5
- export function DropEdgeIndicator({ edge }: { edge: "top" | "bottom" | null }) {
6
- if (edge === "top") {
7
- return <div className="absolute top-0 right-0 left-0 z-10 h-0.5 -translate-y-1/2 bg-primary" />;
8
- }
9
- if (edge === "bottom") {
10
- return <div className="absolute right-0 bottom-0 left-0 z-10 h-0.5 translate-y-1/2 bg-primary" />;
11
- }
12
- return null;
9
+ export function DropEdgeIndicator({ edge, inset = false }: { edge: "top" | "bottom" | null; inset?: boolean }) {
10
+ if (!edge) return null;
11
+ return (
12
+ <div
13
+ className={[
14
+ "absolute right-0 z-10 h-0.5 bg-primary",
15
+ inset ? "left-6" : "left-0",
16
+ edge === "top" ? "top-0 -translate-y-1/2" : "bottom-0 translate-y-1/2",
17
+ ].join(" ")}
18
+ />
19
+ );
13
20
  }