@entifix/next-shell 0.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 (271) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +7 -0
  3. package/dist/index.d.ts +21 -0
  4. package/dist/index.d.ts.map +1 -0
  5. package/dist/index.js +19 -0
  6. package/dist/index.js.map +1 -0
  7. package/dist/lib/back-office/account-menu.d.ts +29 -0
  8. package/dist/lib/back-office/account-menu.d.ts.map +1 -0
  9. package/dist/lib/back-office/account-menu.js +70 -0
  10. package/dist/lib/back-office/account-menu.js.map +1 -0
  11. package/dist/lib/back-office/back-office-shell.d.ts +50 -0
  12. package/dist/lib/back-office/back-office-shell.d.ts.map +1 -0
  13. package/dist/lib/back-office/back-office-shell.js +128 -0
  14. package/dist/lib/back-office/back-office-shell.js.map +1 -0
  15. package/dist/lib/back-office/breadcrumbs.d.ts +16 -0
  16. package/dist/lib/back-office/breadcrumbs.d.ts.map +1 -0
  17. package/dist/lib/back-office/breadcrumbs.js +53 -0
  18. package/dist/lib/back-office/breadcrumbs.js.map +1 -0
  19. package/dist/lib/back-office/group-by-screen-type.d.ts +21 -0
  20. package/dist/lib/back-office/group-by-screen-type.d.ts.map +1 -0
  21. package/dist/lib/back-office/group-by-screen-type.js +39 -0
  22. package/dist/lib/back-office/group-by-screen-type.js.map +1 -0
  23. package/dist/lib/back-office/index.d.ts +6 -0
  24. package/dist/lib/back-office/index.d.ts.map +1 -0
  25. package/dist/lib/back-office/index.js +7 -0
  26. package/dist/lib/back-office/index.js.map +1 -0
  27. package/dist/lib/back-office/nav.d.ts +39 -0
  28. package/dist/lib/back-office/nav.d.ts.map +1 -0
  29. package/dist/lib/back-office/nav.js +3 -0
  30. package/dist/lib/back-office/nav.js.map +1 -0
  31. package/dist/lib/back-office/sidebar-nav.d.ts +26 -0
  32. package/dist/lib/back-office/sidebar-nav.d.ts.map +1 -0
  33. package/dist/lib/back-office/sidebar-nav.js +181 -0
  34. package/dist/lib/back-office/sidebar-nav.js.map +1 -0
  35. package/dist/lib/command-palette/command-palette-host.d.ts +41 -0
  36. package/dist/lib/command-palette/command-palette-host.d.ts.map +1 -0
  37. package/dist/lib/command-palette/command-palette-host.js +196 -0
  38. package/dist/lib/command-palette/command-palette-host.js.map +1 -0
  39. package/dist/lib/command-palette/index.d.ts +9 -0
  40. package/dist/lib/command-palette/index.d.ts.map +1 -0
  41. package/dist/lib/command-palette/index.js +10 -0
  42. package/dist/lib/command-palette/index.js.map +1 -0
  43. package/dist/lib/command-palette/palette-command.d.ts +32 -0
  44. package/dist/lib/command-palette/palette-command.d.ts.map +1 -0
  45. package/dist/lib/command-palette/palette-command.js +19 -0
  46. package/dist/lib/command-palette/palette-command.js.map +1 -0
  47. package/dist/lib/command-palette/use-command-recency.d.ts +28 -0
  48. package/dist/lib/command-palette/use-command-recency.d.ts.map +1 -0
  49. package/dist/lib/command-palette/use-command-recency.js +67 -0
  50. package/dist/lib/command-palette/use-command-recency.js.map +1 -0
  51. package/dist/lib/command-palette/use-command-source.d.ts +17 -0
  52. package/dist/lib/command-palette/use-command-source.d.ts.map +1 -0
  53. package/dist/lib/command-palette/use-command-source.js +70 -0
  54. package/dist/lib/command-palette/use-command-source.js.map +1 -0
  55. package/dist/lib/command-palette/use-nav-source.d.ts +21 -0
  56. package/dist/lib/command-palette/use-nav-source.d.ts.map +1 -0
  57. package/dist/lib/command-palette/use-nav-source.js +78 -0
  58. package/dist/lib/command-palette/use-nav-source.js.map +1 -0
  59. package/dist/lib/command-palette/use-records-source.d.ts +23 -0
  60. package/dist/lib/command-palette/use-records-source.d.ts.map +1 -0
  61. package/dist/lib/command-palette/use-records-source.js +199 -0
  62. package/dist/lib/command-palette/use-records-source.js.map +1 -0
  63. package/dist/lib/command-palette/use-tabs-source.d.ts +20 -0
  64. package/dist/lib/command-palette/use-tabs-source.d.ts.map +1 -0
  65. package/dist/lib/command-palette/use-tabs-source.js +52 -0
  66. package/dist/lib/command-palette/use-tabs-source.js.map +1 -0
  67. package/dist/lib/command-palette/use-use-cases-source.d.ts +22 -0
  68. package/dist/lib/command-palette/use-use-cases-source.d.ts.map +1 -0
  69. package/dist/lib/command-palette/use-use-cases-source.js +93 -0
  70. package/dist/lib/command-palette/use-use-cases-source.js.map +1 -0
  71. package/dist/lib/config/config-route.d.ts +51 -0
  72. package/dist/lib/config/config-route.d.ts.map +1 -0
  73. package/dist/lib/config/config-route.js +65 -0
  74. package/dist/lib/config/config-route.js.map +1 -0
  75. package/dist/lib/config/service-proxy-route.d.ts +28 -0
  76. package/dist/lib/config/service-proxy-route.d.ts.map +1 -0
  77. package/dist/lib/config/service-proxy-route.js +125 -0
  78. package/dist/lib/config/service-proxy-route.js.map +1 -0
  79. package/dist/lib/config/service-token.d.ts +5 -0
  80. package/dist/lib/config/service-token.d.ts.map +1 -0
  81. package/dist/lib/config/service-token.js +18 -0
  82. package/dist/lib/config/service-token.js.map +1 -0
  83. package/dist/lib/crud/entity-crud-form.d.ts +49 -0
  84. package/dist/lib/crud/entity-crud-form.d.ts.map +1 -0
  85. package/dist/lib/crud/entity-crud-form.js +133 -0
  86. package/dist/lib/crud/entity-crud-form.js.map +1 -0
  87. package/dist/lib/crud/hand-off-write.d.ts +35 -0
  88. package/dist/lib/crud/hand-off-write.d.ts.map +1 -0
  89. package/dist/lib/crud/hand-off-write.js +27 -0
  90. package/dist/lib/crud/hand-off-write.js.map +1 -0
  91. package/dist/lib/crud/index.d.ts +9 -0
  92. package/dist/lib/crud/index.d.ts.map +1 -0
  93. package/dist/lib/crud/index.js +10 -0
  94. package/dist/lib/crud/index.js.map +1 -0
  95. package/dist/lib/crud/make-entity-crud.d.ts +28 -0
  96. package/dist/lib/crud/make-entity-crud.d.ts.map +1 -0
  97. package/dist/lib/crud/make-entity-crud.js +318 -0
  98. package/dist/lib/crud/make-entity-crud.js.map +1 -0
  99. package/dist/lib/crud/make-entity-crud.types.d.ts +237 -0
  100. package/dist/lib/crud/make-entity-crud.types.d.ts.map +1 -0
  101. package/dist/lib/crud/make-entity-crud.types.js +6 -0
  102. package/dist/lib/crud/make-entity-crud.types.js.map +1 -0
  103. package/dist/lib/crud/pending-notice.d.ts +31 -0
  104. package/dist/lib/crud/pending-notice.d.ts.map +1 -0
  105. package/dist/lib/crud/pending-notice.js +72 -0
  106. package/dist/lib/crud/pending-notice.js.map +1 -0
  107. package/dist/lib/crud/slug.d.ts +13 -0
  108. package/dist/lib/crud/slug.d.ts.map +1 -0
  109. package/dist/lib/crud/slug.js +13 -0
  110. package/dist/lib/crud/slug.js.map +1 -0
  111. package/dist/lib/crud/use-entity-affordances.d.ts +20 -0
  112. package/dist/lib/crud/use-entity-affordances.d.ts.map +1 -0
  113. package/dist/lib/crud/use-entity-affordances.js +22 -0
  114. package/dist/lib/crud/use-entity-affordances.js.map +1 -0
  115. package/dist/lib/crud/use-entity-bulk.d.ts +43 -0
  116. package/dist/lib/crud/use-entity-bulk.d.ts.map +1 -0
  117. package/dist/lib/crud/use-entity-bulk.js +106 -0
  118. package/dist/lib/crud/use-entity-bulk.js.map +1 -0
  119. package/dist/lib/crud/use-entity-link-sources.d.ts +25 -0
  120. package/dist/lib/crud/use-entity-link-sources.d.ts.map +1 -0
  121. package/dist/lib/crud/use-entity-link-sources.js +37 -0
  122. package/dist/lib/crud/use-entity-link-sources.js.map +1 -0
  123. package/dist/lib/health/health-routes.d.ts +37 -0
  124. package/dist/lib/health/health-routes.d.ts.map +1 -0
  125. package/dist/lib/health/health-routes.js +90 -0
  126. package/dist/lib/health/health-routes.js.map +1 -0
  127. package/dist/lib/i18n/catalog/en.d.ts +4 -0
  128. package/dist/lib/i18n/catalog/en.d.ts.map +1 -0
  129. package/dist/lib/i18n/catalog/en.js +347 -0
  130. package/dist/lib/i18n/catalog/en.js.map +1 -0
  131. package/dist/lib/i18n/catalog/es.d.ts +397 -0
  132. package/dist/lib/i18n/catalog/es.d.ts.map +1 -0
  133. package/dist/lib/i18n/catalog/es.js +407 -0
  134. package/dist/lib/i18n/catalog/es.js.map +1 -0
  135. package/dist/lib/i18n/catalog/index.d.ts +671 -0
  136. package/dist/lib/i18n/catalog/index.d.ts.map +1 -0
  137. package/dist/lib/i18n/catalog/index.js +28 -0
  138. package/dist/lib/i18n/catalog/index.js.map +1 -0
  139. package/dist/lib/i18n/index.d.ts +2 -0
  140. package/dist/lib/i18n/index.d.ts.map +1 -0
  141. package/dist/lib/i18n/index.js +3 -0
  142. package/dist/lib/i18n/index.js.map +1 -0
  143. package/dist/lib/i18n/locale-link.d.ts +23 -0
  144. package/dist/lib/i18n/locale-link.d.ts.map +1 -0
  145. package/dist/lib/i18n/locale-link.js +43 -0
  146. package/dist/lib/i18n/locale-link.js.map +1 -0
  147. package/dist/lib/i18n/spec-catalog.d.ts +2 -0
  148. package/dist/lib/i18n/spec-catalog.d.ts.map +1 -0
  149. package/dist/lib/i18n/spec-catalog.js +84 -0
  150. package/dist/lib/i18n/spec-catalog.js.map +1 -0
  151. package/dist/lib/search/record-search-route.d.ts +44 -0
  152. package/dist/lib/search/record-search-route.d.ts.map +1 -0
  153. package/dist/lib/search/record-search-route.js +158 -0
  154. package/dist/lib/search/record-search-route.js.map +1 -0
  155. package/dist/lib/search/record-search-source.d.ts +59 -0
  156. package/dist/lib/search/record-search-source.d.ts.map +1 -0
  157. package/dist/lib/search/record-search-source.js +118 -0
  158. package/dist/lib/search/record-search-source.js.map +1 -0
  159. package/dist/lib/search/record-search.types.d.ts +85 -0
  160. package/dist/lib/search/record-search.types.d.ts.map +1 -0
  161. package/dist/lib/search/record-search.types.js +29 -0
  162. package/dist/lib/search/record-search.types.js.map +1 -0
  163. package/dist/lib/search/search-records.d.ts +25 -0
  164. package/dist/lib/search/search-records.d.ts.map +1 -0
  165. package/dist/lib/search/search-records.js +36 -0
  166. package/dist/lib/search/search-records.js.map +1 -0
  167. package/dist/lib/session/account-links.d.ts +46 -0
  168. package/dist/lib/session/account-links.d.ts.map +1 -0
  169. package/dist/lib/session/account-links.js +41 -0
  170. package/dist/lib/session/account-links.js.map +1 -0
  171. package/dist/lib/session/bearer.d.ts +25 -0
  172. package/dist/lib/session/bearer.d.ts.map +1 -0
  173. package/dist/lib/session/bearer.js +31 -0
  174. package/dist/lib/session/bearer.js.map +1 -0
  175. package/dist/lib/session/cookies.d.ts +14 -0
  176. package/dist/lib/session/cookies.d.ts.map +1 -0
  177. package/dist/lib/session/cookies.js +33 -0
  178. package/dist/lib/session/cookies.js.map +1 -0
  179. package/dist/lib/session/device.d.ts +31 -0
  180. package/dist/lib/session/device.d.ts.map +1 -0
  181. package/dist/lib/session/device.js +80 -0
  182. package/dist/lib/session/device.js.map +1 -0
  183. package/dist/lib/session/index.d.ts +3 -0
  184. package/dist/lib/session/index.d.ts.map +1 -0
  185. package/dist/lib/session/index.js +12 -0
  186. package/dist/lib/session/index.js.map +1 -0
  187. package/dist/lib/session/refresh-route.d.ts +19 -0
  188. package/dist/lib/session/refresh-route.d.ts.map +1 -0
  189. package/dist/lib/session/refresh-route.js +85 -0
  190. package/dist/lib/session/refresh-route.js.map +1 -0
  191. package/dist/lib/session/session-keepalive.d.ts +52 -0
  192. package/dist/lib/session/session-keepalive.d.ts.map +1 -0
  193. package/dist/lib/session/session-keepalive.js +72 -0
  194. package/dist/lib/session/session-keepalive.js.map +1 -0
  195. package/dist/lib/session/use-session-refresh.d.ts +43 -0
  196. package/dist/lib/session/use-session-refresh.d.ts.map +1 -0
  197. package/dist/lib/session/use-session-refresh.js +120 -0
  198. package/dist/lib/session/use-session-refresh.js.map +1 -0
  199. package/dist/lib/wizard/index.d.ts +4 -0
  200. package/dist/lib/wizard/index.d.ts.map +1 -0
  201. package/dist/lib/wizard/index.js +5 -0
  202. package/dist/lib/wizard/index.js.map +1 -0
  203. package/dist/lib/wizard/use-wizard-draft.d.ts +22 -0
  204. package/dist/lib/wizard/use-wizard-draft.d.ts.map +1 -0
  205. package/dist/lib/wizard/use-wizard-draft.js +39 -0
  206. package/dist/lib/wizard/use-wizard-draft.js.map +1 -0
  207. package/dist/lib/wizard/use-wizard-step-param.d.ts +60 -0
  208. package/dist/lib/wizard/use-wizard-step-param.d.ts.map +1 -0
  209. package/dist/lib/wizard/use-wizard-step-param.js +94 -0
  210. package/dist/lib/wizard/use-wizard-step-param.js.map +1 -0
  211. package/dist/lib/wizard/wizard-tab-kind.d.ts +31 -0
  212. package/dist/lib/wizard/wizard-tab-kind.d.ts.map +1 -0
  213. package/dist/lib/wizard/wizard-tab-kind.js +33 -0
  214. package/dist/lib/wizard/wizard-tab-kind.js.map +1 -0
  215. package/dist/lib/workspace/drafts-state.d.ts +88 -0
  216. package/dist/lib/workspace/drafts-state.d.ts.map +1 -0
  217. package/dist/lib/workspace/drafts-state.js +128 -0
  218. package/dist/lib/workspace/drafts-state.js.map +1 -0
  219. package/dist/lib/workspace/entity-nav.d.ts +26 -0
  220. package/dist/lib/workspace/entity-nav.d.ts.map +1 -0
  221. package/dist/lib/workspace/entity-nav.js +56 -0
  222. package/dist/lib/workspace/entity-nav.js.map +1 -0
  223. package/dist/lib/workspace/entity-tab-kind.d.ts +51 -0
  224. package/dist/lib/workspace/entity-tab-kind.d.ts.map +1 -0
  225. package/dist/lib/workspace/entity-tab-kind.js +47 -0
  226. package/dist/lib/workspace/entity-tab-kind.js.map +1 -0
  227. package/dist/lib/workspace/idb-state-storage.d.ts +9 -0
  228. package/dist/lib/workspace/idb-state-storage.d.ts.map +1 -0
  229. package/dist/lib/workspace/idb-state-storage.js +32 -0
  230. package/dist/lib/workspace/idb-state-storage.js.map +1 -0
  231. package/dist/lib/workspace/index.d.ts +13 -0
  232. package/dist/lib/workspace/index.d.ts.map +1 -0
  233. package/dist/lib/workspace/index.js +14 -0
  234. package/dist/lib/workspace/index.js.map +1 -0
  235. package/dist/lib/workspace/pending-state.d.ts +84 -0
  236. package/dist/lib/workspace/pending-state.d.ts.map +1 -0
  237. package/dist/lib/workspace/pending-state.js +155 -0
  238. package/dist/lib/workspace/pending-state.js.map +1 -0
  239. package/dist/lib/workspace/pending-transactions.d.ts +44 -0
  240. package/dist/lib/workspace/pending-transactions.d.ts.map +1 -0
  241. package/dist/lib/workspace/pending-transactions.js +114 -0
  242. package/dist/lib/workspace/pending-transactions.js.map +1 -0
  243. package/dist/lib/workspace/tab-kind.d.ts +48 -0
  244. package/dist/lib/workspace/tab-kind.d.ts.map +1 -0
  245. package/dist/lib/workspace/tab-kind.js +47 -0
  246. package/dist/lib/workspace/tab-kind.js.map +1 -0
  247. package/dist/lib/workspace/tab-state.d.ts +36 -0
  248. package/dist/lib/workspace/tab-state.d.ts.map +1 -0
  249. package/dist/lib/workspace/tab-state.js +81 -0
  250. package/dist/lib/workspace/tab-state.js.map +1 -0
  251. package/dist/lib/workspace/tabs-state.d.ts +46 -0
  252. package/dist/lib/workspace/tabs-state.d.ts.map +1 -0
  253. package/dist/lib/workspace/tabs-state.js +56 -0
  254. package/dist/lib/workspace/tabs-state.js.map +1 -0
  255. package/dist/lib/workspace/use-entity-draft.d.ts +25 -0
  256. package/dist/lib/workspace/use-entity-draft.d.ts.map +1 -0
  257. package/dist/lib/workspace/use-entity-draft.js +38 -0
  258. package/dist/lib/workspace/use-entity-draft.js.map +1 -0
  259. package/dist/lib/workspace/workspace-scope.d.ts +51 -0
  260. package/dist/lib/workspace/workspace-scope.d.ts.map +1 -0
  261. package/dist/lib/workspace/workspace-scope.js +42 -0
  262. package/dist/lib/workspace/workspace-scope.js.map +1 -0
  263. package/dist/lib/workspace/workspace-shell.d.ts +33 -0
  264. package/dist/lib/workspace/workspace-shell.d.ts.map +1 -0
  265. package/dist/lib/workspace/workspace-shell.js +206 -0
  266. package/dist/lib/workspace/workspace-shell.js.map +1 -0
  267. package/dist/server.d.ts +13 -0
  268. package/dist/server.d.ts.map +1 -0
  269. package/dist/server.js +41 -0
  270. package/dist/server.js.map +1 -0
  271. package/package.json +95 -0
