@open-mercato/ui 0.6.7 → 0.6.8-develop.6874.1.982d6097d8

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 (214) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/AGENTS.md +4 -2
  3. package/agentic/standalone-guide.md +1 -1
  4. package/build.mjs +2 -54
  5. package/dist/ai/AiChat.js +26 -7
  6. package/dist/ai/AiChat.js.map +2 -2
  7. package/dist/ai/LoopDisabledBanner.js +1 -1
  8. package/dist/ai/LoopDisabledBanner.js.map +2 -2
  9. package/dist/ai/parts/ConfirmationCard.js +4 -4
  10. package/dist/ai/parts/ConfirmationCard.js.map +2 -2
  11. package/dist/ai/parts/FieldDiffCard.js +2 -2
  12. package/dist/ai/parts/FieldDiffCard.js.map +2 -2
  13. package/dist/ai/parts/MutationResultCard.js +3 -3
  14. package/dist/ai/parts/MutationResultCard.js.map +2 -2
  15. package/dist/ai/ui-parts/pending-phase3-placeholder.js +1 -1
  16. package/dist/ai/ui-parts/pending-phase3-placeholder.js.map +2 -2
  17. package/dist/backend/AppShell.js +43 -4
  18. package/dist/backend/AppShell.js.map +2 -2
  19. package/dist/backend/AuthSessionGuard.js +14 -0
  20. package/dist/backend/AuthSessionGuard.js.map +2 -2
  21. package/dist/backend/BackendChromeProvider.js +6 -1
  22. package/dist/backend/BackendChromeProvider.js.map +2 -2
  23. package/dist/backend/CrudForm.js +6 -4
  24. package/dist/backend/CrudForm.js.map +2 -2
  25. package/dist/backend/DataTable.js +20 -18
  26. package/dist/backend/DataTable.js.map +2 -2
  27. package/dist/backend/JsonBuilder.js +3 -3
  28. package/dist/backend/JsonBuilder.js.map +2 -2
  29. package/dist/backend/NextStepCallout.js +4 -4
  30. package/dist/backend/NextStepCallout.js.map +1 -1
  31. package/dist/backend/PerspectiveSidebar.js +1 -1
  32. package/dist/backend/PerspectiveSidebar.js.map +2 -2
  33. package/dist/backend/ProfileDropdown.js +2 -1
  34. package/dist/backend/ProfileDropdown.js.map +2 -2
  35. package/dist/backend/RowActions.js +2 -2
  36. package/dist/backend/RowActions.js.map +2 -2
  37. package/dist/backend/confirm-dialog/ConfirmDialog.js +13 -1
  38. package/dist/backend/confirm-dialog/ConfirmDialog.js.map +2 -2
  39. package/dist/backend/crud/useGroupOrder.js +14 -21
  40. package/dist/backend/crud/useGroupOrder.js.map +2 -2
  41. package/dist/backend/custom-fields/FieldDefinitionsManager.js +1 -1
  42. package/dist/backend/custom-fields/FieldDefinitionsManager.js.map +2 -2
  43. package/dist/backend/dashboard/DashboardScreen.js +3 -3
  44. package/dist/backend/dashboard/DashboardScreen.js.map +2 -2
  45. package/dist/backend/detail/AddressTiles.js +4 -5
  46. package/dist/backend/detail/AddressTiles.js.map +2 -2
  47. package/dist/backend/detail/AttachmentDeleteDialog.js +1 -1
  48. package/dist/backend/detail/AttachmentDeleteDialog.js.map +2 -2
  49. package/dist/backend/detail/AttachmentMetadataDialog.js.map +1 -1
  50. package/dist/backend/detail/AttachmentsSection.js +1 -1
  51. package/dist/backend/detail/AttachmentsSection.js.map +2 -2
  52. package/dist/backend/detail/NotesSection.js +1 -1
  53. package/dist/backend/detail/NotesSection.js.map +2 -2
  54. package/dist/backend/detail/TagsSection.js +1 -1
  55. package/dist/backend/detail/TagsSection.js.map +2 -2
  56. package/dist/backend/filters/AdvancedFilterPanel.js +1 -1
  57. package/dist/backend/filters/AdvancedFilterPanel.js.map +2 -2
  58. package/dist/backend/forms/FormHeader.js +1 -2
  59. package/dist/backend/forms/FormHeader.js.map +2 -2
  60. package/dist/backend/icons/lucideRegistry.generated.js +1 -25
  61. package/dist/backend/icons/lucideRegistry.generated.js.map +2 -2
  62. package/dist/backend/icons/lucideRegistry.js +3 -3
  63. package/dist/backend/icons/lucideRegistry.js.map +2 -2
  64. package/dist/backend/icons/lucideRegistryRuntime.js +28 -0
  65. package/dist/backend/icons/lucideRegistryRuntime.js.map +7 -0
  66. package/dist/backend/injection/spotIds.js +23 -18
  67. package/dist/backend/injection/spotIds.js.map +2 -2
  68. package/dist/backend/inputs/ComboboxInput.js.map +1 -1
  69. package/dist/backend/messages/EmailThreadsPanel.js +1 -1
  70. package/dist/backend/messages/EmailThreadsPanel.js.map +2 -2
  71. package/dist/backend/perspectiveState.js +34 -0
  72. package/dist/backend/perspectiveState.js.map +7 -0
  73. package/dist/backend/schedule/ScheduleAgenda.js +3 -3
  74. package/dist/backend/schedule/ScheduleAgenda.js.map +1 -1
  75. package/dist/backend/schedule/ScheduleGrid.js +3 -3
  76. package/dist/backend/schedule/ScheduleGrid.js.map +1 -1
  77. package/dist/backend/schedule/ScheduleToolbar.js +87 -70
  78. package/dist/backend/schedule/ScheduleToolbar.js.map +2 -2
  79. package/dist/backend/sidebar/SidebarCustomizationEditor.js +30 -2
  80. package/dist/backend/sidebar/SidebarCustomizationEditor.js.map +2 -2
  81. package/dist/backend/sidebar/customization-helpers.js +30 -0
  82. package/dist/backend/sidebar/customization-helpers.js.map +2 -2
  83. package/dist/backend/utils/apiCall.js +21 -1
  84. package/dist/backend/utils/apiCall.js.map +2 -2
  85. package/dist/backend/utils/customFieldDefs.js.map +2 -2
  86. package/dist/backend/utils/customFieldForms.js +10 -9
  87. package/dist/backend/utils/customFieldForms.js.map +2 -2
  88. package/dist/backend/utils/fetchAllPages.js +57 -0
  89. package/dist/backend/utils/fetchAllPages.js.map +7 -0
  90. package/dist/backend/utils/nav.js +5 -2
  91. package/dist/backend/utils/nav.js.map +2 -2
  92. package/dist/backend/version-history/VersionHistoryPanel.js +1 -1
  93. package/dist/backend/version-history/VersionHistoryPanel.js.map +2 -2
  94. package/dist/frontend/LanguageSwitcher.js +2 -1
  95. package/dist/frontend/LanguageSwitcher.js.map +2 -2
  96. package/dist/portal/PortalContext.js +1 -1
  97. package/dist/portal/PortalContext.js.map +2 -2
  98. package/dist/portal/hooks/portalBridgeStatus.js +25 -0
  99. package/dist/portal/hooks/portalBridgeStatus.js.map +7 -0
  100. package/dist/portal/hooks/usePortalEventBridge.js +6 -0
  101. package/dist/portal/hooks/usePortalEventBridge.js.map +2 -2
  102. package/dist/portal/hooks/usePortalNotifications.js +42 -4
  103. package/dist/portal/hooks/usePortalNotifications.js.map +2 -2
  104. package/dist/portal/utils/nav.js +2 -3
  105. package/dist/portal/utils/nav.js.map +2 -2
  106. package/dist/primitives/ErrorNotice.js +1 -1
  107. package/dist/primitives/ErrorNotice.js.map +2 -2
  108. package/dist/primitives/Notice.js +1 -1
  109. package/dist/primitives/Notice.js.map +1 -1
  110. package/dist/primitives/button.js +9 -4
  111. package/dist/primitives/button.js.map +2 -2
  112. package/dist/theme/ThemeProvider.js +1 -1
  113. package/dist/theme/ThemeProvider.js.map +2 -2
  114. package/dist/theme/index.js +3 -0
  115. package/dist/theme/index.js.map +2 -2
  116. package/dist/theme/theme-init-script.js +17 -0
  117. package/dist/theme/theme-init-script.js.map +7 -0
  118. package/figma/alert.figma.tsx +35 -0
  119. package/figma/badge.figma.tsx +33 -0
  120. package/figma/button.figma.tsx +33 -0
  121. package/figma/checkbox.figma.tsx +28 -0
  122. package/figma/drawer.figma.tsx +40 -0
  123. package/figma/input-variants.figma.tsx +58 -0
  124. package/figma/radio.figma.tsx +16 -0
  125. package/figma/select.figma.tsx +24 -0
  126. package/figma/status-badge.figma.tsx +24 -0
  127. package/figma/switch.figma.tsx +28 -0
  128. package/figma/tabs.figma.tsx +22 -0
  129. package/figma/tag.figma.tsx +27 -0
  130. package/figma.config.json +6 -0
  131. package/jest.config.cjs +2 -1
  132. package/package.json +21 -18
  133. package/scripts/lucideRegistrySource.cjs +133 -0
  134. package/src/ai/AiChat.tsx +36 -7
  135. package/src/ai/LoopDisabledBanner.tsx +1 -1
  136. package/src/ai/__tests__/AiChat.test.tsx +31 -0
  137. package/src/ai/parts/ConfirmationCard.tsx +4 -4
  138. package/src/ai/parts/FieldDiffCard.tsx +2 -2
  139. package/src/ai/parts/MutationResultCard.tsx +3 -3
  140. package/src/ai/ui-parts/pending-phase3-placeholder.tsx +2 -2
  141. package/src/backend/AppShell.tsx +55 -4
  142. package/src/backend/AuthSessionGuard.tsx +29 -0
  143. package/src/backend/BackendChromeProvider.tsx +19 -1
  144. package/src/backend/CrudForm.tsx +18 -4
  145. package/src/backend/DataTable.tsx +36 -19
  146. package/src/backend/JsonBuilder.tsx +3 -3
  147. package/src/backend/NextStepCallout.tsx +4 -4
  148. package/src/backend/PerspectiveSidebar.tsx +1 -1
  149. package/src/backend/ProfileDropdown.tsx +1 -0
  150. package/src/backend/RowActions.tsx +2 -2
  151. package/src/backend/__tests__/AppShell.test.tsx +65 -7
  152. package/src/backend/__tests__/AuthSessionGuard.test.tsx +52 -0
  153. package/src/backend/__tests__/BackendChromeProvider.test.tsx +98 -2
  154. package/src/backend/__tests__/CrudForm.fieldsetBinding.test.tsx +158 -0
  155. package/src/backend/__tests__/CrudForm.groupInjectionColumn.test.tsx +98 -0
  156. package/src/backend/__tests__/DataTable.metaHiddenLateColumns.test.tsx +105 -0
  157. package/src/backend/__tests__/DataTable.perspectiveStorage.test.ts +62 -1
  158. package/src/backend/__tests__/nav-utils.test.ts +139 -0
  159. package/src/backend/confirm-dialog/ConfirmDialog.tsx +16 -1
  160. package/src/backend/confirm-dialog/__tests__/ConfirmDialog.test.tsx +68 -1
  161. package/src/backend/crud/__tests__/useGroupOrder.test.ts +39 -1
  162. package/src/backend/crud/useGroupOrder.ts +30 -25
  163. package/src/backend/custom-fields/FieldDefinitionsManager.tsx +1 -1
  164. package/src/backend/dashboard/DashboardScreen.tsx +3 -3
  165. package/src/backend/detail/AddressTiles.tsx +3 -6
  166. package/src/backend/detail/AttachmentDeleteDialog.tsx +1 -1
  167. package/src/backend/detail/AttachmentMetadataDialog.tsx +1 -1
  168. package/src/backend/detail/AttachmentsSection.tsx +1 -1
  169. package/src/backend/detail/NotesSection.tsx +1 -1
  170. package/src/backend/detail/TagsSection.tsx +1 -1
  171. package/src/backend/detail/__tests__/AttachmentAssignmentsEditor.test.tsx +21 -0
  172. package/src/backend/filters/AdvancedFilterPanel.tsx +1 -1
  173. package/src/backend/forms/FormHeader.tsx +1 -2
  174. package/src/backend/icons/__tests__/lucideRegistryGenerator.test.ts +177 -0
  175. package/src/backend/icons/lucideRegistry.generated.tsx +3 -37
  176. package/src/backend/icons/lucideRegistry.ts +2 -2
  177. package/src/backend/icons/lucideRegistryRuntime.tsx +36 -0
  178. package/src/backend/injection/__tests__/spotIds.extension-points.test.ts +38 -0
  179. package/src/backend/injection/spotIds.ts +23 -18
  180. package/src/backend/inputs/ComboboxInput.tsx +1 -1
  181. package/src/backend/messages/EmailThreadsPanel.tsx +1 -1
  182. package/src/backend/perspectiveState.ts +44 -0
  183. package/src/backend/schedule/ScheduleAgenda.tsx +3 -3
  184. package/src/backend/schedule/ScheduleGrid.tsx +3 -3
  185. package/src/backend/schedule/ScheduleToolbar.tsx +89 -68
  186. package/src/backend/sidebar/SidebarCustomizationEditor.tsx +42 -3
  187. package/src/backend/sidebar/__tests__/group-visibility.test.ts +131 -0
  188. package/src/backend/sidebar/customization-helpers.ts +47 -0
  189. package/src/backend/utils/__tests__/apiCall.test.ts +61 -0
  190. package/src/backend/utils/__tests__/customFieldForms.optionsPaging.test.ts +95 -0
  191. package/src/backend/utils/__tests__/fetchAllPages.test.ts +103 -0
  192. package/src/backend/utils/apiCall.ts +29 -1
  193. package/src/backend/utils/customFieldDefs.ts +1 -0
  194. package/src/backend/utils/customFieldForms.ts +15 -11
  195. package/src/backend/utils/fetchAllPages.ts +93 -0
  196. package/src/backend/utils/nav.ts +22 -2
  197. package/src/backend/version-history/VersionHistoryPanel.tsx +1 -1
  198. package/src/frontend/LanguageSwitcher.tsx +1 -0
  199. package/src/portal/PortalContext.tsx +1 -1
  200. package/src/portal/hooks/__tests__/usePortalEventBridge.test.tsx +66 -0
  201. package/src/portal/hooks/__tests__/usePortalNotifications.test.tsx +155 -0
  202. package/src/portal/hooks/portalBridgeStatus.ts +29 -0
  203. package/src/portal/hooks/usePortalEventBridge.ts +6 -0
  204. package/src/portal/hooks/usePortalNotifications.ts +55 -12
  205. package/src/portal/utils/__tests__/nav.test.ts +2 -2
  206. package/src/portal/utils/nav.ts +3 -4
  207. package/src/primitives/ErrorNotice.tsx +1 -1
  208. package/src/primitives/Notice.tsx +5 -5
  209. package/src/primitives/__tests__/button.test.tsx +70 -0
  210. package/src/primitives/button.tsx +10 -4
  211. package/src/theme/ThemeProvider.tsx +1 -2
  212. package/src/theme/__tests__/theme-init-script.test.ts +70 -0
  213. package/src/theme/index.ts +1 -0
  214. package/src/theme/theme-init-script.ts +23 -0
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/backend/AuthSessionGuard.tsx"],
4
- "sourcesContent": ["'use client'\nimport * as React from 'react'\nimport { apiCall } from './utils/apiCall'\n\nexport const AUTH_IDENTITY_STORAGE_KEY = 'om:auth:identity'\nexport const AUTH_IDENTITY_BROADCAST_CHANNEL = 'om-auth-identity'\n\ntype FeatureCheckResponse = {\n ok: boolean\n granted?: string[]\n userId?: string\n}\n\nexport type AuthSessionGuardProps = {\n serverUserId: string | null\n}\n\nexport const __reload = {\n fn: (): void => {\n if (typeof window !== 'undefined') window.location.reload()\n },\n}\n\nexport function AuthSessionGuard({ serverUserId }: AuthSessionGuardProps) {\n React.useEffect(() => {\n if (typeof window === 'undefined') return\n let cancelled = false\n let reloadScheduled = false\n\n const triggerReload = () => {\n if (reloadScheduled) return\n reloadScheduled = true\n __reload.fn()\n }\n\n const checkIdentity = async () => {\n if (cancelled || reloadScheduled) return\n try {\n const res = await apiCall<FeatureCheckResponse>('/api/auth/feature-check', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ features: [] }),\n cache: 'no-store',\n })\n if (cancelled || reloadScheduled) return\n if (res.status === 401) {\n if (serverUserId) triggerReload()\n return\n }\n if (!res.ok) return\n const currentUserId = typeof res.result?.userId === 'string' ? res.result.userId : null\n if (!currentUserId) return\n if (currentUserId !== serverUserId) triggerReload()\n } catch {\n // network errors are ignored \u2014 next focus/storage event retries\n }\n }\n\n const onVisibilityOrFocus = () => {\n if (typeof document !== 'undefined' && document.visibilityState === 'hidden') return\n void checkIdentity()\n }\n\n const onStorage = (event: StorageEvent) => {\n if (event.key !== AUTH_IDENTITY_STORAGE_KEY) return\n void checkIdentity()\n }\n\n let broadcastChannel: BroadcastChannel | null = null\n if (typeof BroadcastChannel !== 'undefined') {\n try {\n broadcastChannel = new BroadcastChannel(AUTH_IDENTITY_BROADCAST_CHANNEL)\n broadcastChannel.onmessage = () => { void checkIdentity() }\n } catch {\n broadcastChannel = null\n }\n }\n\n window.addEventListener('focus', onVisibilityOrFocus)\n document.addEventListener('visibilitychange', onVisibilityOrFocus)\n window.addEventListener('storage', onStorage)\n\n return () => {\n cancelled = true\n window.removeEventListener('focus', onVisibilityOrFocus)\n document.removeEventListener('visibilitychange', onVisibilityOrFocus)\n window.removeEventListener('storage', onStorage)\n if (broadcastChannel) {\n broadcastChannel.onmessage = null\n broadcastChannel.close()\n }\n }\n }, [serverUserId])\n\n return null\n}\n\nexport function notifyAuthIdentityChange(): void {\n if (typeof window === 'undefined') return\n try {\n if (typeof BroadcastChannel !== 'undefined') {\n const channel = new BroadcastChannel(AUTH_IDENTITY_BROADCAST_CHANNEL)\n channel.postMessage('changed')\n channel.close()\n }\n } catch {\n // ignore \u2014 fall back to storage event below\n }\n try {\n window.localStorage.setItem(AUTH_IDENTITY_STORAGE_KEY, String(Date.now()))\n } catch {\n // private mode / quota errors are non-fatal\n }\n}\n"],
5
- "mappings": ";AACA,YAAY,WAAW;AACvB,SAAS,eAAe;AAEjB,MAAM,4BAA4B;AAClC,MAAM,kCAAkC;AAYxC,MAAM,WAAW;AAAA,EACtB,IAAI,MAAY;AACd,QAAI,OAAO,WAAW,YAAa,QAAO,SAAS,OAAO;AAAA,EAC5D;AACF;AAEO,SAAS,iBAAiB,EAAE,aAAa,GAA0B;AACxE,QAAM,UAAU,MAAM;AACpB,QAAI,OAAO,WAAW,YAAa;AACnC,QAAI,YAAY;AAChB,QAAI,kBAAkB;AAEtB,UAAM,gBAAgB,MAAM;AAC1B,UAAI,gBAAiB;AACrB,wBAAkB;AAClB,eAAS,GAAG;AAAA,IACd;AAEA,UAAM,gBAAgB,YAAY;AAChC,UAAI,aAAa,gBAAiB;AAClC,UAAI;AACF,cAAM,MAAM,MAAM,QAA8B,2BAA2B;AAAA,UACzE,QAAQ;AAAA,UACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,UAC9C,MAAM,KAAK,UAAU,EAAE,UAAU,CAAC,EAAE,CAAC;AAAA,UACrC,OAAO;AAAA,QACT,CAAC;AACD,YAAI,aAAa,gBAAiB;AAClC,YAAI,IAAI,WAAW,KAAK;AACtB,cAAI,aAAc,eAAc;AAChC;AAAA,QACF;AACA,YAAI,CAAC,IAAI,GAAI;AACb,cAAM,gBAAgB,OAAO,IAAI,QAAQ,WAAW,WAAW,IAAI,OAAO,SAAS;AACnF,YAAI,CAAC,cAAe;AACpB,YAAI,kBAAkB,aAAc,eAAc;AAAA,MACpD,QAAQ;AAAA,MAER;AAAA,IACF;AAEA,UAAM,sBAAsB,MAAM;AAChC,UAAI,OAAO,aAAa,eAAe,SAAS,oBAAoB,SAAU;AAC9E,WAAK,cAAc;AAAA,IACrB;AAEA,UAAM,YAAY,CAAC,UAAwB;AACzC,UAAI,MAAM,QAAQ,0BAA2B;AAC7C,WAAK,cAAc;AAAA,IACrB;AAEA,QAAI,mBAA4C;AAChD,QAAI,OAAO,qBAAqB,aAAa;AAC3C,UAAI;AACF,2BAAmB,IAAI,iBAAiB,+BAA+B;AACvE,yBAAiB,YAAY,MAAM;AAAE,eAAK,cAAc;AAAA,QAAE;AAAA,MAC5D,QAAQ;AACN,2BAAmB;AAAA,MACrB;AAAA,IACF;AAEA,WAAO,iBAAiB,SAAS,mBAAmB;AACpD,aAAS,iBAAiB,oBAAoB,mBAAmB;AACjE,WAAO,iBAAiB,WAAW,SAAS;AAE5C,WAAO,MAAM;AACX,kBAAY;AACZ,aAAO,oBAAoB,SAAS,mBAAmB;AACvD,eAAS,oBAAoB,oBAAoB,mBAAmB;AACpE,aAAO,oBAAoB,WAAW,SAAS;AAC/C,UAAI,kBAAkB;AACpB,yBAAiB,YAAY;AAC7B,yBAAiB,MAAM;AAAA,MACzB;AAAA,IACF;AAAA,EACF,GAAG,CAAC,YAAY,CAAC;AAEjB,SAAO;AACT;AAEO,SAAS,2BAAiC;AAC/C,MAAI,OAAO,WAAW,YAAa;AACnC,MAAI;AACF,QAAI,OAAO,qBAAqB,aAAa;AAC3C,YAAM,UAAU,IAAI,iBAAiB,+BAA+B;AACpE,cAAQ,YAAY,SAAS;AAC7B,cAAQ,MAAM;AAAA,IAChB;AAAA,EACF,QAAQ;AAAA,EAER;AACA,MAAI;AACF,WAAO,aAAa,QAAQ,2BAA2B,OAAO,KAAK,IAAI,CAAC,CAAC;AAAA,EAC3E,QAAQ;AAAA,EAER;AACF;",
4
+ "sourcesContent": ["'use client'\nimport * as React from 'react'\nimport { apiCall } from './utils/apiCall'\nimport { clearAllPerspectiveState } from './perspectiveState'\n\nexport const AUTH_IDENTITY_STORAGE_KEY = 'om:auth:identity'\nexport const AUTH_IDENTITY_BROADCAST_CHANNEL = 'om-auth-identity'\nexport const AUTH_IDENTITY_USER_STORAGE_KEY = 'om:auth:identity:user'\n\n/**\n * Purge browser-local DataTable perspective state whenever the backend renders for a\n * different user than the one it last rendered for (#4185).\n *\n * The login form also purges on submit, but that only fires when its client handler\n * runs: a form submitted before hydration, an SSO/magic-link return, or any other\n * entry point into the backend bypasses it entirely and the previous account's unsaved\n * column widths carry over. Anchoring the purge to the observed server identity covers\n * every route into the backend instead of one form.\n *\n * Missing identity metadata is treated as a legacy browser state and purged once:\n * snapshots may predate this marker and belong to a different account. After the\n * marker is recorded, an account's own unsaved widths survive a plain reload.\n */\nfunction purgePerspectiveStateOnIdentityChange(serverUserId: string | null): void {\n if (typeof window === 'undefined' || !serverUserId) return\n try {\n const lastSeenUserId = window.localStorage.getItem(AUTH_IDENTITY_USER_STORAGE_KEY)\n if (lastSeenUserId === serverUserId) return\n clearAllPerspectiveState()\n window.localStorage.setItem(AUTH_IDENTITY_USER_STORAGE_KEY, serverUserId)\n } catch {\n // private mode / quota errors are non-fatal\n }\n}\n\ntype FeatureCheckResponse = {\n ok: boolean\n granted?: string[]\n userId?: string\n}\n\nexport type AuthSessionGuardProps = {\n serverUserId: string | null\n}\n\nexport const __reload = {\n fn: (): void => {\n if (typeof window !== 'undefined') window.location.reload()\n },\n}\n\nexport function AuthSessionGuard({ serverUserId }: AuthSessionGuardProps) {\n React.useEffect(() => {\n if (typeof window === 'undefined') return\n purgePerspectiveStateOnIdentityChange(serverUserId)\n let cancelled = false\n let reloadScheduled = false\n\n const triggerReload = () => {\n if (reloadScheduled) return\n reloadScheduled = true\n __reload.fn()\n }\n\n const checkIdentity = async () => {\n if (cancelled || reloadScheduled) return\n try {\n const res = await apiCall<FeatureCheckResponse>('/api/auth/feature-check', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ features: [] }),\n cache: 'no-store',\n })\n if (cancelled || reloadScheduled) return\n if (res.status === 401) {\n if (serverUserId) triggerReload()\n return\n }\n if (!res.ok) return\n const currentUserId = typeof res.result?.userId === 'string' ? res.result.userId : null\n if (!currentUserId) return\n if (currentUserId !== serverUserId) triggerReload()\n } catch {\n // network errors are ignored \u2014 next focus/storage event retries\n }\n }\n\n const onVisibilityOrFocus = () => {\n if (typeof document !== 'undefined' && document.visibilityState === 'hidden') return\n void checkIdentity()\n }\n\n const onStorage = (event: StorageEvent) => {\n if (event.key !== AUTH_IDENTITY_STORAGE_KEY) return\n void checkIdentity()\n }\n\n let broadcastChannel: BroadcastChannel | null = null\n if (typeof BroadcastChannel !== 'undefined') {\n try {\n broadcastChannel = new BroadcastChannel(AUTH_IDENTITY_BROADCAST_CHANNEL)\n broadcastChannel.onmessage = () => { void checkIdentity() }\n } catch {\n broadcastChannel = null\n }\n }\n\n window.addEventListener('focus', onVisibilityOrFocus)\n document.addEventListener('visibilitychange', onVisibilityOrFocus)\n window.addEventListener('storage', onStorage)\n\n return () => {\n cancelled = true\n window.removeEventListener('focus', onVisibilityOrFocus)\n document.removeEventListener('visibilitychange', onVisibilityOrFocus)\n window.removeEventListener('storage', onStorage)\n if (broadcastChannel) {\n broadcastChannel.onmessage = null\n broadcastChannel.close()\n }\n }\n }, [serverUserId])\n\n return null\n}\n\nexport function notifyAuthIdentityChange(): void {\n if (typeof window === 'undefined') return\n try {\n if (typeof BroadcastChannel !== 'undefined') {\n const channel = new BroadcastChannel(AUTH_IDENTITY_BROADCAST_CHANNEL)\n channel.postMessage('changed')\n channel.close()\n }\n } catch {\n // ignore \u2014 fall back to storage event below\n }\n try {\n window.localStorage.setItem(AUTH_IDENTITY_STORAGE_KEY, String(Date.now()))\n } catch {\n // private mode / quota errors are non-fatal\n }\n}\n"],
5
+ "mappings": ";AACA,YAAY,WAAW;AACvB,SAAS,eAAe;AACxB,SAAS,gCAAgC;AAElC,MAAM,4BAA4B;AAClC,MAAM,kCAAkC;AACxC,MAAM,iCAAiC;AAgB9C,SAAS,sCAAsC,cAAmC;AAChF,MAAI,OAAO,WAAW,eAAe,CAAC,aAAc;AACpD,MAAI;AACF,UAAM,iBAAiB,OAAO,aAAa,QAAQ,8BAA8B;AACjF,QAAI,mBAAmB,aAAc;AACrC,6BAAyB;AACzB,WAAO,aAAa,QAAQ,gCAAgC,YAAY;AAAA,EAC1E,QAAQ;AAAA,EAER;AACF;AAYO,MAAM,WAAW;AAAA,EACtB,IAAI,MAAY;AACd,QAAI,OAAO,WAAW,YAAa,QAAO,SAAS,OAAO;AAAA,EAC5D;AACF;AAEO,SAAS,iBAAiB,EAAE,aAAa,GAA0B;AACxE,QAAM,UAAU,MAAM;AACpB,QAAI,OAAO,WAAW,YAAa;AACnC,0CAAsC,YAAY;AAClD,QAAI,YAAY;AAChB,QAAI,kBAAkB;AAEtB,UAAM,gBAAgB,MAAM;AAC1B,UAAI,gBAAiB;AACrB,wBAAkB;AAClB,eAAS,GAAG;AAAA,IACd;AAEA,UAAM,gBAAgB,YAAY;AAChC,UAAI,aAAa,gBAAiB;AAClC,UAAI;AACF,cAAM,MAAM,MAAM,QAA8B,2BAA2B;AAAA,UACzE,QAAQ;AAAA,UACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,UAC9C,MAAM,KAAK,UAAU,EAAE,UAAU,CAAC,EAAE,CAAC;AAAA,UACrC,OAAO;AAAA,QACT,CAAC;AACD,YAAI,aAAa,gBAAiB;AAClC,YAAI,IAAI,WAAW,KAAK;AACtB,cAAI,aAAc,eAAc;AAChC;AAAA,QACF;AACA,YAAI,CAAC,IAAI,GAAI;AACb,cAAM,gBAAgB,OAAO,IAAI,QAAQ,WAAW,WAAW,IAAI,OAAO,SAAS;AACnF,YAAI,CAAC,cAAe;AACpB,YAAI,kBAAkB,aAAc,eAAc;AAAA,MACpD,QAAQ;AAAA,MAER;AAAA,IACF;AAEA,UAAM,sBAAsB,MAAM;AAChC,UAAI,OAAO,aAAa,eAAe,SAAS,oBAAoB,SAAU;AAC9E,WAAK,cAAc;AAAA,IACrB;AAEA,UAAM,YAAY,CAAC,UAAwB;AACzC,UAAI,MAAM,QAAQ,0BAA2B;AAC7C,WAAK,cAAc;AAAA,IACrB;AAEA,QAAI,mBAA4C;AAChD,QAAI,OAAO,qBAAqB,aAAa;AAC3C,UAAI;AACF,2BAAmB,IAAI,iBAAiB,+BAA+B;AACvE,yBAAiB,YAAY,MAAM;AAAE,eAAK,cAAc;AAAA,QAAE;AAAA,MAC5D,QAAQ;AACN,2BAAmB;AAAA,MACrB;AAAA,IACF;AAEA,WAAO,iBAAiB,SAAS,mBAAmB;AACpD,aAAS,iBAAiB,oBAAoB,mBAAmB;AACjE,WAAO,iBAAiB,WAAW,SAAS;AAE5C,WAAO,MAAM;AACX,kBAAY;AACZ,aAAO,oBAAoB,SAAS,mBAAmB;AACvD,eAAS,oBAAoB,oBAAoB,mBAAmB;AACpE,aAAO,oBAAoB,WAAW,SAAS;AAC/C,UAAI,kBAAkB;AACpB,yBAAiB,YAAY;AAC7B,yBAAiB,MAAM;AAAA,MACzB;AAAA,IACF;AAAA,EACF,GAAG,CAAC,YAAY,CAAC;AAEjB,SAAO;AACT;AAEO,SAAS,2BAAiC;AAC/C,MAAI,OAAO,WAAW,YAAa;AACnC,MAAI;AACF,QAAI,OAAO,qBAAqB,aAAa;AAC3C,YAAM,UAAU,IAAI,iBAAiB,+BAA+B;AACpE,cAAQ,YAAY,SAAS;AAC7B,cAAQ,MAAM;AAAA,IAChB;AAAA,EACF,QAAQ;AAAA,EAER;AACA,MAAI;AACF,WAAO,aAAa,QAAQ,2BAA2B,OAAO,KAAK,IAAI,CAAC,CAAC;AAAA,EAC3E,QAAQ;AAAA,EAER;AACF;",
6
6
  "names": []
7
7
  }
@@ -78,8 +78,13 @@ function useBackendChrome() {
78
78
  }
79
79
  };
80
80
  }
81
+ function useCurrentOrganization() {
82
+ const { payload } = useBackendChrome();
83
+ return payload?.currentOrganization ?? null;
84
+ }
81
85
  export {
82
86
  BackendChromeProvider,
83
- useBackendChrome
87
+ useBackendChrome,
88
+ useCurrentOrganization
84
89
  };
85
90
  //# sourceMappingURL=BackendChromeProvider.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/backend/BackendChromeProvider.tsx"],
4
- "sourcesContent": ["'use client'\n\nimport * as React from 'react'\nimport { apiCall } from './utils/apiCall'\nimport { subscribeOrganizationScopeChanged, getCurrentOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/organizationEvents'\nimport type { BackendChromePayload } from '@open-mercato/shared/modules/navigation/backendChrome'\n\ntype BackendChromeContextValue = {\n payload: BackendChromePayload | null\n isLoading: boolean\n isReady: boolean\n refresh: () => Promise<void>\n}\n\ntype BackendChromeProviderProps = {\n adminNavApi?: string\n children: React.ReactNode\n}\n\nconst chromeCache = new Map<string, BackendChromePayload>()\nconst BackendChromeContext = React.createContext<BackendChromeContextValue | null>(null)\n\nfunction buildCacheKey(api: string): string {\n return `${api}::scope:${getCurrentOrganizationScopeVersion()}`\n}\n\nexport function BackendChromeProvider({ adminNavApi, children }: BackendChromeProviderProps) {\n const cachedPayload = React.useMemo(() => {\n if (!adminNavApi) return null\n return chromeCache.get(buildCacheKey(adminNavApi)) ?? null\n }, [adminNavApi])\n const [payload, setPayload] = React.useState<BackendChromePayload | null>(cachedPayload)\n const [isLoading, setIsLoading] = React.useState(Boolean(adminNavApi && !cachedPayload))\n\n const refresh = React.useCallback(async () => {\n if (!adminNavApi) return\n setIsLoading(true)\n try {\n const call = await apiCall<BackendChromePayload>(adminNavApi, { credentials: 'include' as never })\n if (!call.ok || !call.result) return\n const nextPayload = call.result\n chromeCache.set(buildCacheKey(adminNavApi), nextPayload)\n setPayload(nextPayload)\n } catch {\n return\n } finally {\n setIsLoading(false)\n }\n }, [adminNavApi])\n\n React.useEffect(() => {\n if (!adminNavApi) {\n setPayload(null)\n setIsLoading(false)\n return\n }\n const cached = chromeCache.get(buildCacheKey(adminNavApi)) ?? null\n setPayload(cached)\n if (!cached) {\n void refresh()\n }\n }, [adminNavApi, refresh])\n\n React.useEffect(() => {\n if (!adminNavApi) return\n const onFocus = () => { void refresh() }\n const onManualRefresh = () => { void refresh() }\n const unsubscribeScope = subscribeOrganizationScopeChanged(() => {\n void refresh()\n })\n window.addEventListener('focus', onFocus)\n window.addEventListener('om:refresh-sidebar', onManualRefresh as EventListener)\n return () => {\n unsubscribeScope()\n window.removeEventListener('focus', onFocus)\n window.removeEventListener('om:refresh-sidebar', onManualRefresh as EventListener)\n }\n }, [adminNavApi, refresh])\n\n const value = React.useMemo<BackendChromeContextValue>(() => ({\n payload,\n isLoading,\n isReady: !adminNavApi || payload !== null,\n refresh,\n }), [adminNavApi, isLoading, payload, refresh])\n\n return (\n <BackendChromeContext.Provider value={value}>\n {children}\n </BackendChromeContext.Provider>\n )\n}\n\nexport function useBackendChrome(): BackendChromeContextValue {\n return React.useContext(BackendChromeContext) ?? {\n payload: null,\n isLoading: false,\n isReady: true,\n refresh: async () => {},\n }\n}\n"],
5
- "mappings": ";AAuFI;AArFJ,YAAY,WAAW;AACvB,SAAS,eAAe;AACxB,SAAS,mCAAmC,0CAA0C;AAetF,MAAM,cAAc,oBAAI,IAAkC;AAC1D,MAAM,uBAAuB,MAAM,cAAgD,IAAI;AAEvF,SAAS,cAAc,KAAqB;AAC1C,SAAO,GAAG,GAAG,WAAW,mCAAmC,CAAC;AAC9D;AAEO,SAAS,sBAAsB,EAAE,aAAa,SAAS,GAA+B;AAC3F,QAAM,gBAAgB,MAAM,QAAQ,MAAM;AACxC,QAAI,CAAC,YAAa,QAAO;AACzB,WAAO,YAAY,IAAI,cAAc,WAAW,CAAC,KAAK;AAAA,EACxD,GAAG,CAAC,WAAW,CAAC;AAChB,QAAM,CAAC,SAAS,UAAU,IAAI,MAAM,SAAsC,aAAa;AACvF,QAAM,CAAC,WAAW,YAAY,IAAI,MAAM,SAAS,QAAQ,eAAe,CAAC,aAAa,CAAC;AAEvF,QAAM,UAAU,MAAM,YAAY,YAAY;AAC5C,QAAI,CAAC,YAAa;AAClB,iBAAa,IAAI;AACjB,QAAI;AACF,YAAM,OAAO,MAAM,QAA8B,aAAa,EAAE,aAAa,UAAmB,CAAC;AACjG,UAAI,CAAC,KAAK,MAAM,CAAC,KAAK,OAAQ;AAC9B,YAAM,cAAc,KAAK;AACzB,kBAAY,IAAI,cAAc,WAAW,GAAG,WAAW;AACvD,iBAAW,WAAW;AAAA,IACxB,QAAQ;AACN;AAAA,IACF,UAAE;AACA,mBAAa,KAAK;AAAA,IACpB;AAAA,EACF,GAAG,CAAC,WAAW,CAAC;AAEhB,QAAM,UAAU,MAAM;AACpB,QAAI,CAAC,aAAa;AAChB,iBAAW,IAAI;AACf,mBAAa,KAAK;AAClB;AAAA,IACF;AACA,UAAM,SAAS,YAAY,IAAI,cAAc,WAAW,CAAC,KAAK;AAC9D,eAAW,MAAM;AACjB,QAAI,CAAC,QAAQ;AACX,WAAK,QAAQ;AAAA,IACf;AAAA,EACF,GAAG,CAAC,aAAa,OAAO,CAAC;AAEzB,QAAM,UAAU,MAAM;AACpB,QAAI,CAAC,YAAa;AAClB,UAAM,UAAU,MAAM;AAAE,WAAK,QAAQ;AAAA,IAAE;AACvC,UAAM,kBAAkB,MAAM;AAAE,WAAK,QAAQ;AAAA,IAAE;AAC/C,UAAM,mBAAmB,kCAAkC,MAAM;AAC/D,WAAK,QAAQ;AAAA,IACf,CAAC;AACD,WAAO,iBAAiB,SAAS,OAAO;AACxC,WAAO,iBAAiB,sBAAsB,eAAgC;AAC9E,WAAO,MAAM;AACX,uBAAiB;AACjB,aAAO,oBAAoB,SAAS,OAAO;AAC3C,aAAO,oBAAoB,sBAAsB,eAAgC;AAAA,IACnF;AAAA,EACF,GAAG,CAAC,aAAa,OAAO,CAAC;AAEzB,QAAM,QAAQ,MAAM,QAAmC,OAAO;AAAA,IAC5D;AAAA,IACA;AAAA,IACA,SAAS,CAAC,eAAe,YAAY;AAAA,IACrC;AAAA,EACF,IAAI,CAAC,aAAa,WAAW,SAAS,OAAO,CAAC;AAE9C,SACE,oBAAC,qBAAqB,UAArB,EAA8B,OAC5B,UACH;AAEJ;AAEO,SAAS,mBAA8C;AAC5D,SAAO,MAAM,WAAW,oBAAoB,KAAK;AAAA,IAC/C,SAAS;AAAA,IACT,WAAW;AAAA,IACX,SAAS;AAAA,IACT,SAAS,YAAY;AAAA,IAAC;AAAA,EACxB;AACF;",
4
+ "sourcesContent": ["'use client'\n\nimport * as React from 'react'\nimport { apiCall } from './utils/apiCall'\nimport { subscribeOrganizationScopeChanged, getCurrentOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/organizationEvents'\nimport type {\n BackendChromeCurrentOrganization,\n BackendChromePayload,\n} from '@open-mercato/shared/modules/navigation/backendChrome'\n\ntype BackendChromeContextValue = {\n payload: BackendChromePayload | null\n isLoading: boolean\n isReady: boolean\n refresh: () => Promise<void>\n}\n\ntype BackendChromeProviderProps = {\n adminNavApi?: string\n children: React.ReactNode\n}\n\nconst chromeCache = new Map<string, BackendChromePayload>()\nconst BackendChromeContext = React.createContext<BackendChromeContextValue | null>(null)\n\nfunction buildCacheKey(api: string): string {\n return `${api}::scope:${getCurrentOrganizationScopeVersion()}`\n}\n\nexport function BackendChromeProvider({ adminNavApi, children }: BackendChromeProviderProps) {\n const cachedPayload = React.useMemo(() => {\n if (!adminNavApi) return null\n return chromeCache.get(buildCacheKey(adminNavApi)) ?? null\n }, [adminNavApi])\n const [payload, setPayload] = React.useState<BackendChromePayload | null>(cachedPayload)\n const [isLoading, setIsLoading] = React.useState(Boolean(adminNavApi && !cachedPayload))\n\n const refresh = React.useCallback(async () => {\n if (!adminNavApi) return\n setIsLoading(true)\n try {\n const call = await apiCall<BackendChromePayload>(adminNavApi, { credentials: 'include' as never })\n if (!call.ok || !call.result) return\n const nextPayload = call.result\n chromeCache.set(buildCacheKey(adminNavApi), nextPayload)\n setPayload(nextPayload)\n } catch {\n return\n } finally {\n setIsLoading(false)\n }\n }, [adminNavApi])\n\n React.useEffect(() => {\n if (!adminNavApi) {\n setPayload(null)\n setIsLoading(false)\n return\n }\n const cached = chromeCache.get(buildCacheKey(adminNavApi)) ?? null\n setPayload(cached)\n if (!cached) {\n void refresh()\n }\n }, [adminNavApi, refresh])\n\n React.useEffect(() => {\n if (!adminNavApi) return\n const onFocus = () => { void refresh() }\n const onManualRefresh = () => { void refresh() }\n const unsubscribeScope = subscribeOrganizationScopeChanged(() => {\n void refresh()\n })\n window.addEventListener('focus', onFocus)\n window.addEventListener('om:refresh-sidebar', onManualRefresh as EventListener)\n return () => {\n unsubscribeScope()\n window.removeEventListener('focus', onFocus)\n window.removeEventListener('om:refresh-sidebar', onManualRefresh as EventListener)\n }\n }, [adminNavApi, refresh])\n\n const value = React.useMemo<BackendChromeContextValue>(() => ({\n payload,\n isLoading,\n isReady: !adminNavApi || payload !== null,\n refresh,\n }), [adminNavApi, isLoading, payload, refresh])\n\n return (\n <BackendChromeContext.Provider value={value}>\n {children}\n </BackendChromeContext.Provider>\n )\n}\n\nexport function useBackendChrome(): BackendChromeContextValue {\n return React.useContext(BackendChromeContext) ?? {\n payload: null,\n isLoading: false,\n isReady: true,\n refresh: async () => {},\n }\n}\n\n/**\n * The organization the backend chrome is currently scoped to, for UI that needs to label\n * \"you are viewing: <name>\".\n *\n * Reads the payload this provider already holds, so it costs no additional request. Returns `null`\n * under an all-organizations selection, outside a provider, or before the payload has arrived \u2014 treat\n * it as \"unknown\", not as \"no organization\".\n *\n * Prefer this over `payload.brand`, which only populates when the organization has a logo configured.\n */\nexport function useCurrentOrganization(): BackendChromeCurrentOrganization | null {\n const { payload } = useBackendChrome()\n return payload?.currentOrganization ?? null\n}\n"],
5
+ "mappings": ";AA0FI;AAxFJ,YAAY,WAAW;AACvB,SAAS,eAAe;AACxB,SAAS,mCAAmC,0CAA0C;AAkBtF,MAAM,cAAc,oBAAI,IAAkC;AAC1D,MAAM,uBAAuB,MAAM,cAAgD,IAAI;AAEvF,SAAS,cAAc,KAAqB;AAC1C,SAAO,GAAG,GAAG,WAAW,mCAAmC,CAAC;AAC9D;AAEO,SAAS,sBAAsB,EAAE,aAAa,SAAS,GAA+B;AAC3F,QAAM,gBAAgB,MAAM,QAAQ,MAAM;AACxC,QAAI,CAAC,YAAa,QAAO;AACzB,WAAO,YAAY,IAAI,cAAc,WAAW,CAAC,KAAK;AAAA,EACxD,GAAG,CAAC,WAAW,CAAC;AAChB,QAAM,CAAC,SAAS,UAAU,IAAI,MAAM,SAAsC,aAAa;AACvF,QAAM,CAAC,WAAW,YAAY,IAAI,MAAM,SAAS,QAAQ,eAAe,CAAC,aAAa,CAAC;AAEvF,QAAM,UAAU,MAAM,YAAY,YAAY;AAC5C,QAAI,CAAC,YAAa;AAClB,iBAAa,IAAI;AACjB,QAAI;AACF,YAAM,OAAO,MAAM,QAA8B,aAAa,EAAE,aAAa,UAAmB,CAAC;AACjG,UAAI,CAAC,KAAK,MAAM,CAAC,KAAK,OAAQ;AAC9B,YAAM,cAAc,KAAK;AACzB,kBAAY,IAAI,cAAc,WAAW,GAAG,WAAW;AACvD,iBAAW,WAAW;AAAA,IACxB,QAAQ;AACN;AAAA,IACF,UAAE;AACA,mBAAa,KAAK;AAAA,IACpB;AAAA,EACF,GAAG,CAAC,WAAW,CAAC;AAEhB,QAAM,UAAU,MAAM;AACpB,QAAI,CAAC,aAAa;AAChB,iBAAW,IAAI;AACf,mBAAa,KAAK;AAClB;AAAA,IACF;AACA,UAAM,SAAS,YAAY,IAAI,cAAc,WAAW,CAAC,KAAK;AAC9D,eAAW,MAAM;AACjB,QAAI,CAAC,QAAQ;AACX,WAAK,QAAQ;AAAA,IACf;AAAA,EACF,GAAG,CAAC,aAAa,OAAO,CAAC;AAEzB,QAAM,UAAU,MAAM;AACpB,QAAI,CAAC,YAAa;AAClB,UAAM,UAAU,MAAM;AAAE,WAAK,QAAQ;AAAA,IAAE;AACvC,UAAM,kBAAkB,MAAM;AAAE,WAAK,QAAQ;AAAA,IAAE;AAC/C,UAAM,mBAAmB,kCAAkC,MAAM;AAC/D,WAAK,QAAQ;AAAA,IACf,CAAC;AACD,WAAO,iBAAiB,SAAS,OAAO;AACxC,WAAO,iBAAiB,sBAAsB,eAAgC;AAC9E,WAAO,MAAM;AACX,uBAAiB;AACjB,aAAO,oBAAoB,SAAS,OAAO;AAC3C,aAAO,oBAAoB,sBAAsB,eAAgC;AAAA,IACnF;AAAA,EACF,GAAG,CAAC,aAAa,OAAO,CAAC;AAEzB,QAAM,QAAQ,MAAM,QAAmC,OAAO;AAAA,IAC5D;AAAA,IACA;AAAA,IACA,SAAS,CAAC,eAAe,YAAY;AAAA,IACrC;AAAA,EACF,IAAI,CAAC,aAAa,WAAW,SAAS,OAAO,CAAC;AAE9C,SACE,oBAAC,qBAAqB,UAArB,EAA8B,OAC5B,UACH;AAEJ;AAEO,SAAS,mBAA8C;AAC5D,SAAO,MAAM,WAAW,oBAAoB,KAAK;AAAA,IAC/C,SAAS;AAAA,IACT,WAAW;AAAA,IACX,SAAS;AAAA,IACT,SAAS,YAAY;AAAA,IAAC;AAAA,EACxB;AACF;AAYO,SAAS,yBAAkE;AAChF,QAAM,EAAE,QAAQ,IAAI,iBAAiB;AACrC,SAAO,SAAS,uBAAuB;AACzC;",
6
6
  "names": []