@@ -0,0 +1,94 @@
1
+ 'use client';
2
+ import { usePathname, useRouter, useSearchParams } from 'next/navigation';
3
+ import { useCallback, useEffect, useRef } from 'react';
4
+ /** The query parameter a wizard route carries its step in. */ export const WIZARD_STEP_PARAM = 'step';
5
+ /**
6
+ * Builds this route's address with a step in it.
7
+ *
8
+ * Shared by the writer and the follower so the two cannot disagree about the
9
+ * shape — and so neither drops the rest of the query, which on a workspace host
10
+ * is the `?tab=` value naming the tab the wizard is inside.
11
+ */ function useStepAddress() {
12
+ const pathname = usePathname();
13
+ const searchParams = useSearchParams();
14
+ return useCallback((stepId)=>{
15
+ const next = new URLSearchParams(searchParams);
16
+ next.set(WIZARD_STEP_PARAM, stepId);
17
+ return `${pathname}?${next.toString()}`;
18
+ }, [
19
+ pathname,
20
+ searchParams
21
+ ]);
22
+ }
23
+ /**
24
+ * Writes a step into the address — what `useWizard`'s `onStepChange` is wired to.
25
+ *
26
+ * ⚠️ **This must be handed to `useWizard` directly, never through a ref.** The
27
+ * push has to land in the same commit as the move: the follower below compares
28
+ * the address with the active step, so a report that arrives a tick late leaves
29
+ * the two disagreeing, and the follower reads that as a Back and moves the
30
+ * wizard *back* to the step it just left. Splitting the writer from the follower
31
+ * is what removes the cycle that made a ref look necessary.
32
+ *
33
+ * It **checks the address before pushing**, and that guard is what stops the
34
+ * loop: a Back drives `goTo`, which reports the move, which would otherwise push
35
+ * the very address Back had just restored — turning one press into an entry the
36
+ * operator has to get past twice.
37
+ */ export function useWizardStepUrl() {
38
+ const router = useRouter();
39
+ const searchParams = useSearchParams();
40
+ const addressOf = useStepAddress();
41
+ return useCallback((stepId)=>{
42
+ if (searchParams.get(WIZARD_STEP_PARAM) === stepId) return;
43
+ router.push(addressOf(stepId));
44
+ }, [
45
+ router,
46
+ searchParams,
47
+ addressOf
48
+ ]);
49
+ }
50
+ /**
51
+ * Moves the wizard when the address moves, so browser-Back moves a step instead
52
+ * of leaving the flow.
53
+ *
54
+ * **Back works; Forward deliberately does not re-advance.** Going back pops the
55
+ * history, so the step Forward names is no longer on the path and `goTo` ignores
56
+ * it — re-entering it would skip the per-step submit that validated everything
57
+ * in between, which is the only thing gating advancement.
58
+ *
59
+ * An address carrying **no** step means the flow's beginning — which is where
60
+ * browser-Back from the second step lands, since the first step is never pushed.
61
+ *
62
+ * ⚠️ **It reacts to the address *changing*, never to what the address says.**
63
+ * Two faults sit on either side of that, and both were measured:
64
+ *
65
+ * - *Rewriting the address to match the wizard* races the advance. The replace
66
+ * is scheduled at mount with the entry step captured, the operator advances
67
+ * before it lands, and the navigation then puts the older step back — this
68
+ * effect reads it and rewinds a step they had already passed. It appeared only
69
+ * under load, which is the kind of race that reaches a user and not a test.
70
+ * - *Acting on the address at mount* breaks resume. A restored wizard takes its
71
+ * position from the persisted draft, so it opens on step four with an address
72
+ * that says nothing — read as "the beginning", that sends it back to step one
73
+ * and the resume is undone.
74
+ *
75
+ * So the first address seen is a **baseline**, and only a move away from it is a
76
+ * navigation. One direction throughout: the writer owns the address, the
77
+ * follower owns the wizard.
78
+ */ export function useFollowWizardStepUrl({ activeStep, entryStep, goTo }) {
79
+ var _searchParams_get;
80
+ const searchParams = useSearchParams();
81
+ const target = (_searchParams_get = searchParams.get(WIZARD_STEP_PARAM)) != null ? _searchParams_get : entryStep;
82
+ const seen = useRef(target);
83
+ useEffect(()=>{
84
+ if (target === seen.current) return;
85
+ seen.current = target;
86
+ if (target !== activeStep) goTo(target);
87
+ }, [
88
+ target,
89
+ activeStep,
90
+ goTo
91
+ ]);
92
+ }
93
+
94
+ //# sourceMappingURL=use-wizard-step-param.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/lib/wizard/use-wizard-step-param.ts"],"sourcesContent":["'use client';\n\nimport { usePathname, useRouter, useSearchParams } from 'next/navigation';\nimport { useCallback, useEffect, useRef } from 'react';\n\n/** The query parameter a wizard route carries its step in. */\nexport const WIZARD_STEP_PARAM = 'step';\n\n/**\n * Builds this route's address with a step in it.\n *\n * Shared by the writer and the follower so the two cannot disagree about the\n * shape — and so neither drops the rest of the query, which on a workspace host\n * is the `?tab=` value naming the tab the wizard is inside.\n */\nfunction useStepAddress(): (stepId: string) => string {\n const pathname = usePathname();\n const searchParams = useSearchParams();\n\n return useCallback(\n (stepId: string) => {\n const next = new URLSearchParams(searchParams);\n next.set(WIZARD_STEP_PARAM, stepId);\n return `${pathname}?${next.toString()}`;\n },\n [pathname, searchParams],\n );\n}\n\n/**\n * Writes a step into the address — what `useWizard`'s `onStepChange` is wired to.\n *\n * ⚠️ **This must be handed to `useWizard` directly, never through a ref.** The\n * push has to land in the same commit as the move: the follower below compares\n * the address with the active step, so a report that arrives a tick late leaves\n * the two disagreeing, and the follower reads that as a Back and moves the\n * wizard *back* to the step it just left. Splitting the writer from the follower\n * is what removes the cycle that made a ref look necessary.\n *\n * It **checks the address before pushing**, and that guard is what stops the\n * loop: a Back drives `goTo`, which reports the move, which would otherwise push\n * the very address Back had just restored — turning one press into an entry the\n * operator has to get past twice.\n */\nexport function useWizardStepUrl(): (stepId: string) => void {\n const router = useRouter();\n const searchParams = useSearchParams();\n const addressOf = useStepAddress();\n\n return useCallback(\n (stepId: string) => {\n if (searchParams.get(WIZARD_STEP_PARAM) === stepId) return;\n router.push(addressOf(stepId));\n },\n [router, searchParams, addressOf],\n );\n}\n\nexport interface FollowWizardStepUrlOptions {\n /** Where the wizard currently is, from `useWizard`. */\n activeStep: string;\n /** The flow's first step — where an address carrying no step points. */\n entryStep: string;\n /**\n * `useWizard`'s `goTo`. It ignores a step that is not on the path already\n * walked, which is what makes browser-Forward safe to hand straight to it.\n */\n goTo: (stepId: string) => void;\n}\n\n/**\n * Moves the wizard when the address moves, so browser-Back moves a step instead\n * of leaving the flow.\n *\n * **Back works; Forward deliberately does not re-advance.** Going back pops the\n * history, so the step Forward names is no longer on the path and `goTo` ignores\n * it — re-entering it would skip the per-step submit that validated everything\n * in between, which is the only thing gating advancement.\n *\n * An address carrying **no** step means the flow's beginning — which is where\n * browser-Back from the second step lands, since the first step is never pushed.\n *\n * ⚠️ **It reacts to the address *changing*, never to what the address says.**\n * Two faults sit on either side of that, and both were measured:\n *\n * - *Rewriting the address to match the wizard* races the advance. The replace\n * is scheduled at mount with the entry step captured, the operator advances\n * before it lands, and the navigation then puts the older step back — this\n * effect reads it and rewinds a step they had already passed. It appeared only\n * under load, which is the kind of race that reaches a user and not a test.\n * - *Acting on the address at mount* breaks resume. A restored wizard takes its\n * position from the persisted draft, so it opens on step four with an address\n * that says nothing — read as \"the beginning\", that sends it back to step one\n * and the resume is undone.\n *\n * So the first address seen is a **baseline**, and only a move away from it is a\n * navigation. One direction throughout: the writer owns the address, the\n * follower owns the wizard.\n */\nexport function useFollowWizardStepUrl({\n activeStep,\n entryStep,\n goTo,\n}: FollowWizardStepUrlOptions): void {\n const searchParams = useSearchParams();\n const target = searchParams.get(WIZARD_STEP_PARAM) ?? entryStep;\n const seen = useRef(target);\n\n useEffect(() => {\n if (target === seen.current) return;\n seen.current = target;\n if (target !== activeStep) goTo(target);\n }, [target, activeStep, goTo]);\n}\n"],"names":["usePathname","useRouter","useSearchParams","useCallback","useEffect","useRef","WIZARD_STEP_PARAM","useStepAddress","pathname","searchParams","stepId","next","URLSearchParams","set","toString","useWizardStepUrl","router","addressOf","get","push","useFollowWizardStepUrl","activeStep","entryStep","goTo","target","seen","current"],"mappings":"AAAA;AAEA,SAASA,WAAW,EAAEC,SAAS,EAAEC,eAAe,QAAQ,kBAAkB;AAC1E,SAASC,WAAW,EAAEC,SAAS,EAAEC,MAAM,QAAQ,QAAQ;AAEvD,4DAA4D,GAC5D,OAAO,MAAMC,oBAAoB,OAAO;AAExC;;;;;;CAMC,GACD,SAASC;IACP,MAAMC,WAAWR;IACjB,MAAMS,eAAeP;IAErB,OAAOC,YACL,CAACO;QACC,MAAMC,OAAO,IAAIC,gBAAgBH;QACjCE,KAAKE,GAAG,CAACP,mBAAmBI;QAC5B,OAAO,GAAGF,SAAS,CAAC,EAAEG,KAAKG,QAAQ,IAAI;IACzC,GACA;QAACN;QAAUC;KAAa;AAE5B;AAEA;;;;;;;;;;;;;;CAcC,GACD,OAAO,SAASM;IACd,MAAMC,SAASf;IACf,MAAMQ,eAAeP;IACrB,MAAMe,YAAYV;IAElB,OAAOJ,YACL,CAACO;QACC,IAAID,aAAaS,GAAG,CAACZ,uBAAuBI,QAAQ;QACpDM,OAAOG,IAAI,CAACF,UAAUP;IACxB,GACA;QAACM;QAAQP;QAAcQ;KAAU;AAErC;AAcA;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA4BC,GACD,OAAO,SAASG,uBAAuB,EACrCC,UAAU,EACVC,SAAS,EACTC,IAAI,EACuB;QAEZd;IADf,MAAMA,eAAeP;IACrB,MAAMsB,UAASf,oBAAAA,aAAaS,GAAG,CAACZ,8BAAjBG,oBAAuCa;IACtD,MAAMG,OAAOpB,OAAOmB;IAEpBpB,UAAU;QACR,IAAIoB,WAAWC,KAAKC,OAAO,EAAE;QAC7BD,KAAKC,OAAO,GAAGF;QACf,IAAIA,WAAWH,YAAYE,KAAKC;IAClC,GAAG;QAACA;QAAQH;QAAYE;KAAK;AAC/B"}
@@ -0,0 +1,31 @@
1
+ import type { ReactNode } from 'react';
2
+ import type { TabKind } from '../workspace/tab-kind.js';
3
+ /** One wizard a workspace may open, as the registry needs it. */
4
+ export interface WizardTabScreen {
5
+ /** The tab caption's catalog key — the wizard's name, never the step's. */
6
+ readonly titleKey: string;
7
+ /** `step` is the third address segment, absent while the flow is at its first. */
8
+ render(step: string | undefined): ReactNode;
9
+ }
10
+ /**
11
+ * The `wizard:` tab kind: `wizard:<key>` opens a flow, `wizard:<key>:<step>`
12
+ * opens it at a step.
13
+ *
14
+ * The third segment is a **step** here and a record under `master:`, which is
15
+ * the one thing [ADR 0045](../../../../../../../docs/adr/0045-the-wizard-a-step-graph-and-a-submit-that-hands-off.md)
16
+ * changed about the address grammar — `screenAddress` and `parseScreenPayload`
17
+ * already produced and accepted the shape.
18
+ *
19
+ * **The caption is the wizard's name and not the step's**, deliberately: a tab
20
+ * whose title changed on every Siguiente would reflow the strip mid-flow and
21
+ * make an open wizard hard to find again by the name it was opened under.
22
+ *
23
+ * A factory rather than a constant, for the reason `masterKind` is built in the
24
+ * host: which screens a workspace offers is the host's decision, and the shells
25
+ * that own the wizards may not import each other.
26
+ */
27
+ export declare function wizardTabKind(screens: Record<string, WizardTabScreen>): TabKind<{
28
+ key: string;
29
+ id?: string;
30
+ }>;
31
+ //# sourceMappingURL=wizard-tab-kind.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wizard-tab-kind.d.ts","sourceRoot":"","sources":["../../../src/lib/wizard/wizard-tab-kind.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEvC,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,0BAA0B,CAAC;AAExD,iEAAiE;AACjE,MAAM,WAAW,eAAe;IAC9B,2EAA2E;IAC3E,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,kFAAkF;IAClF,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,SAAS,CAAC;CAC7C;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,aAAa,CAC3B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,GACvC,OAAO,CAAC;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,EAAE,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAavC"}
@@ -0,0 +1,33 @@
1
+ 'use client';
2
+ import { parseScreenPayload } from '@entifix/authz';
3
+ /**
4
+ * The `wizard:` tab kind: `wizard:<key>` opens a flow, `wizard:<key>:<step>`
5
+ * opens it at a step.
6
+ *
7
+ * The third segment is a **step** here and a record under `master:`, which is
8
+ * the one thing [ADR 0045](../../../../../../../docs/adr/0045-the-wizard-a-step-graph-and-a-submit-that-hands-off.md)
9
+ * changed about the address grammar — `screenAddress` and `parseScreenPayload`
10
+ * already produced and accepted the shape.
11
+ *
12
+ * **The caption is the wizard's name and not the step's**, deliberately: a tab
13
+ * whose title changed on every Siguiente would reflow the strip mid-flow and
14
+ * make an open wizard hard to find again by the name it was opened under.
15
+ *
16
+ * A factory rather than a constant, for the reason `masterKind` is built in the
17
+ * host: which screens a workspace offers is the host's decision, and the shells
18
+ * that own the wizards may not import each other.
19
+ */ export function wizardTabKind(screens) {
20
+ return {
21
+ kind: 'wizard',
22
+ match: (payload)=>{
23
+ const parsed = parseScreenPayload(payload);
24
+ if (parsed === null) return null;
25
+ return parsed.key in screens ? parsed : null;
26
+ },
27
+ toParam: (addr)=>addr.id === undefined ? addr.key : `${addr.key}:${addr.id}`,
28
+ title: (addr, translate)=>translate(screens[addr.key].titleKey),
29
+ render: (addr)=>screens[addr.key].render(addr.id)
30
+ };
31
+ }
32
+
33
+ //# sourceMappingURL=wizard-tab-kind.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/lib/wizard/wizard-tab-kind.tsx"],"sourcesContent":["'use client';\n\nimport { parseScreenPayload } from '@entifix/authz';\nimport type { ReactNode } from 'react';\n\nimport type { TabKind } from '../workspace/tab-kind.js';\n\n/** One wizard a workspace may open, as the registry needs it. */\nexport interface WizardTabScreen {\n /** The tab caption's catalog key — the wizard's name, never the step's. */\n readonly titleKey: string;\n /** `step` is the third address segment, absent while the flow is at its first. */\n render(step: string | undefined): ReactNode;\n}\n\n/**\n * The `wizard:` tab kind: `wizard:<key>` opens a flow, `wizard:<key>:<step>`\n * opens it at a step.\n *\n * The third segment is a **step** here and a record under `master:`, which is\n * the one thing [ADR 0045](../../../../../../../docs/adr/0045-the-wizard-a-step-graph-and-a-submit-that-hands-off.md)\n * changed about the address grammar — `screenAddress` and `parseScreenPayload`\n * already produced and accepted the shape.\n *\n * **The caption is the wizard's name and not the step's**, deliberately: a tab\n * whose title changed on every Siguiente would reflow the strip mid-flow and\n * make an open wizard hard to find again by the name it was opened under.\n *\n * A factory rather than a constant, for the reason `masterKind` is built in the\n * host: which screens a workspace offers is the host's decision, and the shells\n * that own the wizards may not import each other.\n */\nexport function wizardTabKind(\n screens: Record<string, WizardTabScreen>,\n): TabKind<{ key: string; id?: string }> {\n return {\n kind: 'wizard',\n match: payload => {\n const parsed = parseScreenPayload(payload);\n if (parsed === null) return null;\n return parsed.key in screens ? parsed : null;\n },\n toParam: addr =>\n addr.id === undefined ? addr.key : `${addr.key}:${addr.id}`,\n title: (addr, translate) => translate(screens[addr.key].titleKey),\n render: addr => screens[addr.key].render(addr.id),\n };\n}\n"],"names":["parseScreenPayload","wizardTabKind","screens","kind","match","payload","parsed","key","toParam","addr","id","undefined","title","translate","titleKey","render"],"mappings":"AAAA;AAEA,SAASA,kBAAkB,QAAQ,iBAAiB;AAapD;;;;;;;;;;;;;;;;CAgBC,GACD,OAAO,SAASC,cACdC,OAAwC;IAExC,OAAO;QACLC,MAAM;QACNC,OAAOC,CAAAA;YACL,MAAMC,SAASN,mBAAmBK;YAClC,IAAIC,WAAW,MAAM,OAAO;YAC5B,OAAOA,OAAOC,GAAG,IAAIL,UAAUI,SAAS;QAC1C;QACAE,SAASC,CAAAA,OACPA,KAAKC,EAAE,KAAKC,YAAYF,KAAKF,GAAG,GAAG,GAAGE,KAAKF,GAAG,CAAC,CAAC,EAAEE,KAAKC,EAAE,EAAE;QAC7DE,OAAO,CAACH,MAAMI,YAAcA,UAAUX,OAAO,CAACO,KAAKF,GAAG,CAAC,CAACO,QAAQ;QAChEC,QAAQN,CAAAA,OAAQP,OAAO,CAACO,KAAKF,GAAG,CAAC,CAACQ,MAAM,CAACN,KAAKC,EAAE;IAClD;AACF"}
@@ -0,0 +1,88 @@
1
+ import { type JsonValue } from '@entifix/core';
2
+ /**
3
+ * Per-address edit drafts, persisted to IndexedDB so an in-progress edit
4
+ * survives a refresh. Keyed by the tab's `?tab=` param (its address), a draft is
5
+ * whatever the editor needs to restore its fields; it exists until the user
6
+ * saves (or discards). This is the "continuous autosave" seam — workspace-host
7
+ * only, so a plain route stays ephemeral.
8
+ *
9
+ * **A draft is JSON round-trippable, period.** It is written through
10
+ * `createJSONStorage`, so a class instance, an `EntityLink` or a `Date` does not
11
+ * "mostly survive" — it comes back as something else, silently. {@link JsonValue}
12
+ * is the compile-time half of that rule and {@link mergeDrafts} the runtime half.
13
+ * Note that `UiPreferencesState`, the other persisted client store, writes
14
+ * through IndexedDB's structured clone and *does* keep a `Date`; the two
15
+ * contracts differ and must not be conflated.
16
+ */
17
+ export interface DraftsState {
18
+ drafts: Record<string, JsonValue>;
19
+ setDraft(address: string, value: JsonValue): void;
20
+ clearDraft(address: string): void;
21
+ }
22
+ export declare function persistedDrafts(store: DraftsState): Pick<DraftsState, 'drafts'>;
23
+ /**
24
+ * Bump when the *envelope* changes — how a draft is keyed or wrapped — and every
25
+ * persisted draft is discarded on the next load.
26
+ *
27
+ * It cannot see a change to one entity's members, because a draft is opaque
28
+ * here: this store holds `JsonValue`, and only the form that wrote one knows
29
+ * which members it should have. That half is `restoreEntityDraft`, which layers
30
+ * a restored draft over a freshly seeded one so a drifted member is dropped or
31
+ * re-seeded rather than rendered.
32
+ */
33
+ export declare const DRAFTS_VERSION = 2;
34
+ /**
35
+ * Discard, never guess. A draft whose envelope this build does not understand is
36
+ * an unfinished edit, not a record — losing it costs a retype, while migrating
37
+ * it blind risks submitting values whose meaning has changed.
38
+ *
39
+ * Written out rather than left to zustand's default, which discards too but logs
40
+ * `State loaded from storage couldn't be migrated since no migrate function was
41
+ * provided` while doing it — an error message for a decision that was made on
42
+ * purpose.
43
+ */
44
+ export declare function migrateDrafts(): Pick<DraftsState, 'drafts'>;
45
+ /**
46
+ * Restores only the entries that are actually JSON.
47
+ *
48
+ * The type above says what may be written; this is what happens when something
49
+ * else was, by a build that predates the rule or by a `setDraft` reached through
50
+ * a cast. Dropping the offending entry keeps one bad draft from taking the whole
51
+ * workspace's drafts with it — and it is per entry rather than all-or-nothing
52
+ * for that reason.
53
+ */
54
+ export declare function mergeDrafts(persistedState: unknown, currentState: DraftsState): DraftsState;
55
+ export declare const useDraftsState: import("zustand").UseBoundStore<Omit<import("zustand").StoreApi<DraftsState>, "setState" | "persist"> & {
56
+ setState(partial: DraftsState | Partial<DraftsState> | ((state: DraftsState) => DraftsState | Partial<DraftsState>), replace?: false | undefined): unknown;
57
+ setState(state: DraftsState | ((state: DraftsState) => DraftsState), replace: true): unknown;
58
+ persist: {
59
+ setOptions: (options: Partial<import("zustand/middleware").PersistOptions<DraftsState, unknown, unknown>>) => void;
60
+ clearStorage: () => void;
61
+ rehydrate: () => Promise<void> | void;
62
+ hasHydrated: () => boolean;
63
+ onHydrate: (fn: (state: DraftsState) => void) => () => void;
64
+ onFinishHydration: (fn: (state: DraftsState) => void) => () => void;
65
+ getOptions: () => Partial<import("zustand/middleware").PersistOptions<DraftsState, unknown, unknown>>;
66
+ };
67
+ }>;
68
+ /**
69
+ * Bind an editor to its persisted draft. Returns the current draft (restored
70
+ * from IndexedDB on mount), a setter the editor calls as fields change, and a
71
+ * clear for when the edit is committed.
72
+ *
73
+ * `TDraft` is constrained to {@link JsonValue}: an editor that wants to autosave
74
+ * something a JSON round trip would not return is asking for the one thing this
75
+ * store cannot do, and the compile error is the whole point of the constraint.
76
+ *
77
+ * One sharp edge, and the error message does not explain it: **declare a draft
78
+ * type as a `type`, never an `interface`.** TypeScript gives an interface no
79
+ * implicit index signature, so even `interface Draft { name: string }` — as JSON
80
+ * as a value gets — fails the constraint with "Index signature for type 'string'
81
+ * is missing". A `type` alias of the same shape passes.
82
+ */
83
+ export declare function useDraft<TDraft extends JsonValue>(address: string): {
84
+ draft: TDraft | undefined;
85
+ setDraft: (value: TDraft) => void;
86
+ clearDraft: () => void;
87
+ };
88
+ //# sourceMappingURL=drafts-state.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"drafts-state.d.ts","sourceRoot":"","sources":["../../../src/lib/workspace/drafts-state.ts"],"names":[],"mappings":"AAEA,OAAO,EAAe,KAAK,SAAS,EAAE,MAAM,eAAe,CAAC;AAQ5D;;;;;;;;;;;;;;GAcG;AACH,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;IAClC,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,GAAG,IAAI,CAAC;IAClD,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CACnC;AAED,wBAAgB,eAAe,CAC7B,KAAK,EAAE,WAAW,GACjB,IAAI,CAAC,WAAW,EAAE,QAAQ,CAAC,CAE7B;AAID;;;;;;;;;GASG;AAGH,eAAO,MAAM,cAAc,IAAI,CAAC;AAIhC;;;;;;;;;GASG;AACH,wBAAgB,aAAa,IAAI,IAAI,CAAC,WAAW,EAAE,QAAQ,CAAC,CAE3D;AAED;;;;;;;;GAQG;AACH,wBAAgB,WAAW,CACzB,cAAc,EAAE,OAAO,EACvB,YAAY,EAAE,WAAW,GACxB,WAAW,CASb;AAED,eAAO,MAAM,cAAc;;;;;;;;;;;;EA6B1B,CAAC;AAEF;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,QAAQ,CAAC,MAAM,SAAS,SAAS,EAAE,OAAO,EAAE,MAAM;;sBAYtD,MAAM;;EAMjB"}
@@ -0,0 +1,128 @@
1
+ 'use client';
2
+ import { _ as _extends } from "@swc/helpers/_/_extends";
3
+ import { _ as _object_without_properties_loose } from "@swc/helpers/_/_object_without_properties_loose";
4
+ import { _ as _to_property_key } from "@swc/helpers/_/_to_property_key";
5
+ import { isJsonValue } from '@entifix/core';
6
+ import { useCallback, useEffect } from 'react';
7
+ import { create } from 'zustand';
8
+ import { createJSONStorage, persist } from 'zustand/middleware';
9
+ import { makeIndexedDbStateStorage } from './idb-state-storage.js';
10
+ import { WORKSPACE_DB } from './tabs-state.js';
11
+ export function persistedDrafts(store) {
12
+ return {
13
+ drafts: store.drafts
14
+ };
15
+ }
16
+ const DRAFTS_STORE = 'stores';
17
+ /**
18
+ * Bump when the *envelope* changes — how a draft is keyed or wrapped — and every
19
+ * persisted draft is discarded on the next load.
20
+ *
21
+ * It cannot see a change to one entity's members, because a draft is opaque
22
+ * here: this store holds `JsonValue`, and only the form that wrote one knows
23
+ * which members it should have. That half is `restoreEntityDraft`, which layers
24
+ * a restored draft over a freshly seeded one so a drifted member is dropped or
25
+ * re-seeded rather than rendered.
26
+ */ // 2: a draft is keyed by the tab address, and that grammar changed with
27
+ // ADR 0042 — so every persisted draft is filed under a key nothing will ask for.
28
+ export const DRAFTS_VERSION = 2;
29
+ const emptyDrafts = {
30
+ drafts: {}
31
+ };
32
+ /**
33
+ * Discard, never guess. A draft whose envelope this build does not understand is
34
+ * an unfinished edit, not a record — losing it costs a retype, while migrating
35
+ * it blind risks submitting values whose meaning has changed.
36
+ *
37
+ * Written out rather than left to zustand's default, which discards too but logs
38
+ * `State loaded from storage couldn't be migrated since no migrate function was
39
+ * provided` while doing it — an error message for a decision that was made on
40
+ * purpose.
41
+ */ export function migrateDrafts() {
42
+ return emptyDrafts;
43
+ }
44
+ /**
45
+ * Restores only the entries that are actually JSON.
46
+ *
47
+ * The type above says what may be written; this is what happens when something
48
+ * else was, by a build that predates the rule or by a `setDraft` reached through
49
+ * a cast. Dropping the offending entry keeps one bad draft from taking the whole
50
+ * workspace's drafts with it — and it is per entry rather than all-or-nothing
51
+ * for that reason.
52
+ */ export function mergeDrafts(persistedState, currentState) {
53
+ const restored = persistedState == null ? void 0 : persistedState.drafts;
54
+ if (restored === null || typeof restored !== 'object') return currentState;
55
+ const drafts = {};
56
+ for (const [address, value] of Object.entries(restored)){
57
+ if (isJsonValue(value)) drafts[address] = value;
58
+ }
59
+ return _extends({}, currentState, {
60
+ drafts
61
+ });
62
+ }
63
+ export const useDraftsState = create()(persist((set)=>({
64
+ drafts: {},
65
+ setDraft: (address, value)=>set((state)=>({
66
+ drafts: _extends({}, state.drafts, {
67
+ [address]: value
68
+ })
69
+ })),
70
+ clearDraft: (address)=>set((state)=>{
71
+ if (!(address in state.drafts)) return state;
72
+ const _state_drafts = state.drafts, { [address]: _removed } = _state_drafts, rest = _object_without_properties_loose(_state_drafts, [
73
+ _to_property_key(address)
74
+ ]);
75
+ return {
76
+ drafts: rest
77
+ };
78
+ })
79
+ }), {
80
+ // Replaced with a scoped name by `WorkspaceShell` before it rehydrates, so
81
+ // two accounts on one browser profile do not share drafts. This literal is
82
+ // only what an unscoped store would write, and nothing rehydrates without
83
+ // passing through that call.
84
+ name: 'drafts',
85
+ version: DRAFTS_VERSION,
86
+ migrate: migrateDrafts,
87
+ merge: mergeDrafts,
88
+ storage: createJSONStorage(()=>makeIndexedDbStateStorage(WORKSPACE_DB, DRAFTS_STORE)),
89
+ partialize: persistedDrafts,
90
+ skipHydration: true
91
+ }));
92
+ /**
93
+ * Bind an editor to its persisted draft. Returns the current draft (restored
94
+ * from IndexedDB on mount), a setter the editor calls as fields change, and a
95
+ * clear for when the edit is committed.
96
+ *
97
+ * `TDraft` is constrained to {@link JsonValue}: an editor that wants to autosave
98
+ * something a JSON round trip would not return is asking for the one thing this
99
+ * store cannot do, and the compile error is the whole point of the constraint.
100
+ *
101
+ * One sharp edge, and the error message does not explain it: **declare a draft
102
+ * type as a `type`, never an `interface`.** TypeScript gives an interface no
103
+ * implicit index signature, so even `interface Draft { name: string }` — as JSON
104
+ * as a value gets — fails the constraint with "Index signature for type 'string'
105
+ * is missing". A `type` alias of the same shape passes.
106
+ */ export function useDraft(address) {
107
+ const draft = useDraftsState((state)=>state.drafts[address]);
108
+ const set = useDraftsState((state)=>state.setDraft);
109
+ const clear = useDraftsState((state)=>state.clearDraft);
110
+ useEffect(()=>{
111
+ void useDraftsState.persist.rehydrate();
112
+ }, []);
113
+ const setDraft = useCallback((value)=>set(address, value), [
114
+ set,
115
+ address
116
+ ]);
117
+ const clearDraft = useCallback(()=>clear(address), [
118
+ clear,
119
+ address
120
+ ]);
121
+ return {
122
+ draft,
123
+ setDraft,
124
+ clearDraft
125
+ };
126
+ }
127
+
128
+ //# sourceMappingURL=drafts-state.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/lib/workspace/drafts-state.ts"],"sourcesContent":["'use client';\n\nimport { isJsonValue, type JsonValue } from '@entifix/core';\nimport { useCallback, useEffect } from 'react';\nimport { create } from 'zustand';\nimport { createJSONStorage, persist } from 'zustand/middleware';\n\nimport { makeIndexedDbStateStorage } from './idb-state-storage.js';\nimport { WORKSPACE_DB } from './tabs-state.js';\n\n/**\n * Per-address edit drafts, persisted to IndexedDB so an in-progress edit\n * survives a refresh. Keyed by the tab's `?tab=` param (its address), a draft is\n * whatever the editor needs to restore its fields; it exists until the user\n * saves (or discards). This is the \"continuous autosave\" seam — workspace-host\n * only, so a plain route stays ephemeral.\n *\n * **A draft is JSON round-trippable, period.** It is written through\n * `createJSONStorage`, so a class instance, an `EntityLink` or a `Date` does not\n * \"mostly survive\" — it comes back as something else, silently. {@link JsonValue}\n * is the compile-time half of that rule and {@link mergeDrafts} the runtime half.\n * Note that `UiPreferencesState`, the other persisted client store, writes\n * through IndexedDB's structured clone and *does* keep a `Date`; the two\n * contracts differ and must not be conflated.\n */\nexport interface DraftsState {\n drafts: Record<string, JsonValue>;\n setDraft(address: string, value: JsonValue): void;\n clearDraft(address: string): void;\n}\n\nexport function persistedDrafts(\n store: DraftsState,\n): Pick<DraftsState, 'drafts'> {\n return { drafts: store.drafts };\n}\n\nconst DRAFTS_STORE = 'stores';\n\n/**\n * Bump when the *envelope* changes — how a draft is keyed or wrapped — and every\n * persisted draft is discarded on the next load.\n *\n * It cannot see a change to one entity's members, because a draft is opaque\n * here: this store holds `JsonValue`, and only the form that wrote one knows\n * which members it should have. That half is `restoreEntityDraft`, which layers\n * a restored draft over a freshly seeded one so a drifted member is dropped or\n * re-seeded rather than rendered.\n */\n// 2: a draft is keyed by the tab address, and that grammar changed with\n// ADR 0042 — so every persisted draft is filed under a key nothing will ask for.\nexport const DRAFTS_VERSION = 2;\n\nconst emptyDrafts: Pick<DraftsState, 'drafts'> = { drafts: {} };\n\n/**\n * Discard, never guess. A draft whose envelope this build does not understand is\n * an unfinished edit, not a record — losing it costs a retype, while migrating\n * it blind risks submitting values whose meaning has changed.\n *\n * Written out rather than left to zustand's default, which discards too but logs\n * `State loaded from storage couldn't be migrated since no migrate function was\n * provided` while doing it — an error message for a decision that was made on\n * purpose.\n */\nexport function migrateDrafts(): Pick<DraftsState, 'drafts'> {\n return emptyDrafts;\n}\n\n/**\n * Restores only the entries that are actually JSON.\n *\n * The type above says what may be written; this is what happens when something\n * else was, by a build that predates the rule or by a `setDraft` reached through\n * a cast. Dropping the offending entry keeps one bad draft from taking the whole\n * workspace's drafts with it — and it is per entry rather than all-or-nothing\n * for that reason.\n */\nexport function mergeDrafts(\n persistedState: unknown,\n currentState: DraftsState,\n): DraftsState {\n const restored = (persistedState as Partial<DraftsState> | undefined)?.drafts;\n if (restored === null || typeof restored !== 'object') return currentState;\n\n const drafts: Record<string, JsonValue> = {};\n for (const [address, value] of Object.entries(restored)) {\n if (isJsonValue(value)) drafts[address] = value;\n }\n return { ...currentState, drafts };\n}\n\nexport const useDraftsState = create<DraftsState>()(\n persist(\n set => ({\n drafts: {},\n setDraft: (address, value) =>\n set(state => ({ drafts: { ...state.drafts, [address]: value } })),\n clearDraft: address =>\n set(state => {\n if (!(address in state.drafts)) return state;\n const { [address]: _removed, ...rest } = state.drafts;\n return { drafts: rest };\n }),\n }),\n {\n // Replaced with a scoped name by `WorkspaceShell` before it rehydrates, so\n // two accounts on one browser profile do not share drafts. This literal is\n // only what an unscoped store would write, and nothing rehydrates without\n // passing through that call.\n name: 'drafts',\n version: DRAFTS_VERSION,\n migrate: migrateDrafts,\n merge: mergeDrafts,\n storage: createJSONStorage(() =>\n makeIndexedDbStateStorage(WORKSPACE_DB, DRAFTS_STORE),\n ),\n partialize: persistedDrafts,\n skipHydration: true,\n },\n ),\n);\n\n/**\n * Bind an editor to its persisted draft. Returns the current draft (restored\n * from IndexedDB on mount), a setter the editor calls as fields change, and a\n * clear for when the edit is committed.\n *\n * `TDraft` is constrained to {@link JsonValue}: an editor that wants to autosave\n * something a JSON round trip would not return is asking for the one thing this\n * store cannot do, and the compile error is the whole point of the constraint.\n *\n * One sharp edge, and the error message does not explain it: **declare a draft\n * type as a `type`, never an `interface`.** TypeScript gives an interface no\n * implicit index signature, so even `interface Draft { name: string }` — as JSON\n * as a value gets — fails the constraint with \"Index signature for type 'string'\n * is missing\". A `type` alias of the same shape passes.\n */\nexport function useDraft<TDraft extends JsonValue>(address: string) {\n const draft = useDraftsState(\n state => state.drafts[address] as TDraft | undefined,\n );\n const set = useDraftsState(state => state.setDraft);\n const clear = useDraftsState(state => state.clearDraft);\n\n useEffect(() => {\n void useDraftsState.persist.rehydrate();\n }, []);\n\n const setDraft = useCallback(\n (value: TDraft) => set(address, value),\n [set, address],\n );\n const clearDraft = useCallback(() => clear(address), [clear, address]);\n\n return { draft, setDraft, clearDraft };\n}\n"],"names":["isJsonValue","useCallback","useEffect","create","createJSONStorage","persist","makeIndexedDbStateStorage","WORKSPACE_DB","persistedDrafts","store","drafts","DRAFTS_STORE","DRAFTS_VERSION","emptyDrafts","migrateDrafts","mergeDrafts","persistedState","currentState","restored","address","value","Object","entries","useDraftsState","set","setDraft","state","clearDraft","_removed","rest","name","version","migrate","merge","storage","partialize","skipHydration","useDraft","draft","clear","rehydrate"],"mappings":"AAAA;;;;AAEA,SAASA,WAAW,QAAwB,gBAAgB;AAC5D,SAASC,WAAW,EAAEC,SAAS,QAAQ,QAAQ;AAC/C,SAASC,MAAM,QAAQ,UAAU;AACjC,SAASC,iBAAiB,EAAEC,OAAO,QAAQ,qBAAqB;AAEhE,SAASC,yBAAyB,QAAQ,yBAAyB;AACnE,SAASC,YAAY,QAAQ,kBAAkB;AAuB/C,OAAO,SAASC,gBACdC,KAAkB;IAElB,OAAO;QAAEC,QAAQD,MAAMC,MAAM;IAAC;AAChC;AAEA,MAAMC,eAAe;AAErB;;;;;;;;;CASC,GACD,wEAAwE;AACxE,iFAAiF;AACjF,OAAO,MAAMC,iBAAiB,EAAE;AAEhC,MAAMC,cAA2C;IAAEH,QAAQ,CAAC;AAAE;AAE9D;;;;;;;;;CASC,GACD,OAAO,SAASI;IACd,OAAOD;AACT;AAEA;;;;;;;;CAQC,GACD,OAAO,SAASE,YACdC,cAAuB,EACvBC,YAAyB;IAEzB,MAAMC,WAAYF,kCAAD,AAACA,eAAqDN,MAAM;IAC7E,IAAIQ,aAAa,QAAQ,OAAOA,aAAa,UAAU,OAAOD;IAE9D,MAAMP,SAAoC,CAAC;IAC3C,KAAK,MAAM,CAACS,SAASC,MAAM,IAAIC,OAAOC,OAAO,CAACJ,UAAW;QACvD,IAAIlB,YAAYoB,QAAQV,MAAM,CAACS,QAAQ,GAAGC;IAC5C;IACA,OAAO,aAAKH;QAAcP;;AAC5B;AAEA,OAAO,MAAMa,iBAAiBpB,SAC5BE,QACEmB,CAAAA,MAAQ,CAAA;QACNd,QAAQ,CAAC;QACTe,UAAU,CAACN,SAASC,QAClBI,IAAIE,CAAAA,QAAU,CAAA;oBAAEhB,QAAQ,aAAKgB,MAAMhB,MAAM;wBAAE,CAACS,QAAQ,EAAEC;;gBAAQ,CAAA;QAChEO,YAAYR,CAAAA,UACVK,IAAIE,CAAAA;gBACF,IAAI,CAAEP,CAAAA,WAAWO,MAAMhB,MAAM,AAAD,GAAI,OAAOgB;gBACvC,MAAyCA,gBAAAA,MAAMhB,MAAM,EAA/C,EAAE,CAACS,QAAQ,EAAES,QAAQ,EAAW,GAAGF,eAATG,wCAASH;qCAAhCP;;gBACT,OAAO;oBAAET,QAAQmB;gBAAK;YACxB;IACJ,CAAA,GACA;IACE,2EAA2E;IAC3E,2EAA2E;IAC3E,0EAA0E;IAC1E,6BAA6B;IAC7BC,MAAM;IACNC,SAASnB;IACToB,SAASlB;IACTmB,OAAOlB;IACPmB,SAAS9B,kBAAkB,IACzBE,0BAA0BC,cAAcI;IAE1CwB,YAAY3B;IACZ4B,eAAe;AACjB,IAEF;AAEF;;;;;;;;;;;;;;CAcC,GACD,OAAO,SAASC,SAAmClB,OAAe;IAChE,MAAMmB,QAAQf,eACZG,CAAAA,QAASA,MAAMhB,MAAM,CAACS,QAAQ;IAEhC,MAAMK,MAAMD,eAAeG,CAAAA,QAASA,MAAMD,QAAQ;IAClD,MAAMc,QAAQhB,eAAeG,CAAAA,QAASA,MAAMC,UAAU;IAEtDzB,UAAU;QACR,KAAKqB,eAAelB,OAAO,CAACmC,SAAS;IACvC,GAAG,EAAE;IAEL,MAAMf,WAAWxB,YACf,CAACmB,QAAkBI,IAAIL,SAASC,QAChC;QAACI;QAAKL;KAAQ;IAEhB,MAAMQ,aAAa1B,YAAY,IAAMsC,MAAMpB,UAAU;QAACoB;QAAOpB;KAAQ;IAErE,OAAO;QAAEmB;QAAOb;QAAUE;IAAW;AACvC"}
@@ -0,0 +1,26 @@
1
+ import { type ReactNode } from 'react';
2
+ /**
3
+ * Navigation intent, decoupled from the host. A page says "go to this list" or
4
+ * "open this entity"; the host decides how. In a route the destination is a URL
5
+ * push; in the workspace it is a tab open/focus. A page calls {@link useEntityNav}
6
+ * and never learns which host it is in.
7
+ */
8
+ export interface EntityNav {
9
+ toList(entityKey: string): void;
10
+ toEntity(entityKey: string, id: string): void;
11
+ }
12
+ /** Route host: navigation is a canonical URL push under `/catalog`. */
13
+ export declare function useRouteEntityNav(basePath?: string): EntityNav;
14
+ /** Tab host: navigation opens or focuses a workspace tab. */
15
+ export declare function useTabEntityNav(): EntityNav;
16
+ export declare function EntityNavProvider({ value, children, }: {
17
+ value: EntityNav;
18
+ children: ReactNode;
19
+ }): import("react").JSX.Element;
20
+ /**
21
+ * The active {@link EntityNav}. Falls back to route navigation when no provider
22
+ * is mounted, so a page rendered as a plain route (its default host) navigates
23
+ * by URL without any wiring; the workspace wraps its tabs in a tab-nav provider.
24
+ */
25
+ export declare function useEntityNav(): EntityNav;
26
+ //# sourceMappingURL=entity-nav.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"entity-nav.d.ts","sourceRoot":"","sources":["../../../src/lib/workspace/entity-nav.tsx"],"names":[],"mappings":"AAIA,OAAO,EAAiB,KAAK,SAAS,EAAuB,MAAM,OAAO,CAAC;AAI3E;;;;;GAKG;AACH,MAAM,WAAW,SAAS;IACxB,MAAM,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,QAAQ,CAAC,SAAS,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;CAC/C;AAID,uEAAuE;AACvE,wBAAgB,iBAAiB,CAAC,QAAQ,SAAa,GAAG,SAAS,CAUlE;AAED,6DAA6D;AAC7D,wBAAgB,eAAe,IAAI,SAAS,CAiB3C;AAED,wBAAgB,iBAAiB,CAAC,EAChC,KAAK,EACL,QAAQ,GACT,EAAE;IACD,KAAK,EAAE,SAAS,CAAC;IACjB,QAAQ,EAAE,SAAS,CAAC;CACrB,+BAMA;AAED;;;;GAIG;AACH,wBAAgB,YAAY,IAAI,SAAS,CAIxC"}
@@ -0,0 +1,56 @@
1
+ 'use client';
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { screenAddress } from '@entifix/authz';
4
+ import { useRouter } from 'next/navigation';
5
+ import { createContext, useContext, useMemo } from 'react';
6
+ import { useTabsState } from './tabs-state.js';
7
+ const EntityNavContext = /*#__PURE__*/ createContext(null);
8
+ /** Route host: navigation is a canonical URL push under `/catalog`. */ export function useRouteEntityNav(basePath = '/catalog') {
9
+ const router = useRouter();
10
+ return useMemo(()=>({
11
+ toList: (entityKey)=>router.push(`${basePath}/${entityKey}`),
12
+ toEntity: (entityKey, id)=>router.push(`${basePath}/${entityKey}/${id}`)
13
+ }), [
14
+ router,
15
+ basePath
16
+ ]);
17
+ }
18
+ /** Tab host: navigation opens or focuses a workspace tab. */ export function useTabEntityNav() {
19
+ const open = useTabsState((state)=>state.open);
20
+ return useMemo(()=>({
21
+ toList: (entityKey)=>open({
22
+ param: screenAddress({
23
+ type: 'master',
24
+ key: entityKey
25
+ }),
26
+ title: entityKey
27
+ }),
28
+ toEntity: (entityKey, id)=>open({
29
+ param: screenAddress({
30
+ type: 'master',
31
+ key: entityKey,
32
+ id
33
+ }),
34
+ title: `${entityKey} #${id}`
35
+ })
36
+ }), [
37
+ open
38
+ ]);
39
+ }
40
+ export function EntityNavProvider({ value, children }) {
41
+ return /*#__PURE__*/ _jsx(EntityNavContext.Provider, {
42
+ value: value,
43
+ children: children
44
+ });
45
+ }
46
+ /**
47
+ * The active {@link EntityNav}. Falls back to route navigation when no provider
48
+ * is mounted, so a page rendered as a plain route (its default host) navigates
49
+ * by URL without any wiring; the workspace wraps its tabs in a tab-nav provider.
50
+ */ export function useEntityNav() {
51
+ const provided = useContext(EntityNavContext);
52
+ const routeNav = useRouteEntityNav();
53
+ return provided != null ? provided : routeNav;
54
+ }
55
+
56
+ //# sourceMappingURL=entity-nav.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/lib/workspace/entity-nav.tsx"],"sourcesContent":["'use client';\n\nimport { screenAddress } from '@entifix/authz';\nimport { useRouter } from 'next/navigation';\nimport { createContext, type ReactNode, useContext, useMemo } from 'react';\n\nimport { useTabsState } from './tabs-state.js';\n\n/**\n * Navigation intent, decoupled from the host. A page says \"go to this list\" or\n * \"open this entity\"; the host decides how. In a route the destination is a URL\n * push; in the workspace it is a tab open/focus. A page calls {@link useEntityNav}\n * and never learns which host it is in.\n */\nexport interface EntityNav {\n toList(entityKey: string): void;\n toEntity(entityKey: string, id: string): void;\n}\n\nconst EntityNavContext = createContext<EntityNav | null>(null);\n\n/** Route host: navigation is a canonical URL push under `/catalog`. */\nexport function useRouteEntityNav(basePath = '/catalog'): EntityNav {\n const router = useRouter();\n return useMemo(\n () => ({\n toList: entityKey => router.push(`${basePath}/${entityKey}`),\n toEntity: (entityKey, id) =>\n router.push(`${basePath}/${entityKey}/${id}`),\n }),\n [router, basePath],\n );\n}\n\n/** Tab host: navigation opens or focuses a workspace tab. */\nexport function useTabEntityNav(): EntityNav {\n const open = useTabsState(state => state.open);\n return useMemo(\n () => ({\n toList: entityKey =>\n open({\n param: screenAddress({ type: 'master', key: entityKey }),\n title: entityKey,\n }),\n toEntity: (entityKey, id) =>\n open({\n param: screenAddress({ type: 'master', key: entityKey, id }),\n title: `${entityKey} #${id}`,\n }),\n }),\n [open],\n );\n}\n\nexport function EntityNavProvider({\n value,\n children,\n}: {\n value: EntityNav;\n children: ReactNode;\n}) {\n return (\n <EntityNavContext.Provider value={value}>\n {children}\n </EntityNavContext.Provider>\n );\n}\n\n/**\n * The active {@link EntityNav}. Falls back to route navigation when no provider\n * is mounted, so a page rendered as a plain route (its default host) navigates\n * by URL without any wiring; the workspace wraps its tabs in a tab-nav provider.\n */\nexport function useEntityNav(): EntityNav {\n const provided = useContext(EntityNavContext);\n const routeNav = useRouteEntityNav();\n return provided ?? routeNav;\n}\n"],"names":["screenAddress","useRouter","createContext","useContext","useMemo","useTabsState","EntityNavContext","useRouteEntityNav","basePath","router","toList","entityKey","push","toEntity","id","useTabEntityNav","open","state","param","type","key","title","EntityNavProvider","value","children","Provider","useEntityNav","provided","routeNav"],"mappings":"AAAA;;AAEA,SAASA,aAAa,QAAQ,iBAAiB;AAC/C,SAASC,SAAS,QAAQ,kBAAkB;AAC5C,SAASC,aAAa,EAAkBC,UAAU,EAAEC,OAAO,QAAQ,QAAQ;AAE3E,SAASC,YAAY,QAAQ,kBAAkB;AAa/C,MAAMC,iCAAmBJ,cAAgC;AAEzD,qEAAqE,GACrE,OAAO,SAASK,kBAAkBC,WAAW,UAAU;IACrD,MAAMC,SAASR;IACf,OAAOG,QACL,IAAO,CAAA;YACLM,QAAQC,CAAAA,YAAaF,OAAOG,IAAI,CAAC,GAAGJ,SAAS,CAAC,EAAEG,WAAW;YAC3DE,UAAU,CAACF,WAAWG,KACpBL,OAAOG,IAAI,CAAC,GAAGJ,SAAS,CAAC,EAAEG,UAAU,CAAC,EAAEG,IAAI;QAChD,CAAA,GACA;QAACL;QAAQD;KAAS;AAEtB;AAEA,2DAA2D,GAC3D,OAAO,SAASO;IACd,MAAMC,OAAOX,aAAaY,CAAAA,QAASA,MAAMD,IAAI;IAC7C,OAAOZ,QACL,IAAO,CAAA;YACLM,QAAQC,CAAAA,YACNK,KAAK;oBACHE,OAAOlB,cAAc;wBAAEmB,MAAM;wBAAUC,KAAKT;oBAAU;oBACtDU,OAAOV;gBACT;YACFE,UAAU,CAACF,WAAWG,KACpBE,KAAK;oBACHE,OAAOlB,cAAc;wBAAEmB,MAAM;wBAAUC,KAAKT;wBAAWG;oBAAG;oBAC1DO,OAAO,GAAGV,UAAU,EAAE,EAAEG,IAAI;gBAC9B;QACJ,CAAA,GACA;QAACE;KAAK;AAEV;AAEA,OAAO,SAASM,kBAAkB,EAChCC,KAAK,EACLC,QAAQ,EAIT;IACC,qBACE,KAAClB,iBAAiBmB,QAAQ;QAACF,OAAOA;kBAC/BC;;AAGP;AAEA;;;;CAIC,GACD,OAAO,SAASE;IACd,MAAMC,WAAWxB,WAAWG;IAC5B,MAAMsB,WAAWrB;IACjB,OAAOoB,mBAAAA,WAAYC;AACrB"}
@@ -0,0 +1,51 @@
1
+ import { type ScreenType } from '@entifix/authz';
2
+ import type { ReactNode } from 'react';
3
+ import type { TabKind } from './tab-kind.js';
4
+ /** One list a workspace may open — the collection half of a screen. */
5
+ export interface EntityTabList {
6
+ /** The tab caption's catalog key. `@entity({ pluralKey })` for a generated screen. */
7
+ readonly titleKey: string;
8
+ render(): ReactNode;
9
+ }
10
+ /** One record a workspace may open — the same screen, with an id. */
11
+ export interface EntityTabRecord {
12
+ /** The tab caption's catalog key. `@entity({ labelKey })` for a generated screen. */
13
+ readonly labelKey: string;
14
+ render(id: string): ReactNode;
15
+ }
16
+ export interface EntityTabScreens {
17
+ readonly lists: Record<string, EntityTabList>;
18
+ readonly records: Record<string, EntityTabRecord>;
19
+ }
20
+ /**
21
+ * An entity tab kind: `<type>:<key>` for a list, `<type>:<key>:<id>` for one
22
+ * record.
23
+ *
24
+ * **The list and the record are not different kinds.** They are the same screen
25
+ * with and without a record, which is exactly what the optional id in the
26
+ * payload says — three kinds (`catalog:`, `entity:`, `system:`) collapsed into
27
+ * one for that reason, and the prefix names the *taxonomy* now
28
+ * ([ADR 0042](../../../../../../../docs/adr/0042-the-workspace-address-is-the-taxonomy-serialized.md)).
29
+ *
30
+ * A factory over the {@link ScreenType} rather than a constant, and that is the
31
+ * whole of what makes an Operaciones screen addressable. It was written inline
32
+ * in back-office-app as a `masterKind`, so `master:` was the only entity
33
+ * address any host could resolve and `operation:` matched nothing at all — a
34
+ * second copy of this parser beside it is exactly the drift the registry was
35
+ * consolidated to stop, and the two would have had to agree about a grammar
36
+ * neither owns.
37
+ *
38
+ * Which screens a host offers stays the host's decision — a second host
39
+ * mounting the same shells may want a different set — so the screens arrive as
40
+ * an argument. What no longer varies is how one is addressed.
41
+ *
42
+ * `render` takes the id rather than a component, so the caller decides what
43
+ * hosting a record means: in a workspace that is an editor wired to an
44
+ * address-keyed autosaved draft, and the address it keys on has to carry this
45
+ * same `type`.
46
+ */
47
+ export declare function entityTabKind(type: ScreenType, screens: EntityTabScreens): TabKind<{
48
+ key: string;
49
+ id?: string;
50
+ }>;
51
+ //# sourceMappingURL=entity-tab-kind.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"entity-tab-kind.d.ts","sourceRoot":"","sources":["../../../src/lib/workspace/entity-tab-kind.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAsB,KAAK,UAAU,EAAE,MAAM,gBAAgB,CAAC;AACrE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEvC,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAE7C,uEAAuE;AACvE,MAAM,WAAW,aAAa;IAC5B,sFAAsF;IACtF,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,MAAM,IAAI,SAAS,CAAC;CACrB;AAED,qEAAqE;AACrE,MAAM,WAAW,eAAe;IAC9B,qFAAqF;IACrF,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,SAAS,CAAC;CAC/B;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;IAC9C,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;CACnD;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAgB,aAAa,CAC3B,IAAI,EAAE,UAAU,EAChB,OAAO,EAAE,gBAAgB,GACxB,OAAO,CAAC;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,EAAE,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CA0BvC"}
@@ -0,0 +1,47 @@
1
+ 'use client';
2
+ import { parseScreenPayload } from '@entifix/authz';
3
+ /**
4
+ * An entity tab kind: `<type>:<key>` for a list, `<type>:<key>:<id>` for one
5
+ * record.
6
+ *
7
+ * **The list and the record are not different kinds.** They are the same screen
8
+ * with and without a record, which is exactly what the optional id in the
9
+ * payload says — three kinds (`catalog:`, `entity:`, `system:`) collapsed into
10
+ * one for that reason, and the prefix names the *taxonomy* now
11
+ * ([ADR 0042](../../../../../../../docs/adr/0042-the-workspace-address-is-the-taxonomy-serialized.md)).
12
+ *
13
+ * A factory over the {@link ScreenType} rather than a constant, and that is the
14
+ * whole of what makes an Operaciones screen addressable. It was written inline
15
+ * in back-office-app as a `masterKind`, so `master:` was the only entity
16
+ * address any host could resolve and `operation:` matched nothing at all — a
17
+ * second copy of this parser beside it is exactly the drift the registry was
18
+ * consolidated to stop, and the two would have had to agree about a grammar
19
+ * neither owns.
20
+ *
21
+ * Which screens a host offers stays the host's decision — a second host
22
+ * mounting the same shells may want a different set — so the screens arrive as
23
+ * an argument. What no longer varies is how one is addressed.
24
+ *
25
+ * `render` takes the id rather than a component, so the caller decides what
26
+ * hosting a record means: in a workspace that is an editor wired to an
27
+ * address-keyed autosaved draft, and the address it keys on has to carry this
28
+ * same `type`.
29
+ */ export function entityTabKind(type, screens) {
30
+ return {
31
+ kind: type,
32
+ match: (payload)=>{
33
+ const parsed = parseScreenPayload(payload);
34
+ if (parsed === null) return null;
35
+ // A key is valid for the half it names: a record address for a screen
36
+ // with a list and no record editor resolves to nothing, and the workspace
37
+ // answers with its fallback rather than rendering an empty editor.
38
+ const known = parsed.id === undefined ? parsed.key in screens.lists : parsed.key in screens.records;
39
+ return known ? parsed : null;
40
+ },
41
+ toParam: (addr)=>addr.id === undefined ? addr.key : `${addr.key}:${addr.id}`,
42
+ title: (addr, translate)=>addr.id === undefined ? translate(screens.lists[addr.key].titleKey) : `${translate(screens.records[addr.key].labelKey)} #${addr.id}`,
43
+ render: (addr)=>addr.id === undefined ? screens.lists[addr.key].render() : screens.records[addr.key].render(addr.id)
44
+ };
45
+ }
46
+
47
+ //# sourceMappingURL=entity-tab-kind.js.map