7
7
  }
@@ -100,6 +100,7 @@ import { InjectedField } from "./injection/InjectedField.js";
100
100
  import { insertByInjectionPlacement } from "@open-mercato/shared/modules/widgets/injection-position";
101
101
  import { evaluateInjectedVisibility } from "./injection/visibility-utils.js";
102
102
  import { ComponentReplacementHandles } from "@open-mercato/shared/modules/widgets/component-registry";
103
+ import { crudFormExtensionSpotId, extensionSpotChildId } from "@open-mercato/shared/modules/widgets/extension-points";
103
104
  import { RichEditor } from "../primitives/rich-editor.js";
104
105
  import MarkdownField from "./inputs/MarkdownField.js";
105
106
  const logger = createLogger("ui").child({ component: "CrudForm" });
@@ -453,7 +454,7 @@ function CrudForm({
453
454
  if (injectionSpotId) return injectionSpotId;
454
455
  if (resolvedEntityIds.length) {
455
456
  const normalized = resolvedEntityIds[0].replace(/[:]+/g, ".");
456
- return `crud-form:${normalized}`;
457
+ return crudFormExtensionSpotId(normalized);
457
458
  }
458
459
  return void 0;
459
460
  }, [injectionSpotId, resolvedEntityIds]);
@@ -462,7 +463,7 @@ function CrudForm({
462
463
  if (resolvedEntityIds.length) return ComponentReplacementHandles.crudForm(resolvedEntityIds[0].replace(/[:]+/g, "."));
463
464
  return ComponentReplacementHandles.crudForm("unknown");
464
465
  }, [replacementHandle, resolvedEntityIds]);
465
- const headerInjectionSpotId = resolvedInjectionSpotId ? `${resolvedInjectionSpotId}:header` : void 0;
466
+ const headerInjectionSpotId = resolvedInjectionSpotId ? extensionSpotChildId(resolvedInjectionSpotId, "header") : void 0;
466
467
  const recordId = React.useMemo(() => {
467
468
  const raw = values.id;
468
469
  if (typeof raw === "string") return raw;
@@ -679,7 +680,7 @@ function CrudForm({
679
680
  triggerOnLoad: true
680
681
  });
681
682
  const { widgets: injectedFieldWidgets } = useInjectionDataWidgets(
682
- resolvedInjectionSpotId ? `${resolvedInjectionSpotId}:fields` : "__disabled__:fields"
683
+ resolvedInjectionSpotId ? extensionSpotChildId(resolvedInjectionSpotId, "fields") : "__disabled__:fields"
683
684
  );
684
685
  const { triggerEvent: triggerInjectionEvent } = useInjectionSpotEvents(resolvedInjectionSpotId ?? "", injectionWidgets);
685
686
  const extendedInjectionEventsEnabled = CRUDFORM_EXTENDED_EVENTS_ENABLED && Boolean(resolvedInjectionSpotId);
@@ -1798,10 +1799,11 @@ function CrudForm({
1798
1799
  }, [validateFieldOnBlur]);
1799
1800
  const handleFieldsetSelectionChange = React.useCallback(
1800
1801
  (entityId2, nextCode) => {
1802
+ if (!nextCode) return;
1801
1803
  setCfFieldsetSelections((prev) => ({ ...prev, [entityId2]: nextCode }));
1802
1804
  const bindingKey = customFieldsetBindings?.[entityId2]?.valueKey;
1803
1805
  if (bindingKey) {
1804
- setValue(bindingKey, nextCode ?? void 0);
1806
+ setValue(bindingKey, nextCode);
1805
1807
  }
1806
1808
  },
1807
1809
  [customFieldsetBindings, setValue]