@burojs/ui-shadcn 0.5.0 → 0.7.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 (172) hide show
  1. package/README.md +9 -0
  2. package/dist/bulk-summary-BWSAS7k2.js +143 -0
  3. package/dist/bulk-summary-BWSAS7k2.js.map +1 -0
  4. package/dist/bulk-summary-CrEij5vq.cjs +143 -0
  5. package/dist/bulk-summary-CrEij5vq.cjs.map +1 -0
  6. package/dist/candidates-relation-picker-CS_JZ4Fd.js +31 -0
  7. package/dist/candidates-relation-picker-CS_JZ4Fd.js.map +1 -0
  8. package/dist/candidates-relation-picker-y5M6_oe4.cjs +31 -0
  9. package/dist/candidates-relation-picker-y5M6_oe4.cjs.map +1 -0
  10. package/dist/{check-confirm-dialog-CkaVTPT9.js → check-confirm-dialog-2j0gOd7x.js} +392 -103
  11. package/dist/check-confirm-dialog-2j0gOd7x.js.map +1 -0
  12. package/dist/{check-confirm-dialog-BRByTFpg.cjs → check-confirm-dialog-C-NHgMST.cjs} +476 -102
  13. package/dist/check-confirm-dialog-C-NHgMST.cjs.map +1 -0
  14. package/dist/{dialog-B_rt9S1c.js → dialog-C123sVQ8.js} +4 -4
  15. package/dist/dialog-C123sVQ8.js.map +1 -0
  16. package/dist/{dialog-DRNUq9EA.cjs → dialog-vmMdfUWs.cjs} +3 -3
  17. package/dist/dialog-vmMdfUWs.cjs.map +1 -0
  18. package/dist/{filter-builder-B3mZFL-K.cjs → filter-builder-DFu0JP-1.cjs} +959 -128
  19. package/dist/filter-builder-DFu0JP-1.cjs.map +1 -0
  20. package/dist/{filter-builder-DOnaXNfP.js → filter-builder-DaWHUZuV.js} +863 -116
  21. package/dist/filter-builder-DaWHUZuV.js.map +1 -0
  22. package/dist/{flow-runtime-B6zOHYqI.cjs → flow-runtime-BpmrIUb6.cjs} +61 -29
  23. package/dist/flow-runtime-BpmrIUb6.cjs.map +1 -0
  24. package/dist/{flow-runtime-D8h99FSg.js → flow-runtime-DyqTmK3H.js} +62 -30
  25. package/dist/flow-runtime-DyqTmK3H.js.map +1 -0
  26. package/dist/{flow-screen-CX05b3G9.cjs → flow-screen-Bcz9NF2V.cjs} +132 -88
  27. package/dist/flow-screen-Bcz9NF2V.cjs.map +1 -0
  28. package/dist/{flow-screen-CmbARSqg.js → flow-screen-DCdli6Ha.js} +132 -88
  29. package/dist/flow-screen-DCdli6Ha.js.map +1 -0
  30. package/dist/index.cjs +951 -605
  31. package/dist/index.cjs.map +1 -1
  32. package/dist/index.d.cts +618 -332
  33. package/dist/index.d.ts +618 -332
  34. package/dist/index.js +863 -522
  35. package/dist/index.js.map +1 -1
  36. package/dist/{inline-flow-host-B7U1OJUe.cjs → inline-flow-host-3vw7LzzD.cjs} +5 -5
  37. package/dist/inline-flow-host-3vw7LzzD.cjs.map +1 -0
  38. package/dist/{inline-flow-host-C3H2UpSA.js → inline-flow-host-C0rI9B7f.js} +5 -5
  39. package/dist/inline-flow-host-C0rI9B7f.js.map +1 -0
  40. package/dist/label-66W1qbke.js.map +1 -1
  41. package/dist/label-w6JYf2cF.cjs.map +1 -1
  42. package/dist/lazy-relation-picker-dialog-Bt8lrEIu.cjs +153 -0
  43. package/dist/lazy-relation-picker-dialog-Bt8lrEIu.cjs.map +1 -0
  44. package/dist/lazy-relation-picker-dialog-CAScLPb2.js +135 -0
  45. package/dist/lazy-relation-picker-dialog-CAScLPb2.js.map +1 -0
  46. package/dist/markdown/index.d.cts +2 -3
  47. package/dist/markdown/index.d.ts +2 -3
  48. package/dist/{page-flow-route-CyXSvXSE.js → page-flow-route-BHUEpn0I.js} +36 -23
  49. package/dist/page-flow-route-BHUEpn0I.js.map +1 -0
  50. package/dist/{page-flow-route-CQ7HsbBm.cjs → page-flow-route-DS8IVTVH.cjs} +41 -28
  51. package/dist/page-flow-route-DS8IVTVH.cjs.map +1 -0
  52. package/dist/{place-node-SIp4XYQH.cjs → place-node-CNb3w-c7.cjs} +2227 -999
  53. package/dist/place-node-CNb3w-c7.cjs.map +1 -0
  54. package/dist/{place-node--GdsP688.js → place-node-CfzQvh4Y.js} +2164 -984
  55. package/dist/place-node-CfzQvh4Y.js.map +1 -0
  56. package/dist/{related-list-cards-B6x9HQQp.js → related-list-cards-BIqqY69Y.js} +9 -4
  57. package/dist/{related-list-cards-B6x9HQQp.js.map → related-list-cards-BIqqY69Y.js.map} +1 -1
  58. package/dist/{related-list-cards-nxuODlIA.cjs → related-list-cards-CelMxl4q.cjs} +9 -4
  59. package/dist/{related-list-cards-nxuODlIA.cjs.map → related-list-cards-CelMxl4q.cjs.map} +1 -1
  60. package/dist/{related-list-timeline-DEdfd-rr.js → related-list-timeline-CRh7xwLD.js} +6 -7
  61. package/dist/related-list-timeline-CRh7xwLD.js.map +1 -0
  62. package/dist/{related-list-timeline-BYJKEbmA.cjs → related-list-timeline-DsLdi8sv.cjs} +5 -6
  63. package/dist/related-list-timeline-DsLdi8sv.cjs.map +1 -0
  64. package/dist/{related-rows-B4levHcR.js → related-rows--uP2iKQC.js} +27 -17
  65. package/dist/related-rows--uP2iKQC.js.map +1 -0
  66. package/dist/{related-rows-zh5f_4QR.cjs → related-rows-MPnLCmPF.cjs} +32 -22
  67. package/dist/related-rows-MPnLCmPF.cjs.map +1 -0
  68. package/dist/{search-access-CURsWn-U.js → relation-create-C43cdPQt.js} +596 -29
  69. package/dist/relation-create-C43cdPQt.js.map +1 -0
  70. package/dist/{search-access-B7G5oR8n.cjs → relation-create-DDEYcKfw.cjs} +630 -32
  71. package/dist/relation-create-DDEYcKfw.cjs.map +1 -0
  72. package/dist/{relation-many-picker-gdAOD-qK.js → relation-many-picker-BJhaEqxz.js} +210 -107
  73. package/dist/relation-many-picker-BJhaEqxz.js.map +1 -0
  74. package/dist/{relation-many-picker-B-NCEUgX.cjs → relation-many-picker-DzuGJpEl.cjs} +218 -115
  75. package/dist/relation-many-picker-DzuGJpEl.cjs.map +1 -0
  76. package/dist/{relation-picker-BAY77ivO.js → relation-picker-D-tBBZW_.js} +40 -45
  77. package/dist/relation-picker-D-tBBZW_.js.map +1 -0
  78. package/dist/{relation-picker-Da_uhFvA.cjs → relation-picker-DaIMzf2H.cjs} +45 -50
  79. package/dist/relation-picker-DaIMzf2H.cjs.map +1 -0
  80. package/dist/{relation-picker-dialog-M0pQaWlp.cjs → relation-picker-dialog-9Vg5vvR1.cjs} +142 -40
  81. package/dist/relation-picker-dialog-9Vg5vvR1.cjs.map +1 -0
  82. package/dist/{relation-picker-dialog-hA9rYrZB.js → relation-picker-dialog-DoEY8EkH.js} +143 -41
  83. package/dist/relation-picker-dialog-DoEY8EkH.js.map +1 -0
  84. package/dist/relation-select-B8Fq4o-t.js +48 -0
  85. package/dist/relation-select-B8Fq4o-t.js.map +1 -0
  86. package/dist/relation-select-Cr3CfNLy.cjs +55 -0
  87. package/dist/relation-select-Cr3CfNLy.cjs.map +1 -0
  88. package/dist/{resource-page-router-OSQyRA5b.cjs → resource-page-router-C_kGW2o_.cjs} +552 -303
  89. package/dist/resource-page-router-C_kGW2o_.cjs.map +1 -0
  90. package/dist/{resource-page-router-T4vS5gbJ.js → resource-page-router-Diw6S6TK.js} +507 -258
  91. package/dist/resource-page-router-Diw6S6TK.js.map +1 -0
  92. package/dist/{rich-text-vTyVjDoa.cjs → rich-text-C_7JCvRo.cjs} +2 -2
  93. package/dist/{rich-text-vTyVjDoa.cjs.map → rich-text-C_7JCvRo.cjs.map} +1 -1
  94. package/dist/{rich-text-DFerywgz.js → rich-text-RcPBf6hr.js} +2 -2
  95. package/dist/{rich-text-DFerywgz.js.map → rich-text-RcPBf6hr.js.map} +1 -1
  96. package/dist/success-notices-Dl3M1fZP.cjs +46 -0
  97. package/dist/success-notices-Dl3M1fZP.cjs.map +1 -0
  98. package/dist/success-notices-LDEgEkc4.js +41 -0
  99. package/dist/success-notices-LDEgEkc4.js.map +1 -0
  100. package/dist/{toaster-C7CiYA4Z.cjs → toaster-BbByOytP.cjs} +72 -80
  101. package/dist/toaster-BbByOytP.cjs.map +1 -0
  102. package/dist/{toaster-CfCRLgR-.js → toaster-Ct_YHGs6.js} +66 -68
  103. package/dist/toaster-Ct_YHGs6.js.map +1 -0
  104. package/dist/{use-dialog-filter-state-BDcgS5Oz.js → use-dialog-filter-state-B_MqzHaT.js} +5 -4
  105. package/dist/use-dialog-filter-state-B_MqzHaT.js.map +1 -0
  106. package/dist/{use-dialog-filter-state-BBCC0OEK.cjs → use-dialog-filter-state-tNvqAAjh.cjs} +6 -5
  107. package/dist/use-dialog-filter-state-tNvqAAjh.cjs.map +1 -0
  108. package/dist/use-theme-DFlpXils.cjs.map +1 -1
  109. package/dist/use-theme-dnE3Od3I.js.map +1 -1
  110. package/package.json +11 -11
  111. package/dist/bulk-summary-CwSd6Yjb.cjs +0 -108
  112. package/dist/bulk-summary-CwSd6Yjb.cjs.map +0 -1
  113. package/dist/bulk-summary-GxMDP4bw.js +0 -108
  114. package/dist/bulk-summary-GxMDP4bw.js.map +0 -1
  115. package/dist/candidates-gate-CX2PBu6l.js +0 -63
  116. package/dist/candidates-gate-CX2PBu6l.js.map +0 -1
  117. package/dist/candidates-gate-DiFI52r7.cjs +0 -70
  118. package/dist/candidates-gate-DiFI52r7.cjs.map +0 -1
  119. package/dist/candidates-relation-picker-CBsVcnPR.js +0 -82
  120. package/dist/candidates-relation-picker-CBsVcnPR.js.map +0 -1
  121. package/dist/candidates-relation-picker-Dd_GVLlu.cjs +0 -84
  122. package/dist/candidates-relation-picker-Dd_GVLlu.cjs.map +0 -1
  123. package/dist/check-confirm-dialog-BRByTFpg.cjs.map +0 -1
  124. package/dist/check-confirm-dialog-CkaVTPT9.js.map +0 -1
  125. package/dist/dialog-B_rt9S1c.js.map +0 -1
  126. package/dist/dialog-DRNUq9EA.cjs.map +0 -1
  127. package/dist/filter-builder-B3mZFL-K.cjs.map +0 -1
  128. package/dist/filter-builder-DOnaXNfP.js.map +0 -1
  129. package/dist/flow-runtime-B6zOHYqI.cjs.map +0 -1
  130. package/dist/flow-runtime-D8h99FSg.js.map +0 -1
  131. package/dist/flow-screen-CX05b3G9.cjs.map +0 -1
  132. package/dist/flow-screen-CmbARSqg.js.map +0 -1
  133. package/dist/inline-flow-host-B7U1OJUe.cjs.map +0 -1
  134. package/dist/inline-flow-host-C3H2UpSA.js.map +0 -1
  135. package/dist/lazy-relation-picker-dialog-2oxnA6IR.js +0 -14
  136. package/dist/lazy-relation-picker-dialog-2oxnA6IR.js.map +0 -1
  137. package/dist/lazy-relation-picker-dialog-BfAGKzUG.cjs +0 -19
  138. package/dist/lazy-relation-picker-dialog-BfAGKzUG.cjs.map +0 -1
  139. package/dist/page-flow-route-CQ7HsbBm.cjs.map +0 -1
  140. package/dist/page-flow-route-CyXSvXSE.js.map +0 -1
  141. package/dist/peek-filter-CnqHwTMU.cjs +0 -51
  142. package/dist/peek-filter-CnqHwTMU.cjs.map +0 -1
  143. package/dist/peek-filter-ErLfO153.js +0 -28
  144. package/dist/peek-filter-ErLfO153.js.map +0 -1
  145. package/dist/place-node--GdsP688.js.map +0 -1
  146. package/dist/place-node-SIp4XYQH.cjs.map +0 -1
  147. package/dist/related-list-timeline-BYJKEbmA.cjs.map +0 -1
  148. package/dist/related-list-timeline-DEdfd-rr.js.map +0 -1
  149. package/dist/related-rows-B4levHcR.js.map +0 -1
  150. package/dist/related-rows-zh5f_4QR.cjs.map +0 -1
  151. package/dist/relation-create-BOaUe12G.cjs +0 -362
  152. package/dist/relation-create-BOaUe12G.cjs.map +0 -1
  153. package/dist/relation-create-Bg-eEJLs.js +0 -337
  154. package/dist/relation-create-Bg-eEJLs.js.map +0 -1
  155. package/dist/relation-many-picker-B-NCEUgX.cjs.map +0 -1
  156. package/dist/relation-many-picker-gdAOD-qK.js.map +0 -1
  157. package/dist/relation-picker-BAY77ivO.js.map +0 -1
  158. package/dist/relation-picker-Da_uhFvA.cjs.map +0 -1
  159. package/dist/relation-picker-dialog-M0pQaWlp.cjs.map +0 -1
  160. package/dist/relation-picker-dialog-hA9rYrZB.js.map +0 -1
  161. package/dist/resource-page-router-OSQyRA5b.cjs.map +0 -1
  162. package/dist/resource-page-router-T4vS5gbJ.js.map +0 -1
  163. package/dist/search-access-B7G5oR8n.cjs.map +0 -1
  164. package/dist/search-access-CURsWn-U.js.map +0 -1
  165. package/dist/shell-BPlk8hEa.js +0 -205
  166. package/dist/shell-BPlk8hEa.js.map +0 -1
  167. package/dist/shell-DxhlsPwm.cjs +0 -271
  168. package/dist/shell-DxhlsPwm.cjs.map +0 -1
  169. package/dist/toaster-C7CiYA4Z.cjs.map +0 -1
  170. package/dist/toaster-CfCRLgR-.js.map +0 -1
  171. package/dist/use-dialog-filter-state-BBCC0OEK.cjs.map +0 -1
  172. package/dist/use-dialog-filter-state-BDcgS5Oz.js.map +0 -1
@@ -1,20 +1,22 @@
1
1
  import { l as cn } from "./use-theme-dnE3Od3I.js";
2
- import { d as useLinkComponent, f as Popover, g as PopoverTrigger, h as PopoverContent, n as toast, r as toFriendlyError } from "./toaster-CfCRLgR-.js";
3
- import { C as loadFailedText, D as preloadFlowRuntime, F as DropdownMenuGroup, I as DropdownMenuItem, K as DropdownMenuTrigger, L as DropdownMenuLabel, M as DropdownMenu, O as useLoaded, P as DropdownMenuContent, S as loadBulkSummary, T as loadInlineFlowHost, V as DropdownMenuSeparator, Z as DocInfo, _ as FlowBoundary, b as RuntimeBulkFlowRunner, c as useListSlot, d as useToBrowserHref, f as FlowActionButton, g as ActionInner, h as useFlowChrome, k as Icon, m as text, n as ActionButton, o as canonicalResourceName, p as FlowMenuItem, r as DisabledAction, t as CheckConfirmDialog, x as RuntimeFlowRunner, y as RuntimeAppFlowRunner } from "./check-confirm-dialog-CkaVTPT9.js";
2
+ import { i as isNetworkFailure, l as useLinkComponent, m as PopoverTrigger, n as toast, o as technicalTexts, p as PopoverContent, r as friendlyStringsFrom, s as toFriendlyError, u as Popover } from "./toaster-Ct_YHGs6.js";
3
+ import { B as DropdownMenuContent, C as loadBulkSummary, E as loadInlineFlowHost, F as Icon, H as DropdownMenuItem, O as preloadFlowRuntime, Q as DropdownMenuTrigger, R as DropdownMenu, S as RuntimeFlowRunner, U as DropdownMenuLabel, V as DropdownMenuGroup, _ as reasonText, b as RuntimeAppFlowRunner, c as useListSlot, d as useToBrowserHref, dt as DocInfo, f as FlowActionButton, g as ActionInner, k as useLoaded, m as useFlowChrome, n as ActionButton, o as canonicalResourceName, p as FlowMenuItem, q as DropdownMenuSeparator, r as DisabledAction, t as CheckConfirmDialog, v as FlowBoundary, w as loadFailedText, x as RuntimeBulkFlowRunner } from "./check-confirm-dialog-2j0gOd7x.js";
4
4
  import { t as Button } from "./button-MWVeZaqj.js";
5
5
  import { t as Input } from "./input-CUPStzZO.js";
6
- import { a as DialogFooter, i as DialogDescription, o as DialogHeader, r as DialogContent, s as DialogTitle, t as Dialog } from "./dialog-B_rt9S1c.js";
7
- import { m as ClaimedQuickLink, s as relationLinkClassName } from "./search-access-CURsWn-U.js";
6
+ import { a as DialogFooter, i as DialogDescription, o as DialogHeader, r as DialogContent, s as DialogTitle, t as Dialog } from "./dialog-C123sVQ8.js";
7
+ import { g as ClaimedQuickLink, l as relationLinkClassName, y as LoadError } from "./relation-create-C43cdPQt.js";
8
8
  import { t as Skeleton } from "./skeleton-U2noefj5.js";
9
+ import { A as TableRow, C as SelectValue, D as TableCell, F as ResolvedFieldInput, I as useFieldMetaDiagnostic, L as fieldDescription, N as MetaPlaceholder, O as TableHead, P as ResolvedFieldDisplay, R as lowerLegacyField, S as SelectTrigger, T as TableBody, _ as SelectItem, h as SelectContent, j as Badge, k as TableHeader, m as Select, t as FilterBuilder, w as Table$1 } from "./filter-builder-DaWHUZuV.js";
9
10
  import { t as Label } from "./label-66W1qbke.js";
10
- import { C as TableHeader, S as TableHead, T as Badge, _ as SelectValue, c as Select, d as SelectItem, g as SelectTrigger, l as SelectContent, t as FilterBuilder, v as Table$1, w as TableRow, x as TableCell, y as TableBody } from "./filter-builder-DOnaXNfP.js";
11
- import { BULK_EACH_LIMIT, FORM_SLOT, LAYOUT_SIZES, builtinFrames, canonicalEntityNodeKey, compileFlow, compiledScreenFor, composeListFilter, dtoFields, enumFallbackKey, enumNamespaceOf, facts, fieldOptionKey, fieldSensitivity, formatEntityRef, grantedPlacementKeys, hasRecordPage, humanizeKey, i18nPrefixToRef, i18nPrefixToRef as i18nPrefixToRef$1, inlineFlow, isBuiltinItemAction, isDataError, isScreenFailure, planItemActions, projectDto, recordEntries, relatedList, relatedRecord, resolveActionDensity, resolveActionSurfaceMode, resolveContentSlot, resolveEnumLabel, resolveFieldLabel, resolveFieldSlot, resolveNavLabel, resolveResourceLabel, resolveStaticAvailability, resourceKey, validatePlacementProps } from "@burojs/core";
12
- import { DismissGuardScope, FlowRowBatchContext, LOCAL_USER_SCOPE_ID, QUICK_ANCHOR_ATTRIBUTE, ResourceListProvider, RouteSourceContext, ScreenStateProvider, implementFrame, policyRecordOf, policyRowsOf, resolveRoute, resolveValidationMessage, resourceFlowsOn, useApp, useApplicationSnapshot, useAvailability, useCapability, useCapabilityResolver, useComponent, useCtx, useCurrentUserId, useCustomQuery, useDataRuntime, useDismissGuard, useEntryGate, useEnumValues, useExportRuns, useFieldAccess, useFieldImplementation, useFieldMeta, useFieldMetaSources, useFlowRowBatch, useFlowRowResolver, useFormController, useFrameImplementations, useFramePiece, useFrameT, useI18nCaps, useItemActions, useListAccess, useLocale, useNavigationBlocker, useOne, useOptionalDataRuntime, useOptionalRecordFrame, useOptionalRouterAdapter, useOptionalT, usePageFlowGate, usePageScreen, usePlacement, useProjectedRecord, useProjectedTitle, usePublishDocVariant, useQuick, useQuickEntry, useQuickOpener, useRecordFrame, useRecordTitle, useResource, useResourceExport, useResourceList, useResourceListContext, useRouteNavigate, useScreenState, useSearchParamsWriter, useT, useTestIds, useViewRow } from "@burojs/react";
13
- import { IconAlertCircle, IconAlertTriangle, IconArrowDown, IconArrowLeft, IconArrowRight, IconArrowUp, IconBookmark, IconCheck, IconChevronLeft, IconChevronRight, IconChevronsLeft, IconChevronsRight, IconColumns3, IconCopy, IconDots, IconDownload, IconEyeOff, IconFilterX, IconInbox, IconLoader2, IconLock, IconMinus, IconPencil, IconPinned, IconPinnedOff, IconPlus, IconRefresh, IconSelector, IconSettings2, IconTrash, IconX } from "@tabler/icons-react";
11
+ import { n as useCandidateSelection, t as LazyRelationPickerDialog } from "./lazy-relation-picker-dialog-CAScLPb2.js";
12
+ import { t as useRelationSelect } from "./relation-select-B8Fq4o-t.js";
13
+ import { BULK_EACH_LIMIT, FORM_SLOT, LAYOUT_SIZES, builtinFrames, canonicalEntityNodeKey, columnName, compileFlow, compiledScreenFor, declaredField, declaredText, displayClassOf, dtoFields, enumFallbackKey, enumFallbackText, enumNamespaceOf, enumValueText, exportFormatFieldKey, exportFormatLabelKey, exportFormatOptionKey, facts, fieldOptionText, fieldSensitivity, flowRecordOf, formatEntityRef, grantedPlacementKeys, hasRecordPage, humanizeKey, i18nPrefixToRef, i18nPrefixToRef as i18nPrefixToRef$1, inlineFlow, isBuiltinItemAction, isColumnSortable, isRelationField, isScreenFailure, linkOf, normalizeDataError, normalizeEntityRef, planItemActions, projectDto, recordEntries, relatedList, relatedRecord, resolveActionDensity, resolveActionSurfaceMode, resolveColumnUi, resolveContentSlot, resolveEnumLabel, resolveFieldLabel, resolveFieldOptionLabel, resolveFieldSlot, resolveNavLabel, resolveResourceLabel, resolveStaticAvailability, resolveSurfaceUi, resolveTextSlot, resourceKey, resourceShowHref, reverseLinkOf, routeResourceKey, routeServerIssues, savedViewKey, validatePlacementProps, whereEntryToFilter } from "@burojs/core";
14
+ import { ActionRouteContext, DismissGuardScope, FlowRowBatchContext, LOCAL_USER_SCOPE_ID, QUICK_ANCHOR_ATTRIBUTE, RECORD_KIND_KEYS, RecordScopeChain, ResourceListProvider, RouteSourceContext, ScreenStateProvider, columnTypeWidth, implementFrame, kindErrorText, linkCapabilitiesOf, policyRecordOf, policyRowsOf, recordKeyOf, resolveColumnField, resolveErrorText, resolveRoute, resolveValidationMessage, resourceFlowsOn, specificErrorText, tableLayoutSetting, useApp, useApplicationSnapshot, useAvailability, useAvailabilityResolver, useCapability, useCapabilityResolver, useChromeT, useClipboard, useComponent, useCtx, useCurrentUserId, useCustomQuery, useDataRuntime, useDismissGuard, useEntryGate, useEnumValues, useExportRuns, useFieldAccess, useFieldImplementation, useFieldMeta, useFieldMetaSources, useFlowRowBatch, useFlowRowResolver, useFormController, useFrameImplementations, useFramePiece, useFrameT, useI18nCaps, useItemActions, useLinkRights, useLinkWriter, useLinkedKeys, useListAccess, useLocale, useNavigationBlocker, useOne, useOptionalDataRuntime, useOptionalRecordFrame, useOptionalRouterAdapter, useOptionalT, useOptionalUiSettings, usePageFlowGate, usePageScreen, usePathAccess, usePlacement, useProjectedRecord, useProjectedTitle, usePublishDocVariant, useQuick, useQuickEntry, useQuickOpener, useRecordFrame, useRecordScopeFrames, useRecordTitle, useResource, useResourceExport, useResourceList, useResourceListContext, useRouteNavigate, useScreenState, useSearchParamsWriter, useT, useTestIds, useViewRow } from "@burojs/react";
15
+ import { IconAlertCircle, IconAlertTriangle, IconArrowDown, IconArrowLeft, IconArrowRight, IconArrowUp, IconBookmark, IconCheck, IconChevronLeft, IconChevronRight, IconChevronsLeft, IconChevronsRight, IconColumns3, IconCopy, IconDots, IconDownload, IconEyeOff, IconFilterX, IconInbox, IconLink, IconLoader2, IconLock, IconMinus, IconPencil, IconPinned, IconPinnedOff, IconPlus, IconRefresh, IconSelector, IconSettings2, IconTrash, IconUnlink, IconX } from "@tabler/icons-react";
14
16
  import * as React from "react";
15
17
  import { Component, Fragment, Suspense, createContext, createElement, lazy, memo, useCallback, useContext, useEffect, useId, useMemo, useRef, useState, useSyncExternalStore } from "react";
16
18
  import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
17
- import { isFlowAction } from "@burojs/core/internal";
19
+ import { actionWhenHolds, enumBindingKey, hasNullRef, isFlowAction, whereToFilter } from "@burojs/core/internal";
18
20
  import * as TabsPrimitive from "@radix-ui/react-tabs";
19
21
  import * as CheckboxPrimitive from "@radix-ui/react-checkbox";
20
22
  import { flexRender, getCoreRowModel, getFacetedRowModel, getFacetedUniqueValues, useReactTable } from "@tanstack/react-table";
@@ -117,432 +119,91 @@ function toDisplayText(value) {
117
119
  }
118
120
  }
119
121
  //#endregion
120
- //#region src/fields/legacy-field.ts
121
- const STRING_ITEM = Object.freeze({ type: "string" });
122
- const LEGACY_KIND_DECLARATIONS = Object.freeze({
123
- string: { type: "string" },
124
- text: {
125
- type: "string",
126
- format: "text"
127
- },
128
- textarea: {
129
- type: "string",
130
- format: "textarea"
131
- },
132
- code: {
133
- type: "string",
134
- format: "code"
135
- },
136
- email: {
137
- type: "string",
138
- format: "email"
139
- },
140
- url: {
141
- type: "string",
142
- format: "url"
143
- },
144
- uuid: {
145
- type: "string",
146
- format: "uuid"
147
- },
148
- number: { type: "number" },
149
- boolean: { type: "boolean" },
150
- date: {
151
- type: "string",
152
- format: "date"
153
- },
154
- dateTime: {
155
- type: "string",
156
- format: "date-time"
157
- },
158
- enum: {
159
- type: "string",
160
- format: "enum"
161
- },
162
- json: {
163
- type: "object",
164
- format: "json"
165
- },
166
- richText: {
167
- type: "string",
168
- format: "rich-text"
169
- },
170
- file: {
171
- type: "array",
172
- format: "file",
173
- items: STRING_ITEM
174
- },
175
- relation: { type: "relation" },
176
- relationMany: {
177
- type: "array",
178
- format: "relation",
179
- items: STRING_ITEM
180
- }
181
- });
182
- const SEMANTIC_KEYS = Object.freeze([
183
- "type",
184
- "format",
185
- "meta",
186
- "required",
187
- "nullable",
188
- "empty",
189
- "default",
190
- "constraints",
191
- "label",
192
- "description",
193
- "options",
194
- "items",
195
- "properties",
196
- "additionalProperties",
197
- "variants"
198
- ]);
199
- function ownData(value, key) {
200
- if (value === null || typeof value !== "object" && typeof value !== "function") return { present: false };
201
- const descriptor = Object.getOwnPropertyDescriptor(value, key);
202
- if (descriptor === void 0 || !Object.prototype.propertyIsEnumerable.call(value, key) || !("value" in descriptor)) return { present: false };
203
- return {
204
- present: true,
205
- value: descriptor.value
206
- };
207
- }
208
- function ownString(value, key) {
209
- const candidate = ownData(value, key).value;
210
- return typeof candidate === "string" && candidate.length > 0 ? candidate : void 0;
211
- }
212
- function projectUiSelection(input) {
213
- const selection = {};
214
- const inputId = ownString(input, "input");
215
- const displayId = ownString(input, "display");
216
- if (inputId !== void 0) selection.input = inputId;
217
- if (displayId !== void 0) selection.display = displayId;
218
- return inputId === void 0 && displayId === void 0 ? void 0 : Object.freeze(selection);
219
- }
220
- function projectFieldDeclaration(input) {
221
- const type = ownString(input, "type");
222
- if (type === void 0) return void 0;
223
- const projected = { type };
224
- for (const key of SEMANTIC_KEYS) {
225
- if (key === "type" || key === "items" || key === "properties" || key === "variants") continue;
226
- const candidate = ownData(input, key);
227
- if (candidate.present && candidate.value !== void 0) projected[key] = candidate.value;
228
- }
229
- const ui = projectUiSelection(ownData(input, "ui").value);
230
- if (ui !== void 0) projected.ui = ui;
231
- if (type === "array") {
232
- const item = projectFieldDeclaration(ownData(input, "items").value);
233
- if (item !== void 0) projected.items = item;
234
- }
235
- if (type === "object") {
236
- const properties = ownData(input, "properties").value;
237
- if (properties !== null && typeof properties === "object" && !Array.isArray(properties)) {
238
- const output = {};
239
- for (const key of Object.keys(properties)) {
240
- const child = projectFieldDeclaration(ownData(properties, key).value);
241
- if (child !== void 0) output[key] = child;
242
- }
243
- projected.properties = Object.freeze(output);
244
- }
245
- }
246
- if (type === "union") {
247
- const variants = ownData(input, "variants").value;
248
- if (Array.isArray(variants)) {
249
- const output = [];
250
- for (let index = 0; index < variants.length; index += 1) {
251
- const child = projectFieldDeclaration(ownData(variants, index).value);
252
- if (child !== void 0) output.push(child);
253
- }
254
- projected.variants = Object.freeze(output);
255
- }
256
- }
257
- return Object.freeze(projected);
258
- }
259
- function projectUiHints(input) {
260
- if (input === null || typeof input !== "object" || Array.isArray(input)) return void 0;
261
- const projected = {};
262
- for (const key of Object.keys(input)) {
263
- const candidate = ownData(input, key);
264
- if (candidate.present) projected[key] = candidate.value;
265
- }
266
- return Object.freeze(projected);
267
- }
268
- function lowerLegacyField(field) {
269
- const declaration = projectFieldDeclaration(ownData(field, "declaration").value);
270
- if (declaration !== void 0) return declaration;
271
- const direct = projectFieldDeclaration(field);
272
- const kind = ownString(field, "kind");
273
- const mapping = kind === void 0 ? void 0 : LEGACY_KIND_DECLARATIONS[kind];
274
- if (direct !== void 0) {
275
- const format = direct.format ?? mapping?.format ?? kind;
276
- return format === void 0 ? direct : Object.freeze({
277
- ...direct,
278
- format
279
- });
280
- }
281
- const ui = projectUiHints(ownData(field, "ui").value);
282
- const type = kind === "file" && ui?.multiple !== true ? "unknown" : mapping?.type ?? "unknown";
283
- const format = ownString(field, "format") ?? mapping?.format ?? kind;
284
- const items = type === "array" ? projectFieldDeclaration(ownData(field, "items").value) ?? mapping?.items ?? STRING_ITEM : void 0;
285
- const meta = ownData(field, "meta");
286
- const required = ownData(field, "required");
287
- const nullable = ownData(field, "nullable");
288
- const opts = ownData(field, "opts").value;
289
- const optionRequired = ownData(opts, "required");
290
- const optionNullable = ownData(opts, "nullable");
291
- const requiredValue = required.present ? required.value : optionRequired.value;
292
- const nullableValue = nullable.present ? nullable.value : optionNullable.value;
293
- return Object.freeze({
294
- type,
295
- ...format === void 0 ? {} : { format },
296
- ...items === void 0 ? {} : { items },
297
- ...meta.present && meta.value !== void 0 ? { meta: meta.value } : {},
298
- ...requiredValue === true ? { required: true } : {},
299
- ...nullableValue === true ? { nullable: true } : {}
300
- });
301
- }
302
- function lowerLegacyFieldSource(field) {
303
- const declaration = lowerLegacyField(field);
304
- const ui = projectUiHints(ownData(field, "ui").value);
305
- if (ui === void 0) return declaration;
306
- return Object.freeze({
307
- ...declaration,
308
- ui
309
- });
310
- }
311
- function fieldDescription(field) {
312
- const source = field;
313
- for (const value of [
314
- source?.declaration?.description,
315
- source?.description,
316
- source?.opts?.description,
317
- source?.meta?.description
318
- ]) if (typeof value === "string" && value !== "") return value;
122
+ //#region src/data-table/annotate-if-clipped.ts
123
+ function annotateIfClipped(e) {
124
+ const el = e.currentTarget;
125
+ const text = el.scrollWidth > el.clientWidth || Array.from(el.querySelectorAll("*")).some((c) => c.scrollWidth > c.clientWidth) ? (el.textContent ?? "").trim() : "";
126
+ if (text) el.setAttribute("title", text);
127
+ else el.removeAttribute("title");
319
128
  }
320
129
  //#endregion
321
- //#region src/fields/resolved-field.tsx
322
- const EMPTY = Object.freeze({});
323
- const EMPTY_ISSUES$1 = Object.freeze([]);
324
- const EMPTY_OPTIONS = Object.freeze([]);
325
- function canonical(field) {
326
- return lowerLegacyField(field);
327
- }
328
- const warnedObjectLabels = new Set();
329
- function optionsFor(field, name) {
330
- if (Array.isArray(field.options)) return field.options;
331
- const candidate = field.meta?.options;
332
- if (!Array.isArray(candidate)) return EMPTY_OPTIONS;
333
- const output = [];
334
- for (const item of candidate) {
335
- if (item === null || typeof item !== "object" || Array.isArray(item)) continue;
336
- const raw = item;
337
- if (typeof raw.value !== "string" && typeof raw.value !== "number" && typeof raw.value !== "boolean" && raw.value !== null) continue;
338
- const { value, label, description, ...legacyMeta } = raw;
339
- if (typeof label !== "string" && process.env.NODE_ENV !== "production" && !warnedObjectLabels.has(`${name}:${value}`)) {
340
- warnedObjectLabels.add(`${name}:${value}`);
341
- console.warn(`[buro/ui-shadcn] field '${name}': option '${value}' has a non-string label, so it renders its raw value. Declare a plain string label and put the translations under fields.${name}.options.${value} (see docs/i18n-guide.md).`);
342
- }
343
- output.push(Object.freeze({
344
- value,
345
- label: typeof label === "string" ? label : String(value),
346
- ...typeof description === "string" ? { description } : {},
347
- ...Object.keys(legacyMeta).length === 0 ? {} : { meta: Object.freeze(legacyMeta) }
348
- }));
349
- }
350
- return Object.freeze(output);
351
- }
352
- const readScope = (ctx) => ctx.scope;
353
- function useFieldRuntimeContext(resource, record, values, metaSources) {
354
- const scope = useCtx(readScope);
355
- const locale = useLocale();
356
- const caps = useI18nCaps(formatEntityRef(resource));
357
- const metaSourcesOut = useFieldMetaSources({
358
- record,
359
- values,
360
- extra: metaSources
361
- });
362
- return useMemo(() => Object.freeze({
363
- i18n: Object.freeze({
364
- locale,
365
- has: caps.has,
366
- t: caps.t
367
- }),
368
- sources: Object.freeze({
369
- record: record ?? EMPTY,
370
- values: values ?? EMPTY,
371
- scope: scope ?? EMPTY
372
- }),
373
- metaSources: metaSourcesOut
374
- }), [
375
- caps,
376
- locale,
377
- record,
378
- scope,
379
- values,
380
- metaSourcesOut
381
- ]);
382
- }
383
- function useFieldMetaDiagnostic(state, resource, name, field, surface) {
384
- const reported = useRef(void 0);
385
- const reason = state.status === "failed" ? state.reason : void 0;
386
- useEffect(() => {
387
- if (reason === void 0 || reported.current === reason || process.env.NODE_ENV === "production") return;
388
- reported.current = reason;
389
- const named = field.ui?.[surface];
390
- const implementation = typeof named === "string" ? named : field.format ?? field.type;
391
- console.error(`[burojs] field.meta-invalid: ${formatEntityRef(resource)}.${name} (${implementation}): ${reason}`);
392
- }, [
393
- reason,
394
- resource,
395
- name,
396
- field,
397
- surface
398
- ]);
399
- }
400
- function MetaPlaceholder({ state }) {
130
+ //#region src/fields/field-value-frame.tsx
131
+ const COPY_CLASSES = [
132
+ "string",
133
+ "number",
134
+ "date"
135
+ ];
136
+ const COPIED_MS = 2e3;
137
+ const isRecord$1 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
138
+ const isEmpty = (value) => value === null || value === void 0 || value === "";
139
+ function copyText(value) {
140
+ if (value instanceof Date) return value.toISOString();
141
+ return typeof value === "string" ? value : String(value);
142
+ }
143
+ function FieldValueFrame({ field, value, surface, entry, children }) {
401
144
  const t = useOptionalT();
402
- if (state.status === "pending") return jsx(Skeleton, {
403
- "data-buro-skeleton": "",
404
- "data-buro-field-meta": "pending",
405
- className: "h-9 w-full"
406
- });
407
- return jsx("div", {
408
- role: "alert",
409
- "data-buro-field-meta": "failed",
410
- className: "rounded-md border border-dashed px-3 py-2 text-sm text-muted-foreground",
411
- children: t("field.misconfigured", { defaultValue: "This field is misconfigured" })
412
- });
413
- }
414
- function useFieldImplementationDiagnostic(diagnostic) {
415
- const reported = useRef(void 0);
145
+ const clipboard = useClipboard();
146
+ const [copied, setCopied] = useState(false);
416
147
  useEffect(() => {
417
- if (diagnostic === void 0 || reported.current === diagnostic || process.env.NODE_ENV === "production") return;
418
- reported.current = diagnostic;
419
- console.warn("[buro/ui-shadcn] missing-field-implementation; rendering fallback.", diagnostic);
420
- }, [diagnostic]);
421
- }
422
- function ResolvedFieldDisplay({ resource, name, field: source, value, record, testId, fallback, presentation, metaSources }) {
423
- const sourceAtEdge = lowerLegacyFieldSource(source);
424
- const field = canonical(sourceAtEdge);
425
- const resolved = useFieldImplementation({
426
- field,
427
- surface: "display"
428
- });
429
- const runtimeContext = useFieldRuntimeContext(resource, record, record, metaSources);
430
- const metaState = useFieldMeta(field, resolved.implementation, runtimeContext.metaSources);
431
- useFieldMetaDiagnostic(metaState, resource, name, field, "display");
432
- useFieldImplementationDiagnostic(resolved.diagnostic);
433
- if (resolved.Component === void 0) return jsx(Fragment$1, { children: fallback });
434
- if (metaState.status !== "ready") return jsx(MetaPlaceholder, { state: metaState });
435
- const Component = resolved.Component;
436
- const context = {
437
- owner: resource,
438
- resource,
148
+ if (!copied) return;
149
+ const timer = setTimeout(() => setCopied(false), COPIED_MS);
150
+ return () => clearTimeout(timer);
151
+ }, [copied]);
152
+ const ui = isRecord$1(field.ui) ? field.ui : void 0;
153
+ const asksCopy = ui?.copy === true;
154
+ const valueClass = useMemo(() => asksCopy ? displayClassOf({
155
+ ...lowerLegacyField(field),
156
+ ...ui === void 0 ? {} : { ui }
157
+ }) : void 0, [
158
+ asksCopy,
439
159
  field,
440
- path: Object.freeze([name]),
441
- surface: "display",
442
- presentation: presentation ?? "default",
443
- record,
444
- meta: metaState.meta,
445
- ui: sourceAtEdge.ui ?? EMPTY,
446
- options: optionsFor(field, name),
447
- i18n: runtimeContext.i18n,
448
- sources: runtimeContext.sources,
449
- pending: false,
450
- touched: false,
451
- issues: EMPTY_ISSUES$1,
452
- accessibility: EMPTY,
453
- testIds: testId === void 0 ? EMPTY : Object.freeze({ display: testId })
454
- };
455
- if (resolved.multiple) return createElement(Component, {
456
- ...context,
457
- multiple: true,
458
- value: Array.isArray(value) ? value : []
459
- });
460
- return createElement(Component, {
461
- ...context,
462
- multiple: false,
463
- value
464
- });
465
- }
466
- function ResolvedFieldInput({ resource, name, field: source, value, record, form, testId, inputId, onChange, onBlur, disabled, required, placeholder, descriptionId, errorId, errorMessage, pending = false, touched = errorMessage != null, issues: suppliedIssues, relationSeeds, fixedFilters, metaSources, fallback }) {
467
- const sourceAtEdge = lowerLegacyFieldSource(source);
468
- const base = canonical(sourceAtEdge);
469
- const field = required === void 0 || required === base.required ? base : Object.freeze({
470
- ...base,
471
- required
472
- });
473
- const ui = Object.freeze({
474
- ...sourceAtEdge.ui ?? EMPTY,
475
- ...placeholder === void 0 ? {} : { placeholder }
476
- });
477
- const resolved = useFieldImplementation({
478
- field: {
479
- ...field,
480
- ui
481
- },
482
- surface: "input"
160
+ ui
161
+ ]);
162
+ const { overflow: declared } = resolveSurfaceUi(field, surface, entry);
163
+ const copyable = asksCopy && clipboard !== void 0 && !isEmpty(value) && valueClass !== void 0 && COPY_CLASSES.includes(valueClass);
164
+ if (!copyable && declared === void 0) return jsx(Fragment$1, { children });
165
+ const body = (declared ?? (surface === "column" ? "truncate" : "wrap")) === "wrap" ? jsx("span", {
166
+ className: "min-w-0 whitespace-normal [overflow-wrap:anywhere]",
167
+ "data-buro-overflow": "wrap",
168
+ children
169
+ }) : jsx("span", {
170
+ className: "inline-block min-w-0 max-w-full truncate align-bottom",
171
+ "data-buro-overflow": "truncate",
172
+ onPointerEnter: annotateIfClipped,
173
+ children
483
174
  });
484
- const runtimeContext = useFieldRuntimeContext(resource, record, form, metaSources);
485
- const metaState = useFieldMeta(field, resolved.implementation, runtimeContext.metaSources);
486
- useFieldMetaDiagnostic(metaState, resource, name, field, "input");
487
- useFieldImplementationDiagnostic(resolved.diagnostic);
488
- if (resolved.Component === void 0) return jsx(Fragment$1, { children: fallback });
489
- if (metaState.status !== "ready") return jsx(MetaPlaceholder, { state: metaState });
490
- const Component = resolved.Component;
491
- const issues = suppliedIssues ?? (errorMessage == null ? EMPTY_ISSUES$1 : Object.freeze([Object.freeze({
492
- code: "ui.field-error",
493
- message: {
494
- key: "ui.field-error",
495
- default: errorMessage
496
- },
497
- path: Object.freeze([]),
498
- source: "field",
499
- severity: "error"
500
- })]));
501
- const context = {
502
- owner: resource,
503
- resource,
504
- field,
505
- path: Object.freeze([name]),
506
- surface: "input",
507
- presentation: "default",
508
- record,
509
- form,
510
- meta: metaState.meta,
511
- ui,
512
- options: optionsFor(field, name),
513
- i18n: runtimeContext.i18n,
514
- sources: runtimeContext.sources,
515
- pending,
516
- touched,
517
- issues,
518
- accessibility: Object.freeze({
519
- input: inputId ?? name,
520
- description: descriptionId,
521
- error: errorId
522
- }),
523
- testIds: testId === void 0 ? EMPTY : Object.freeze({ input: testId })
175
+ if (!copyable) return jsx(Fragment$1, { children: body });
176
+ const label = t("field.copy", { defaultValue: "Copy" });
177
+ const copy = (event) => {
178
+ event.preventDefault();
179
+ event.stopPropagation();
180
+ clipboard(copyText(value)).then(() => setCopied(true), () => void 0);
524
181
  };
525
- if (resolved.multiple) return createElement(Component, {
526
- ...context,
527
- multiple: true,
528
- value: Array.isArray(value) ? value : [],
529
- onChange,
530
- onBlur,
531
- disabled,
532
- relationSeeds,
533
- fixedFilters,
534
- metaSources: runtimeContext.metaSources
535
- });
536
- return createElement(Component, {
537
- ...context,
538
- multiple: false,
539
- value,
540
- onChange,
541
- onBlur,
542
- disabled,
543
- relationSeeds,
544
- fixedFilters,
545
- metaSources: runtimeContext.metaSources
182
+ return jsxs("span", {
183
+ className: "inline-flex min-w-0 max-w-full items-center gap-1 align-bottom",
184
+ children: [
185
+ body,
186
+ jsx("button", {
187
+ type: "button",
188
+ "data-testid": `field-copy:${field.name}`,
189
+ "aria-label": label,
190
+ title: label,
191
+ onClick: copy,
192
+ className: "inline-flex size-6 shrink-0 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
193
+ children: copied ? jsx(IconCheck, {
194
+ "aria-hidden": "true",
195
+ className: "size-3.5"
196
+ }) : jsx(IconCopy, {
197
+ "aria-hidden": "true",
198
+ className: "size-3.5"
199
+ })
200
+ }),
201
+ jsx("span", {
202
+ "aria-live": "polite",
203
+ className: "sr-only",
204
+ children: copied ? t("field.copied", { defaultValue: "Copied" }) : ""
205
+ })
206
+ ]
546
207
  });
547
208
  }
548
209
  //#endregion
@@ -582,16 +243,26 @@ function Field({ resource, i18nPrefix, fallback, fixedFilters, field, doc, ...re
582
243
  });
583
244
  }
584
245
  function FieldDisplay({ resource, i18nPrefix, fallback, value, ...rest }) {
585
- return jsx(ResolvedFieldDisplay, {
586
- ...rest,
246
+ const frameField = useMemo(() => ({
247
+ ...rest.field,
248
+ name: rest.name
249
+ }), [rest.field, rest.name]);
250
+ return jsx(FieldValueFrame, {
251
+ field: frameField,
587
252
  value,
588
- resource: resolveRef(resource, i18nPrefix),
589
- fallback: fallback ?? jsx("span", { children: value == null || value === "" ? "—" : String(value) })
253
+ surface: "display",
254
+ children: jsx(ResolvedFieldDisplay, {
255
+ ...rest,
256
+ value,
257
+ resource: resolveRef(resource, i18nPrefix),
258
+ fallback: fallback ?? jsx("span", { children: value == null || value === "" ? "—" : String(value) })
259
+ })
590
260
  });
591
261
  }
592
262
  //#endregion
593
263
  //#region src/screen/field-leaf.tsx
594
264
  const SIZES = new Set(LAYOUT_SIZES);
265
+ const SHOWN_ISSUES = 3;
595
266
  const FOCUSABLE = "input:not([type=\"hidden\"]):not([disabled]), textarea:not([disabled]), select:not([disabled]), button:not([disabled]), [contenteditable=\"true\"], [tabindex]:not([tabindex=\"-1\"])";
596
267
  function useLeafSize(field) {
597
268
  const implementation = useFieldImplementation({
@@ -637,9 +308,9 @@ function MisconfiguredLeaf({ node }) {
637
308
  values: controller.values,
638
309
  extra: controller.sources
639
310
  });
640
- const state = useFieldMeta(field, resolved.implementation, sources);
311
+ const state = useFieldMeta(field, resolved.implementation, sources, resolved.params);
641
312
  useFieldMetaDiagnostic(state, binding.resource, name, field, surface);
642
- const label = resolveFieldLabel(caps, name, typeof binding.field.label === "string" ? binding.field.label : void 0);
313
+ const label = resolveFieldLabel(caps, name, declaredText("field", binding.field, "label"));
643
314
  const testId = leafIds === "record" ? tid.formField(resource, mode, name) : `${leafIds.prefix}:field:${name}`;
644
315
  return jsxs("div", {
645
316
  ...leafAttributes("md", void 0),
@@ -664,16 +335,12 @@ function RecordLeaf({ node }) {
664
335
  const weight = compiled.nodes[node.index]?.options.weight;
665
336
  const caps = useI18nCaps(resource);
666
337
  const tid = useTestIds();
667
- const declared = binding.field;
668
- const label = resolveFieldLabel(caps, name, typeof declared.label === "string" ? declared.label : void 0);
338
+ const label = resolveFieldLabel(caps, name, declaredText("field", binding.field, "label"));
669
339
  const attributes = leafAttributes(size, weight);
670
340
  const t = useOptionalT();
671
341
  if (node.restricted === true) {
672
342
  const rawReason = node.restrictedReason;
673
- const reason = rawReason === void 0 || rawReason === "" ? void 0 : resolveContentSlot(caps, [rawReason], {
674
- resource,
675
- default: rawReason
676
- });
343
+ const reason = rawReason === void 0 || rawReason === "" ? void 0 : reasonText(caps, rawReason, resource);
677
344
  const restrictedId = tid.formFieldRestricted(resource, mode, name);
678
345
  return jsxs("div", {
679
346
  ...attributes,
@@ -736,10 +403,7 @@ function RecordLeaf({ node }) {
736
403
  });
737
404
  if (node.editable !== true) {
738
405
  const rawReason = node.lockReason;
739
- const reason = rawReason === void 0 ? void 0 : resolveContentSlot(caps, [rawReason], {
740
- resource,
741
- default: rawReason
742
- });
406
+ const reason = rawReason === void 0 ? void 0 : reasonText(caps, rawReason, resource);
743
407
  const locked = tid.formFieldLocked(resource, mode, name);
744
408
  return jsxs("div", {
745
409
  ...attributes,
@@ -791,7 +455,6 @@ function RecordLeaf({ node }) {
791
455
  function ActiveLeaf({ node, binding, label, attributes, inputId }) {
792
456
  const { resource, mode, controller, disabled: locked } = useScreenTree();
793
457
  const caps = useI18nCaps(resource);
794
- const t = useOptionalT();
795
458
  const tid = useTestIds();
796
459
  const name = binding.name;
797
460
  const record = controller.record;
@@ -807,24 +470,21 @@ function ActiveLeaf({ node, binding, label, attributes, inputId }) {
807
470
  const messageId = `${mode}-${name}-msg`;
808
471
  const placeholder = resolveFieldSlot(caps, name, "placeholder");
809
472
  const hint = resolveFieldSlot(caps, name, "description", fieldDescription(binding.field));
473
+ const chromeT = useChromeT();
810
474
  const fieldKey = `resources.${resource}.fields.${name}`;
811
- const errorText = binding.error === void 0 ? null : resolveValidationMessage({
812
- code: binding.error.code ?? "",
813
- message: binding.error.message,
814
- ...binding.error.params === void 0 ? {} : { params: binding.error.params }
815
- }, {
816
- t,
817
- fieldKey
818
- });
819
- const warning = errorText === null ? binding.warnings[0] : void 0;
820
- const warningText = warning === void 0 ? null : resolveValidationMessage({
821
- code: warning.code ?? "",
822
- message: warning.message,
823
- ...warning.params === void 0 ? {} : { params: warning.params }
475
+ const text = (issue) => resolveValidationMessage({
476
+ code: issue.code ?? "",
477
+ message: issue.message,
478
+ ...issue.check === void 0 ? {} : { check: issue.check }
824
479
  }, {
825
- t,
480
+ has: caps.has,
481
+ t: caps.t,
826
482
  fieldKey
827
483
  });
484
+ const shown = binding.issues.slice(0, SHOWN_ISSUES);
485
+ const more = binding.issues.length - shown.length;
486
+ const firstError = shown.find((issue) => issue.severity === "error");
487
+ const errorText = firstError === void 0 ? null : text(firstError);
828
488
  const disabled = binding.disabled || controller.isSubmitting || locked === true;
829
489
  const describedBy = errorText === null && hint ? messageId : void 0;
830
490
  const errorMessageId = errorText === null ? void 0 : messageId;
@@ -838,6 +498,7 @@ function ActiveLeaf({ node, binding, label, attributes, inputId }) {
838
498
  "data-buro-region": "field-label",
839
499
  className: "flex items-center gap-2",
840
500
  children: [jsx(Label, {
501
+ id: `${inputId}-label`,
841
502
  htmlFor: inputId,
842
503
  required: binding.required,
843
504
  children: label
@@ -853,6 +514,7 @@ function ActiveLeaf({ node, binding, label, attributes, inputId }) {
853
514
  resource: binding.resource,
854
515
  name,
855
516
  inputId,
517
+ labelId: `${inputId}-label`,
856
518
  field: binding.field,
857
519
  value: binding.value,
858
520
  onChange: binding.onChange,
@@ -866,8 +528,10 @@ function ActiveLeaf({ node, binding, label, attributes, inputId }) {
866
528
  ...describedBy === void 0 ? {} : { descriptionId: describedBy },
867
529
  ...errorMessageId === void 0 ? {} : { errorId: errorMessageId },
868
530
  errorMessage: errorText,
531
+ issues: binding.issues,
869
532
  ...binding.fixedFilters === void 0 ? {} : { fixedFilters: binding.fixedFilters },
870
533
  ...binding.relationSeeds === void 0 ? {} : { relationSeeds: binding.relationSeeds },
534
+ ...binding.link === void 0 ? {} : { link: binding.link },
871
535
  fallback: jsx(Input, {
872
536
  id: inputId,
873
537
  name: inputId,
@@ -882,18 +546,23 @@ function ActiveLeaf({ node, binding, label, attributes, inputId }) {
882
546
  ...errorMessageId === void 0 ? {} : { "aria-errormessage": errorMessageId },
883
547
  onChange: (event) => binding.onChange(event.target.value)
884
548
  })
885
- }), errorText !== null ? jsx("p", {
886
- id: messageId,
887
- "aria-live": "polite",
888
- className: "text-sm text-destructive",
889
- "data-testid": tid.formFieldError(resource, mode, name),
890
- children: errorText
891
- }) : warningText !== null ? jsx("p", {
549
+ }), shown.length > 0 ? jsxs("div", {
892
550
  id: messageId,
893
551
  "aria-live": "polite",
894
- className: "text-sm text-warning-emphasis",
895
- "data-testid": `form-field-warning:${resource}:${mode}:${name}`,
896
- children: warningText
552
+ className: cn("space-y-0.5 text-sm", errorText === null ? "text-warning-emphasis" : "text-destructive"),
553
+ "data-testid": errorText === null ? `form-field-warning:${resource}:${mode}:${name}` : tid.formFieldError(resource, mode, name),
554
+ children: [shown.map((issue, index) => jsx("p", {
555
+ "data-buro-severity": issue.severity,
556
+ className: issue.severity === "error" ? "text-destructive" : "text-warning-emphasis",
557
+ children: text(issue)
558
+ }, index)), more > 0 ? jsx("p", {
559
+ className: "text-muted-foreground",
560
+ "data-testid": `form-field-more:${resource}:${mode}:${name}`,
561
+ children: chromeT("validation.moreIssues", {
562
+ count: more,
563
+ defaultValue: "+{{count}} more"
564
+ })
565
+ }) : null]
897
566
  }) : hint ? jsx("p", {
898
567
  id: messageId,
899
568
  "data-testid": tid.formFieldHelp(resource, mode, name),
@@ -913,7 +582,7 @@ function DeclarativeLeaf({ node, prefix, inputIds }) {
913
582
  const docRef = i18nPrefixToRef(prefix);
914
583
  const docScope = docRef.namespace === "default" ? docRef.id : formatEntityRef(docRef);
915
584
  const caps = useI18nCaps(docScope);
916
- const t = useOptionalT();
585
+ const issueCaps = useI18nCaps();
917
586
  const uid = useId();
918
587
  const root = useRef(null);
919
588
  const inputId = `${inputIds ?? uid}-${name}`;
@@ -925,7 +594,7 @@ function DeclarativeLeaf({ node, prefix, inputIds }) {
925
594
  if (byId !== null && element.ownerDocument.activeElement === byId) return;
926
595
  element.querySelector(FOCUSABLE)?.focus();
927
596
  });
928
- const label = resolveFieldLabel(caps, name, binding.field.label);
597
+ const label = resolveFieldLabel(caps, name, declaredText("field", binding.field, "label"));
929
598
  const hint = resolveFieldSlot(caps, name, "description", fieldDescription(binding.field));
930
599
  const info = jsx(DocInfo, {
931
600
  address: `${docScope}/fields/${name}`,
@@ -967,9 +636,10 @@ function DeclarativeLeaf({ node, prefix, inputIds }) {
967
636
  const text = (issue) => resolveValidationMessage({
968
637
  code: issue.code ?? "",
969
638
  message: issue.message,
970
- ...issue.params === void 0 ? {} : { params: issue.params }
639
+ ...issue.check === void 0 ? {} : { check: issue.check }
971
640
  }, {
972
- t,
641
+ has: issueCaps.has,
642
+ t: issueCaps.t,
973
643
  fieldKey
974
644
  });
975
645
  const defaultNode = jsx(Field, {
@@ -977,6 +647,7 @@ function DeclarativeLeaf({ node, prefix, inputIds }) {
977
647
  field: binding.field,
978
648
  metaSources: controller.sources,
979
649
  inputId,
650
+ labelId: `${inputId}-label`,
980
651
  i18nPrefix: prefix,
981
652
  disabled: disabled === true || binding.disabled
982
653
  });
@@ -992,6 +663,7 @@ function DeclarativeLeaf({ node, prefix, inputIds }) {
992
663
  "data-buro-region": "field-label",
993
664
  className: "flex items-center gap-2",
994
665
  children: [jsx(Label, {
666
+ id: `${inputId}-label`,
995
667
  htmlFor: inputId,
996
668
  required: binding.required,
997
669
  children: label
@@ -1032,9 +704,10 @@ function Notice({ children }) {
1032
704
  children
1033
705
  });
1034
706
  }
1035
- function FactsView({ props }) {
707
+ function FactsView({ props, empty }) {
1036
708
  const own = props;
1037
709
  const t = useOptionalT();
710
+ const chromeT = useChromeT();
1038
711
  const snapshot = useApplicationSnapshot();
1039
712
  const queryId = formatEntityRef(own.query);
1040
713
  const caps = useI18nCaps();
@@ -1051,7 +724,10 @@ function FactsView({ props }) {
1051
724
  kind: "custom-query",
1052
725
  ref: own.query
1053
726
  }), [own.query]);
1054
- const result = useCustomQuery(handle, blocked ? {} : input, { enabled: !blocked });
727
+ const result = useCustomQuery(handle, blocked ? {} : input, {
728
+ enabled: !blocked,
729
+ ...own.refetchInterval === void 0 ? {} : { refetchInterval: own.refetchInterval }
730
+ });
1055
731
  const shown = useMemo(() => {
1056
732
  const byName = new Map(fields.map((field) => [field.name, field]));
1057
733
  return (own.rows ?? fields.map((field) => field.name)).flatMap((name) => {
@@ -1059,7 +735,7 @@ function FactsView({ props }) {
1059
735
  return field === void 0 ? [] : [field];
1060
736
  });
1061
737
  }, [fields, own.rows]);
1062
- if (blocked) return jsx(Notice, { children: own.missing ?? t("facts.empty", { defaultValue: "No data" }) });
738
+ if (blocked) return jsx(Notice, { children: own.missing ?? empty ?? t("facts.empty", { defaultValue: "No data" }) });
1063
739
  if (result.isError) return jsxs("div", {
1064
740
  role: "alert",
1065
741
  className: "flex flex-wrap items-center gap-2 rounded-md border border-dashed px-3 py-2 text-sm text-muted-foreground",
@@ -1068,7 +744,7 @@ function FactsView({ props }) {
1068
744
  variant: "outline",
1069
745
  size: "sm",
1070
746
  onClick: () => void result.refetch(),
1071
- children: t("actions.retry", { defaultValue: "Retry" })
747
+ children: chromeT("actions.retry", { defaultValue: "Retry" })
1072
748
  })]
1073
749
  });
1074
750
  if (result.data === void 0) return own.view === "list" ? jsx(Skeleton, {
@@ -1082,23 +758,28 @@ function FactsView({ props }) {
1082
758
  }, field.name))
1083
759
  });
1084
760
  const data = result.data;
1085
- if (data === null || typeof data !== "object" || shown.length === 0) return jsx(Notice, { children: own.missing ?? t("facts.empty", { defaultValue: "No data" }) });
761
+ if (data === null || typeof data !== "object" || shown.length === 0) return jsx(Notice, { children: own.missing ?? empty ?? t("facts.empty", { defaultValue: "No data" }) });
1086
762
  const pairs = shown.map((field) => {
1087
763
  const value = data[field.name];
1088
764
  const label = resolveContentSlot(caps, [`queries.${queryId}.fields.${field.name}.label`, `queries.${queryId}.fields.${field.name}`], {
1089
- ...typeof field.label === "string" ? { default: field.label } : {},
765
+ ...declaredText("field", field, "label") === void 0 ? {} : { default: declaredText("field", field, "label") },
1090
766
  humanize: () => humanizeKey(field.name)
1091
767
  });
1092
768
  const body = value === null || value === void 0 ? jsx("span", {
1093
769
  className: "text-muted-foreground",
1094
770
  children: "—"
1095
- }) : jsx(ResolvedFieldDisplay, {
1096
- resource: own.query,
1097
- name: field.name,
771
+ }) : jsx(FieldValueFrame, {
1098
772
  field,
1099
773
  value,
1100
- record: data,
1101
- fallback: toDisplayText(value).text
774
+ surface: "display",
775
+ children: jsx(ResolvedFieldDisplay, {
776
+ resource: own.query,
777
+ name: field.name,
778
+ field,
779
+ value,
780
+ record: data,
781
+ fallback: toDisplayText(value).text
782
+ })
1102
783
  });
1103
784
  return {
1104
785
  name: field.name,
@@ -1152,6 +833,9 @@ const PlacementFrameContext = createContext(null);
1152
833
  function usePlacementFrame() {
1153
834
  return useContext(PlacementFrameContext);
1154
835
  }
836
+ function placementLayoutId(frame) {
837
+ return frame?.owner === void 0 ? void 0 : `${frame.owner}/${frame.key.split(".").join("/")}`;
838
+ }
1155
839
  //#endregion
1156
840
  //#region src/flows/app-flow-button.tsx
1157
841
  function AppFlowButton({ action, variant = "ghost", size = "sm", className }) {
@@ -1271,7 +955,7 @@ function RecordInlineFlow({ name }) {
1271
955
  return jsx(InlineHostLoader, {
1272
956
  resource,
1273
957
  name: flowName,
1274
- record: row,
958
+ ...hostRecord(flowRecordOf(compiled, row)),
1275
959
  placementKey: key,
1276
960
  lease: kept,
1277
961
  stale: true,
@@ -1287,7 +971,7 @@ function RecordInlineFlow({ name }) {
1287
971
  if (control.state === "disabled" && kept !== void 0 && kept.dirty) return jsx(InlineHostLoader, {
1288
972
  resource,
1289
973
  name: flowName,
1290
- record: row,
974
+ ...hostRecord(flowRecordOf(compiled, row)),
1291
975
  placementKey: key,
1292
976
  lease: kept,
1293
977
  stale: true,
@@ -1299,11 +983,12 @@ function RecordInlineFlow({ name }) {
1299
983
  return jsx(InlineHostLoader, {
1300
984
  resource,
1301
985
  name: flowName,
1302
- record: control.record ?? row,
986
+ ...hostRecord(flowRecordOf(compiled, control.record ?? row)),
1303
987
  placementKey: key,
1304
988
  ...lease === void 0 ? {} : { lease }
1305
989
  });
1306
990
  }
991
+ const hostRecord = (record) => record === void 0 ? {} : { record };
1307
992
  function InlineHostLoader(props) {
1308
993
  const loaded = useLoaded(loadInlineFlowHost);
1309
994
  if (loaded.error !== void 0) return jsx("p", {
@@ -1357,11 +1042,11 @@ function PageInlineFlow({ flow }) {
1357
1042
  "data-buro-flow-state": "pending"
1358
1043
  });
1359
1044
  const appAction = "appAction" in target ? app.registry.appActions.get(target.appAction) : void 0;
1360
- const label = appAction === void 0 ? void 0 : resolveNavLabel(caps, `appActions.${"appAction" in target ? target.appAction : ""}`, appAction.label);
1045
+ const label = appAction === void 0 ? void 0 : resolveNavLabel(caps, `appActions.${"appAction" in target ? target.appAction : ""}`, declaredText("flow", appAction, "label"));
1361
1046
  const button = "appAction" in target ? jsx(AppFlowButton, {
1362
1047
  action: {
1363
1048
  name: target.appAction,
1364
- label: appAction?.label ?? target.appAction,
1049
+ label: declaredText("flow", appAction, "label") ?? target.appAction,
1365
1050
  ...appAction?.icon === void 0 ? {} : { icon: appAction.icon },
1366
1051
  ...answer.state === "disabled" ? { disabledReason: answer.reason ?? "" } : {}
1367
1052
  },
@@ -1519,6 +1204,7 @@ function placementSeeds(where, resolved, closure) {
1519
1204
  where.forEach((entry, index) => {
1520
1205
  const value = resolved[index]?.value;
1521
1206
  if (entry.op !== "eq" || value === null || value === void 0) return;
1207
+ if (typeof entry.field !== "string" || entry.field.includes(".")) return;
1522
1208
  if (resolved[index]?.field !== entry.field) return;
1523
1209
  const source = refSource(entry.value);
1524
1210
  if (source === null ? isLiteral(entry.value) : source === "record" || source === "user" || !UNTRUSTED.includes(source) && Object.hasOwn(closure, source) && !(closure[source] ?? []).some((input) => UNTRUSTED.includes(input))) seeds[entry.field] = value;
@@ -1536,7 +1222,8 @@ function BadgeBody({ icon, label }) {
1536
1222
  }) : null, label]
1537
1223
  });
1538
1224
  }
1539
- function EnumBadge({ enumName, value, testid, className, presentation }) {
1225
+ function EnumBadge({ enumName: enumInput, value, testid, className, presentation }) {
1226
+ const enumName = enumBindingKey(enumInput) ?? "";
1540
1227
  const app = useApp();
1541
1228
  const caps = useI18nCaps();
1542
1229
  const def = app.registry.enums.get(enumName);
@@ -1579,7 +1266,7 @@ function EnumBadge({ enumName, value, testid, className, presentation }) {
1579
1266
  if (entry) {
1580
1267
  const body = jsx(BadgeBody, {
1581
1268
  icon: entry.icon,
1582
- label: resolveEnumLabel(caps, ns, value, entry.label)
1269
+ label: resolveEnumLabel(caps, ns, value, enumValueText(def, value) ?? entry.label)
1583
1270
  });
1584
1271
  if (presentation === "plain") return jsx("span", {
1585
1272
  "data-testid": id,
@@ -1595,15 +1282,16 @@ function EnumBadge({ enumName, value, testid, className, presentation }) {
1595
1282
  children: body
1596
1283
  });
1597
1284
  }
1598
- if (def.fallback) {
1599
- const fallbackLabel = resolveContentSlot(caps, [enumFallbackKey(ns)], { default: def.fallback.label }) ?? value;
1285
+ const declaredFallback = enumFallbackText(def);
1286
+ if (def?.fallback !== void 0 || declaredFallback !== void 0) {
1287
+ const fallbackLabel = resolveContentSlot(caps, [enumFallbackKey(ns)], { default: declaredFallback }) ?? value;
1600
1288
  if (presentation === "plain") return jsx("span", {
1601
1289
  "data-testid": `${id}:fallback`,
1602
1290
  className,
1603
1291
  children: fallbackLabel
1604
1292
  });
1605
1293
  return jsx(Badge, {
1606
- variant: def.fallback.color ?? "outline",
1294
+ variant: def?.fallback?.color ?? "outline",
1607
1295
  className,
1608
1296
  "data-testid": `${id}:fallback`,
1609
1297
  children: fallbackLabel
@@ -1681,6 +1369,7 @@ function useExportRunWatcher() {
1681
1369
  const runtime = useDataRuntime();
1682
1370
  const runs = useExportRuns();
1683
1371
  const t = useOptionalT();
1372
+ const caps = useI18nCaps();
1684
1373
  React.useEffect(() => {
1685
1374
  const announced = announcedRunsFor(runtime);
1686
1375
  const unsubscribes = runs.map((run) => {
@@ -1699,7 +1388,7 @@ function useExportRunWatcher() {
1699
1388
  label: t("export.toast.download"),
1700
1389
  onClick: () => downloadArtifact(artifact)
1701
1390
  } });
1702
- } else if (state.status === "failed") toast.error(t("export.toast.failed", { params: { message: state.error.message } }));
1391
+ } else if (state.status === "failed") toast.error(t("export.toast.failed", { params: { message: resolveErrorText(state.error, caps) } }));
1703
1392
  };
1704
1393
  check();
1705
1394
  return run.handle.subscribe(check);
@@ -1710,24 +1399,27 @@ function useExportRunWatcher() {
1710
1399
  }, [
1711
1400
  runs,
1712
1401
  runtime,
1713
- t
1402
+ t,
1403
+ caps
1714
1404
  ]);
1715
1405
  }
1716
1406
  //#endregion
1717
1407
  //#region src/actions/action-overflow.tsx
1718
1408
  const RowActionsCollapseContext = createContext(false);
1719
- function ActionOverflow({ resource, record, surface, className, hideFlows, collapse }) {
1720
- const t = useOptionalT();
1409
+ function ActionOverflow({ resource: resourceInput, record, surface, className, hideFlows, collapse, sources }) {
1410
+ const resource = routeResourceKey(resourceInput);
1411
+ const chromeT = useChromeT();
1721
1412
  const tid = useTestIds();
1413
+ const recordKey = String(recordKeyOf(useApp().registry.get(resource), record) ?? "");
1722
1414
  const collapsedRows = useContext(RowActionsCollapseContext);
1723
1415
  const [flowFor, setFlowFor] = useState(null);
1724
- const actions = useShownItemActions(resource, record, surface, hideFlows);
1416
+ const actions = useShownItemActions(resource, record, surface, hideFlows, sources);
1725
1417
  if (actions.length === 0) return null;
1726
1418
  const isRow = surface === "row";
1727
1419
  const folded = isRow && (collapse ?? collapsedRows);
1728
1420
  const standalone = folded ? [] : actions.filter((a) => a.placement === "standalone");
1729
1421
  const menu = folded ? actions : actions.filter((a) => a.placement === "menu");
1730
- const menuTestId = isRow ? tid.listRowActionsMenu(resource, record.id) : tid.formActionsMenu(resource, surface);
1422
+ const menuTestId = isRow ? tid.listRowActionsMenu(resource, recordKey) : tid.formActionsMenu(resource, surface);
1731
1423
  const firstDestructive = menu.findIndex((a) => a.variant === "destructive");
1732
1424
  const overlays = jsx(Fragment$1, { children: flowFor != null ? jsx(RuntimeFlowRunner, {
1733
1425
  resource,
@@ -1741,6 +1433,7 @@ function ActionOverflow({ resource, record, surface, className, hideFlows, colla
1741
1433
  actionName: a.name,
1742
1434
  record,
1743
1435
  scope: surface,
1436
+ ...sources === void 0 ? {} : { sources },
1744
1437
  density: a.density
1745
1438
  }, a.name));
1746
1439
  return jsxs("div", {
@@ -1764,7 +1457,7 @@ function ActionOverflow({ resource, record, surface, className, hideFlows, colla
1764
1457
  size: "icon",
1765
1458
  className: "size-7",
1766
1459
  "data-testid": menuTestId,
1767
- "aria-label": t("actions.more", { defaultValue: "More" }),
1460
+ "aria-label": chromeT("actions.more", { defaultValue: "More" }),
1768
1461
  children: jsx(IconDots, {
1769
1462
  className: "h-4 w-4",
1770
1463
  "aria-hidden": "true"
@@ -1778,15 +1471,16 @@ function ActionOverflow({ resource, record, surface, className, hideFlows, colla
1778
1471
  children: [jsx(IconDots, {
1779
1472
  className: "h-4 w-4",
1780
1473
  "aria-hidden": "true"
1781
- }), t("actions.more", { defaultValue: "More" })]
1474
+ }), chromeT("actions.more", { defaultValue: "More" })]
1782
1475
  })
1783
1476
  }), jsx(DropdownMenuContent, {
1784
1477
  align: "end",
1785
- children: menu.map((a, index) => jsxs(Fragment, { children: [index === firstDestructive && index > 0 && jsx(DropdownMenuSeparator, { "data-testid": isRow ? `list-row-actions-separator:${resource}:${record.id}` : `form-actions-separator:${resource}:${surface}` }), a.flow === void 0 ? jsx(ActionButton, {
1478
+ children: menu.map((a, index) => jsxs(Fragment, { children: [index === firstDestructive && index > 0 && jsx(DropdownMenuSeparator, { "data-testid": isRow ? `list-row-actions-separator:${resource}:${recordKey}` : `form-actions-separator:${resource}:${surface}` }), a.flow === void 0 ? jsx(ActionButton, {
1786
1479
  resource,
1787
1480
  actionName: a.name,
1788
1481
  record,
1789
1482
  scope: surface,
1483
+ ...sources === void 0 ? {} : { sources },
1790
1484
  asMenuItem: true
1791
1485
  }) : jsx(FlowMenuItem, {
1792
1486
  resource,
@@ -1806,11 +1500,12 @@ function ActionOverflow({ resource, record, surface, className, hideFlows, colla
1806
1500
  ]
1807
1501
  });
1808
1502
  }
1809
- function useShownItemActions(resource, record, surface, hideFlows) {
1503
+ function useShownItemActions(resource, record, surface, hideFlows, sources) {
1810
1504
  const resolved = useItemActions({
1811
1505
  resource,
1812
1506
  record,
1813
- surface
1507
+ surface,
1508
+ ...sources === void 0 ? {} : { sources }
1814
1509
  });
1815
1510
  const def = useApp().registry.get(resource);
1816
1511
  const shown = def !== void 0 && isBuiltinItemAction((def.actions?.item)?.find((action) => action.name === "edit"), "edit") && !recordEntries(def).page.edit && recordEntries(def).quick.edit === null ? resolved.filter((a) => a.name !== "edit") : resolved;
@@ -1903,8 +1598,14 @@ function DocumentTitleProvider({ routeKey, children }) {
1903
1598
  });
1904
1599
  }
1905
1600
  //#endregion
1601
+ //#region src/resource/resource-title.ts
1602
+ function resolveResourceTitle(caps, t, resource, slot, params) {
1603
+ return resolveContentSlot(caps, [`resources.${resource}.${slot}`], { params: { ...params } }) ?? t(`pageHeader.${slot}`, { params: { ...params } });
1604
+ }
1605
+ //#endregion
1906
1606
  //#region src/resource/page-header.tsx
1907
- function PageHeader({ resource, mode, recordName, backHref, backLabel, actions, title, breadcrumbs, backLink = false, visibleTitle = false, labelOverride, className }) {
1607
+ function PageHeader({ resource: resourceInput, mode, recordName, backHref, backLabel, actions, title, breadcrumbs, backLink = false, visibleTitle = false, labelOverride, className }) {
1608
+ const resource = routeResourceKey(resourceInput);
1908
1609
  const app = useApp();
1909
1610
  const t = useT();
1910
1611
  const def = app.registry.get(resource);
@@ -1913,10 +1614,10 @@ function PageHeader({ resource, mode, recordName, backHref, backLabel, actions,
1913
1614
  const resourceSingle = resolveResourceLabel(caps, resource, "single", def);
1914
1615
  const labelSingle = labelOverride ?? resourceSingle;
1915
1616
  const labelPlural = resolveResourceLabel(caps, resource, "plural", def);
1916
- const computedTitle = title ?? (mode === "list" ? labelPlural : mode === "show" ? recordName ?? labelSingle : mode === "edit" ? recordName ? t("pageHeader.editTitleNamed", { params: {
1617
+ const computedTitle = title ?? (mode === "list" ? labelPlural : mode === "show" ? recordName ?? labelSingle : mode === "edit" ? recordName ? resolveResourceTitle(caps, t, resource, "editTitleNamed", {
1917
1618
  label: labelSingle,
1918
1619
  name: recordName
1919
- } }) : t("pageHeader.editTitle", { params: { label: labelSingle } }) : t("pageHeader.createTitle", { params: { label: labelSingle } }));
1620
+ }) : resolveResourceTitle(caps, t, resource, "editTitle", { label: labelSingle }) : resolveResourceTitle(caps, t, resource, "createTitle", { label: labelSingle }));
1920
1621
  useDocumentTitle(app?.name ? `${computedTitle} · ${app.name}` : computedTitle);
1921
1622
  const showBack = backLink && mode !== "list" && !breadcrumbs;
1922
1623
  const computedBackHref = backHref ?? def?.basePath ?? `/${resource}`;
@@ -1947,9 +1648,10 @@ function PageHeader({ resource, mode, recordName, backHref, backLabel, actions,
1947
1648
  ]
1948
1649
  });
1949
1650
  }
1950
- function ShowPageHeader({ resource, record, extraActions, breadcrumbs }) {
1651
+ function ShowPageHeader({ resource: resourceInput, record, extraActions, breadcrumbs }) {
1652
+ const resource = routeResourceKey(resourceInput);
1951
1653
  const def = useApp().registry.get(resource);
1952
- const recordName = useProjectedTitle(resource, record?.id, record);
1654
+ const recordName = useProjectedTitle(resource, recordKeyOf(def, record), record);
1953
1655
  usePublishDocVariant(resource, record);
1954
1656
  if (!def) return null;
1955
1657
  return jsx(PageHeader, {
@@ -1964,16 +1666,18 @@ function ShowPageHeader({ resource, record, extraActions, breadcrumbs }) {
1964
1666
  }), extraActions] })
1965
1667
  });
1966
1668
  }
1967
- function EditPageHeader({ resource, record, backHref, breadcrumbs }) {
1669
+ function EditPageHeader({ resource: resourceInput, record, backHref, breadcrumbs }) {
1670
+ const resource = routeResourceKey(resourceInput);
1968
1671
  const app = useApp();
1969
1672
  const def = app.registry.get(resource);
1970
1673
  const caps = useI18nCaps(resource);
1971
- const recordName = useProjectedTitle(resource, record?.id, record);
1674
+ const recordKey = recordKeyOf(def, record);
1675
+ const recordName = useProjectedTitle(resource, recordKey, record);
1972
1676
  return jsx(PageHeader, {
1973
1677
  resource,
1974
1678
  mode: "edit",
1975
1679
  recordName,
1976
- backHref: editBackHref(app.registry, resource, String(record.id), backHref),
1680
+ backHref: editBackHref(app.registry, resource, String(recordKey ?? ""), backHref),
1977
1681
  breadcrumbs,
1978
1682
  backLabel: recordName ? `${resolveResourceLabel(caps, resource, "single", def)}: ${recordName}` : void 0,
1979
1683
  actions: jsx(ActionOverflow, {
@@ -2173,7 +1877,8 @@ function ItemNode({ node, row, fallback }) {
2173
1877
  const viewProps = {
2174
1878
  props,
2175
1879
  data: {},
2176
- t: placement.viewProps?.t ?? ((name) => name)
1880
+ t: placement.viewProps?.t ?? ((name) => name),
1881
+ ...placement.viewProps?.empty === void 0 ? {} : { empty: placement.viewProps.empty }
2177
1882
  };
2178
1883
  return jsx(View, { ...viewProps });
2179
1884
  }
@@ -2210,15 +1915,7 @@ function nodeAttributes(node, kind = node.kind, tone) {
2210
1915
  }
2211
1916
  function useNodeText() {
2212
1917
  const caps = useI18nCaps();
2213
- return (slot) => {
2214
- if (slot === void 0) return void 0;
2215
- if (caps.has(slot.key)) return caps.t(slot.key);
2216
- if (slot.entry !== void 0 && caps.has(slot.entry)) {
2217
- const entry = caps.t(slot.entry);
2218
- if (typeof entry === "string" && entry !== slot.entry) return entry;
2219
- }
2220
- return slot.default;
2221
- };
1918
+ return (slot) => slot === void 0 ? void 0 : resolveTextSlot(caps, slot);
2222
1919
  }
2223
1920
  function NodeInfo({ node, name }) {
2224
1921
  const { resource } = useScreenTree();
@@ -2263,6 +1960,7 @@ function BuroPanelFrame({ body, title, description, collapsible, open, onToggle,
2263
1960
  const t = useOptionalT();
2264
1961
  return jsxs("section", {
2265
1962
  ...nodeAttributes,
1963
+ ...title === void 0 ? {} : { "aria-labelledby": titleId },
2266
1964
  children: [
2267
1965
  title === void 0 && info === null && !collapsible ? null : jsxs("div", {
2268
1966
  "data-buro-region": "panel-title",
@@ -2322,7 +2020,6 @@ function BuroTabsFrame({ tabs, active, onSelect, info, nodeAttributes }) {
2322
2020
  }), info]
2323
2021
  }), tabs.map((tab) => jsx(TabsContent, {
2324
2022
  value: tab.key,
2325
- forceMount: true,
2326
2023
  "data-buro-region": "tab-panel",
2327
2024
  "data-buro-key": tab.key,
2328
2025
  children: tab.panel
@@ -2410,17 +2107,17 @@ function BuroStepsFrame({ wizard, steps, goTo, back, next, headingRef, info, nod
2410
2107
  //#region src/primitives/checkbox.tsx
2411
2108
  const Checkbox = React.forwardRef(({ className, checked, ...props }, ref) => jsx(CheckboxPrimitive.Root, {
2412
2109
  ref,
2413
- className: cn("peer h-4 w-4 shrink-0 rounded-sm border border-primary ring-offset-background focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground data-[state=indeterminate]:bg-primary data-[state=indeterminate]:text-primary-foreground", className),
2110
+ className: cn("peer inline-flex h-4 w-4 shrink-0 items-center justify-center align-middle rounded-[4px] border border-primary ring-offset-background focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground data-[state=indeterminate]:bg-primary data-[state=indeterminate]:text-primary-foreground", className),
2414
2111
  checked,
2415
2112
  ...props,
2416
2113
  children: jsx(CheckboxPrimitive.Indicator, {
2417
2114
  className: cn("flex items-center justify-center text-current"),
2418
2115
  children: checked === "indeterminate" ? jsx(IconMinus, {
2419
2116
  "data-icon": "minus",
2420
- className: "h-4 w-4"
2117
+ className: "size-3.5"
2421
2118
  }) : jsx(IconCheck, {
2422
2119
  "data-icon": "check",
2423
- className: "h-4 w-4"
2120
+ className: "size-3.5"
2424
2121
  })
2425
2122
  })
2426
2123
  }));
@@ -2445,6 +2142,17 @@ function useContainerNarrow(maxRem = 40) {
2445
2142
  return [setElement, narrow];
2446
2143
  }
2447
2144
  //#endregion
2145
+ //#region src/data-table/fill-column.ts
2146
+ function fillColumnId(columns) {
2147
+ const eligible = columns.filter((c) => !c.pinned && !c.service);
2148
+ const declared = eligible.find((c) => c.fill);
2149
+ if (declared !== void 0) return declared.id;
2150
+ for (let i = eligible.length - 1; i >= 0; i--) {
2151
+ const c = eligible[i];
2152
+ if (c.resize && c.max === void 0) return c.id;
2153
+ }
2154
+ }
2155
+ //#endregion
2448
2156
  //#region src/data-table/data-table.tsx
2449
2157
  function pinEdgeClass(isLastLeft, isFirstRight) {
2450
2158
  if (isLastLeft) return "after:pointer-events-none after:absolute after:inset-y-0 after:left-full after:z-10 after:w-1.5 after:bg-gradient-to-r after:from-overlay/[0.07] after:to-transparent dark:after:from-overlay/30";
@@ -2456,7 +2164,8 @@ function pinEdgeClassFor(column, overflowing) {
2456
2164
  if (!pinned || !overflowing) return "";
2457
2165
  return pinEdgeClass(pinned === "left" && column.getIsLastColumn("left"), pinned === "right" && column.getIsFirstColumn("right"));
2458
2166
  }
2459
- function getPinStyles(column, overflowing) {
2167
+ function getPinStyles(column, overflowing, fill = false) {
2168
+ if (fill) return {};
2460
2169
  const explicitWidth = { width: `calc(var(--col-${column.id}-size) * 1px)` };
2461
2170
  const pinned = column.getIsPinned();
2462
2171
  if (!pinned || !overflowing) return explicitWidth;
@@ -2481,21 +2190,35 @@ function columnMetaNoRowNav(meta) {
2481
2190
  function columnMetaNoClip(meta) {
2482
2191
  return meta?.noClip === true;
2483
2192
  }
2484
- function annotateIfClipped(e) {
2485
- const el = e.currentTarget;
2486
- const text = el.scrollWidth > el.clientWidth || Array.from(el.querySelectorAll("*")).some((c) => c.scrollWidth > c.clientWidth) ? (el.textContent ?? "").trim() : "";
2487
- if (text) el.setAttribute("title", text);
2488
- else el.removeAttribute("title");
2193
+ function columnMetaWrap(meta) {
2194
+ return meta?.wrap === true;
2195
+ }
2196
+ function columnMetaFill(meta) {
2197
+ return meta?.fill === true;
2198
+ }
2199
+ function tableFillColumnId(table) {
2200
+ if (table.options.enableColumnResizing !== true) return void 0;
2201
+ return fillColumnId(table.getVisibleLeafColumns().map((c) => {
2202
+ const max = c.columnDef.maxSize;
2203
+ return {
2204
+ id: c.id,
2205
+ fill: columnMetaFill(c.columnDef.meta),
2206
+ resize: c.getCanResize(),
2207
+ ...max !== void 0 && max < Number.MAX_SAFE_INTEGER ? { max } : {},
2208
+ pinned: c.getIsPinned() !== false,
2209
+ service: c.id.startsWith("__")
2210
+ };
2211
+ }));
2489
2212
  }
2490
- function renderCellMarkup({ cell, resizable, overflowing, testId, onCellClick, noRowNav }) {
2213
+ function renderCellMarkup({ cell, resizable, overflowing, fill, testId, onCellClick, noRowNav }) {
2491
2214
  const liveColumn = cell.getContext().table.getColumn(cell.column.id) ?? cell.column;
2492
2215
  const pinEdge = pinEdgeClassFor(liveColumn, overflowing);
2493
- const noClip = columnMetaNoClip(cell.column.columnDef.meta);
2216
+ const noClip = columnMetaNoClip(cell.column.columnDef.meta) || columnMetaWrap(cell.column.columnDef.meta);
2494
2217
  return jsx(TableCell, {
2495
2218
  "data-testid": testId,
2496
2219
  "data-no-row-nav": columnMetaNoRowNav(cell.column.columnDef.meta) || noRowNav ? "" : void 0,
2497
2220
  onClick: onCellClick,
2498
- style: getPinStyles(liveColumn, overflowing),
2221
+ style: getPinStyles(liveColumn, overflowing, fill),
2499
2222
  className: cn(resizable && !pinEdge && !noClip && "overflow-hidden", pinEdge && "relative", pinEdge, columnMetaClassName(cell.column.columnDef.meta), onCellClick && "cursor-pointer"),
2500
2223
  children: resizable && !noClip ? jsx("div", {
2501
2224
  className: "truncate",
@@ -2507,7 +2230,7 @@ function renderCellMarkup({ cell, resizable, overflowing, testId, onCellClick, n
2507
2230
  function MemoCellInner(props) {
2508
2231
  return renderCellMarkup(props);
2509
2232
  }
2510
- const MemoCell = memo(MemoCellInner, (prev, next) => prev.rowOriginal === next.rowOriginal && prev.cell.column.id === next.cell.column.id && prev.resizable === next.resizable && (!prev.pinned && !next.pinned || prev.overflowing === next.overflowing) && prev.pinned === next.pinned && prev.testId === next.testId);
2233
+ const MemoCell = memo(MemoCellInner, (prev, next) => prev.rowOriginal === next.rowOriginal && prev.cell.column.id === next.cell.column.id && prev.resizable === next.resizable && (!prev.pinned && !next.pinned || prev.overflowing === next.overflowing) && prev.pinned === next.pinned && prev.fill === next.fill && prev.testId === next.testId);
2511
2234
  function SelectionCellInner(props) {
2512
2235
  const row = props.cell.row;
2513
2236
  const toggle = (e) => {
@@ -2521,7 +2244,7 @@ function SelectionCellInner(props) {
2521
2244
  });
2522
2245
  }
2523
2246
  const SelectionCell = memo(SelectionCellInner);
2524
- function DataRowInner({ row, isSelected, resizable, overflowing, selectionColumnId, getCellTestId, getRowTestId, rowHref, onRowOpen, activateRow, rowClassName }) {
2247
+ function DataRowInner({ row, isSelected, resizable, overflowing, fillColumnId, selectionColumnId, getCellTestId, getRowTestId, rowHref, onRowOpen, activateRow, rowClassName }) {
2525
2248
  const clickable = rowHref !== void 0 || onRowOpen !== void 0;
2526
2249
  return jsx(TableRow, {
2527
2250
  "data-state": isSelected ? "selected" : void 0,
@@ -2534,6 +2257,7 @@ function DataRowInner({ row, isSelected, resizable, overflowing, selectionColumn
2534
2257
  isSelected,
2535
2258
  resizable,
2536
2259
  overflowing,
2260
+ fill: cell.column.id === fillColumnId,
2537
2261
  pinned: cell.getContext().table.getColumn(cell.column.id)?.getIsPinned() ?? false,
2538
2262
  testId: getCellTestId?.(row.original, cell.column.id)
2539
2263
  }, cell.id) : jsx(MemoCell, {
@@ -2541,13 +2265,14 @@ function DataRowInner({ row, isSelected, resizable, overflowing, selectionColumn
2541
2265
  rowOriginal: row.original,
2542
2266
  resizable,
2543
2267
  overflowing,
2268
+ fill: cell.column.id === fillColumnId,
2544
2269
  pinned: cell.getContext().table.getColumn(cell.column.id)?.getIsPinned() ?? false,
2545
2270
  testId: getCellTestId?.(row.original, cell.column.id)
2546
2271
  }, cell.id))
2547
2272
  });
2548
2273
  }
2549
- const MemoRow = memo(DataRowInner, (prev, next) => prev.row.original === next.row.original && prev.row.id === next.row.id && prev.isSelected === next.isSelected && prev.resizable === next.resizable && prev.overflowing === next.overflowing && prev.columnLayoutKey === next.columnLayoutKey && prev.selectionColumnId === next.selectionColumnId && prev.getCellTestId === next.getCellTestId && prev.getRowTestId === next.getRowTestId && prev.rowHref === next.rowHref && prev.onRowOpen === next.onRowOpen && prev.activateRow === next.activateRow && prev.rowClassName === next.rowClassName);
2550
- function DataTableBodyInner({ rows, isLoading, loadingRowCount, visibleColumnCount, emptyState, emptyText, resizable, overflowing, columnLayoutKey, selectionColumnId, getCellTestId, getRowTestId, rowHref, onRowOpen, activateRow, rowClassName }) {
2274
+ const MemoRow = memo(DataRowInner, (prev, next) => prev.row.original === next.row.original && prev.row.id === next.row.id && prev.isSelected === next.isSelected && prev.resizable === next.resizable && prev.overflowing === next.overflowing && prev.columnLayoutKey === next.columnLayoutKey && prev.fillColumnId === next.fillColumnId && prev.selectionColumnId === next.selectionColumnId && prev.getCellTestId === next.getCellTestId && prev.getRowTestId === next.getRowTestId && prev.rowHref === next.rowHref && prev.onRowOpen === next.onRowOpen && prev.activateRow === next.activateRow && prev.rowClassName === next.rowClassName);
2275
+ function DataTableBodyInner({ rows, isLoading, loadingRowCount, visibleColumnCount, emptyState, emptyText, resizable, overflowing, columnLayoutKey, fillColumnId, selectionColumnId, getCellTestId, getRowTestId, rowHref, onRowOpen, activateRow, rowClassName }) {
2551
2276
  return jsx(TableBody, { children: isLoading ? Array.from({ length: loadingRowCount }).map((_, i) => jsx(TableRow, {
2552
2277
  "aria-busy": "true",
2553
2278
  children: Array.from({ length: Math.max(1, visibleColumnCount) }).map((__, j) => jsx(TableCell, { children: jsx("div", { className: "h-4 w-3/4 animate-pulse rounded bg-muted" }) }, `skeleton-${i}-${j}`))
@@ -2564,6 +2289,7 @@ function DataTableBodyInner({ rows, isLoading, loadingRowCount, visibleColumnCou
2564
2289
  resizable,
2565
2290
  overflowing,
2566
2291
  columnLayoutKey,
2292
+ fillColumnId,
2567
2293
  selectionColumnId,
2568
2294
  getCellTestId,
2569
2295
  getRowTestId,
@@ -2575,7 +2301,7 @@ function DataTableBodyInner({ rows, isLoading, loadingRowCount, visibleColumnCou
2575
2301
  }
2576
2302
  const DataTableBody = memo(DataTableBodyInner, (prev, next) => {
2577
2303
  if (!next.isResizing) return false;
2578
- return prev.rows === next.rows && prev.overflowing === next.overflowing && prev.isLoading === next.isLoading && prev.columnLayoutKey === next.columnLayoutKey && prev.selectionColumnId === next.selectionColumnId;
2304
+ return prev.rows === next.rows && prev.overflowing === next.overflowing && prev.isLoading === next.isLoading && prev.columnLayoutKey === next.columnLayoutKey && prev.fillColumnId === next.fillColumnId && prev.selectionColumnId === next.selectionColumnId;
2579
2305
  });
2580
2306
  function DataTable({ table, tableTestId, getRowTestId, getCellTestId, isLoading = false, loadingRowCount = 5, emptyState, errorState, rowClassName, ariaLabel, onColumnReorder, rowHref, onRowOpen, selectionColumnId, className }) {
2581
2307
  const t = useOptionalT();
@@ -2605,8 +2331,10 @@ function DataTable({ table, tableTestId, getRowTestId, getCellTestId, isLoading
2605
2331
  const rows = table.getRowModel().rows;
2606
2332
  const headerGroups = table.getHeaderGroups();
2607
2333
  const visibleColumnCount = table.getVisibleLeafColumns().length;
2334
+ const fillId = tableFillColumnId(table);
2608
2335
  const columnLayoutKey = table.getVisibleLeafColumns().map((c) => `${c.id}:${c.getIsPinned() || ""}`).join("|");
2609
2336
  const resizable = table.options.enableColumnResizing === true;
2337
+ const widthSum = resizable ? table.getVisibleLeafColumns().reduce((sum, c) => sum + c.getSize(), 0) : 0;
2610
2338
  const hasPinned = table.getState().columnPinning != null && (table.getState().columnPinning.left?.length || table.getState().columnPinning.right?.length);
2611
2339
  const sizingState = table.getState().columnSizing;
2612
2340
  const sizingInfo = table.getState().columnSizingInfo;
@@ -2698,7 +2426,7 @@ function DataTable({ table, tableTestId, getRowTestId, getCellTestId, isLoading
2698
2426
  className,
2699
2427
  style: resizable ? {
2700
2428
  ...columnSizeVars,
2701
- minWidth: "100%",
2429
+ width: fillId !== void 0 ? `max(100%, ${widthSum}px)` : `${widthSum}px`,
2702
2430
  tableLayout: "fixed"
2703
2431
  } : void 0,
2704
2432
  children: [jsx(TableHeader, { children: headerGroups.map((hg) => jsx(TableRow, { children: hg.headers.map((header) => {
@@ -2709,7 +2437,7 @@ function DataTable({ table, tableTestId, getRowTestId, getCellTestId, isLoading
2709
2437
  return jsxs(TableHead, {
2710
2438
  colSpan: header.colSpan,
2711
2439
  "aria-sort": ariaSort,
2712
- style: getPinStyles(header.column, overflowing),
2440
+ style: getPinStyles(header.column, overflowing, header.column.id === fillId),
2713
2441
  className: cn("relative", resizable && !columnMetaNoClip(header.column.columnDef.meta) && (pinEdgeClassFor(header.column, overflowing) ? "[&>*:not([data-testid^=col-resize])]:truncate" : "overflow-hidden [&>*:not([data-testid^=col-resize])]:truncate"), canReorder && "cursor-grab", pinEdgeClassFor(header.column, overflowing), columnMetaClassName(header.column.columnDef.meta)),
2714
2442
  draggable: canReorder || void 0,
2715
2443
  onDragStart: canReorder ? (e) => {
@@ -2768,6 +2496,7 @@ function DataTable({ table, tableTestId, getRowTestId, getCellTestId, isLoading
2768
2496
  resizable,
2769
2497
  overflowing,
2770
2498
  columnLayoutKey,
2499
+ fillColumnId: fillId,
2771
2500
  selectionColumnId,
2772
2501
  isResizing: Boolean(sizingInfo.isResizingColumn),
2773
2502
  getCellTestId,
@@ -2820,69 +2549,103 @@ function ColumnHeaderMenu({ label, sortDirection, sortable, onSort, pinned, onPi
2820
2549
  sortable && onSort && jsxs(DropdownMenuGroup, { children: [
2821
2550
  jsx(DropdownMenuLabel, {
2822
2551
  className: "text-xs font-medium text-muted-foreground",
2823
- children: tr("columnMenu.sortSection", "Сортировка")
2552
+ children: tr("columnMenu.sortSection", "Sort")
2824
2553
  }),
2825
2554
  jsxs(DropdownMenuItem, {
2826
2555
  onClick: () => onSort("asc"),
2827
2556
  "data-testid": testid ? `${testid}:sort-asc` : void 0,
2828
- children: [jsx(IconArrowUp, { className: "mr-2 size-4 text-muted-foreground" }), tr("columnMenu.sortAsc", "По возрастанию")]
2557
+ children: [jsx(IconArrowUp, { className: "mr-2 size-4 text-muted-foreground" }), tr("columnMenu.sortAsc", "Ascending")]
2829
2558
  }),
2830
2559
  jsxs(DropdownMenuItem, {
2831
2560
  onClick: () => onSort("desc"),
2832
2561
  "data-testid": testid ? `${testid}:sort-desc` : void 0,
2833
- children: [jsx(IconArrowDown, { className: "mr-2 size-4 text-muted-foreground" }), tr("columnMenu.sortDesc", "По убыванию")]
2562
+ children: [jsx(IconArrowDown, { className: "mr-2 size-4 text-muted-foreground" }), tr("columnMenu.sortDesc", "Descending")]
2834
2563
  }),
2835
2564
  sortDirection && jsxs(DropdownMenuItem, {
2836
2565
  onClick: () => onSort(null),
2837
- children: [jsx(IconX, { className: "mr-2 size-4 text-muted-foreground" }), tr("columnMenu.clearSort", "Сбросить")]
2566
+ children: [jsx(IconX, { className: "mr-2 size-4 text-muted-foreground" }), tr("columnMenu.clearSort", "Clear")]
2838
2567
  })
2839
2568
  ] }),
2840
2569
  onPin && jsxs(Fragment$1, { children: [sortable && onSort && jsx(DropdownMenuSeparator, {}), jsxs(DropdownMenuGroup, { children: [
2841
2570
  jsx(DropdownMenuLabel, {
2842
2571
  className: "text-xs font-medium text-muted-foreground",
2843
- children: tr("columnMenu.pinSection", "Закрепление")
2572
+ children: tr("columnMenu.pinSection", "Pin")
2844
2573
  }),
2845
2574
  pinned !== "left" && jsxs(DropdownMenuItem, {
2846
2575
  onClick: () => onPin("left"),
2847
2576
  "data-testid": testid ? `${testid}:pin-left` : void 0,
2848
- children: [jsx(IconPinned, { className: "mr-2 size-4 text-muted-foreground" }), tr("columnMenu.pinLeft", "Слева")]
2577
+ children: [jsx(IconPinned, { className: "mr-2 size-4 text-muted-foreground" }), tr("columnMenu.pinLeft", "Left")]
2849
2578
  }),
2850
2579
  pinned !== "right" && jsxs(DropdownMenuItem, {
2851
2580
  onClick: () => onPin("right"),
2852
- children: [jsx(IconPinned, { className: "mr-2 size-4 -scale-x-100 text-muted-foreground" }), tr("columnMenu.pinRight", "Справа")]
2581
+ children: [jsx(IconPinned, { className: "mr-2 size-4 -scale-x-100 text-muted-foreground" }), tr("columnMenu.pinRight", "Right")]
2853
2582
  }),
2854
2583
  pinned !== false && jsxs(DropdownMenuItem, {
2855
2584
  onClick: () => onPin(false),
2856
2585
  "data-testid": testid ? `${testid}:unpin` : void 0,
2857
- children: [jsx(IconPinnedOff, { className: "mr-2 size-4 text-muted-foreground" }), tr("columnMenu.unpin", "Открепить")]
2586
+ children: [jsx(IconPinnedOff, { className: "mr-2 size-4 text-muted-foreground" }), tr("columnMenu.unpin", "Unpin")]
2858
2587
  })
2859
2588
  ] })] }),
2860
2589
  canMove && jsxs(Fragment$1, { children: [jsx(DropdownMenuSeparator, {}), jsxs(DropdownMenuGroup, { children: [
2861
2590
  jsx(DropdownMenuLabel, {
2862
2591
  className: "text-xs font-medium text-muted-foreground",
2863
- children: tr("columnMenu.moveSection", "Перемещение")
2592
+ children: tr("columnMenu.moveSection", "Move")
2864
2593
  }),
2865
2594
  canMoveLeft && jsxs(DropdownMenuItem, {
2866
2595
  onClick: () => onMove?.("left"),
2867
2596
  "data-testid": testid ? `${testid}:move-left` : void 0,
2868
- children: [jsx(IconArrowLeft, { className: "mr-2 size-4 text-muted-foreground" }), tr("columnMenu.moveLeft", "Влево")]
2597
+ children: [jsx(IconArrowLeft, { className: "mr-2 size-4 text-muted-foreground" }), tr("columnMenu.moveLeft", "Left")]
2869
2598
  }),
2870
2599
  canMoveRight && jsxs(DropdownMenuItem, {
2871
2600
  onClick: () => onMove?.("right"),
2872
2601
  "data-testid": testid ? `${testid}:move-right` : void 0,
2873
- children: [jsx(IconArrowRight, { className: "mr-2 size-4 text-muted-foreground" }), tr("columnMenu.moveRight", "Вправо")]
2602
+ children: [jsx(IconArrowRight, { className: "mr-2 size-4 text-muted-foreground" }), tr("columnMenu.moveRight", "Right")]
2874
2603
  })
2875
2604
  ] })] }),
2876
2605
  canHide && onHide && jsxs(Fragment$1, { children: [jsx(DropdownMenuSeparator, {}), jsxs(DropdownMenuItem, {
2877
2606
  onClick: onHide,
2878
2607
  "data-testid": testid ? `${testid}:hide` : void 0,
2879
- children: [jsx(IconEyeOff, { className: "mr-2 size-4 text-muted-foreground" }), tr("columnMenu.hide", "Скрыть колонку")]
2608
+ children: [jsx(IconEyeOff, { className: "mr-2 size-4 text-muted-foreground" }), tr("columnMenu.hide", "Hide column")]
2880
2609
  })] })
2881
2610
  ]
2882
2611
  })]
2883
2612
  });
2884
2613
  }
2885
2614
  //#endregion
2615
+ //#region src/resource/column-accessor.ts
2616
+ function columnAccessor(column) {
2617
+ return column.includes(".") ? { accessorFn: (row) => row[column] } : { accessorKey: column };
2618
+ }
2619
+ //#endregion
2620
+ //#region src/resource/column-target.ts
2621
+ function columnTarget(registry, resource, column) {
2622
+ if (!column.includes(".")) return void 0;
2623
+ const resolved = resolveColumnField(registry, resource, column);
2624
+ if (resolved === void 0) return void 0;
2625
+ const ref = registry.get(resolved.resource)?.ref ?? {
2626
+ namespace: "default",
2627
+ id: resolved.resource
2628
+ };
2629
+ return {
2630
+ field: resolved.field,
2631
+ name: column.slice(column.lastIndexOf(".") + 1),
2632
+ resource: resolved.resource,
2633
+ ref
2634
+ };
2635
+ }
2636
+ function capsOf(caps, resource) {
2637
+ return {
2638
+ has: (key, options) => caps.has(key, {
2639
+ ...options,
2640
+ resource: options?.resource ?? resource
2641
+ }),
2642
+ t: (key, options) => caps.t(key, {
2643
+ ...options,
2644
+ resource: options?.resource ?? resource
2645
+ })
2646
+ };
2647
+ }
2648
+ //#endregion
2886
2649
  //#region src/data-table/use-buro-data-table.ts
2887
2650
  function useBuroDataTable(opts) {
2888
2651
  const { data, columns, total, getRowId, pagination, onPaginationChange, sorting, onSortingChange, columnFilters, onColumnFiltersChange, columnVisibility, onColumnVisibilityChange, rowSelection, onRowSelectionChange, enableRowSelection, columnPinning, onColumnPinningChange, columnSizing, onColumnSizingChange, columnOrder, onColumnOrderChange, enableColumnResizing } = opts;
@@ -2957,64 +2720,247 @@ function resolveUpdater(updater, prev) {
2957
2720
  }
2958
2721
  //#endregion
2959
2722
  //#region src/resource/list-error-state.tsx
2960
- function ListErrorState({ error, onRetry, resource, title, className }) {
2961
- const t = useT();
2962
- const locale = useLocale();
2963
- const raw = error.message ?? "";
2964
- const friendly = toFriendlyError(raw, "", locale);
2965
- const friendlyError = friendly.replaced ? friendly.detail : raw;
2966
- return jsxs("div", {
2967
- role: "alert",
2968
- className: cn("flex flex-col items-center justify-center gap-3 px-6 py-16 rounded-lg border border-destructive/40 bg-destructive/5 text-center", className),
2969
- "data-testid": "error",
2970
- children: [
2971
- jsx("div", {
2972
- className: "rounded-full bg-destructive/10 p-3 text-destructive",
2973
- children: jsx(IconAlertCircle, {
2974
- className: "size-6",
2975
- "aria-hidden": "true"
2976
- })
2977
- }),
2978
- jsxs("div", {
2979
- className: "space-y-1",
2980
- children: [jsx("h3", {
2981
- className: "text-base font-semibold tracking-tight text-destructive",
2982
- children: title ?? t("list.errorTitle")
2983
- }), jsx("p", {
2984
- className: "max-w-md text-sm text-muted-foreground",
2985
- children: friendlyError || t("list.errorBody")
2986
- })]
2987
- }),
2988
- raw && raw !== friendlyError ? jsxs("details", {
2989
- className: "max-w-md text-left",
2990
- children: [jsx("summary", {
2991
- className: "cursor-pointer text-xs text-muted-foreground hover:text-foreground",
2992
- children: t("list.errorDetails")
2993
- }), jsx("p", {
2994
- className: "mt-1.5 break-all font-mono text-xs text-muted-foreground",
2995
- "data-testid": "list-error-detail",
2996
- children: raw
2997
- })]
2998
- }) : null,
2999
- onRetry && jsx("div", {
3000
- className: "flex justify-center pt-2",
3001
- children: jsxs(Button, {
3002
- type: "button",
3003
- variant: "outline",
3004
- size: "sm",
3005
- "data-testid": resource ? `list-error-retry:${resource}` : "list-error-retry",
3006
- onClick: onRetry,
3007
- className: "gap-1.5",
3008
- children: [jsx(IconRefresh, {
3009
- className: "size-3.5",
3010
- "aria-hidden": "true"
3011
- }), t("actions.retry")]
3012
- })
3013
- })
3014
- ]
2723
+ function ListErrorState(props) {
2724
+ const { error, onRetry, resource, title, className } = props;
2725
+ return jsx(LoadError, {
2726
+ surface: "list",
2727
+ error,
2728
+ ...onRetry === void 0 ? {} : { retry: onRetry },
2729
+ ...resource === void 0 ? {} : { resource },
2730
+ ...title === void 0 ? {} : { title },
2731
+ ...className === void 0 ? {} : { className }
3015
2732
  });
3016
2733
  }
3017
2734
  //#endregion
2735
+ //#region src/resource/use-table-layout.ts
2736
+ const WRITE_DEBOUNCE_MS = 200;
2737
+ function mutate(run, ok, then) {
2738
+ try {
2739
+ run().then((result) => {
2740
+ if (ok(result.status)) then();
2741
+ }, () => void 0);
2742
+ } catch {}
2743
+ }
2744
+ function resolve(updater, current) {
2745
+ return typeof updater === "function" ? updater(current) : updater;
2746
+ }
2747
+ function sameNames(a, b) {
2748
+ return a.length === b.length && a.every((name, index) => name === b[index]);
2749
+ }
2750
+ function readSizes(sizes, rule) {
2751
+ const out = {};
2752
+ for (const [column, size] of Object.entries(sizes)) {
2753
+ const of = rule(column);
2754
+ if (of === void 0 || !of.resize) continue;
2755
+ out[column] = Math.min(Math.max(size, of.min), of.max ?? Number.POSITIVE_INFINITY);
2756
+ }
2757
+ return out;
2758
+ }
2759
+ function useTableLayout({ resource, layoutId, rule, defaultPinning, resetLayout }) {
2760
+ const params = useMemo(() => layoutId === void 0 ? null : {
2761
+ resource,
2762
+ list: layoutId
2763
+ }, [resource, layoutId]);
2764
+ const settings = useOptionalUiSettings(tableLayoutSetting, params);
2765
+ const settingsRef = useRef(settings);
2766
+ settingsRef.current = settings;
2767
+ const key = layoutId === void 0 ? void 0 : `${resource}\u0000${layoutId}`;
2768
+ const fallback = useMemo(() => ({
2769
+ sizes: {},
2770
+ order: [],
2771
+ pinning: defaultPinning
2772
+ }), [defaultPinning]);
2773
+ const [local, setLocal] = useState();
2774
+ const stored = settings?.snapshot.entries[0]?.value;
2775
+ const settled = settings === void 0 || !settings.snapshot.pending;
2776
+ const held = useRef(null);
2777
+ const layout = (local !== void 0 && local.key === key ? local.value : void 0) ?? stored ?? fallback;
2778
+ const latest = useRef(layout);
2779
+ latest.current = layout;
2780
+ const sizing = useMemo(() => readSizes(layout.sizes, rule), [layout.sizes, rule]);
2781
+ const sizingRef = useRef(sizing);
2782
+ sizingRef.current = sizing;
2783
+ const generation = useRef(0);
2784
+ const timer = useRef(null);
2785
+ const pending = useRef(null);
2786
+ const confirm = useCallback((sent) => {
2787
+ if (generation.current === sent && pending.current === null) setLocal(void 0);
2788
+ }, []);
2789
+ const write = useCallback((target, value) => {
2790
+ const scopeId = target.snapshot.entries[0]?.scope.id ?? target.snapshot.writeScopes[0]?.id;
2791
+ if (scopeId === void 0) return;
2792
+ const sent = generation.current;
2793
+ mutate(() => target.set({
2794
+ scopeId,
2795
+ value
2796
+ }), (status) => status === "created" || status === "updated", () => confirm(sent));
2797
+ }, [confirm]);
2798
+ const store = useCallback((target, next) => {
2799
+ if (!(Object.keys(next.sizes).length === 0 && next.order.length === 0 && sameNames(next.pinning.left, defaultPinning.left) && sameNames(next.pinning.right, defaultPinning.right))) {
2800
+ write(target, next);
2801
+ return;
2802
+ }
2803
+ const entry = target.snapshot.entries[0];
2804
+ if (entry === void 0) {
2805
+ setLocal(void 0);
2806
+ return;
2807
+ }
2808
+ const sent = generation.current;
2809
+ mutate(() => target.remove({ id: entry.id }), (status) => status === "removed", () => confirm(sent));
2810
+ }, [
2811
+ defaultPinning,
2812
+ write,
2813
+ confirm
2814
+ ]);
2815
+ const flush = useCallback(() => {
2816
+ if (timer.current !== null) clearTimeout(timer.current);
2817
+ timer.current = null;
2818
+ const due = pending.current;
2819
+ pending.current = null;
2820
+ if (due !== null) write(due.target, due.value);
2821
+ }, [write]);
2822
+ const change = useCallback((next, debounced, touch) => {
2823
+ latest.current = next;
2824
+ generation.current += 1;
2825
+ setLocal({
2826
+ key,
2827
+ value: next
2828
+ });
2829
+ const target = settingsRef.current;
2830
+ if (target === void 0) return;
2831
+ if (target.snapshot.pending || held.current !== null && held.current.key === key) {
2832
+ const edits = held.current !== null && held.current.key === key ? held.current : {
2833
+ key,
2834
+ resetAll: false,
2835
+ resetSizes: false,
2836
+ sizes: new Map()
2837
+ };
2838
+ held.current = edits;
2839
+ touch(edits);
2840
+ return;
2841
+ }
2842
+ pending.current = {
2843
+ target,
2844
+ value: next
2845
+ };
2846
+ if (!debounced) {
2847
+ flush();
2848
+ return;
2849
+ }
2850
+ if (timer.current !== null) clearTimeout(timer.current);
2851
+ timer.current = setTimeout(flush, WRITE_DEBOUNCE_MS);
2852
+ }, [key, flush]);
2853
+ useEffect(() => flush, [key]);
2854
+ useEffect(() => {
2855
+ const edits = held.current;
2856
+ if (edits === null) return;
2857
+ if (edits.key !== key) {
2858
+ held.current = null;
2859
+ return;
2860
+ }
2861
+ const target = settingsRef.current;
2862
+ if (!settled || target === void 0) return;
2863
+ held.current = null;
2864
+ const base = edits.resetAll ? fallback : stored ?? fallback;
2865
+ const sizes = edits.resetAll || edits.resetSizes ? {} : { ...readSizes(base.sizes, rule) };
2866
+ for (const [column, size] of edits.sizes) if (size === void 0) delete sizes[column];
2867
+ else sizes[column] = size;
2868
+ const next = {
2869
+ sizes,
2870
+ order: edits.order ?? base.order,
2871
+ pinning: edits.pinning ?? base.pinning
2872
+ };
2873
+ latest.current = next;
2874
+ generation.current += 1;
2875
+ setLocal({
2876
+ key,
2877
+ value: next
2878
+ });
2879
+ store(target, next);
2880
+ }, [settled, key]);
2881
+ const resetToken = resetLayout?.token ?? 0;
2882
+ const seenToken = useRef(resetToken);
2883
+ useEffect(() => {
2884
+ if (resetToken === seenToken.current) return;
2885
+ seenToken.current = resetToken;
2886
+ const all = resetLayout?.mode === "all";
2887
+ const next = all ? fallback : {
2888
+ ...latest.current,
2889
+ sizes: {}
2890
+ };
2891
+ if (timer.current !== null) clearTimeout(timer.current);
2892
+ timer.current = null;
2893
+ pending.current = null;
2894
+ latest.current = next;
2895
+ generation.current += 1;
2896
+ setLocal({
2897
+ key,
2898
+ value: next
2899
+ });
2900
+ const target = settingsRef.current;
2901
+ if (target === void 0) return;
2902
+ if (target.snapshot.pending || held.current !== null && held.current.key === key) {
2903
+ const prior = held.current !== null && held.current.key === key ? held.current : void 0;
2904
+ held.current = all ? {
2905
+ key,
2906
+ resetAll: true,
2907
+ resetSizes: false,
2908
+ sizes: new Map()
2909
+ } : {
2910
+ key,
2911
+ resetAll: prior?.resetAll ?? false,
2912
+ resetSizes: true,
2913
+ sizes: new Map(),
2914
+ ...prior?.order === void 0 ? {} : { order: prior.order },
2915
+ ...prior?.pinning === void 0 ? {} : { pinning: prior.pinning }
2916
+ };
2917
+ return;
2918
+ }
2919
+ store(target, next);
2920
+ }, [resetToken]);
2921
+ const setColumnSizing = useCallback((updater) => {
2922
+ const before = sizingRef.current;
2923
+ const sizes = resolve(updater, before);
2924
+ sizingRef.current = sizes;
2925
+ change({
2926
+ ...latest.current,
2927
+ sizes
2928
+ }, true, (edits) => {
2929
+ for (const column of new Set([...Object.keys(before), ...Object.keys(sizes)])) if (before[column] !== sizes[column]) edits.sizes.set(column, sizes[column]);
2930
+ });
2931
+ }, [change]);
2932
+ const setColumnOrder = useCallback((updater) => {
2933
+ const order = resolve(updater, latest.current.order);
2934
+ change({
2935
+ ...latest.current,
2936
+ order
2937
+ }, false, (edits) => {
2938
+ edits.order = order;
2939
+ });
2940
+ }, [change]);
2941
+ const setColumnPinning = useCallback((updater) => {
2942
+ const resolved = resolve(updater, latest.current.pinning);
2943
+ const pinning = {
2944
+ left: resolved.left ?? [],
2945
+ right: resolved.right ?? []
2946
+ };
2947
+ change({
2948
+ ...latest.current,
2949
+ pinning
2950
+ }, false, (edits) => {
2951
+ edits.pinning = pinning;
2952
+ });
2953
+ }, [change]);
2954
+ return {
2955
+ columnSizing: sizing,
2956
+ setColumnSizing,
2957
+ columnOrder: layout.order,
2958
+ setColumnOrder,
2959
+ columnPinning: layout.pinning,
2960
+ setColumnPinning
2961
+ };
2962
+ }
2963
+ //#endregion
3018
2964
  //#region src/list-kit/cursor-load-more.tsx
3019
2965
  function CursorLoadMore({ hasMore, loading, onMore }) {
3020
2966
  const t = useT();
@@ -3056,6 +3002,10 @@ function CountRowsTrigger({ label, loading, onClick, testid, variant, className
3056
3002
  }
3057
3003
  const SELECT_COL_ID = "__select__";
3058
3004
  const ACTIONS_COL_ID = "__actions__";
3005
+ const DEFAULT_PINNING = Object.freeze({
3006
+ left: Object.freeze([SELECT_COL_ID, "id"]),
3007
+ right: Object.freeze([ACTIONS_COL_ID])
3008
+ });
3059
3009
  const ROW_ACTION_CONTROL_PX = 28;
3060
3010
  const ROW_ACTION_GAP_PX = 4;
3061
3011
  const ACTIONS_CELL_PADDING_PX = 16;
@@ -3067,29 +3017,30 @@ function actionsColumnSize(items) {
3067
3017
  const strip = controls * ROW_ACTION_CONTROL_PX + (controls - 1) * ROW_ACTION_GAP_PX + ACTIONS_CELL_PADDING_PX;
3068
3018
  return Math.max(ACTIONS_COL_MIN_PX, strip);
3069
3019
  }
3070
- const COLUMN_SIZE_BY_TYPE = {
3071
- boolean: 96,
3072
- number: 112
3020
+ const ALIGN_CLASS = {
3021
+ start: "text-start",
3022
+ center: "text-center",
3023
+ end: "text-end"
3073
3024
  };
3074
- const COLUMN_SIZE_BY_FORMAT = {
3075
- date: 132,
3076
- "date-time": 132
3077
- };
3078
- function columnSize(field) {
3079
- if (field?.format !== void 0 && COLUMN_SIZE_BY_FORMAT[field.format] !== void 0) return COLUMN_SIZE_BY_FORMAT[field.format];
3080
- if (field?.type !== void 0 && COLUMN_SIZE_BY_TYPE[field.type] !== void 0) return COLUMN_SIZE_BY_TYPE[field.type];
3081
- if (field?.kind === "date" || field?.kind === "dateTime") return COLUMN_SIZE_BY_FORMAT[field.kind === "dateTime" ? "date-time" : "date"];
3082
- return field?.kind === void 0 ? void 0 : COLUMN_SIZE_BY_TYPE[field.kind];
3083
- }
3084
- function ResourceList({ resource, data, total, page = 1, pageSize = 10, onPageSize, onRequestCount, isCountingRows = false, pageSizeOptions = [
3025
+ const STANDALONE_COLUMN_UI = Object.freeze({
3026
+ default: 160,
3027
+ min: 90,
3028
+ align: "start",
3029
+ resize: true,
3030
+ fill: false,
3031
+ display: Object.freeze({ params: Object.freeze({}) })
3032
+ });
3033
+ function ResourceList({ resource: resourceInput, data, total, page = 1, pageSize = 10, onPageSize, onRequestCount, isCountingRows = false, pageSizeOptions = [
3085
3034
  10,
3086
3035
  25,
3087
3036
  50,
3088
3037
  100
3089
- ], onPage, loadMore, selectable = false, selectedIds, onSelectionChange, className, isLoading = false, error = null, onRetry, createHref, isFiltered = false, onClearFilter, emptyState, rowsView, rowHref, rowQuick, rowLinkKey, renderCell, sort, onSort, onHideColumn, requiredColumns, resetLayout, columns: columnsOverride, tableRef }) {
3038
+ ], onPage, loadMore, selectable = false, rowActions = true, selectedIds, onSelectionChange, className, isLoading = false, error = null, onRetry, createHref, isFiltered = false, onClearFilter, emptyState, rowsView, rowHref, rowQuick, rowLinkKey, renderCell, sort, onSort, sortable, onHideColumn, requiredColumns, resetLayout, columns: columnsOverride, columnUi, layoutId, tableRef }) {
3039
+ const resource = routeResourceKey(resourceInput);
3090
3040
  const app = useApp();
3091
3041
  const tid = useTestIds();
3092
3042
  const t = useOptionalT();
3043
+ const chromeT = useChromeT();
3093
3044
  const locale = useLocale();
3094
3045
  const nf = useMemo(() => new Intl.NumberFormat(locale), [locale]);
3095
3046
  const i18nCaps = useI18nCaps(resource);
@@ -3111,38 +3062,25 @@ function ResourceList({ resource, data, total, page = 1, pageSize = 10, onPageSi
3111
3062
  useEffect(() => {
3112
3063
  emittedRef.current = null;
3113
3064
  }, [controlledSelection]);
3114
- const layoutKey = `buro:table-layout:${resource}`;
3115
- const [columnSizing, setColumnSizing] = usePersistedState(`${layoutKey}:sizing`, {}, 200);
3116
- const [columnOrder, setColumnOrder] = usePersistedState(`${layoutKey}:order`, []);
3117
- const DEFAULT_PINNING = {
3118
- left: [SELECT_COL_ID, "id"],
3119
- right: [ACTIONS_COL_ID]
3120
- };
3121
- const [columnPinning, setColumnPinning] = usePersistedState(`${layoutKey}:pinning`, DEFAULT_PINNING);
3122
- const resetToken = resetLayout?.token ?? 0;
3123
- useEffect(() => {
3124
- if (!resetToken) return;
3125
- setColumnSizing({});
3126
- if (resetLayout?.mode === "all") {
3127
- setColumnOrder([]);
3128
- setColumnPinning(DEFAULT_PINNING);
3129
- }
3130
- }, [resetToken]);
3131
3065
  useEffect(() => {
3132
3066
  if (controlled) return;
3133
3067
  setOwnSelection({});
3134
3068
  }, [data]);
3135
3069
  const listAccess = useListAccess(resource);
3136
- const accessPending = listAccess.status === "pending";
3070
+ const pathAccess = usePathAccess(resource);
3071
+ const accessPending = listAccess.status === "pending" || pathAccess.pending;
3137
3072
  const declaredColumnKeys = useMemo(() => {
3138
3073
  if (!def) return [];
3139
- return [...columnsOverride ?? def.views?.list?.columns ?? Object.keys(def.fields)];
3074
+ return [...columnsOverride ?? def.views?.list?.columns?.map(columnName) ?? Object.keys(def.fields)];
3140
3075
  }, [def, columnsOverride]);
3141
- const visibleColumnKeys = useMemo(() => accessPending ? [] : declaredColumnKeys.filter((col) => listAccess.columnVisible(col)), [
3076
+ const visibleColumnKeys = useMemo(() => accessPending ? [] : declaredColumnKeys.filter((col) => listAccess.columnVisible(col) && (columnTarget(app.registry, resource, col) === void 0 || listAccess.columnVisible(col.split(".")[0]) && pathAccess.visible(col))), [
3142
3077
  accessPending,
3143
3078
  declaredColumnKeys,
3144
- listAccess
3145
- ]);
3079
+ listAccess,
3080
+ pathAccess,
3081
+ app,
3082
+ resource
3083
+ ]);
3146
3084
  const skeletonColumnKeys = accessPending ? declaredColumnKeys : visibleColumnKeys;
3147
3085
  const rows = useMemo(() => data ?? [], [data]);
3148
3086
  const rowFlows = useMemo(() => {
@@ -3183,10 +3121,11 @@ function ResourceList({ resource, data, total, page = 1, pageSize = 10, onPageSi
3183
3121
  const resourceLabel = resolveResourceLabel(i18nCaps, resource, "plural", def);
3184
3122
  const recordSingular = resolveResourceLabel(i18nCaps, resource, "single", def);
3185
3123
  const ariaLabel = resourceLabel;
3186
- const actionsHeader = t("actions.column", { defaultValue: "Actions" });
3124
+ const actionsHeader = chromeT("actions.column", { defaultValue: "Actions" });
3187
3125
  const viewRow = useViewRow(resource);
3188
3126
  const volatileRef = useRef({
3189
3127
  onSort,
3128
+ sortable,
3190
3129
  onHideColumn,
3191
3130
  requiredColumns,
3192
3131
  renderCell,
@@ -3198,6 +3137,7 @@ function ResourceList({ resource, data, total, page = 1, pageSize = 10, onPageSi
3198
3137
  });
3199
3138
  volatileRef.current = {
3200
3139
  onSort,
3140
+ sortable,
3201
3141
  onHideColumn,
3202
3142
  requiredColumns,
3203
3143
  renderCell,
@@ -3221,6 +3161,45 @@ function ResourceList({ resource, data, total, page = 1, pageSize = 10, onPageSi
3221
3161
  }, [rowLinkKey]);
3222
3162
  const stableRowClassName = useCallback((row) => volatileRef.current.rowHref?.(volatileRef.current.viewRow(row.original)) || (volatileRef.current.rowQuick?.(row.original) ?? null) !== null ? "cursor-pointer" : void 0, [rowLinkKey]);
3223
3163
  const [listRoot, narrow] = useContainerNarrow();
3164
+ const columnUiOf = useMemo(() => {
3165
+ const entries = new Map();
3166
+ for (const entry of [...def?.views?.list?.columns ?? [], ...def?.views?.list?.optionalColumns ?? []]) {
3167
+ const name = columnName(entry);
3168
+ if (!entries.has(name)) entries.set(name, entry);
3169
+ }
3170
+ return (col) => columnUi?.[col] ?? (def === void 0 ? STANDALONE_COLUMN_UI : resolveColumnUi(def, entries.get(col) ?? col, app.registry, columnTypeWidth));
3171
+ }, [
3172
+ app,
3173
+ def,
3174
+ columnUi
3175
+ ]);
3176
+ const { columnSizing, setColumnSizing, columnOrder, setColumnOrder, columnPinning, setColumnPinning } = useTableLayout({
3177
+ resource,
3178
+ layoutId,
3179
+ rule: useMemo(() => {
3180
+ const known = new Set([
3181
+ ...declaredColumnKeys,
3182
+ ...(def?.views?.list?.columns ?? []).map(columnName),
3183
+ ...(def?.views?.list?.optionalColumns ?? []).map(columnName),
3184
+ ...Object.keys(def?.fields ?? {})
3185
+ ]);
3186
+ return (col) => {
3187
+ if (!known.has(col)) return void 0;
3188
+ const ui = columnUiOf(col);
3189
+ return {
3190
+ min: ui.min,
3191
+ max: ui.max,
3192
+ resize: ui.resize
3193
+ };
3194
+ };
3195
+ }, [
3196
+ declaredColumnKeys,
3197
+ def,
3198
+ columnUiOf
3199
+ ]),
3200
+ defaultPinning: DEFAULT_PINNING,
3201
+ resetLayout
3202
+ });
3224
3203
  const tableColumns = useMemo(() => {
3225
3204
  const cols = [];
3226
3205
  if (selectable) cols.push({
@@ -3242,16 +3221,33 @@ function ResourceList({ resource, data, total, page = 1, pageSize = 10, onPageSi
3242
3221
  })
3243
3222
  });
3244
3223
  for (const col of visibleColumnKeys) {
3245
- const field = def?.fields?.[col];
3246
- const headerLabel = resolveFieldLabel(volatileRef.current.i18nCaps, col, field?.label);
3247
- const kindSize = columnSize(field);
3224
+ const target = columnTarget(app.registry, resource, col);
3225
+ const field = target !== void 0 ? target.field : def === void 0 ? void 0 : declaredField(def, col);
3226
+ const declaredLabel = declaredText("field", field, "label");
3227
+ const headerLabel = target === void 0 ? resolveFieldLabel(volatileRef.current.i18nCaps, col, declaredLabel) : resolveFieldLabel(capsOf(volatileRef.current.i18nCaps, target.resource), target.name, declaredLabel);
3228
+ const ui = columnUiOf(col);
3229
+ const frameField = {
3230
+ ...field ?? { type: "unknown" },
3231
+ name: target === void 0 ? col : target.name
3232
+ };
3233
+ const cellEntry = { display: ui.display };
3234
+ const wraps = ui.display.overflow === "wrap";
3235
+ const columnSortable = volatileRef.current.sortable?.(col) ?? (def !== void 0 && isColumnSortable(def, col, app, app.registry));
3248
3236
  cols.push({
3249
3237
  id: col,
3250
- accessorKey: col,
3251
- ...kindSize !== void 0 ? { size: kindSize } : {},
3252
- enableSorting: Boolean(volatileRef.current.onSort),
3238
+ ...columnAccessor(col),
3239
+ size: ui.default,
3240
+ minSize: ui.min,
3241
+ maxSize: ui.max ?? Number.MAX_SAFE_INTEGER,
3242
+ enableResizing: ui.resize,
3243
+ enableSorting: columnSortable && Boolean(volatileRef.current.onSort),
3253
3244
  enableHiding: true,
3254
- meta: { testid: col },
3245
+ meta: {
3246
+ testid: col,
3247
+ className: ALIGN_CLASS[ui.align],
3248
+ ...wraps ? { wrap: true } : {},
3249
+ ...ui.fill ? { fill: true } : {}
3250
+ },
3255
3251
  header: ({ column, table }) => {
3256
3252
  const v = volatileRef.current;
3257
3253
  const pinSide = column.getIsPinned();
@@ -3282,9 +3278,9 @@ function ResourceList({ resource, data, total, page = 1, pageSize = 10, onPageSi
3282
3278
  };
3283
3279
  return jsx(ColumnHeaderMenu, {
3284
3280
  label: headerLabel,
3285
- sortable: Boolean(v.onSort),
3281
+ sortable: columnSortable && Boolean(v.onSort),
3286
3282
  sortDirection: v.sort?.[0]?.field === col ? v.sort[0].direction : null,
3287
- onSort: v.onSort ? (dir) => v.onSort?.(col, dir) : void 0,
3283
+ onSort: columnSortable && v.onSort ? (dir) => v.onSort?.(col, dir) : void 0,
3288
3284
  pinned: column.getIsPinned(),
3289
3285
  onPin: (side) => column.pin(side),
3290
3286
  canHide: Boolean(v.onHideColumn) && !v.requiredColumns?.includes(col),
@@ -3301,17 +3297,26 @@ function ResourceList({ resource, data, total, page = 1, pageSize = 10, onPageSi
3301
3297
  const value = record[col];
3302
3298
  const customCell = volatileRef.current.renderCell?.(col, value, record);
3303
3299
  const cellInner = customCell !== void 0 ? customCell : jsx(ResolvedFieldDisplay, {
3304
- resource: resourceRef,
3305
- name: col,
3300
+ resource: target === void 0 ? resourceRef : target.ref,
3301
+ name: target === void 0 ? col : target.name,
3306
3302
  field: field ?? { type: "unknown" },
3307
3303
  value,
3308
3304
  record,
3305
+ uiSurface: "column",
3306
+ columnEntry: cellEntry,
3309
3307
  fallback: jsx("span", { children: String(value ?? "") })
3310
3308
  });
3309
+ const framed = (node) => customCell !== void 0 ? node : jsx(FieldValueFrame, {
3310
+ field: frameField,
3311
+ value,
3312
+ surface: "column",
3313
+ entry: cellEntry,
3314
+ children: node
3315
+ });
3311
3316
  const href = volatileRef.current.rowHref?.(record);
3312
3317
  const isFirstContentCell = visibleColumnKeys.indexOf(col) === 0;
3313
3318
  const quick = isFirstContentCell ? volatileRef.current.rowQuick?.(raw) ?? null : null;
3314
- if (quick !== null) return href ? jsx(ClaimedQuickLink, {
3319
+ if (quick !== null) return framed(href ? jsx(ClaimedQuickLink, {
3315
3320
  href,
3316
3321
  className: relationLinkClassName,
3317
3322
  onOpen: quick,
@@ -3325,21 +3330,22 @@ function ResourceList({ resource, data, total, page = 1, pageSize = 10, onPageSi
3325
3330
  quick();
3326
3331
  },
3327
3332
  children: cellInner
3328
- });
3329
- return isFirstContentCell && href ? jsx(RouterLink, {
3333
+ }));
3334
+ return framed(isFirstContentCell && href ? jsx(RouterLink, {
3330
3335
  href,
3331
3336
  className: relationLinkClassName,
3332
3337
  onClick: (e) => e.stopPropagation(),
3333
3338
  children: cellInner
3334
- }) : cellInner;
3339
+ }) : cellInner);
3335
3340
  }
3336
3341
  });
3337
3342
  }
3343
+ if (!rowActions) return cols;
3338
3344
  cols.push({
3339
3345
  id: ACTIONS_COL_ID,
3340
3346
  enableSorting: false,
3341
3347
  enableResizing: false,
3342
- size: narrow ? 44 : actionsColumnSize(def?.actions?.item),
3348
+ size: narrow || typeof rowActions === "function" ? 44 : actionsColumnSize(def?.actions?.item),
3343
3349
  enableHiding: false,
3344
3350
  header: () => jsx("span", {
3345
3351
  className: "sr-only",
@@ -3350,7 +3356,7 @@ function ResourceList({ resource, data, total, page = 1, pageSize = 10, onPageSi
3350
3356
  noRowNav: true,
3351
3357
  noClip: true
3352
3358
  },
3353
- cell: ({ row }) => jsx(ActionOverflow, {
3359
+ cell: ({ row }) => typeof rowActions === "function" ? rowActions(row.original) : jsx(ActionOverflow, {
3354
3360
  resource,
3355
3361
  record: row.original,
3356
3362
  surface: "row"
@@ -3358,7 +3364,9 @@ function ResourceList({ resource, data, total, page = 1, pageSize = 10, onPageSi
3358
3364
  });
3359
3365
  return cols;
3360
3366
  }, [
3367
+ app,
3361
3368
  selectable,
3369
+ rowActions,
3362
3370
  resourceRef,
3363
3371
  visibleColumnKeys,
3364
3372
  def,
@@ -3368,7 +3376,8 @@ function ResourceList({ resource, data, total, page = 1, pageSize = 10, onPageSi
3368
3376
  actionsHeader,
3369
3377
  i18nCaps,
3370
3378
  rowLinkKey,
3371
- narrow
3379
+ narrow,
3380
+ columnUiOf
3372
3381
  ]);
3373
3382
  const handleSelectionChange = (updater) => {
3374
3383
  if (controlled) {
@@ -3471,8 +3480,23 @@ function ResourceList({ resource, data, total, page = 1, pageSize = 10, onPageSi
3471
3480
  const isWrongPage = page > 1;
3472
3481
  const EmptyIcon = isFiltered ? IconFilterX : IconInbox;
3473
3482
  const items = resourceLabel.toLowerCase();
3474
- const heading = isWrongPage ? t("pagination.emptyPage") : isFiltered ? t("list.emptyFilteredTitle", { params: { items } }) : t("list.emptyTitle", { params: { items } });
3475
- const body = isWrongPage ? null : isFiltered ? t("list.emptyFilteredBody", { params: { items } }) : createHref ? t("list.emptyBody", { params: { item: recordSingular.toLowerCase() } }) : t("list.emptyBodyReadOnly", { params: { items } });
3483
+ const heading = isWrongPage ? t("pagination.emptyPage") : isFiltered ? t("list.emptyFilteredTitle", {
3484
+ resource,
3485
+ params: { items }
3486
+ }) : t("list.emptyTitle", {
3487
+ resource,
3488
+ params: { items }
3489
+ });
3490
+ const body = isWrongPage ? null : isFiltered ? t("list.emptyFilteredBody", {
3491
+ resource,
3492
+ params: { items }
3493
+ }) : createHref ? t("list.emptyBody", {
3494
+ resource,
3495
+ params: { item: recordSingular.toLowerCase() }
3496
+ }) : t("list.emptyBodyReadOnly", {
3497
+ resource,
3498
+ params: { items }
3499
+ });
3476
3500
  return jsxs(Fragment$1, { children: [jsxs("div", {
3477
3501
  className: cn("flex flex-col items-center justify-center gap-3 px-6 py-16 rounded-lg border border-dashed text-center", className),
3478
3502
  "data-testid": tid.listEmpty(resource),
@@ -3497,7 +3521,7 @@ function ResourceList({ resource, data, total, page = 1, pageSize = 10, onPageSi
3497
3521
  jsxs("div", {
3498
3522
  className: "flex justify-center gap-2 pt-2",
3499
3523
  children: [
3500
- isFiltered && onClearFilter && jsx(Button, {
3524
+ onClearFilter && jsx(Button, {
3501
3525
  type: "button",
3502
3526
  variant: "outline",
3503
3527
  size: "sm",
@@ -3685,43 +3709,46 @@ function ResourceList({ resource, data, total, page = 1, pageSize = 10, onPageSi
3685
3709
  })
3686
3710
  });
3687
3711
  }
3688
- function usePersistedState(key, initial, debounceMs = 0) {
3689
- const [state, setState] = useState(() => {
3690
- if (typeof window === "undefined") return initial;
3691
- try {
3692
- const raw = window.localStorage.getItem(key);
3693
- return raw ? JSON.parse(raw) : initial;
3694
- } catch {
3695
- return initial;
3696
- }
3712
+ //#endregion
3713
+ //#region src/blocks/related-list-unbound.tsx
3714
+ function UnboundRelatedList(props) {
3715
+ const t = useOptionalT();
3716
+ return jsxs("div", {
3717
+ className: "flex flex-col gap-2",
3718
+ "data-testid": `list-unbound:${props.resource}`,
3719
+ children: [props.title === void 0 ? null : jsx("h3", {
3720
+ className: "text-sm font-semibold",
3721
+ children: props.title
3722
+ }), jsx("p", {
3723
+ className: "text-sm text-muted-foreground",
3724
+ children: props.empty ?? t("table.empty", { defaultValue: "No records" })
3725
+ })]
3697
3726
  });
3698
- useEffect(() => {
3699
- try {
3700
- if (window.localStorage.getItem(key) == null) window.localStorage.setItem(key, JSON.stringify(initial));
3701
- } catch {}
3702
- }, [key]);
3703
- const writeTimer = useRef(null);
3704
- const write = (value) => {
3705
- const doWrite = () => {
3706
- try {
3707
- window.localStorage.setItem(key, JSON.stringify(value));
3708
- } catch {}
3709
- };
3710
- if (debounceMs <= 0) {
3711
- doWrite();
3712
- return;
3713
- }
3714
- if (writeTimer.current) clearTimeout(writeTimer.current);
3715
- writeTimer.current = setTimeout(doWrite, debounceMs);
3716
- };
3717
- const set = (updater) => {
3718
- setState((prev) => {
3719
- const next = typeof updater === "function" ? updater(prev) : updater;
3720
- write(next);
3721
- return next;
3722
- });
3723
- };
3724
- return [state, set];
3727
+ }
3728
+ function placementScope(scope, owner) {
3729
+ return scope ?? (owner === void 0 ? "session" : "none");
3730
+ }
3731
+ //#endregion
3732
+ //#region src/list-kit/pieces/bulk-reach.tsx
3733
+ function useBulkFlowReachable(resource) {
3734
+ const app = useApp();
3735
+ const control = useFlowRowResolver(resource);
3736
+ return (app.registry.get(resource)?.actions.bulk ?? []).some((action) => isFlowAction(action) && control(action.name, void 0).state !== "absent");
3737
+ }
3738
+ function ExportReach(props) {
3739
+ const { available } = useResourceExport(props.resource);
3740
+ const capability = useCapability("run", {
3741
+ resource: props.resource,
3742
+ target: {
3743
+ kind: "action",
3744
+ name: "export"
3745
+ },
3746
+ mode: "list"
3747
+ });
3748
+ return props.children(available && capability.allowed);
3749
+ }
3750
+ function BulkExportReach(props) {
3751
+ return useOptionalDataRuntime() !== null ? jsx(ExportReach, { ...props }) : props.children(false);
3725
3752
  }
3726
3753
  //#endregion
3727
3754
  //#region src/list-kit/pieces/piece.tsx
@@ -3789,15 +3816,47 @@ function ForceTableView(props) {
3789
3816
  }
3790
3817
  //#endregion
3791
3818
  //#region src/list-kit/pieces/list-body.tsx
3819
+ function LinkDetachRowButton(props) {
3820
+ const { resource, linkEditing } = useListSurface();
3821
+ const t = useOptionalT();
3822
+ const [busy, setBusy] = useState(false);
3823
+ const detach = linkEditing?.detach;
3824
+ if (detach === void 0) return null;
3825
+ const label = t("field.link.detach", { defaultValue: "Detach" });
3826
+ return jsx(Button, {
3827
+ type: "button",
3828
+ size: "icon",
3829
+ variant: "ghost",
3830
+ className: "size-7",
3831
+ "aria-label": label,
3832
+ title: label,
3833
+ disabled: busy,
3834
+ "data-testid": `list-link-detach:${resource}:${props.row.id}`,
3835
+ onClick: (event) => {
3836
+ event.stopPropagation();
3837
+ setBusy(true);
3838
+ detach.onDetach([props.row]).finally(() => setBusy(false));
3839
+ },
3840
+ children: jsx(IconUnlink, {
3841
+ className: "size-4",
3842
+ "aria-hidden": "true"
3843
+ })
3844
+ });
3845
+ }
3846
+ const linkDetachRow = (row) => jsx(LinkDetachRowButton, { row });
3792
3847
  function ListBodyView() {
3793
3848
  usePieceMount("body");
3794
3849
  const list = useResourceListContext();
3795
- const { resource, route, tableView, createAvailable, createPage, rowProps, rowsViewFor, emptyState, renderCell } = useListSurface();
3850
+ const { resource, route, tableView, createAvailable, createPage, rowProps, rowsViewFor, emptyState, renderCell, readOnly, linkEditing, layoutId } = useListSurface();
3851
+ const detachable = readOnly && linkEditing?.detach !== void 0;
3852
+ const flowReachable = useBulkFlowReachable(resource);
3853
+ const selectable = (exportReachable) => tableView && (detachable || !readOnly && (flowReachable || exportReachable));
3796
3854
  const item = useContext(ListItemContext);
3797
3855
  const rowsView = useMemo(() => rowsViewFor?.(item), [rowsViewFor, item]);
3798
3856
  const { query, pagination, cursor, count, columns, selection, filters, sorting, routing } = list;
3799
3857
  const rows = policyRowsOf(list, list.rows);
3800
- return jsx(ResourceList, {
3858
+ if (query.unbound) return jsx(UnboundRelatedList, { resource });
3859
+ const view = (exportReachable) => jsx(ResourceList, {
3801
3860
  resource,
3802
3861
  data: rows,
3803
3862
  total: pagination.total,
@@ -3814,28 +3873,50 @@ function ListBodyView() {
3814
3873
  onRequestCount: count.available ? count.request : void 0,
3815
3874
  isCountingRows: count.isLoading,
3816
3875
  columns: columns.visible,
3876
+ columnUi: columns.ui,
3817
3877
  isLoading: query.isLoading,
3818
3878
  error: query.error,
3819
3879
  onRetry: query.refetch,
3820
3880
  createHref: route && createAvailable && createPage ? routing.createHref : void 0,
3821
3881
  ...rowProps,
3822
3882
  ...rowsView === void 0 ? {} : { rowsView },
3823
- selectable: tableView,
3883
+ selectable: selectable(exportReachable),
3884
+ rowActions: detachable ? linkDetachRow : !readOnly,
3824
3885
  selectedIds: selection.ids,
3825
3886
  onSelectionChange: selection.set,
3826
3887
  isFiltered: filters.isFiltered,
3827
- onClearFilter: filters.clearAll,
3888
+ onClearFilter: filters.canReset ? filters.reset : void 0,
3828
3889
  emptyState,
3829
3890
  sort: sorting.value ?? [],
3830
3891
  requiredColumns: columns.required,
3831
3892
  resetLayout: columns.resetLayout,
3893
+ layoutId,
3832
3894
  onHideColumn: columns.hide,
3833
3895
  onSort: sorting.toggle,
3896
+ sortable: sorting.sortable,
3834
3897
  renderCell
3835
3898
  });
3899
+ return jsx(BulkExportReach, {
3900
+ resource,
3901
+ children: view
3902
+ });
3836
3903
  }
3837
3904
  const ListBody = listPiece("ListBody", ListBodyView);
3838
3905
  //#endregion
3906
+ //#region src/resource/form-context.tsx
3907
+ const FormControlContext = createContext(null);
3908
+ const FormControlProvider = FormControlContext.Provider;
3909
+ function useFormControlOf(controller) {
3910
+ const latest = useRef(controller);
3911
+ latest.current = controller;
3912
+ return useMemo(() => ({ setValues: (patch) => {
3913
+ for (const [name, value] of Object.entries(patch)) latest.current.setValue(name, value);
3914
+ } }), []);
3915
+ }
3916
+ function useFormControl() {
3917
+ return useContext(FormControlContext);
3918
+ }
3919
+ //#endregion
3839
3920
  //#region src/record/route-screen-context.ts
3840
3921
  const RouteScreenClaimContext = createContext(false);
3841
3922
  const RouteScreenContext = createContext(false);
@@ -3931,7 +4012,10 @@ function PanelHost({ node, region = "panel-body", renderChildren }) {
3931
4012
  const state = useScreenState();
3932
4013
  const text = useNodeText();
3933
4014
  const options = compiled.nodes[node.index].options;
3934
- const title = text(options.title);
4015
+ const title = options.title?.humanized === true ? text({
4016
+ ...options.title,
4017
+ default: ""
4018
+ }) || void 0 : text(options.title);
3935
4019
  const description = text(options.description);
3936
4020
  const collapsible = options.collapse !== void 0;
3937
4021
  const open = !collapsible || node.open !== false;
@@ -3956,7 +4040,7 @@ function PanelHost({ node, region = "panel-body", renderChildren }) {
3956
4040
  region,
3957
4041
  form,
3958
4042
  labelledBy: title === void 0 ? void 0 : titleId,
3959
- children: renderChildren(node.children)
4043
+ children: renderChildren(node.children, node)
3960
4044
  }) : null,
3961
4045
  ...title === void 0 ? {} : { title },
3962
4046
  ...description === void 0 ? {} : { description },
@@ -3991,22 +4075,24 @@ function TabsHost({ node, renderChildren }) {
3991
4075
  const state = useScreenState();
3992
4076
  const text = useNodeText();
3993
4077
  const panels = node.children.filter((child) => child.state !== "absent");
3994
- const open = panels.find((child) => child.open === true) ?? panels[0];
4078
+ const declared = compiled.nodes[node.index].options.default;
4079
+ const effectiveDefault = (declared === void 0 ? void 0 : panels.find((child) => child.key === declared)) ?? panels[0];
4080
+ const open = panels.find((child) => child.open === true) ?? effectiveDefault;
3995
4081
  const urlSelect = useTabsUrlSelect();
3996
4082
  const select = (panel, reveal) => {
3997
- if (urlSelect !== null && node.key !== void 0 && panel.key !== void 0) urlSelect(node.key, panel.key, panel === panels[0], reveal);
4083
+ if (urlSelect !== null && node.key !== void 0 && panel.key !== void 0) urlSelect(node.key, panel.key, panel === effectiveDefault, reveal);
3998
4084
  else state?.openPanel(node.id, panelValue(panel));
3999
4085
  };
4000
4086
  const onSelect = (key) => {
4001
4087
  const panel = panels.find((candidate) => panelValue(candidate) === key);
4002
4088
  if (panel !== void 0) select(panel, false);
4003
4089
  };
4004
- const titleOf = (panel) => text(compiled.nodes[panel.index].options.title) ?? panel.key ?? panel.id;
4090
+ const titleOf = (panel) => text(compiled.nodes[panel.index].options.title) ?? panel.id;
4005
4091
  const tabs = panels.map((panel) => ({
4006
4092
  key: panelValue(panel),
4007
4093
  title: titleOf(panel),
4008
4094
  errorCount: panel.key === void 0 ? 0 : controller.errorsIn(panel.key),
4009
- panel: panels.length === 1 || panel.open === true ? jsx(Fragment$1, { children: renderChildren(panel.children) }) : null
4095
+ panel: panels.length === 1 || panel.open === true ? jsx(Fragment$1, { children: renderChildren(panel.children, panel) }) : null
4010
4096
  }));
4011
4097
  const { view: View, id } = useNodeView(node, "tabs");
4012
4098
  const t = useFrameT(id);
@@ -4044,7 +4130,7 @@ function StackedStepsHost({ node, renderChildren }) {
4044
4130
  const t = useFrameT(id);
4045
4131
  const steps = node.children.filter((child) => child.state !== "absent").map((panel) => {
4046
4132
  const options = compiled.nodes[panel.index].options;
4047
- const title = text(options.title) ?? panel.key ?? panel.id;
4133
+ const title = text(options.title) ?? panel.id;
4048
4134
  const description = text(options.description);
4049
4135
  return {
4050
4136
  key: panel.key ?? panel.id,
@@ -4092,7 +4178,7 @@ function WizardHost({ node, renderChildren }) {
4092
4178
  const current = panels.find((child) => child.open === true) ?? panels[0];
4093
4179
  const index = current === void 0 ? -1 : panels.indexOf(current);
4094
4180
  const idOf = (panel) => panel.key ?? panel.id;
4095
- const titleOf = (panel) => text(compiled.nodes[panel.index].options.title) ?? panel.key ?? panel.id;
4181
+ const titleOf = (panel) => text(compiled.nodes[panel.index].options.title) ?? panel.id;
4096
4182
  const headingRef = useRef(null);
4097
4183
  const shown = useRef(void 0);
4098
4184
  const currentId = current === void 0 ? void 0 : idOf(current);
@@ -4117,7 +4203,7 @@ function WizardHost({ node, renderChildren }) {
4117
4203
  region: "step-panel",
4118
4204
  form,
4119
4205
  labelledBy: titleId,
4120
- children: renderChildren(panel.children)
4206
+ children: renderChildren(panel.children, panel)
4121
4207
  }),
4122
4208
  ...description === void 0 ? {} : { description },
4123
4209
  titleId,
@@ -4163,18 +4249,23 @@ function WizardHost({ node, renderChildren }) {
4163
4249
  //#region src/screen/nodes.tsx
4164
4250
  function FailedNode({ node }) {
4165
4251
  const { controller } = useScreenTree();
4166
- const t = useOptionalT();
4167
- return jsxs("div", {
4168
- ...nodeAttributes(node),
4169
- role: "alert",
4170
- className: "flex flex-wrap items-center gap-2 rounded-md border border-dashed px-3 py-2 text-sm text-muted-foreground",
4171
- children: [jsx("span", { children: t("screen.failed", { defaultValue: "This section could not be loaded." }) }), jsx(Button, {
4172
- type: "button",
4173
- variant: "outline",
4174
- size: "sm",
4175
- onClick: () => controller.retry(node.failedOn),
4176
- children: t("actions.retry", { defaultValue: "Retry" })
4177
- })]
4252
+ let error;
4253
+ for (const name of node.failedOn) {
4254
+ const source = controller.sources[name];
4255
+ if (source?.status === "failed") {
4256
+ error = source.error;
4257
+ break;
4258
+ }
4259
+ }
4260
+ return jsx(LoadError, {
4261
+ surface: "tab",
4262
+ error,
4263
+ retry: () => controller.retry(node.failedOn),
4264
+ fallback: {
4265
+ key: "screen.failed",
4266
+ default: "This section could not be loaded."
4267
+ },
4268
+ attributes: nodeAttributes(node)
4178
4269
  });
4179
4270
  }
4180
4271
  function failedOnlyOnMeta(node) {
@@ -4187,8 +4278,17 @@ function drawn(node, hiddenFields) {
4187
4278
  if (node.state === "absent") return false;
4188
4279
  return !(node.kind === "field" && node.field !== void 0 && hiddenFields?.has(node.field));
4189
4280
  }
4190
- function SectionChildren({ nodes }) {
4191
- const { hiddenFields } = useScreenTree();
4281
+ function SectionChildren({ nodes, owner }) {
4282
+ const { compiled, hiddenFields } = useScreenTree();
4283
+ const text = useNodeText();
4284
+ if (owner !== void 0 && !nodes.some((node) => drawn(node, hiddenFields))) {
4285
+ const empty = text(compiled.nodes[owner.index]?.options.empty);
4286
+ return empty === void 0 || empty === "" ? null : jsx("p", {
4287
+ "data-buro-region": "empty",
4288
+ className: "text-sm text-muted-foreground",
4289
+ children: empty
4290
+ });
4291
+ }
4192
4292
  const out = [];
4193
4293
  let run = [];
4194
4294
  const flush = () => {
@@ -4210,8 +4310,11 @@ function SectionChildren({ nodes }) {
4210
4310
  flush();
4211
4311
  return out;
4212
4312
  }
4213
- function renderSection(nodes) {
4214
- return jsx(SectionChildren, { nodes });
4313
+ function renderSection(nodes, owner) {
4314
+ return jsx(SectionChildren, {
4315
+ nodes,
4316
+ owner
4317
+ });
4215
4318
  }
4216
4319
  function Layout({ node }) {
4217
4320
  return jsx("div", {
@@ -4333,17 +4436,14 @@ function pageHost(page) {
4333
4436
  }
4334
4437
  //#endregion
4335
4438
  //#region src/forms/declarative-form.tsx
4336
- function translateOrFallback(t, key, fallback) {
4337
- const translated = t(key);
4338
- return translated === key ? fallback : translated;
4339
- }
4340
- function issueText(issue, t, i18nPrefix) {
4439
+ function issueText(issue, caps, i18nPrefix) {
4341
4440
  return resolveValidationMessage({
4342
4441
  code: issue.code ?? "",
4343
4442
  message: issue.message,
4344
- ...issue.params === void 0 ? {} : { params: issue.params }
4443
+ ...issue.check === void 0 ? {} : { check: issue.check }
4345
4444
  }, {
4346
- t,
4445
+ has: caps.has,
4446
+ t: caps.t,
4347
4447
  ...issue.field === "_form" ? {} : { fieldKey: `resources.${i18nPrefix}.fields.${issue.field}` }
4348
4448
  });
4349
4449
  }
@@ -4410,7 +4510,9 @@ function DeclarativeFormBody(props) {
4410
4510
  ...props.surface === void 0 ? {} : { surface: props.surface },
4411
4511
  ...props.onSubmit === void 0 ? {} : { onSubmit: props.onSubmit }
4412
4512
  });
4413
- const t = useOptionalT();
4513
+ const formControl = useFormControlOf(form);
4514
+ const chromeT = useChromeT();
4515
+ const caps = useI18nCaps();
4414
4516
  const disabled = props.disabled === true;
4415
4517
  const uid = useId();
4416
4518
  const leafIds = useMemo(() => ({
@@ -4420,8 +4522,8 @@ function DeclarativeFormBody(props) {
4420
4522
  const formErrors = form.errors.filter((issue) => issue.field === "_form");
4421
4523
  const retryableChecks = [];
4422
4524
  for (const issue of form.checkOutcome?.blockers ?? []) {
4423
- const checkId = issue.params?.checkId;
4424
- if (issue.params?.cause === void 0 || typeof checkId !== "string") continue;
4525
+ const checkId = issue.check?.id;
4526
+ if (issue.check?.cause === void 0 || typeof checkId !== "string") continue;
4425
4527
  if (!retryableChecks.includes(checkId)) retryableChecks.push(checkId);
4426
4528
  }
4427
4529
  const formWarnings = form.warnings.filter((issue) => issue.field === "_form");
@@ -4434,7 +4536,7 @@ function DeclarativeFormBody(props) {
4434
4536
  formErrors.length > 0 && jsx("div", {
4435
4537
  role: "alert",
4436
4538
  className: "mb-4 flex flex-col gap-1 rounded-md border border-destructive/30 bg-destructive/10 px-3 py-2 text-sm text-destructive",
4437
- children: formErrors.map((issue, index) => jsx("p", { children: issueText(issue, t, prefix) }, index))
4539
+ children: formErrors.map((issue, index) => jsx("p", { children: issueText(issue, caps, prefix) }, index))
4438
4540
  }),
4439
4541
  retryableChecks.length > 0 && jsx("div", {
4440
4542
  className: "mb-4 flex flex-wrap gap-2",
@@ -4444,25 +4546,28 @@ function DeclarativeFormBody(props) {
4444
4546
  size: "sm",
4445
4547
  "data-testid": `form-check-retry:${checkId}`,
4446
4548
  onClick: () => void form.revalidate(),
4447
- children: translateOrFallback(t, "actions.retry", "Retry")
4549
+ children: chromeT("actions.retry", { defaultValue: "Retry" })
4448
4550
  }, checkId))
4449
4551
  }),
4450
4552
  formWarnings.length > 0 && jsx("div", {
4451
4553
  role: "status",
4452
4554
  "aria-live": "polite",
4453
4555
  className: "mb-4 flex flex-col gap-1 rounded-md border border-warning/50 bg-warning/10 px-3 py-2 text-sm text-warning-emphasis",
4454
- children: formWarnings.map((issue, index) => jsx("p", { children: issueText(issue, t, prefix) }, index))
4556
+ children: formWarnings.map((issue, index) => jsx("p", { children: issueText(issue, caps, prefix) }, index))
4455
4557
  }),
4456
- jsx(ScreenTree, {
4457
- region: "form-body",
4458
- resource: prefix,
4459
- mode: "create",
4460
- compiled: form.screen,
4461
- controller: form,
4462
- leafIds,
4463
- ...props.renderField === void 0 ? {} : { renderField: props.renderField },
4464
- disabled,
4465
- ...props.bodyClassName === void 0 ? {} : { className: props.bodyClassName }
4558
+ jsx(FormControlProvider, {
4559
+ value: formControl,
4560
+ children: jsx(ScreenTree, {
4561
+ region: "form-body",
4562
+ resource: prefix,
4563
+ mode: "create",
4564
+ compiled: form.screen,
4565
+ controller: form,
4566
+ leafIds,
4567
+ ...props.renderField === void 0 ? {} : { renderField: props.renderField },
4568
+ disabled,
4569
+ ...props.bodyClassName === void 0 ? {} : { className: props.bodyClassName }
4570
+ })
4466
4571
  }),
4467
4572
  props.slots?.footer === void 0 ? jsx("div", {
4468
4573
  className: "mt-4 flex justify-end",
@@ -4470,7 +4575,7 @@ function DeclarativeFormBody(props) {
4470
4575
  type: "submit",
4471
4576
  disabled: !form.canSubmit || disabled,
4472
4577
  onMouseDown: (event) => event.preventDefault(),
4473
- children: props.submitLabel ?? translateOrFallback(t, "actions.save", "Save")
4578
+ children: props.submitLabel ?? chromeT("actions.save", { defaultValue: "Save" })
4474
4579
  })
4475
4580
  }) : typeof props.slots.footer === "function" ? props.slots.footer({ canSubmit: form.canSubmit && !disabled }) : props.slots.footer
4476
4581
  ]
@@ -4484,28 +4589,39 @@ function DeclarativeFormBody(props) {
4484
4589
  }
4485
4590
  //#endregion
4486
4591
  //#region src/export/format-option-labels.ts
4487
- function exportFormatOptionKey(formatId, field, value) {
4488
- return value === void 0 ? `export.format.${formatId}.${field}.label` : `export.format.${formatId}.${field}.option.${value}`;
4489
- }
4490
4592
  function localizeFormatOptions(caps, format) {
4491
4593
  const fields = format.options;
4492
4594
  if (fields === void 0) return void 0;
4493
4595
  const out = {};
4494
4596
  for (const [name, field] of Object.entries(fields)) {
4495
4597
  const declared = field;
4598
+ const label = resolveContentSlot(caps, [exportFormatFieldKey(format.id, name)], {
4599
+ default: declaredText("field", field, "label"),
4600
+ humanize: () => humanizeKey(name)
4601
+ });
4602
+ const options = declared.options?.map((option) => ({
4603
+ value: String(option.value),
4604
+ label: resolveContentSlot(caps, [exportFormatOptionKey(format.id, name, String(option.value))], {
4605
+ default: fieldOptionText(field, String(option.value)),
4606
+ humanize: () => String(option.value)
4607
+ })
4608
+ }));
4609
+ const declaredTexts = typeof declared.texts === "object" && declared.texts !== null ? declared.texts : {};
4610
+ const declaredOptions = declaredTexts.options ?? {};
4611
+ const texts = {
4612
+ ...declaredTexts,
4613
+ label,
4614
+ ...options === void 0 ? {} : { options: Object.fromEntries(options.map(({ value, label: optionLabel }) => {
4615
+ const entry = declaredOptions[value];
4616
+ return [value, typeof entry === "object" && entry !== null ? {
4617
+ ...entry,
4618
+ label: optionLabel
4619
+ } : optionLabel];
4620
+ })) }
4621
+ };
4496
4622
  out[name] = {
4497
4623
  ...field,
4498
- label: resolveContentSlot(caps, [exportFormatOptionKey(format.id, name)], {
4499
- default: typeof declared.label === "string" ? declared.label : void 0,
4500
- humanize: () => humanizeKey(name)
4501
- }),
4502
- ...declared.options === void 0 ? {} : { options: declared.options.map((option) => ({
4503
- ...option,
4504
- label: resolveContentSlot(caps, [exportFormatOptionKey(format.id, name, String(option.value))], {
4505
- default: option.label,
4506
- humanize: () => String(option.value)
4507
- })
4508
- })) }
4624
+ texts
4509
4625
  };
4510
4626
  }
4511
4627
  return out;
@@ -4544,10 +4660,14 @@ function ExportOptionsDirty({ dirty }) {
4544
4660
  return null;
4545
4661
  }
4546
4662
  function getNoRunState() {}
4547
- function ExportDialog({ open, onOpenChange, resource, target, visibleColumns, estimatedCount }) {
4663
+ function ExportDialog({ open, onOpenChange, resource: resourceInput, target, visibleColumns, estimatedCount, scope }) {
4664
+ const resource = routeResourceKey(resourceInput);
4548
4665
  const t = useOptionalT();
4549
4666
  const dialogTestId = useTestIds().exportDialog(resource);
4550
- const { formats, start, exporter, available, error, loading } = useResourceExport(resource, { load: open });
4667
+ const { formats, start, exporter, available, error, loading } = useResourceExport(resource, {
4668
+ load: open,
4669
+ ...scope === void 0 ? {} : { scope }
4670
+ });
4551
4671
  const [mode, setMode] = React.useState("form");
4552
4672
  const [formatIdOverride, setFormatIdOverride] = React.useState(void 0);
4553
4673
  const [columnsMode, setColumnsMode] = React.useState("visible");
@@ -4569,6 +4689,15 @@ function ExportDialog({ open, onOpenChange, resource, target, visibleColumns, es
4569
4689
  ]);
4570
4690
  const requestColumns = columnsMode === "visible" && visibleColumns.length > 0 ? exportableVisible : allowedListColumns;
4571
4691
  const selectedFormat = formats.find((f) => f.id === formatIdOverride) ?? formats[0];
4692
+ const reportedLabels = React.useRef(new Set());
4693
+ React.useEffect(() => {
4694
+ if (process.env.NODE_ENV === "production") return;
4695
+ for (const format of formats) {
4696
+ if ((Object.hasOwn(format, "label") ? format.label : void 0) === void 0 || reportedLabels.current.has(format.id)) continue;
4697
+ reportedLabels.current.add(format.id);
4698
+ console.error(`[ExportDialog] export format '${format.id}' declares label, which was removed: write texts — the text the bundle holds at common.${exportFormatLabelKey(format.id)} (see docs/i18n-guide.md).`);
4699
+ }
4700
+ }, [formats]);
4572
4701
  const caps = useI18nCaps();
4573
4702
  const localizedOptions = React.useMemo(() => selectedFormat === void 0 ? void 0 : localizeFormatOptions(caps, selectedFormat), [caps, selectedFormat]);
4574
4703
  const wasOpenRef = React.useRef(open);
@@ -4654,14 +4783,15 @@ function ExportDialog({ open, onOpenChange, resource, target, visibleColumns, es
4654
4783
  })] }), jsx(ExportProgressView, {
4655
4784
  state: runState,
4656
4785
  dialogTestId,
4657
- t
4786
+ t,
4787
+ caps
4658
4788
  })] }) : jsxs(Fragment$1, { children: [jsxs(DialogHeader, { children: [jsx(DialogTitle, { children: titleText }), jsx(DialogDescription, {
4659
4789
  className: "sr-only",
4660
4790
  children: titleText
4661
4791
  })] }), !available ? jsx("p", {
4662
4792
  role: "alert",
4663
4793
  "data-testid": `${dialogTestId}:unavailable`,
4664
- children: error?.message ?? t("export.dialog.unavailable")
4794
+ children: error === void 0 || error === null ? t("export.dialog.unavailable") : resolveErrorText(error, caps)
4665
4795
  }) : jsxs(Fragment$1, { children: [
4666
4796
  loading ? jsx("p", {
4667
4797
  role: "status",
@@ -4682,7 +4812,10 @@ function ExportDialog({ open, onOpenChange, resource, target, visibleColumns, es
4682
4812
  }), jsx(SelectContent, { children: formats.map((format) => jsx(SelectItem, {
4683
4813
  value: format.id,
4684
4814
  "data-testid": `${dialogTestId}:format:option:${format.id}`,
4685
- children: typeof format.label === "string" ? format.label : format.id
4815
+ children: resolveContentSlot(caps, [exportFormatLabelKey(format.id)], {
4816
+ default: declaredText("export-format", format, "label"),
4817
+ humanize: () => format.id
4818
+ })
4686
4819
  }, format.id)) })]
4687
4820
  })]
4688
4821
  }),
@@ -4737,7 +4870,7 @@ function ExportDialog({ open, onOpenChange, resource, target, visibleColumns, es
4737
4870
  })
4738
4871
  });
4739
4872
  }
4740
- function ExportProgressView({ state, dialogTestId, t }) {
4873
+ function ExportProgressView({ state, dialogTestId, t, caps }) {
4741
4874
  if (state === void 0) return null;
4742
4875
  if (state.status === "running") {
4743
4876
  const total = state.progress?.total;
@@ -4758,7 +4891,7 @@ function ExportProgressView({ state, dialogTestId, t }) {
4758
4891
  if (state.status === "failed") return jsx("p", {
4759
4892
  role: "alert",
4760
4893
  "data-testid": `${dialogTestId}:error`,
4761
- children: `${t("export.dialog.failed")}: ${state.error.message}`
4894
+ children: `${t("export.dialog.failed")}: ${resolveErrorText(state.error, caps)}`
4762
4895
  });
4763
4896
  if (state.status === "canceled") return jsx("p", {
4764
4897
  "data-testid": `${dialogTestId}:canceled`,
@@ -4771,6 +4904,7 @@ function ExportProgressView({ state, dialogTestId, t }) {
4771
4904
  function BulkSummary(props) {
4772
4905
  const loaded = useLoaded(loadBulkSummary);
4773
4906
  const t = useOptionalT();
4907
+ const chromeT = useChromeT();
4774
4908
  const failed = (retry) => jsxs("div", {
4775
4909
  role: "alert",
4776
4910
  className: "flex items-center gap-2 text-sm",
@@ -4779,7 +4913,7 @@ function BulkSummary(props) {
4779
4913
  size: "sm",
4780
4914
  variant: "outline",
4781
4915
  onClick: retry,
4782
- children: text(t, "actions.retry", "Retry")
4916
+ children: chromeT("actions.retry", { defaultValue: "Retry" })
4783
4917
  })]
4784
4918
  });
4785
4919
  if (loaded.error !== void 0) return failed(loaded.retry);
@@ -4797,13 +4931,14 @@ function BulkFlowButton({ resource, actionName, ids, onSelectionChange }) {
4797
4931
  const control = useFlowRowResolver(resource)(actionName, void 0);
4798
4932
  const chrome = useFlowChrome(resource, actionName);
4799
4933
  const def = app.registry.get(resource);
4800
- const each = useMemo(() => {
4934
+ const compiled = useMemo(() => {
4801
4935
  try {
4802
- return def === void 0 ? false : compileFlow(def, actionName).each;
4936
+ return def === void 0 ? void 0 : compileFlow(def, actionName);
4803
4937
  } catch {
4804
- return false;
4938
+ return;
4805
4939
  }
4806
4940
  }, [def, actionName]);
4941
+ const each = compiled?.each ?? false;
4807
4942
  const [running, setRunning] = useState(false);
4808
4943
  const [summary, setSummary] = useState(null);
4809
4944
  const testid = tid.bulkAction(resource, actionName);
@@ -4811,7 +4946,10 @@ function BulkFlowButton({ resource, actionName, ids, onSelectionChange }) {
4811
4946
  const variant = chrome.destructive ? "destructive" : "outline";
4812
4947
  const overLimit = each && ids.length > BULK_EACH_LIMIT;
4813
4948
  if (overLimit || control.state === "disabled") return jsx(DisabledAction, {
4814
- reason: overLimit ? text(t, "flow.bulkLimit", `Select at most ${BULK_EACH_LIMIT}.`, { count: BULK_EACH_LIMIT }) : control.reason,
4949
+ reason: overLimit ? t("flow.bulkLimit", {
4950
+ defaultValue: `Select at most ${BULK_EACH_LIMIT}.`,
4951
+ count: BULK_EACH_LIMIT
4952
+ }) : control.reason,
4815
4953
  label: chrome.label,
4816
4954
  icon: chrome.icon,
4817
4955
  testid,
@@ -4873,14 +5011,15 @@ function BulkFlowButton({ resource, actionName, ids, onSelectionChange }) {
4873
5011
  actionName,
4874
5012
  label: chrome.labelText,
4875
5013
  total: summary.total,
4876
- progress: summary.progress
5014
+ progress: summary.progress,
5015
+ texts: compiled?.flow.texts
4877
5016
  }),
4878
5017
  jsx(DialogFooter, { children: jsx(Button, {
4879
5018
  type: "button",
4880
5019
  variant: "outline",
4881
5020
  "data-testid": `${tid.bulkSummary(resource, actionName)}:close`,
4882
5021
  onClick: () => setSummary(null),
4883
- children: text(t, "flow.close", "Close")
5022
+ children: t("flow.close", { defaultValue: "Close" })
4884
5023
  }) })
4885
5024
  ]
4886
5025
  })
@@ -4896,10 +5035,12 @@ function orderBulkActions(actions) {
4896
5035
  seq: index
4897
5036
  })).sort((a, b) => a.weight - b.weight || a.seq - b.seq).map(({ action }) => action);
4898
5037
  }
4899
- function BulkActionToolbar({ resource, table, selectedRecords, selectedIds, onClearSelection, onSelectionChange, className, visibleColumns }) {
5038
+ function BulkActionToolbar({ resource: resourceInput, table, selectedRecords, selectedIds, onClearSelection, onSelectionChange, className, visibleColumns, scope, actions }) {
5039
+ const resource = routeResourceKey(resourceInput);
4900
5040
  const app = useApp();
4901
5041
  const tid = useTestIds();
4902
5042
  const t = useOptionalT();
5043
+ const chromeT = useChromeT();
4903
5044
  const def = app.registry.get(resource);
4904
5045
  const records = selectedRecords ?? (table ? table.getFilteredSelectedRowModel().rows.map((r) => r.original) : []);
4905
5046
  const ids = selectedIds ?? records.map((record) => record.id);
@@ -4932,7 +5073,8 @@ function BulkActionToolbar({ resource, table, selectedRecords, selectedIds, onCl
4932
5073
  className: "mx-0.5 h-6 w-px bg-border",
4933
5074
  "aria-hidden": "true"
4934
5075
  }),
4935
- bulkActions.map((action) => jsx(BulkFlowButton, {
5076
+ actions !== void 0 ? actions : null,
5077
+ actions === void 0 && bulkActions.map((action) => jsx(BulkFlowButton, {
4936
5078
  resource,
4937
5079
  actionName: action.name,
4938
5080
  ids,
@@ -4941,10 +5083,11 @@ function BulkActionToolbar({ resource, table, selectedRecords, selectedIds, onCl
4941
5083
  else if (next.length === 0) handleClear();
4942
5084
  }
4943
5085
  }, action.name)),
4944
- jsx(BulkExportButton, {
5086
+ actions === void 0 && jsx(BulkExportButton, {
4945
5087
  resource,
4946
5088
  ids,
4947
- visibleColumns: visibleColumns ?? []
5089
+ visibleColumns: visibleColumns ?? [],
5090
+ ...scope === void 0 ? {} : { scope }
4948
5091
  }),
4949
5092
  jsxs(Button, {
4950
5093
  type: "button",
@@ -4957,7 +5100,7 @@ function BulkActionToolbar({ resource, table, selectedRecords, selectedIds, onCl
4957
5100
  "aria-hidden": "true"
4958
5101
  }), jsx("span", {
4959
5102
  className: "hidden sm:inline",
4960
- children: t("actions.clearSelection", { defaultValue: "Clear" })
5103
+ children: chromeT("actions.clearSelection", { defaultValue: "Clear" })
4961
5104
  })]
4962
5105
  })
4963
5106
  ]
@@ -4967,15 +5110,16 @@ function BulkActionToolbar({ resource, table, selectedRecords, selectedIds, onCl
4967
5110
  className: "h-20 sm:h-24"
4968
5111
  })] });
4969
5112
  }
4970
- function BulkExportButton({ resource, ids, visibleColumns }) {
5113
+ function BulkExportButton({ resource, ids, visibleColumns, scope }) {
4971
5114
  if (!(useOptionalDataRuntime() !== null)) return null;
4972
5115
  return jsx(BulkExportButtonActive, {
4973
5116
  resource,
4974
5117
  ids,
4975
- visibleColumns
5118
+ visibleColumns,
5119
+ ...scope === void 0 ? {} : { scope }
4976
5120
  });
4977
5121
  }
4978
- function BulkExportButtonActive({ resource, ids, visibleColumns }) {
5122
+ function BulkExportButtonActive({ resource, ids, visibleColumns, scope }) {
4979
5123
  const tid = useTestIds();
4980
5124
  const t = useOptionalT();
4981
5125
  const { available, prefetch } = useResourceExport(resource);
@@ -5010,23 +5154,49 @@ function BulkExportButtonActive({ resource, ids, visibleColumns }) {
5010
5154
  onOpenChange: setOpen,
5011
5155
  resource,
5012
5156
  target,
5013
- visibleColumns
5157
+ visibleColumns,
5158
+ ...scope === void 0 ? {} : { scope }
5014
5159
  })] });
5015
5160
  }
5016
5161
  //#endregion
5017
5162
  //#region src/list-kit/pieces/list-bulk-bar.tsx
5163
+ function LinkDetachBulkButton(props) {
5164
+ const { selection } = useResourceListContext();
5165
+ const { resource } = useListSurface();
5166
+ const t = useOptionalT();
5167
+ const [busy, setBusy] = useState(false);
5168
+ return jsxs(Button, {
5169
+ type: "button",
5170
+ size: "sm",
5171
+ variant: "ghost",
5172
+ className: "h-8 gap-1.5 rounded-full px-2.5 text-destructive",
5173
+ disabled: busy,
5174
+ "data-testid": `list-link-detach-bulk:${resource}`,
5175
+ onClick: () => {
5176
+ setBusy(true);
5177
+ props.onDetach(selection.records).finally(() => setBusy(false));
5178
+ },
5179
+ children: [jsx(IconUnlink, {
5180
+ className: "size-4",
5181
+ "aria-hidden": "true"
5182
+ }), t("field.link.detach", { defaultValue: "Detach" })]
5183
+ });
5184
+ }
5018
5185
  function ListBulkBarView() {
5019
5186
  usePieceMount("bulk-bar");
5020
- const { selection, columns } = useResourceListContext();
5021
- const { resource, tableView } = useListSurface();
5022
- if (!tableView || selection.ids.length === 0) return null;
5187
+ const { selection, columns, scope } = useResourceListContext();
5188
+ const { resource, tableView, readOnly, linkEditing } = useListSurface();
5189
+ const detach = readOnly ? linkEditing?.detach : void 0;
5190
+ if (!tableView || readOnly && detach === void 0 || selection.ids.length === 0) return null;
5023
5191
  return jsx(BulkActionToolbar, {
5192
+ ...detach === void 0 ? {} : { actions: jsx(LinkDetachBulkButton, { onDetach: detach.onDetach }) },
5024
5193
  resource,
5025
5194
  selectedRecords: selection.records,
5026
5195
  selectedIds: selection.ids,
5027
5196
  onClearSelection: selection.clear,
5028
5197
  onSelectionChange: selection.set,
5029
- visibleColumns: columns.visible
5198
+ visibleColumns: columns.visible,
5199
+ scope
5030
5200
  });
5031
5201
  }
5032
5202
  const ListBulkBar = listPiece("ListBulkBar", ListBulkBarView);
@@ -5108,9 +5278,11 @@ function DataTableViewOptions({ table, getColumnLabel, isColumnRequired, getTest
5108
5278
  }
5109
5279
  //#endregion
5110
5280
  //#region src/list-kit/columns-toggle.tsx
5111
- function ColumnsToggle({ resource, def, allAvailable, required, visibleColumns, defaultVisible, onChange, onResetSizes, onRestoreDefaults }) {
5281
+ function ColumnsToggle({ resource: resourceInput, def, allAvailable, required, visibleColumns, defaultVisible, onChange, onResetSizes, onRestoreDefaults }) {
5282
+ const resource = routeResourceKey(resourceInput);
5112
5283
  const t = useT();
5113
5284
  const columnsCaps = useI18nCaps(resource);
5285
+ const app = useApp();
5114
5286
  const visibleSet = useMemo(() => new Set(visibleColumns), [visibleColumns]);
5115
5287
  const requiredSet = useMemo(() => new Set(required), [required]);
5116
5288
  const defaultSet = useMemo(() => new Set(defaultVisible), [defaultVisible]);
@@ -5175,8 +5347,9 @@ function ColumnsToggle({ resource, def, allAvailable, required, visibleColumns,
5175
5347
  testid: `list-columns-restore-defaults:${resource}`
5176
5348
  }] : []],
5177
5349
  getColumnLabel: (column) => {
5178
- const fieldDef = def.fields?.[column.id];
5179
- return resolveFieldLabel(columnsCaps, column.id, fieldDef?.label);
5350
+ const target = columnTarget(app.registry, resource, column.id);
5351
+ if (target !== void 0) return resolveFieldLabel(capsOf(columnsCaps, target.resource), target.name, declaredText("field", target.field, "label"));
5352
+ return resolveFieldLabel(columnsCaps, column.id, declaredText("field", def.fields?.[column.id], "label"));
5180
5353
  },
5181
5354
  isColumnRequired: (column) => requiredSet.has(column.id),
5182
5355
  getTestId: (role, columnId) => {
@@ -5213,12 +5386,16 @@ function ListColumnsView() {
5213
5386
  const ListColumns = listPiece("ListColumns", ListColumnsView);
5214
5387
  //#endregion
5215
5388
  //#region src/resource/create-button.tsx
5216
- function CreateButton({ resource, href, testid, size = "sm", variant, label, className, onOpen }) {
5389
+ function CreateButton({ resource: resourceInput, href, testid, size = "sm", variant, label, className, onOpen }) {
5390
+ const resource = routeResourceKey(resourceInput);
5217
5391
  const t = useOptionalT();
5218
5392
  const Link = useLinkComponent();
5219
5393
  const availability = useAvailability(resource, "create");
5220
5394
  const gate = useEntryGate();
5221
- const text = label ?? t("actions.create", { defaultValue: "Create" });
5395
+ const text = label ?? t("actions.create", {
5396
+ resource,
5397
+ defaultValue: "Create"
5398
+ });
5222
5399
  if (onOpen !== void 0) {
5223
5400
  const answer = gate(resource, "create");
5224
5401
  if (answer.state === "disabled") return jsx(DisabledAction, {
@@ -5289,7 +5466,8 @@ const ListCreate = listPiece("ListCreate", ListCreateView);
5289
5466
  //#endregion
5290
5467
  //#region src/export/export-button.tsx
5291
5468
  const warnedProviderQueryResources = new Set();
5292
- function ExportButton({ resource, className }) {
5469
+ function ExportButton({ resource: resourceInput, className }) {
5470
+ const resource = routeResourceKey(resourceInput);
5293
5471
  if (!(useOptionalDataRuntime() !== null)) return null;
5294
5472
  return jsx(ExportButtonActive, {
5295
5473
  resource,
@@ -5310,36 +5488,19 @@ function ExportButtonActive({ resource, className }) {
5310
5488
  const tid = useTestIds();
5311
5489
  const t = useOptionalT();
5312
5490
  const [open, setOpen] = React.useState(false);
5313
- const searchContext = React.useMemo(() => ({
5314
- resource,
5315
- def: list.def
5316
- }), [resource, list.def]);
5491
+ const sent = list.query.sent;
5317
5492
  const target = React.useMemo(() => {
5318
- const filter = composeListFilter({
5319
- fixed: list.filters.fixed,
5320
- pinned: list.filters.value,
5321
- group: list.filters.group,
5322
- search: list.filters.search,
5323
- searchDecl: list.filters.searchEnabled ? list.filters.searchDecl ?? list.def?.views?.list?.search : void 0,
5324
- searchContext
5325
- });
5326
- const sort = list.sorting.value;
5493
+ if (sent === null) return null;
5494
+ const { filter, sort } = sent;
5327
5495
  return {
5328
5496
  kind: "filter",
5329
5497
  ...filter === void 0 ? {} : { filter },
5330
5498
  ...sort.length > 0 ? { sort } : {}
5331
5499
  };
5332
- }, [
5333
- list.filters.fixed,
5334
- list.filters.value,
5335
- list.filters.group,
5336
- list.filters.search,
5337
- list.filters.searchDecl,
5338
- list.filters.searchEnabled,
5339
- list.def,
5340
- searchContext,
5341
- list.sorting.value
5342
- ]);
5500
+ }, [sent]);
5501
+ const lastTarget = React.useRef(target);
5502
+ if (target !== null) lastTarget.current = target;
5503
+ const dialogTarget = target ?? lastTarget.current;
5343
5504
  React.useEffect(() => {
5344
5505
  if (!available || !capability.allowed) return;
5345
5506
  if (list.providerQuery.value === void 0) return;
@@ -5353,7 +5514,9 @@ function ExportButtonActive({ resource, className }) {
5353
5514
  resource
5354
5515
  ]);
5355
5516
  if (!available || !capability.allowed || list.isForbidden) return null;
5356
- return jsxs(Fragment$1, { children: [jsx(Button, {
5517
+ if (target === null && !open) return null;
5518
+ if (dialogTarget === null) return null;
5519
+ return jsxs(Fragment$1, { children: [target === null ? null : jsx(Button, {
5357
5520
  type: "button",
5358
5521
  variant: "outline",
5359
5522
  size: "icon",
@@ -5372,9 +5535,10 @@ function ExportButtonActive({ resource, className }) {
5372
5535
  open,
5373
5536
  onOpenChange: setOpen,
5374
5537
  resource,
5375
- target,
5538
+ target: dialogTarget,
5376
5539
  visibleColumns: list.columns.visible,
5377
- estimatedCount: list.pagination.total
5540
+ estimatedCount: list.pagination.total,
5541
+ scope: list.scope
5378
5542
  })] });
5379
5543
  }
5380
5544
  //#endregion
@@ -5382,15 +5546,16 @@ function ExportButtonActive({ resource, className }) {
5382
5546
  function ListExportView() {
5383
5547
  usePieceMount("export");
5384
5548
  const { def } = useResourceListContext();
5385
- const { resource } = useListSurface();
5386
- if (!def) return null;
5549
+ const { resource, exportable } = useListSurface();
5550
+ if (!def || !exportable) return null;
5387
5551
  return jsx(ExportButton, { resource });
5388
5552
  }
5389
5553
  const ListExport = listPiece("ListExport", ListExportView);
5390
5554
  //#endregion
5391
5555
  //#region src/list-kit/list-search-input.tsx
5392
- function ListSearchInput({ resource, label, value, onCommit, placeholder }) {
5393
- const t = useT();
5556
+ function ListSearchInput({ resource: resourceInput, value, onCommit, placeholder }) {
5557
+ const resource = routeResourceKey(resourceInput);
5558
+ const caps = useI18nCaps(resource);
5394
5559
  const [text, setText] = useState(value);
5395
5560
  const lastPushedRef = useRef(value);
5396
5561
  const timerRef = useRef(null);
@@ -5405,7 +5570,7 @@ function ListSearchInput({ resource, label, value, onCommit, placeholder }) {
5405
5570
  }, []);
5406
5571
  return jsx(Input, {
5407
5572
  type: "search",
5408
- placeholder: placeholder ?? t("toolbar.searchBy", { params: { name: label } }),
5573
+ placeholder: placeholder ?? resolveContentSlot(caps, ["list.search"]) ?? "Search",
5409
5574
  value: text,
5410
5575
  "data-testid": `list-search:${resource}`,
5411
5576
  className: "h-8 w-full min-[420px]:w-72",
@@ -5423,24 +5588,18 @@ function ListSearchInput({ resource, label, value, onCommit, placeholder }) {
5423
5588
  //#endregion
5424
5589
  //#region src/list-kit/filter-builder/filter-region.tsx
5425
5590
  function FilterRegion({ search } = {}) {
5426
- const { resource, def, filters, pagination, access } = useResourceListContext();
5427
- const t = useT();
5428
- const listCaps = useI18nCaps(resource);
5591
+ const { resource, def, filters, pagination } = useResourceListContext();
5592
+ const chromeT = useChromeT();
5429
5593
  const testIdBase = `list-filter:${resource}`;
5594
+ const caps = useI18nCaps(resource);
5595
+ const placeholder = search === void 0 ? resolveContentSlot(caps, ["list.searchPlaceholder", "list.search"]) ?? "Search" : void 0;
5430
5596
  const searchDecl = search === void 0 ? def?.views?.list?.search : search ?? void 0;
5431
- const searchField = searchDecl !== void 0 && typeof searchDecl !== "function" ? (() => {
5432
- return searchDecl.fields.map((entry) => typeof entry === "string" ? entry : entry.field).find((name) => access.fieldVisible(name));
5433
- })() : void 0;
5434
- const searchGenericLabel = resolveContentSlot(listCaps, ["list.search"]) ?? "Search";
5435
- const searchLabel = searchField ? resolveFieldLabel(listCaps, searchField, (def?.fields[searchField])?.label) : searchGenericLabel;
5436
- const searchPlaceholder = searchField ? t("toolbar.searchBy", { params: { name: searchLabel } }) : searchGenericLabel;
5437
5597
  return jsxs("div", {
5438
5598
  className: "flex flex-wrap items-center gap-2",
5439
5599
  children: [
5440
5600
  searchDecl !== void 0 && filters.searchEnabled && jsx(ListSearchInput, {
5441
5601
  resource,
5442
- label: searchLabel,
5443
- placeholder: searchPlaceholder,
5602
+ ...placeholder === void 0 ? {} : { placeholder },
5444
5603
  value: filters.search,
5445
5604
  onCommit: (v) => {
5446
5605
  pagination.setPage(1);
@@ -5452,19 +5611,21 @@ function FilterRegion({ search } = {}) {
5452
5611
  setGroup: filters.setGroup,
5453
5612
  available: filters.available,
5454
5613
  fixed: filters.fixed,
5614
+ presets: filters.presets,
5615
+ setPreset: filters.setPreset,
5455
5616
  pinnedFields: filters.pinnedFields,
5456
5617
  pinnedValue: filters.value,
5457
5618
  setPinnedValue: filters.set,
5458
5619
  setPage: pagination.setPage,
5459
5620
  testIdBase
5460
5621
  }),
5461
- filters.isFiltered && jsx(Button, {
5622
+ filters.canReset && jsx(Button, {
5462
5623
  type: "button",
5463
5624
  variant: "ghost",
5464
5625
  size: "sm",
5465
5626
  "data-testid": `${testIdBase}:clear-all`,
5466
- onClick: filters.clearAll,
5467
- children: t("filters.clearAll")
5627
+ onClick: filters.reset,
5628
+ children: chromeT("filters.clearAll")
5468
5629
  })
5469
5630
  ]
5470
5631
  });
@@ -5578,16 +5739,23 @@ function ListLiveView() {
5578
5739
  const ListLive = listPiece("ListLive", ListLiveView);
5579
5740
  //#endregion
5580
5741
  //#region src/saved-views/saved-views-dropdown.tsx
5581
- function ViewItem({ view, onClick, testid }) {
5742
+ function ViewItem({ view, caps, resource, onClick, testid }) {
5743
+ const own = resource === void 0 ? void 0 : `resources.${resource}.${savedViewKey(view.id)}`;
5744
+ const declared = declaredText("saved-view", view, "label");
5745
+ const label = own === void 0 ? declared ?? humanizeKey(view.id) : resolveContentSlot(caps, [[`${own}.label`, own]], {
5746
+ default: declared,
5747
+ humanize: () => humanizeKey(view.id)
5748
+ });
5582
5749
  return jsx("button", {
5583
5750
  type: "button",
5584
5751
  onClick,
5585
5752
  "data-testid": testid,
5586
5753
  className: "w-full text-left rounded-sm px-2 py-1.5 text-sm hover:bg-accent hover:text-accent-foreground focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring",
5587
- children: view.label
5754
+ children: label
5588
5755
  });
5589
5756
  }
5590
5757
  function EntryRow({ entry, onApply, onRename, onFork, onDelete, forkAvailable, applyTestId, renameTestId, forkTestId, deleteTestId, tr }) {
5758
+ const chromeT = useChromeT();
5591
5759
  const canRename = entry.permissions.update && onRename !== void 0;
5592
5760
  const canFork = forkAvailable && entry.permissions.fork && onFork !== void 0;
5593
5761
  const canDelete = entry.permissions.remove && onDelete !== void 0;
@@ -5633,8 +5801,8 @@ function EntryRow({ entry, onApply, onRename, onFork, onDelete, forkAvailable, a
5633
5801
  canDelete && jsx("button", {
5634
5802
  type: "button",
5635
5803
  onClick: onDelete,
5636
- "aria-label": tr("actions.delete", "Delete"),
5637
- title: tr("actions.delete", "Delete"),
5804
+ "aria-label": chromeT("actions.delete", { defaultValue: "Delete" }),
5805
+ title: chromeT("actions.delete", { defaultValue: "Delete" }),
5638
5806
  "data-testid": deleteTestId,
5639
5807
  className: "shrink-0 rounded-sm p-1 text-muted-foreground hover:bg-accent hover:text-accent-foreground focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring",
5640
5808
  children: jsx(IconTrash, {
@@ -5674,8 +5842,10 @@ function groupByScope(entries) {
5674
5842
  }
5675
5843
  return order.map((id) => byId.get(id));
5676
5844
  }
5677
- function SavedViewsDropdown({ adopterViews, entries, issues = [], onApply, onSaveCurrent, canSaveCurrent = true, onManage, onRenameEntry, onDeleteEntry, onForkEntry, canFork = true, getTestId, triggerLabel = "Views", iconOnly = false, triggerClassName = "h-9", popoverContentClassName = "w-64 p-1", popoverAlign = "start" }) {
5845
+ function SavedViewsDropdown({ resource: resourceInput, adopterViews, entries, issues = [], onApply, onSaveCurrent, canSaveCurrent = true, onManage, onRenameEntry, onDeleteEntry, onForkEntry, canFork = true, getTestId, triggerLabel = "Views", iconOnly = false, triggerClassName = "h-9", popoverContentClassName = "w-64 p-1", popoverAlign = "start" }) {
5846
+ const resource = resourceInput === void 0 ? void 0 : routeResourceKey(resourceInput);
5678
5847
  const t = useOptionalT();
5848
+ const caps = useI18nCaps(resource);
5679
5849
  const tr = (key, fallback) => t?.(key, { defaultValue: fallback }) ?? fallback;
5680
5850
  const [open, setOpen] = useState(false);
5681
5851
  const hasAdopter = adopterViews.length > 0;
@@ -5729,6 +5899,8 @@ function SavedViewsDropdown({ adopterViews, entries, issues = [], onApply, onSav
5729
5899
  className: "max-h-[200px] overflow-auto",
5730
5900
  children: adopterViews.map((v) => jsx(ViewItem, {
5731
5901
  view: v,
5902
+ caps,
5903
+ resource,
5732
5904
  testid: getTestId?.("view-item", v.id),
5733
5905
  onClick: closeAfter(() => onApply(v))
5734
5906
  }, v.id))
@@ -5785,6 +5957,7 @@ function ListSavedViewsView() {
5785
5957
  const { resource, route, savedViewsUi } = useListSurface();
5786
5958
  if (!def || !route) return null;
5787
5959
  return jsx(SavedViewsDropdown, {
5960
+ resource,
5788
5961
  adopterViews: savedViews.adopter,
5789
5962
  entries: savedViews.entries,
5790
5963
  issues: savedViews.issues,
@@ -5884,9 +6057,10 @@ function useOptionalFrameChrome() {
5884
6057
  //#endregion
5885
6058
  //#region src/record/frame-pieces.tsx
5886
6059
  function useFrameId() {
5887
- const { recordId, record } = useRecordFrame();
6060
+ const { resource, recordId, record } = useRecordFrame();
6061
+ const def = useApp().registry.get(resource);
5888
6062
  if (recordId !== void 0) return recordId;
5889
- const id = record?.id;
6063
+ const id = recordKeyOf(def, record);
5890
6064
  return typeof id === "string" || typeof id === "number" ? String(id) : void 0;
5891
6065
  }
5892
6066
  function RecordTitleView() {
@@ -5897,7 +6071,7 @@ function RecordTitleView() {
5897
6071
  const t = useOptionalT();
5898
6072
  const title = useRecordTitle(resource, mode === "create" ? void 0 : id, record);
5899
6073
  if (useFrameChrome().hostTitle === true) return null;
5900
- const text = mode === "create" ? t("pageHeader.createTitle", { params: { label: resolveResourceLabel(caps, resource, "single", app.registry.get(resource)) } }) : title;
6074
+ const text = mode === "create" ? resolveResourceTitle(caps, t, resource, "createTitle", { label: resolveResourceLabel(caps, resource, "single", app.registry.get(resource)) }) : title;
5901
6075
  if (text === "") return null;
5902
6076
  return jsx("h2", {
5903
6077
  className: "min-w-0 break-words text-xl font-semibold tracking-tight",
@@ -5921,6 +6095,7 @@ function RecordActionsView() {
5921
6095
  resource,
5922
6096
  record: row,
5923
6097
  surface: mode,
6098
+ sources: controller.sources,
5924
6099
  ...holder === void 0 ? {} : { hideFlows: (name) => holder.suppressed(name) }
5925
6100
  }), mode === "show" ? extraActions : null]
5926
6101
  });
@@ -5950,9 +6125,15 @@ function FormActionsView() {
5950
6125
  const app = useApp();
5951
6126
  const navigate = useRouteNavigate();
5952
6127
  const t = useOptionalT();
6128
+ const chromeT = useChromeT();
6129
+ const caps = useI18nCaps();
6130
+ const labelCaps = useI18nCaps(resource);
5953
6131
  const tid = useTestIds();
5954
- const locale = useLocale();
5955
6132
  const id = useFrameId();
6133
+ const failedIssues = chrome.serverIssues;
6134
+ const livePlan = controller.plan;
6135
+ const alertIssues = useMemo(() => failedIssues === void 0 || failedIssues.length === 0 ? NO_ALERT_ISSUES : routeServerIssues({ issues: failedIssues }, (segment) => segment, (field) => field !== "_form" && (livePlan === null || livePlan.fields[field] === "active")).form, [failedIssues, livePlan]);
6136
+ const fieldDefs = app.registry.get(resource)?.fields;
5956
6137
  if (mode === "show") return null;
5957
6138
  const busy = controller.isSubmitting;
5958
6139
  const cancel = () => {
@@ -5976,21 +6157,43 @@ function FormActionsView() {
5976
6157
  const text = (issue) => resolveValidationMessage({
5977
6158
  code: issue.code ?? "",
5978
6159
  message: issue.message,
5979
- ...issue.params === void 0 ? {} : { params: issue.params }
5980
- }, { t });
6160
+ ...issue.check === void 0 ? {} : { check: issue.check }
6161
+ }, {
6162
+ has: caps.has,
6163
+ t: caps.t
6164
+ });
5981
6165
  const retryable = [];
5982
6166
  for (const issue of controller.checkOutcome?.blockers ?? []) {
5983
- const checkId = issue.params?.checkId;
5984
- if (issue.params?.cause === void 0 || typeof checkId !== "string") continue;
6167
+ const checkId = issue.check?.id;
6168
+ if (issue.check?.cause === void 0 || typeof checkId !== "string") continue;
5985
6169
  if (!retryable.includes(checkId)) retryable.push(checkId);
5986
6170
  }
5987
- const failure = chrome.submitError === void 0 ? void 0 : failureText(chrome.submitError, t, locale);
6171
+ const failure = chrome.submitError === void 0 ? void 0 : failureText(chrome.submitError, chrome.submitApplied, t, caps);
6172
+ const serverIssues = bySeverity(alertIssues).map((issue) => {
6173
+ const head = issue.path[0];
6174
+ const name = typeof head === "string" ? head : void 0;
6175
+ const declared = name === void 0 ? void 0 : fieldDefs?.[name];
6176
+ const message = resolveValidationMessage({
6177
+ code: issue.code,
6178
+ message: issue.message,
6179
+ ...issue.check === void 0 ? {} : { check: issue.check }
6180
+ }, {
6181
+ has: caps.has,
6182
+ t: caps.t,
6183
+ ...declared === void 0 ? {} : { fieldKey: `resources.${resource}.fields.${name}` }
6184
+ });
6185
+ return {
6186
+ label: declared === void 0 || name === void 0 ? void 0 : resolveFieldLabel(labelCaps, name, declaredText("field", declared, "label")),
6187
+ message,
6188
+ severity: issue.severity
6189
+ };
6190
+ });
5988
6191
  const steps = controller.steps;
5989
6192
  const midSteps = steps !== null && steps.ids.length > 1 && steps.current !== void 0 && steps.current !== steps.ids[steps.ids.length - 1];
5990
6193
  return jsxs("div", {
5991
6194
  className: "flex flex-col gap-3",
5992
6195
  children: [
5993
- failure === void 0 && formErrors.length === 0 ? null : jsxs("div", {
6196
+ failure === void 0 && formErrors.length === 0 && serverIssues.length === 0 ? null : jsxs("div", {
5994
6197
  role: "alert",
5995
6198
  className: "flex items-start gap-2 rounded-md border border-destructive/30 bg-destructive/10 px-3 py-2 text-sm text-destructive",
5996
6199
  children: [
@@ -6000,10 +6203,22 @@ function FormActionsView() {
6000
6203
  }),
6001
6204
  jsxs("div", {
6002
6205
  className: "leading-snug",
6003
- children: [failure === void 0 ? null : jsx("p", {
6004
- className: "font-medium",
6005
- children: failure
6006
- }), formErrors.map((issue) => jsx("p", { children: text(issue) }, issueKey(issue)))]
6206
+ children: [
6207
+ failure === void 0 ? null : jsx("p", {
6208
+ className: "font-medium",
6209
+ children: failure
6210
+ }),
6211
+ serverIssues.map((issue, index) => jsxs("p", {
6212
+ "data-testid": `form-server-issue:${resource}:${mode}:${index}`,
6213
+ "data-buro-severity": issue.severity,
6214
+ className: issue.severity === "error" ? void 0 : "text-warning-emphasis",
6215
+ children: [issue.label === void 0 ? null : jsxs("span", {
6216
+ className: "font-medium",
6217
+ children: [issue.label, ": "]
6218
+ }), issue.message]
6219
+ }, index)),
6220
+ formErrors.map((issue) => jsx("p", { children: text(issue) }, issueKey(issue)))
6221
+ ]
6007
6222
  }),
6008
6223
  failure !== void 0 && steps !== null ? jsx(Button, {
6009
6224
  type: "button",
@@ -6012,7 +6227,7 @@ function FormActionsView() {
6012
6227
  className: "ml-auto",
6013
6228
  onMouseDown: (event) => event.preventDefault(),
6014
6229
  onClick: () => void chrome.submit(),
6015
- children: t("actions.retry", { defaultValue: "Retry" })
6230
+ children: chromeT("actions.retry", { defaultValue: "Retry" })
6016
6231
  }) : null
6017
6232
  ]
6018
6233
  }),
@@ -6024,7 +6239,7 @@ function FormActionsView() {
6024
6239
  size: "sm",
6025
6240
  "data-testid": `form-check-retry:${checkId}`,
6026
6241
  onClick: () => void controller.revalidate(),
6027
- children: t("actions.retry", { defaultValue: "Retry" })
6242
+ children: chromeT("actions.retry", { defaultValue: "Retry" })
6028
6243
  }, checkId))
6029
6244
  }),
6030
6245
  formWarnings.length === 0 ? null : jsxs("div", {
@@ -6050,7 +6265,7 @@ function FormActionsView() {
6050
6265
  children: [jsx(IconLoader2, {
6051
6266
  className: "h-3.5 w-3.5 animate-spin",
6052
6267
  "aria-hidden": "true"
6053
- }), jsx("span", { children: t("actions.validating", { defaultValue: "Validating…" }) })]
6268
+ }), jsx("span", { children: chromeT("actions.validating", { defaultValue: "Validating…" }) })]
6054
6269
  }) : null,
6055
6270
  jsx(Button, {
6056
6271
  type: "button",
@@ -6058,7 +6273,7 @@ function FormActionsView() {
6058
6273
  onClick: cancel,
6059
6274
  onMouseDown: (event) => event.preventDefault(),
6060
6275
  "data-testid": tid.formCancel(resource, mode),
6061
- children: t("actions.cancel", { defaultValue: "Cancel" })
6276
+ children: chromeT("actions.cancel", { defaultValue: "Cancel" })
6062
6277
  }),
6063
6278
  midSteps ? null : jsxs(Button, {
6064
6279
  type: "submit",
@@ -6075,37 +6290,71 @@ function FormActionsView() {
6075
6290
  children: [busy ? jsx(IconLoader2, {
6076
6291
  className: "mr-2 h-4 w-4 animate-spin",
6077
6292
  "aria-hidden": "true"
6078
- }) : null, busy ? mode === "edit" ? t("actions.saving", { defaultValue: "Saving…" }) : t("actions.creating", { defaultValue: "Creating…" }) : chrome.submitLabel ?? (mode === "edit" ? t("actions.save", { defaultValue: "Save" }) : t("actions.create", { defaultValue: "Create" }))]
6293
+ }) : null, busy ? mode === "edit" ? chromeT("actions.saving", { defaultValue: "Saving…" }) : chromeT("actions.creating", { defaultValue: "Creating…" }) : chrome.submitLabel ?? (mode === "edit" ? chromeT("actions.save", { defaultValue: "Save" }) : chromeT("actions.create", { defaultValue: "Create" }))]
6079
6294
  })
6080
6295
  ]
6081
6296
  })
6082
6297
  ]
6083
6298
  });
6084
6299
  }
6300
+ const NO_ALERT_ISSUES = Object.freeze([]);
6301
+ const SEVERITY_RANK = {
6302
+ error: 0,
6303
+ warning: 1,
6304
+ info: 2
6305
+ };
6306
+ function bySeverity(issues) {
6307
+ return [...issues].sort((a, b) => (SEVERITY_RANK[a.severity] ?? 3) - (SEVERITY_RANK[b.severity] ?? 3));
6308
+ }
6085
6309
  function firstPreferred(issues) {
6086
- const first = issues.find((issue) => issue.params?.checkId === void 0) ?? issues[0];
6310
+ const first = issues.find((issue) => issue.check?.id === void 0) ?? issues[0];
6087
6311
  return first === void 0 ? [] : [first];
6088
6312
  }
6089
6313
  function issueKey(issue) {
6090
6314
  return JSON.stringify([
6091
6315
  issue.code ?? "",
6092
6316
  issue.message.key,
6093
- issue.params ?? null
6317
+ issue.check?.id ?? null
6094
6318
  ]);
6095
6319
  }
6096
- function failureText(error, t, locale) {
6097
- const friendly = toFriendlyError(safeMessage(error), void 0, locale);
6098
- return friendly.replaced ? friendly.title : t("errors.failed", { defaultValue: "Failed" });
6320
+ function failureText(error, settled, t, caps) {
6321
+ const texts = technicalTexts(error).filter((text) => text.length <= 1024);
6322
+ const strings = friendlyStringsFrom(t);
6323
+ const friendly = texts.map((text) => toFriendlyError(text, void 0, strings)).find((f) => f.replaced) ?? toFriendlyError(texts[0], void 0, strings);
6324
+ const applied = settled ?? ownDataProperty(error, "applied");
6325
+ if (applied === "partial" || applied === "unknown") {
6326
+ const key = applied === "partial" ? "errors.partlyApplied" : "errors.mayHaveBeenApplied";
6327
+ const sentence = caps.has(key) ? caps.t(key) : applied === "partial" ? "Part of this may have been saved. Check the record before trying again." : "It may have been saved. Check the record before trying again.";
6328
+ const lead = texts.some(isNetworkFailure) ? strings.offline : "";
6329
+ return lead === "" ? sentence : `${lead} ${sentence}`;
6330
+ }
6331
+ const code = ownDataProperty(error, "code");
6332
+ const kind = ownDataProperty(error, "kind");
6333
+ const hasOwnSentence = typeof code === "string" && typeof kind === "string" && kind !== "transport" && Object.hasOwn(RECORD_KIND_KEYS, kind);
6334
+ const message = ownDataProperty(error, "message");
6335
+ if (friendly.replaced && !hasOwnSentence) return (typeof code === "string" ? specificErrorText({
6336
+ code,
6337
+ ...isMessageReference(message) ? { message } : {}
6338
+ }, caps, { record: true }) : void 0) ?? friendly.title;
6339
+ if (typeof code !== "string") return strings.title;
6340
+ const reference = {
6341
+ code,
6342
+ ...isMessageReference(message) ? { message } : {}
6343
+ };
6344
+ return specificErrorText(reference, caps, { record: true }) ?? (typeof kind === "string" ? kindErrorText(kind, caps, { record: true }) : void 0) ?? strings.title;
6099
6345
  }
6100
- function safeMessage(error) {
6346
+ function ownDataProperty(error, name) {
6101
6347
  if (error === null || typeof error !== "object") return void 0;
6102
6348
  try {
6103
- const descriptor = Object.getOwnPropertyDescriptor(error, "message");
6104
- return descriptor !== void 0 && "value" in descriptor && typeof descriptor.value === "string" && descriptor.value.length <= 1024 ? descriptor.value : void 0;
6349
+ const descriptor = Object.getOwnPropertyDescriptor(error, name);
6350
+ return descriptor !== void 0 && "value" in descriptor ? descriptor.value : void 0;
6105
6351
  } catch {
6106
6352
  return;
6107
6353
  }
6108
6354
  }
6355
+ function isMessageReference(value) {
6356
+ return value !== null && typeof value === "object" && typeof value.key === "string";
6357
+ }
6109
6358
  function inlineFlowNames(screen, plan) {
6110
6359
  if (screen === null) return [];
6111
6360
  const names = [];
@@ -6378,31 +6627,35 @@ function PlannedFrame(props) {
6378
6627
  })
6379
6628
  }, name);
6380
6629
  const { PlacementScope } = page;
6630
+ const claimedRoute = useContext(RouteSourceContext);
6381
6631
  const itemNode = page.plan.slots.item?.[0];
6382
6632
  const item = useMemo(() => itemNode === void 0 ? void 0 : itemHandle(itemNode), [itemNode]);
6383
6633
  return jsx(RouteSourceContext.Provider, {
6384
6634
  value: null,
6385
- children: jsx(PlacementScope, { children: jsx(ScreenTreeScope, {
6386
- resource,
6387
- mode: "show",
6388
- compiled,
6389
- page,
6390
- children: jsx(ListItemContext.Provider, {
6391
- value: item,
6392
- children: jsx(FrameView, {
6393
- resource,
6394
- compiled,
6395
- slots
6635
+ children: jsx(ActionRouteContext.Provider, {
6636
+ value: claimedRoute,
6637
+ children: jsx(PlacementScope, { children: jsx(ScreenTreeScope, {
6638
+ resource,
6639
+ mode: "show",
6640
+ compiled,
6641
+ page,
6642
+ children: jsx(ListItemContext.Provider, {
6643
+ value: item,
6644
+ children: jsx(FrameView, {
6645
+ resource,
6646
+ compiled,
6647
+ slots
6648
+ })
6396
6649
  })
6397
- })
6398
- }) })
6650
+ }) })
6651
+ })
6399
6652
  });
6400
6653
  }
6401
6654
  function ListFrameHost({ resource }) {
6402
6655
  const compiled = useCompiledListScreen(resource);
6403
6656
  const hasDataRuntime = useOptionalDataRuntime() !== null;
6404
6657
  const { def } = useResourceListContext();
6405
- const rowsView = (def?.views?.list)?.view ?? "table";
6658
+ const rowsView = def?.views?.list?.initial?.view ?? "table";
6406
6659
  if (compiled === null) return jsx(Arrangement, {});
6407
6660
  if (compiled.nodes.length === 0 && rowsView === "table") {
6408
6661
  const empty = {};
@@ -6421,17 +6674,30 @@ function ListFrameHost({ resource }) {
6421
6674
  }
6422
6675
  //#endregion
6423
6676
  //#region src/list-kit/resource-list-surface.tsx
6424
- const RelatedCards = lazy(async () => ({ default: (await import("./related-list-cards-B6x9HQQp.js")).RelatedCards }));
6425
- const RelatedTimeline = lazy(async () => ({ default: (await import("./related-list-timeline-DEdfd-rr.js")).RelatedTimeline }));
6677
+ function declaresDisplay(field) {
6678
+ return field !== void 0 && resolveSurfaceUi(field, "column").use !== void 0;
6679
+ }
6680
+ const NO_INITIAL_FILTERS = Object.freeze({});
6681
+ const RelatedCards = lazy(async () => ({ default: (await import("./related-list-cards-BIqqY69Y.js")).RelatedCards }));
6682
+ const RelatedTimeline = lazy(async () => ({ default: (await import("./related-list-timeline-CRh7xwLD.js")).RelatedTimeline }));
6426
6683
  function discriminatorOptionLabel(field, value) {
6684
+ const text = fieldOptionText(field, value);
6685
+ if (text !== void 0) return text;
6427
6686
  const declared = field;
6428
6687
  const label = (Array.isArray(declared?.options) ? declared.options : Array.isArray(declared?.meta?.options) ? declared.meta.options : []).find((option) => option?.value === value)?.label;
6429
6688
  return typeof label === "string" ? label : value;
6430
6689
  }
6431
- function ResourceListSurface({ resource, variant, state, fixedFilters, pageSize, search: searchField, columns: columnNames, open, title, view, item, placementKey, onCreate, meta, renderCell, emptyState }) {
6690
+ function declaredInitialSort(def) {
6691
+ return def?.views?.list?.initial?.sort;
6692
+ }
6693
+ function declaredRouteView(def) {
6694
+ return def?.views?.list?.initial?.view;
6695
+ }
6696
+ function ResourceListSurface({ resource, variant, state, fixedFilters, initial, search: searchField, columns: columnEntries, open, title, empty, scope, view, item, placementKey, onCreate, count: countPolicy, rowTarget, readOnly, linkEditing, exportable, layoutId, meta, renderCell, emptyState }) {
6432
6697
  const route = variant === "route";
6433
6698
  const t = useT();
6434
6699
  const optionalT = useOptionalT();
6700
+ const chromeT = useChromeT();
6435
6701
  const tid = useTestIds();
6436
6702
  const listCaps = useI18nCaps(resource);
6437
6703
  const RouteFiltersSlot = useListSlot("filters", resource);
@@ -6446,15 +6712,45 @@ function ResourceListSurface({ resource, variant, state, fixedFilters, pageSize,
6446
6712
  console.error(`[burojs] list.body-removed: list.body.${canonicalResourceName(resource)} is no longer rendered; a whole-body override is a list frame (views.list.frame).`);
6447
6713
  }, [removedBody, resource]);
6448
6714
  const searchDecl = useMemo(() => searchField === void 0 ? void 0 : { fields: [searchField] }, [searchField]);
6449
- const list = useResourceList(resource, route ? { meta } : {
6715
+ const appRegistry = useApp().registry;
6716
+ const effectiveView = route ? declaredRouteView(appRegistry.get(resource)) ?? "table" : view ?? "table";
6717
+ const timeline = effectiveView === "timeline";
6718
+ const initialKey = JSON.stringify(initial ?? null);
6719
+ const placementInitial = useMemo(() => route ? void 0 : {
6720
+ ...initial,
6721
+ filters: initial?.filters ?? NO_INITIAL_FILTERS
6722
+ }, [route, initialKey]);
6723
+ const timelineAxis = (placementInitial?.sort ?? declaredInitialSort(appRegistry.get(resource)))?.[0]?.field;
6724
+ const columnNames = useMemo(() => columnEntries?.map(columnName), [columnEntries]);
6725
+ const list = useResourceList(resource, route ? {
6726
+ meta,
6727
+ ...timeline ? { timeline } : {}
6728
+ } : {
6729
+ ...timeline ? { timeline } : {},
6450
6730
  ...state === void 0 ? {} : { state },
6451
6731
  ...fixedFilters === void 0 ? {} : { fixedFilters },
6452
- ...pageSize === void 0 ? {} : { pageSize },
6732
+ ...placementInitial === void 0 ? {} : { initial: placementInitial },
6453
6733
  ...searchDecl === void 0 ? {} : { search: searchDecl },
6454
- ...columnNames === void 0 ? {} : { columns: columnNames }
6734
+ ...columnEntries === void 0 ? {} : { columns: columnEntries },
6735
+ ...scope === void 0 ? {} : { scope },
6736
+ ...countPolicy === void 0 ? {} : { countPolicy }
6455
6737
  });
6456
6738
  const { def, query, pagination, cursor, count, filters, sorting, columns, selection, savedViews, routing, live, droppedPredicates } = list;
6457
6739
  policyRowsOf(list, list.rows);
6740
+ const selectionRef = useRef(selection);
6741
+ selectionRef.current = selection;
6742
+ const onDetach = linkEditing?.detach?.onDetach;
6743
+ const surfaceLinkEditing = useMemo(() => onDetach === void 0 ? void 0 : { detach: { onDetach: async (detached) => {
6744
+ const left = await onDetach(detached);
6745
+ if (left.length > 0) {
6746
+ const gone = new Set(left);
6747
+ const current = selectionRef.current;
6748
+ if (current.ids.some((id) => gone.has(id))) current.set(current.ids.filter((id) => !gone.has(id)));
6749
+ }
6750
+ return left;
6751
+ } } }, [onDetach]);
6752
+ const onAttach = linkEditing?.attach?.onAttach;
6753
+ const attachBusy = linkEditing?.attach?.busy === true;
6458
6754
  const hasDataRuntime = useOptionalDataRuntime() !== null;
6459
6755
  const scopeLabel = (scope) => scope.id === LOCAL_USER_SCOPE_ID ? t("toolbar.viewsMyScope") : scope.label;
6460
6756
  const createAvailable = useAvailability(resource, "create").state === "available";
@@ -6464,7 +6760,7 @@ function ResourceListSurface({ resource, variant, state, fixedFilters, pageSize,
6464
6760
  const gate = useEntryGate();
6465
6761
  const openQuick = useQuickOpener();
6466
6762
  const effectiveOpen = open ?? (route ? def !== void 0 && !hasRecordPage(def, "show") ? "quick" : "page" : "quick");
6467
- const quickRows = effectiveOpen === "quick";
6763
+ const quickRows = effectiveOpen === "quick" && rowTarget === void 0;
6468
6764
  useDeepLinkOpen(resource, resource, route && quickRows);
6469
6765
  const rowQuick = useMemo(() => quickRows ? (row) => gate(resource, "show", row).state === "open" ? () => void openQuick(resource, String(row.id), { row }) : null : void 0, [
6470
6766
  quickRows,
@@ -6472,14 +6768,36 @@ function ResourceListSurface({ resource, variant, state, fixedFilters, pageSize,
6472
6768
  openQuick,
6473
6769
  resource
6474
6770
  ]);
6475
- const rowHref = useMemo(() => effectiveOpen === "none" ? void 0 : quickRows ? (row) => gate(resource, "show", row).state === "undecided" ? void 0 : routing.rowHref(row) : routing.rowHref, [
6771
+ const registry = useApp().registry;
6772
+ const targetName = rowTarget?.resource;
6773
+ const targetField = rowTarget?.field;
6774
+ const targetGate = useAvailabilityResolver(targetName ?? resource);
6775
+ const targetHref = useMemo(() => {
6776
+ if (targetName === void 0 || targetField === void 0) return void 0;
6777
+ const targetDef = registry.get(targetName);
6778
+ const delegate = targetDef?.routesTo?.show;
6779
+ const showDef = delegate === void 0 ? targetDef : registry.get(delegate);
6780
+ const showPage = targetDef !== void 0 && showDef !== void 0 && hasRecordPage(showDef, "show");
6781
+ return (row) => {
6782
+ const key = row[targetField];
6783
+ if (!showPage || key === null || key === void 0 || key === "") return void 0;
6784
+ if (targetGate("show").state !== "available") return void 0;
6785
+ return resourceShowHref(registry, targetDef, targetName, String(key));
6786
+ };
6787
+ }, [
6788
+ registry,
6789
+ targetName,
6790
+ targetField,
6791
+ targetGate
6792
+ ]);
6793
+ const rowHref = useMemo(() => effectiveOpen === "none" ? void 0 : targetHref !== void 0 ? targetHref : quickRows ? (row) => gate(resource, "show", row).state === "undecided" ? void 0 : routing.rowHref(row) : routing.rowHref, [
6476
6794
  effectiveOpen,
6795
+ targetHref,
6477
6796
  quickRows,
6478
6797
  gate,
6479
6798
  resource,
6480
6799
  routing.rowHref
6481
6800
  ]);
6482
- const effectiveView = route ? (def?.views?.list)?.view ?? "table" : view ?? "table";
6483
6801
  const tableView = effectiveView === "table";
6484
6802
  const rowsViewFor = useMemo(() => tableView ? void 0 : (planned) => ({
6485
6803
  skeleton: effectiveView === "cards" ? jsx(RelatedCardsSkeleton, {}) : jsx(RelatedTimelineSkeleton, {}),
@@ -6491,11 +6809,15 @@ function ResourceListSurface({ resource, variant, state, fixedFilters, pageSize,
6491
6809
  item: route ? planned : item,
6492
6810
  columns: route ? columns.visible : columnNames,
6493
6811
  rowQuick,
6494
- rowHref
6812
+ rowHref,
6813
+ ...empty === void 0 ? {} : { empty }
6495
6814
  };
6496
6815
  return jsx(Suspense, {
6497
6816
  fallback: effectiveView === "cards" ? jsx(RelatedCardsSkeleton, {}) : jsx(RelatedTimelineSkeleton, {}),
6498
- children: effectiveView === "cards" ? jsx(RelatedCards, { ...common }) : jsx(RelatedTimeline, { ...common })
6817
+ children: effectiveView === "cards" ? jsx(RelatedCards, { ...common }) : jsx(RelatedTimeline, {
6818
+ ...common,
6819
+ axis: timelineAxis
6820
+ })
6499
6821
  });
6500
6822
  }
6501
6823
  }), [
@@ -6508,7 +6830,9 @@ function ResourceListSurface({ resource, variant, state, fixedFilters, pageSize,
6508
6830
  columns.visible,
6509
6831
  columnNames,
6510
6832
  rowQuick,
6511
- rowHref
6833
+ rowHref,
6834
+ empty,
6835
+ timelineAxis
6512
6836
  ]);
6513
6837
  const pageTitle = resolveResourceLabel(listCaps, resource, "plural", def);
6514
6838
  const [saveOpen, setSaveOpen] = useState(false);
@@ -6590,6 +6914,10 @@ function ResourceListSurface({ resource, variant, state, fixedFilters, pageSize,
6590
6914
  toast.error(t("toolbar.viewsForkFailed"));
6591
6915
  }
6592
6916
  };
6917
+ const columnNamesDisplay = (col, field) => {
6918
+ const resolved = list.columns.ui[col];
6919
+ return resolved === void 0 ? declaresDisplay(field) : resolved.display.use !== void 0;
6920
+ };
6593
6921
  const renderCellLadder = (col, value, row) => {
6594
6922
  if (def?.types && col === def.types.by) {
6595
6923
  const raw = typeof value === "string" ? value : "";
@@ -6601,20 +6929,21 @@ function ResourceListSurface({ resource, variant, state, fixedFilters, pageSize,
6601
6929
  const fieldDef = def.fields[col];
6602
6930
  const inline = discriminatorOptionLabel(fieldDef, raw);
6603
6931
  const namespace = enumNamespaceOf(fieldDef);
6604
- const label = namespace === void 0 ? resolveContentSlot(listCaps, [fieldOptionKey(col, raw)], { default: inline }) : resolveEnumLabel(listCaps, namespace, raw, inline);
6932
+ const label = namespace === void 0 ? resolveFieldOptionLabel(listCaps, col, raw, inline) : resolveEnumLabel(listCaps, namespace, raw, inline);
6605
6933
  return jsx(Badge, {
6606
6934
  variant: "secondary",
6607
6935
  "data-testid": `list-cell-type:${resource}:${raw}`,
6608
6936
  children: label
6609
6937
  });
6610
6938
  }
6939
+ const custom = (renderCell ?? CellSlot)?.(col, value, row);
6940
+ if (custom !== void 0) return custom;
6611
6941
  const fieldDef = def?.fields[col];
6612
- const enumName = fieldDef?.opts?.enum ?? fieldDef?.ui?.enum;
6613
- if (enumName && typeof value === "string") return jsx(EnumBadge, {
6942
+ const enumName = enumBindingKey(fieldDef?.opts?.enum ?? fieldDef?.ui?.enum);
6943
+ if (enumName && typeof value === "string" && !columnNamesDisplay(col, fieldDef)) return jsx(EnumBadge, {
6614
6944
  enumName,
6615
6945
  value
6616
6946
  });
6617
- return (renderCell ?? CellSlot)?.(col, value, row);
6618
6947
  };
6619
6948
  const surface = {
6620
6949
  resource,
@@ -6642,8 +6971,16 @@ function ResourceListSurface({ resource, variant, state, fixedFilters, pageSize,
6642
6971
  }
6643
6972
  },
6644
6973
  rowsViewFor,
6645
- emptyState,
6646
- renderCell: renderCellLadder
6974
+ emptyState: empty !== void 0 && !list.filters.isFiltered && list.pagination.page <= 1 && (list.cursor === null || list.cursor.next === null) ? jsx("p", {
6975
+ "data-buro-region": "empty",
6976
+ className: "text-sm text-muted-foreground",
6977
+ children: empty
6978
+ }) : emptyState,
6979
+ renderCell: renderCellLadder,
6980
+ readOnly: readOnly === true,
6981
+ exportable: exportable !== false,
6982
+ ...layoutId === void 0 ? {} : { layoutId },
6983
+ ...surfaceLinkEditing === void 0 ? {} : { linkEditing: surfaceLinkEditing }
6647
6984
  };
6648
6985
  return jsx(ResourceListProvider, {
6649
6986
  value: list,
@@ -6656,22 +6993,44 @@ function ResourceListSurface({ resource, variant, state, fixedFilters, pageSize,
6656
6993
  resource,
6657
6994
  mode: "list",
6658
6995
  title: pageTitle
6659
- }) : title === void 0 && onCreate === void 0 ? null : jsxs("div", {
6996
+ }) : title === void 0 && onCreate === void 0 && onAttach === void 0 ? null : jsxs("div", {
6660
6997
  className: "flex items-center justify-between gap-2",
6661
- children: [title === void 0 ? jsx("span", {}) : jsx("h3", {
6662
- className: "text-sm font-semibold",
6663
- children: title
6664
- }), onCreate === void 0 ? null : jsxs(Button, {
6665
- type: "button",
6666
- size: "sm",
6667
- variant: "outline",
6668
- "data-testid": `list-create:${resource}`,
6669
- onClick: onCreate,
6670
- children: [jsx(IconPlus, {
6671
- className: "mr-1 h-4 w-4",
6672
- "aria-hidden": "true"
6673
- }), optionalT("actions.create", { defaultValue: "Create" })]
6674
- })]
6998
+ children: [
6999
+ title === void 0 ? jsx("span", {}) : jsx("h3", {
7000
+ className: "text-sm font-semibold",
7001
+ children: title
7002
+ }),
7003
+ onAttach === void 0 ? null : jsxs(Button, {
7004
+ type: "button",
7005
+ size: "sm",
7006
+ variant: "outline",
7007
+ "data-testid": `list-link-attach:${resource}`,
7008
+ onClick: onAttach,
7009
+ disabled: attachBusy,
7010
+ "aria-busy": attachBusy,
7011
+ children: [attachBusy ? jsx(IconLoader2, {
7012
+ className: "mr-1 h-4 w-4 animate-spin",
7013
+ "aria-hidden": "true"
7014
+ }) : jsx(IconLink, {
7015
+ className: "mr-1 h-4 w-4",
7016
+ "aria-hidden": "true"
7017
+ }), optionalT("field.link.attach", { defaultValue: "Attach" })]
7018
+ }),
7019
+ onCreate === void 0 ? null : jsxs(Button, {
7020
+ type: "button",
7021
+ size: "sm",
7022
+ variant: "outline",
7023
+ "data-testid": `list-create:${resource}`,
7024
+ onClick: onCreate,
7025
+ children: [jsx(IconPlus, {
7026
+ className: "mr-1 h-4 w-4",
7027
+ "aria-hidden": "true"
7028
+ }), optionalT("actions.create", {
7029
+ resource,
7030
+ defaultValue: "Create"
7031
+ })]
7032
+ })
7033
+ ]
6675
7034
  }),
6676
7035
  hasDataRuntime && jsx(ExportRunWatcherMount, {}),
6677
7036
  droppedPredicates > 0 && jsx("p", {
@@ -6720,13 +7079,13 @@ function ResourceListSurface({ resource, variant, state, fixedFilters, pageSize,
6720
7079
  variant: "outline",
6721
7080
  onClick: () => setSaveOpen(false),
6722
7081
  "data-testid": `list-saved-view-save-cancel:${resource}`,
6723
- children: t("actions.cancel")
7082
+ children: chromeT("actions.cancel")
6724
7083
  }), jsx(Button, {
6725
7084
  type: "button",
6726
7085
  onClick: handleSaveConfirm,
6727
7086
  disabled: saveLabel.trim().length === 0,
6728
7087
  "data-testid": `list-saved-view-save-confirm:${resource}`,
6729
- children: t("actions.save")
7088
+ children: chromeT("actions.save")
6730
7089
  })] })
6731
7090
  ]
6732
7091
  })
@@ -6763,7 +7122,7 @@ function ResourceListSurface({ resource, variant, state, fixedFilters, pageSize,
6763
7122
  variant: "outline",
6764
7123
  onClick: () => handleDeleteUserView(entry.id),
6765
7124
  "data-testid": `list-saved-view-manage-delete:${resource}:${entry.id}`,
6766
- children: t("actions.delete")
7125
+ children: chromeT("actions.delete")
6767
7126
  }) : jsx("span", {
6768
7127
  className: "shrink-0 text-xs text-muted-foreground",
6769
7128
  children: t("toolbar.viewsReadOnly")
@@ -6775,7 +7134,7 @@ function ResourceListSurface({ resource, variant, state, fixedFilters, pageSize,
6775
7134
  variant: "outline",
6776
7135
  onClick: () => setManageOpen(false),
6777
7136
  "data-testid": `list-saved-view-manage-close:${resource}`,
6778
- children: t("actions.close")
7137
+ children: chromeT("actions.close")
6779
7138
  }) })
6780
7139
  ]
6781
7140
  })
@@ -6808,13 +7167,13 @@ function ResourceListSurface({ resource, variant, state, fixedFilters, pageSize,
6808
7167
  setRenameTarget(null);
6809
7168
  },
6810
7169
  "data-testid": `list-saved-view-rename-cancel:${resource}`,
6811
- children: t("actions.cancel")
7170
+ children: chromeT("actions.cancel")
6812
7171
  }), jsx(Button, {
6813
7172
  type: "button",
6814
7173
  onClick: handleRenameConfirm,
6815
7174
  disabled: renameLabel.trim().length === 0,
6816
7175
  "data-testid": `list-saved-view-rename-confirm:${resource}`,
6817
- children: t("actions.save")
7176
+ children: chromeT("actions.save")
6818
7177
  })] })
6819
7178
  ]
6820
7179
  })
@@ -6849,7 +7208,7 @@ function ResourceListSurface({ resource, variant, state, fixedFilters, pageSize,
6849
7208
  setForkTarget(null);
6850
7209
  },
6851
7210
  "data-testid": `list-saved-view-fork-cancel:${resource}`,
6852
- children: t("actions.cancel")
7211
+ children: chromeT("actions.cancel")
6853
7212
  }), jsx(Button, {
6854
7213
  type: "button",
6855
7214
  onClick: handleForkConfirm,
@@ -6872,6 +7231,797 @@ function ExportRunWatcherMount() {
6872
7231
  return null;
6873
7232
  }
6874
7233
  //#endregion
7234
+ //#region src/fields/use-relation-picker.tsx
7235
+ const noop = () => void 0;
7236
+ function safeKey(input) {
7237
+ if (input === void 0 || input === null) return void 0;
7238
+ try {
7239
+ return resourceKey(input);
7240
+ } catch {
7241
+ return;
7242
+ }
7243
+ }
7244
+ function sessionIdentity(runtime) {
7245
+ const state = runtime.dataSession.getState();
7246
+ const snapshot = state.status === "ready" ? state.snapshot : state.target;
7247
+ const selection = snapshot.scope?.selection;
7248
+ const partition = selection === void 0 ? snapshot.scopeKey : [snapshot.scopeKey, selection];
7249
+ let scopeKey;
7250
+ try {
7251
+ scopeKey = JSON.stringify(partition) ?? "";
7252
+ } catch {
7253
+ scopeKey = String(snapshot.scopeKey);
7254
+ }
7255
+ return {
7256
+ sessionKey: snapshot.sessionKey,
7257
+ scopeKey
7258
+ };
7259
+ }
7260
+ function PickerHost(props) {
7261
+ const { register } = props;
7262
+ React.useEffect(() => register(), [register]);
7263
+ return jsx(Fragment$1, { children: props.children });
7264
+ }
7265
+ function useRelationPicker(options) {
7266
+ const app = useApp();
7267
+ const runtime = useDataRuntime();
7268
+ const frame = useOptionalRecordFrame();
7269
+ const frames = useRecordScopeFrames();
7270
+ const multiple = options.multiple === true;
7271
+ const fieldName = options.field;
7272
+ let to;
7273
+ let declaredBy;
7274
+ let declaredCandidates;
7275
+ if (fieldName !== void 0) {
7276
+ const meta = ((frame === null ? void 0 : app.registry.get(frame.resource)?.fields)?.[fieldName])?.meta;
7277
+ to = safeKey(meta?.to);
7278
+ declaredBy = typeof meta?.by === "string" ? meta.by : void 0;
7279
+ declaredCandidates = Array.isArray(meta?.candidates) ? meta.candidates : void 0;
7280
+ } else to = safeKey(options.to);
7281
+ const target = to ?? "";
7282
+ const by = options.valueField ?? declaredBy;
7283
+ const scope = options.scope ?? "context";
7284
+ const selection = useCandidateSelection({
7285
+ to,
7286
+ candidates: options.candidates ?? declaredCandidates,
7287
+ sources: frame?.controller.sources,
7288
+ resource: frame?.resource ?? "",
7289
+ fieldName: fieldName ?? target,
7290
+ fixedFilters: options.fixedFilters
7291
+ });
7292
+ const held = selection.gate !== void 0;
7293
+ const pendingRef = React.useRef(null);
7294
+ const [active, setActive] = React.useState(null);
7295
+ const activeRef = React.useRef(null);
7296
+ activeRef.current = active;
7297
+ const rowsRef = React.useRef(new Map());
7298
+ const createdRowRef = React.useRef(void 0);
7299
+ const alive = React.useRef(true);
7300
+ const mounts = React.useRef(0);
7301
+ const sequence = React.useRef(0);
7302
+ const latest = React.useRef({
7303
+ multiple,
7304
+ select: options.select,
7305
+ idField: "id",
7306
+ valueField: "id",
7307
+ available: false,
7308
+ frames
7309
+ });
7310
+ const project = React.useCallback((row) => {
7311
+ const select = latest.current.select;
7312
+ if (select === void 0) return { ...row };
7313
+ const out = {};
7314
+ const key = row[latest.current.idField] !== void 0 ? row[latest.current.idField] : row.id;
7315
+ if (key !== void 0) out.id = key;
7316
+ const { valueField } = latest.current;
7317
+ if (Object.hasOwn(row, valueField)) out[valueField] = row[valueField];
7318
+ for (const name of select) if (Object.hasOwn(row, name)) out[name] = row[name];
7319
+ return out;
7320
+ }, []);
7321
+ const settle = React.useCallback((result) => {
7322
+ const pending = pendingRef.current;
7323
+ if (pending === null) return;
7324
+ pendingRef.current = null;
7325
+ pending.detach();
7326
+ if (alive.current) setActive(null);
7327
+ pending.resolve(result);
7328
+ }, []);
7329
+ const toggle = React.useCallback((key, row) => {
7330
+ setActive((current) => {
7331
+ if (current === null) return current;
7332
+ const has = current.selected.some((k) => String(k) === String(key));
7333
+ if (has) rowsRef.current.delete(String(key));
7334
+ else if (row !== void 0) rowsRef.current.set(String(key), row);
7335
+ const selected = has ? current.selected.filter((k) => String(k) !== String(key)) : [...current.selected, key];
7336
+ const next = {
7337
+ ...current,
7338
+ selected
7339
+ };
7340
+ activeRef.current = next;
7341
+ return next;
7342
+ });
7343
+ }, []);
7344
+ const onValue = React.useCallback((value) => {
7345
+ const row = createdRowRef.current;
7346
+ createdRowRef.current = void 0;
7347
+ if (latest.current.multiple) {
7348
+ const current = activeRef.current;
7349
+ if (current !== null && !current.selected.some((k) => String(k) === String(value))) toggle(value, row);
7350
+ return;
7351
+ }
7352
+ settle({
7353
+ kind: "selected",
7354
+ keys: [value],
7355
+ rows: row === void 0 ? [] : [project(row)]
7356
+ });
7357
+ }, [
7358
+ project,
7359
+ settle,
7360
+ toggle
7361
+ ]);
7362
+ const wiring = useRelationSelect({
7363
+ to: target,
7364
+ by,
7365
+ fixedFilters: selection.fixedFilters,
7366
+ scope,
7367
+ ...fieldName === void 0 ? {} : { scopeField: fieldName },
7368
+ held,
7369
+ onValue
7370
+ });
7371
+ latest.current = {
7372
+ multiple,
7373
+ select: options.select,
7374
+ idField: wiring.idField,
7375
+ valueField: wiring.valueField,
7376
+ available: to !== void 0 && wiring.targetDef !== void 0 && !held,
7377
+ frames
7378
+ };
7379
+ const { create: shared } = wiring;
7380
+ const create = React.useMemo(() => ({
7381
+ ...shared,
7382
+ start: (openCreate) => shared.start(() => {
7383
+ const opened = openCreate();
7384
+ opened.then((row) => {
7385
+ createdRowRef.current = row === null ? void 0 : { ...row };
7386
+ }, noop);
7387
+ return opened;
7388
+ })
7389
+ }), [shared]);
7390
+ React.useEffect(() => runtime.dataSession.subscribe(() => {
7391
+ const pending = pendingRef.current;
7392
+ if (pending === null) return;
7393
+ const now = sessionIdentity(runtime);
7394
+ if (now.sessionKey !== pending.sessionKey || now.scopeKey !== pending.scopeKey) settle({
7395
+ kind: "cancelled",
7396
+ reason: "session-changed"
7397
+ });
7398
+ }), [runtime, settle]);
7399
+ React.useEffect(() => {
7400
+ alive.current = true;
7401
+ return () => {
7402
+ alive.current = false;
7403
+ settle({
7404
+ kind: "cancelled",
7405
+ reason: "unmounted"
7406
+ });
7407
+ };
7408
+ }, [settle]);
7409
+ React.useEffect(() => {
7410
+ if (held) settle({
7411
+ kind: "cancelled",
7412
+ reason: "unavailable"
7413
+ });
7414
+ }, [held, settle]);
7415
+ const register = React.useCallback(() => {
7416
+ mounts.current += 1;
7417
+ return () => {
7418
+ mounts.current -= 1;
7419
+ if (mounts.current === 0) settle({
7420
+ kind: "cancelled",
7421
+ reason: "unmounted"
7422
+ });
7423
+ };
7424
+ }, [settle]);
7425
+ const open = React.useCallback((opts = {}) => new Promise((resolve) => {
7426
+ settle({
7427
+ kind: "cancelled",
7428
+ reason: "superseded"
7429
+ });
7430
+ if (mounts.current === 0) {
7431
+ if (process.env.NODE_ENV !== "production") console.error("[burojs] useRelationPicker: open() was called but its element is not rendered — render `handle.element` once in your tree.");
7432
+ resolve({
7433
+ kind: "cancelled",
7434
+ reason: "not-mounted"
7435
+ });
7436
+ return;
7437
+ }
7438
+ const { signal } = opts;
7439
+ if (signal?.aborted === true) {
7440
+ resolve({
7441
+ kind: "cancelled",
7442
+ reason: "aborted"
7443
+ });
7444
+ return;
7445
+ }
7446
+ if (!latest.current.available) {
7447
+ resolve({
7448
+ kind: "cancelled",
7449
+ reason: "unavailable"
7450
+ });
7451
+ return;
7452
+ }
7453
+ sequence.current += 1;
7454
+ const id = sequence.current;
7455
+ const onAbort = () => {
7456
+ if (pendingRef.current?.id === id) settle({
7457
+ kind: "cancelled",
7458
+ reason: "aborted"
7459
+ });
7460
+ };
7461
+ signal?.addEventListener("abort", onAbort, { once: true });
7462
+ pendingRef.current = {
7463
+ id,
7464
+ ...sessionIdentity(runtime),
7465
+ resolve,
7466
+ detach: () => signal?.removeEventListener("abort", onAbort)
7467
+ };
7468
+ rowsRef.current = new Map();
7469
+ createdRowRef.current = void 0;
7470
+ const next = {
7471
+ id,
7472
+ frames: latest.current.frames,
7473
+ selected: opts.selected ?? []
7474
+ };
7475
+ activeRef.current = next;
7476
+ setActive(next);
7477
+ }), [runtime, settle]);
7478
+ const onSelectRow = React.useCallback((row) => {
7479
+ const field = wiring.valueField;
7480
+ const value = field === "id" ? row.id : row[field];
7481
+ if (typeof value !== "string" && typeof value !== "number") return;
7482
+ settle({
7483
+ kind: "selected",
7484
+ keys: [value],
7485
+ rows: [project(row)]
7486
+ });
7487
+ }, [
7488
+ wiring.valueField,
7489
+ settle,
7490
+ project
7491
+ ]);
7492
+ const onDone = React.useCallback(() => {
7493
+ const keys = activeRef.current?.selected ?? [];
7494
+ const rows = keys.flatMap((key) => {
7495
+ const row = rowsRef.current.get(String(key));
7496
+ return row === void 0 ? [] : [project(row)];
7497
+ });
7498
+ settle({
7499
+ kind: "selected",
7500
+ keys,
7501
+ rows
7502
+ });
7503
+ }, [settle, project]);
7504
+ const onOpenChange = React.useCallback((next) => {
7505
+ if (!next) settle({
7506
+ kind: "cancelled",
7507
+ reason: "dismissed"
7508
+ });
7509
+ }, [settle]);
7510
+ return {
7511
+ open,
7512
+ element: jsx(PickerHost, {
7513
+ register,
7514
+ children: active === null || held ? null : jsxs(RecordScopeChain, {
7515
+ frames: active.frames,
7516
+ children: [jsx(LazyRelationPickerDialog, {
7517
+ to: target,
7518
+ open: true,
7519
+ onOpenChange,
7520
+ onSelect: noop,
7521
+ onSelectRow,
7522
+ valueField: wiring.valueField,
7523
+ ...selection.fixedFilters === void 0 ? {} : { fixedFilters: selection.fixedFilters },
7524
+ scope,
7525
+ ...fieldName === void 0 ? {} : { scopeField: fieldName },
7526
+ ...options.create === void 0 ? {} : {
7527
+ create,
7528
+ ...options.create.seeds === void 0 ? {} : { createSeeds: options.create.seeds }
7529
+ },
7530
+ fieldName: fieldName ?? target,
7531
+ ...options.disabledKeys === void 0 ? {} : { disabledKeys: options.disabledKeys },
7532
+ ...options.disabledLabel === void 0 ? {} : { disabledLabel: options.disabledLabel },
7533
+ ...multiple ? {
7534
+ multiple: true,
7535
+ selected: active.selected,
7536
+ onToggle: (value, row) => toggle(value, row),
7537
+ onDone
7538
+ } : {}
7539
+ }), wiring.reader]
7540
+ }, active.id)
7541
+ })
7542
+ };
7543
+ }
7544
+ //#endregion
7545
+ //#region src/blocks/link-write-summary.tsx
7546
+ function LinkWriteSummary(props) {
7547
+ const { report, titles, onClose } = props;
7548
+ const testid = props.testid ?? "link-write-summary";
7549
+ const t = useOptionalT();
7550
+ const caps = useI18nCaps();
7551
+ const chromeT = useChromeT();
7552
+ const heading = t("field.link.partial", { defaultValue: "Some links were not changed" });
7553
+ return jsx(Dialog, {
7554
+ open: true,
7555
+ onOpenChange: (open) => {
7556
+ if (!open) onClose();
7557
+ },
7558
+ children: jsxs(DialogContent, {
7559
+ "data-testid": testid,
7560
+ className: "flex max-h-[85vh] flex-col overflow-hidden",
7561
+ children: [
7562
+ jsxs(DialogHeader, { children: [jsx(DialogTitle, { children: heading }), jsx(DialogDescription, {
7563
+ className: "sr-only",
7564
+ children: heading
7565
+ })] }),
7566
+ jsx("ul", {
7567
+ className: "flex min-h-0 flex-1 flex-col gap-2 overflow-y-auto overscroll-contain",
7568
+ children: report.failed.map(({ id, error }) => jsxs("li", {
7569
+ "data-testid": `${testid}:item:${id}`,
7570
+ className: "text-sm",
7571
+ children: [jsx("span", {
7572
+ className: "font-medium",
7573
+ children: titles[id] ?? id
7574
+ }), jsx("span", {
7575
+ className: "text-destructive",
7576
+ children: `: ${resolveErrorText(error, caps)}`
7577
+ })]
7578
+ }, id))
7579
+ }),
7580
+ jsx(DialogFooter, { children: jsx(Button, {
7581
+ type: "button",
7582
+ onClick: onClose,
7583
+ "data-testid": `${testid}:close`,
7584
+ children: chromeT("actions.close", { defaultValue: "Close" })
7585
+ }) })
7586
+ ]
7587
+ })
7588
+ });
7589
+ }
7590
+ //#endregion
7591
+ //#region src/blocks/link-attach.tsx
7592
+ const NO_ISSUES = Object.freeze([]);
7593
+ function useLinkPanelWrites(options) {
7594
+ const { owner, junction, target, link, targetKey, valueField, junctionIdField, displayField, scope, record, ownerKey, placement } = options;
7595
+ const app = useApp();
7596
+ const runtime = useDataRuntime();
7597
+ const t = useOptionalT();
7598
+ const rights = useLinkRights(owner, link, {
7599
+ surface: "panel",
7600
+ record,
7601
+ ...placement === void 0 ? {} : { placement }
7602
+ });
7603
+ const writer = useLinkWriter(owner, link);
7604
+ const linkedKeys = useLinkedKeys(owner, link);
7605
+ const reverse = useMemo(() => reverseLinkOf((name) => app.registry.get(name)?.fields, owner, link), [
7606
+ app.registry,
7607
+ owner,
7608
+ link
7609
+ ]);
7610
+ let liveHides = false;
7611
+ if (reverse !== void 0) try {
7612
+ const { providerId } = runtime.app.resolveDataBinding(target);
7613
+ liveHides = runtime.providers.resolve(providerId).provider.readRelations?.links === true && linkCapabilitiesOf(runtime, providerId)?.links?.filter.none !== false;
7614
+ } catch {
7615
+ liveHides = false;
7616
+ }
7617
+ const liveHidesRef = useRef(liveHides);
7618
+ liveHidesRef.current = liveHides;
7619
+ const [frozenHides, setFrozenHides] = useState(null);
7620
+ const hides = frozenHides ?? liveHides;
7621
+ const ownerKeyId = JSON.stringify(ownerKey ?? null);
7622
+ const hidden = useMemo(() => hides && reverse !== void 0 ? [{
7623
+ field: reverse,
7624
+ op: "hasNone",
7625
+ value: [ownerKey]
7626
+ }] : void 0, [
7627
+ hides,
7628
+ reverse,
7629
+ ownerKeyId
7630
+ ]);
7631
+ const disabledKeys = useCallback((keys, signal) => linkedKeys(record, keys, signal), [linkedKeys, record]);
7632
+ const disabledLabel = useCallback((count) => t("field.link.alreadyLinked", {
7633
+ count,
7634
+ defaultValue: `${count} already linked`
7635
+ }), [t]);
7636
+ const picker = useRelationPicker({
7637
+ to: target,
7638
+ multiple: true,
7639
+ valueField,
7640
+ scope,
7641
+ ...hidden === void 0 ? {
7642
+ disabledKeys,
7643
+ disabledLabel
7644
+ } : { fixedFilters: hidden }
7645
+ });
7646
+ const openPicker = picker.open;
7647
+ const [summary, setSummary] = useState(null);
7648
+ const finish = useCallback((report, titles, done) => {
7649
+ if (report.outcome !== "success") {
7650
+ setSummary({
7651
+ report,
7652
+ titles
7653
+ });
7654
+ return;
7655
+ }
7656
+ const count = report.succeeded.length;
7657
+ if (count === 0) return;
7658
+ toast.success(t(`field.link.${done}`, {
7659
+ count,
7660
+ defaultValue: `${count} ${count === 1 ? "link" : "links"} ${done}`
7661
+ }));
7662
+ }, [t]);
7663
+ const attaching = useRef(false);
7664
+ const [attachWriting, setAttachWriting] = useState(false);
7665
+ const onAttach = useCallback(() => {
7666
+ if (attaching.current) return;
7667
+ attaching.current = true;
7668
+ (async () => {
7669
+ try {
7670
+ setFrozenHides(liveHidesRef.current);
7671
+ let result;
7672
+ try {
7673
+ result = await openPicker();
7674
+ } finally {
7675
+ setFrozenHides(null);
7676
+ }
7677
+ if (result.kind !== "selected" || result.keys.length === 0) return;
7678
+ const titles = {};
7679
+ for (const row of result.rows) {
7680
+ const key = valueField === "id" ? row.id : row[valueField];
7681
+ const title = row[displayField];
7682
+ if (key !== void 0 && key !== null && title !== void 0 && title !== null) titles[String(key)] = String(title);
7683
+ }
7684
+ setAttachWriting(true);
7685
+ try {
7686
+ finish(await writer.attach(result.keys, record), titles, "attached");
7687
+ } finally {
7688
+ setAttachWriting(false);
7689
+ }
7690
+ } finally {
7691
+ attaching.current = false;
7692
+ }
7693
+ })();
7694
+ }, [
7695
+ displayField,
7696
+ finish,
7697
+ openPicker,
7698
+ record,
7699
+ valueField,
7700
+ writer
7701
+ ]);
7702
+ const pending = useRef(null);
7703
+ const [confirming, setConfirming] = useState(null);
7704
+ const detaching = useRef(false);
7705
+ const [busy, setBusy] = useState(false);
7706
+ const onDetach = useCallback((rows) => new Promise((resolve) => {
7707
+ pending.current?.resolve([]);
7708
+ pending.current = {
7709
+ rows,
7710
+ resolve
7711
+ };
7712
+ setConfirming(rows.length);
7713
+ }), []);
7714
+ useEffect(() => () => {
7715
+ pending.current?.resolve([]);
7716
+ pending.current = null;
7717
+ }, []);
7718
+ const cancel = useCallback(() => {
7719
+ if (detaching.current) return;
7720
+ pending.current?.resolve([]);
7721
+ pending.current = null;
7722
+ setConfirming(null);
7723
+ }, []);
7724
+ const proceed = useCallback(() => {
7725
+ const current = pending.current;
7726
+ if (current === null || detaching.current) return;
7727
+ detaching.current = true;
7728
+ setBusy(true);
7729
+ (async () => {
7730
+ try {
7731
+ const titles = {};
7732
+ for (const row of current.rows) {
7733
+ const id = row[junctionIdField];
7734
+ if (id === void 0 || id === null) continue;
7735
+ const title = row[`${targetKey}.${displayField}`] ?? row[targetKey];
7736
+ if (title !== void 0 && title !== null) titles[String(id)] = String(title);
7737
+ }
7738
+ const report = await writer.detach(current.rows, record);
7739
+ pending.current = null;
7740
+ setConfirming(null);
7741
+ finish(report, titles, "detached");
7742
+ const left = new Set(report.succeeded);
7743
+ current.resolve(current.rows.filter((row) => {
7744
+ const id = row[junctionIdField];
7745
+ return id !== void 0 && id !== null && left.has(String(id));
7746
+ }).map((row) => String(row.id)));
7747
+ } finally {
7748
+ detaching.current = false;
7749
+ setBusy(false);
7750
+ }
7751
+ })();
7752
+ }, [
7753
+ displayField,
7754
+ finish,
7755
+ junctionIdField,
7756
+ record,
7757
+ targetKey,
7758
+ writer
7759
+ ]);
7760
+ const detachLabel = t("field.link.detach", { defaultValue: "Detach" });
7761
+ const count = confirming ?? 0;
7762
+ const gate = {
7763
+ resource: junction,
7764
+ open: confirming !== null,
7765
+ busy,
7766
+ header: t("field.link.confirmDetach", {
7767
+ count,
7768
+ defaultValue: `Detach ${count} ${count === 1 ? "link" : "links"}? The linked records themselves are not deleted.`
7769
+ }),
7770
+ blockers: NO_ISSUES,
7771
+ confirmable: NO_ISSUES,
7772
+ proceed,
7773
+ cancel
7774
+ };
7775
+ const attachAllowed = rights.attach.allowed;
7776
+ const detachAllowed = rights.detach.allowed;
7777
+ return {
7778
+ linkEditing: useMemo(() => !attachAllowed && !detachAllowed ? void 0 : {
7779
+ ...attachAllowed ? { attach: {
7780
+ onAttach,
7781
+ busy: attachWriting
7782
+ } } : {},
7783
+ ...detachAllowed ? { detach: { onDetach } } : {}
7784
+ }, [
7785
+ attachAllowed,
7786
+ attachWriting,
7787
+ detachAllowed,
7788
+ onAttach,
7789
+ onDetach
7790
+ ]),
7791
+ element: jsxs(Fragment$1, { children: [
7792
+ picker.element,
7793
+ jsx(CheckConfirmDialog, {
7794
+ state: gate,
7795
+ testid: `link-detach-confirm:${junction}`,
7796
+ title: detachLabel,
7797
+ proceedLabel: detachLabel,
7798
+ tone: "destructive"
7799
+ }),
7800
+ summary === null ? null : jsx(LinkWriteSummary, {
7801
+ report: summary.report,
7802
+ titles: summary.titles,
7803
+ onClose: () => setSummary(null),
7804
+ testid: `link-write-summary:${junction}`
7805
+ })
7806
+ ] })
7807
+ };
7808
+ }
7809
+ //#endregion
7810
+ //#region src/quick/peek-filter.ts
7811
+ function peekFieldDefinition(def, name) {
7812
+ return declaredField(def, name);
7813
+ }
7814
+ function peekLeafAllowed(def, name, capabilityFor, fieldVisible) {
7815
+ const field = peekFieldDefinition(def, name);
7816
+ if (field === void 0 || fieldSensitivity(field) !== void 0) return false;
7817
+ if (capabilityFor("view", {
7818
+ kind: "column",
7819
+ name
7820
+ }).allowed === false) return false;
7821
+ return fieldVisible(name);
7822
+ }
7823
+ function peekRow(def, row, idField, capabilityFor, fieldVisible, drawn) {
7824
+ const out = {};
7825
+ for (const [name, value] of Object.entries(row)) out[name] = name === idField || (drawn === void 0 || drawn.has(name)) && peekLeafAllowed(def, name, capabilityFor, fieldVisible) ? value : null;
7826
+ return Object.freeze(out);
7827
+ }
7828
+ function peekRecord(def, row, idField, capabilityFor, fieldVisible) {
7829
+ if (def === void 0) return Object.freeze({ [idField]: row[idField] });
7830
+ return peekRow(def, row, idField, capabilityFor, fieldVisible);
7831
+ }
7832
+ //#endregion
7833
+ //#region src/blocks/link-panel.tsx
7834
+ const isRecord = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
7835
+ const isResourceHandle = (value) => isRecord(value) && value.kind === "resource";
7836
+ function qualify(ref) {
7837
+ try {
7838
+ return resourceKey(normalizeEntityRef(isResourceHandle(ref) ? ref.ref : ref));
7839
+ } catch {
7840
+ return;
7841
+ }
7842
+ }
7843
+ function linkField(registry, owner, link) {
7844
+ if (owner === void 0) return void 0;
7845
+ const def = registry.get(owner);
7846
+ const field = def === void 0 ? void 0 : peekFieldDefinition(def, link);
7847
+ if (!isRecord(field)) return void 0;
7848
+ const declaration = linkOf(field);
7849
+ return declaration === void 0 ? void 0 : {
7850
+ field,
7851
+ declaration
7852
+ };
7853
+ }
7854
+ function linkTarget(registry, owner, link) {
7855
+ const found = linkField(registry, owner, link);
7856
+ if (found === void 0 || owner === void 0) return void 0;
7857
+ return qualify(found.field.meta?.to);
7858
+ }
7859
+ function idFieldOf(def) {
7860
+ const declared = isRecord(def) && isRecord(def.data) ? def.data.idField : void 0;
7861
+ return typeof declared === "string" && declared.length > 0 ? declared : "id";
7862
+ }
7863
+ function byOf(field) {
7864
+ const by = isRecord(field) && isRecord(field.meta) ? field.meta.by : void 0;
7865
+ return typeof by === "string" && by.length > 0 ? by : void 0;
7866
+ }
7867
+ const MAX_TARGET_HOPS = 2;
7868
+ function targetColumns(registry, target, def) {
7869
+ if (!isRecord(def) || !isRecord(def.fields)) return [];
7870
+ const source = def;
7871
+ const views = isRecord(def.views) ? def.views : void 0;
7872
+ const list = views !== void 0 && isRecord(views.list) ? views.list : void 0;
7873
+ return (list !== void 0 && Array.isArray(list.columns) ? list.columns.flatMap((entry) => {
7874
+ const name = isRecord(entry) ? entry.field : entry;
7875
+ return typeof name === "string" ? [name] : [];
7876
+ }) : dtoFields(source, "list")).filter((name) => {
7877
+ if (name.includes(".")) return name.split(".").length - 1 <= MAX_TARGET_HOPS && resolveColumnField(registry, target, name) !== void 0;
7878
+ const field = peekFieldDefinition(source, name);
7879
+ return isRecord(field) && !isRelationField(field) && fieldSensitivity(field) === void 0;
7880
+ });
7881
+ }
7882
+ function LinkPanel(props) {
7883
+ const { link, owner, own, empty } = props;
7884
+ const app = useApp();
7885
+ const frame = usePlacementFrame();
7886
+ const routeScreen = useIsRouteScreen();
7887
+ const record = useOptionalRecordFrame()?.record;
7888
+ const runtime = useOptionalDataRuntime();
7889
+ const storage = useMemo(() => {
7890
+ const found = linkField(app.registry, owner, link);
7891
+ if (found === void 0) return void 0;
7892
+ const { declaration } = found;
7893
+ const junction = qualify(declaration.junction);
7894
+ const target = qualify(found.field.meta?.to);
7895
+ if (junction === void 0 || target === void 0) return void 0;
7896
+ const junctionDef = app.registry.get(junction);
7897
+ if (junctionDef === void 0) return void 0;
7898
+ const junctionFields = junctionDef.fields;
7899
+ const targetDef = app.registry.get(target);
7900
+ const targetBy = byOf(junctionFields[declaration.targetKey]);
7901
+ const declaredScope = found.field.meta?.scope;
7902
+ return {
7903
+ junction,
7904
+ target,
7905
+ sourceKey: declaration.sourceKey,
7906
+ targetKey: declaration.targetKey,
7907
+ ownerField: byOf(junctionFields[declaration.sourceKey]) ?? idFieldOf(app.registry.get(owner)),
7908
+ targetKeyedBy: targetBy !== void 0,
7909
+ targetValueField: targetBy ?? idFieldOf(targetDef),
7910
+ junctionIdField: idFieldOf(junctionDef),
7911
+ where: declaration.where,
7912
+ targetDef,
7913
+ pickerScope: declaredScope === "context" || declaredScope === "session" || declaredScope === "none" ? declaredScope : void 0
7914
+ };
7915
+ }, [
7916
+ app.registry,
7917
+ owner,
7918
+ link
7919
+ ]);
7920
+ const ownerKey = storage === void 0 ? void 0 : record?.[storage.ownerField];
7921
+ const whereKey = JSON.stringify(own.where ?? []);
7922
+ const fixedFilters = useMemo(() => {
7923
+ if (storage === void 0 || ownerKey === null || ownerKey === void 0) return void 0;
7924
+ const { sourceKey, targetKey } = storage;
7925
+ const placed = whereToFilter(own.where ?? [], (field) => `${targetKey}.${field}`);
7926
+ return [
7927
+ whereEntryToFilter(sourceKey, "eq", ownerKey),
7928
+ ...storage.where.map((entry) => whereEntryToFilter(entry.field, entry.op, entry.value)),
7929
+ ...placed === void 0 ? [] : [placed]
7930
+ ];
7931
+ }, [
7932
+ storage,
7933
+ ownerKey,
7934
+ whereKey
7935
+ ]);
7936
+ const columnsKey = JSON.stringify([own.columns ?? null, own.linkColumns ?? null]);
7937
+ const columns = useMemo(() => {
7938
+ if (storage === void 0) return void 0;
7939
+ const shown = own.columns ?? targetColumns(app.registry, storage.target, storage.targetDef);
7940
+ const across = (entry) => typeof entry === "string" ? `${storage.targetKey}.${entry}` : {
7941
+ ...entry,
7942
+ field: `${storage.targetKey}.${entry.field}`
7943
+ };
7944
+ return [...shown.map(across), ...own.linkColumns ?? []];
7945
+ }, [
7946
+ storage,
7947
+ columnsKey,
7948
+ app.registry
7949
+ ]);
7950
+ const rowTarget = useMemo(() => storage === void 0 || storage.targetKeyedBy ? void 0 : {
7951
+ resource: storage.target,
7952
+ field: storage.targetKey
7953
+ }, [storage]);
7954
+ const unbound = fixedFilters === void 0 || own.where !== void 0 && hasNullRef(own.where);
7955
+ if (storage === void 0 || unbound) return jsx(UnboundRelatedList, {
7956
+ resource: storage?.target ?? "",
7957
+ title: own.title,
7958
+ ...empty === void 0 ? {} : { empty }
7959
+ });
7960
+ const state = frame?.state === "url" && routeScreen && !frame.nested ? { url: frame.key } : "memory";
7961
+ let providerCounts;
7962
+ if (runtime !== null) try {
7963
+ providerCounts = linkCapabilitiesOf(runtime, runtime.app.resolveDataBinding(storage.junction).providerId)?.links?.count;
7964
+ } catch {
7965
+ providerCounts = void 0;
7966
+ }
7967
+ const count = own.count === false ? "off" : own.count ?? (providerCounts === false ? "off" : void 0);
7968
+ const layoutId = frame === null ? void 0 : placementLayoutId({
7969
+ owner,
7970
+ key: frame.key
7971
+ });
7972
+ const surface = {
7973
+ resource: storage.junction,
7974
+ variant: "placement",
7975
+ state,
7976
+ fixedFilters,
7977
+ ...own.initial?.pageSize === void 0 ? {} : { initial: { pageSize: own.initial.pageSize } },
7978
+ ...own.search === void 0 ? {} : { search: `${storage.targetKey}.${own.search}` },
7979
+ ...columns === void 0 ? {} : { columns },
7980
+ open: rowTarget === void 0 || own.open === "none" ? "none" : "page",
7981
+ ...rowTarget === void 0 ? {} : { rowTarget },
7982
+ ...frame === null ? {} : { placementKey: frame.key },
7983
+ ...layoutId === void 0 ? {} : { layoutId },
7984
+ ...own.title === void 0 ? {} : { title: own.title },
7985
+ ...empty === void 0 ? {} : { empty },
7986
+ scope: placementScope(own.scope, owner),
7987
+ ...count === void 0 ? {} : { count },
7988
+ readOnly: true,
7989
+ exportable: false
7990
+ };
7991
+ if (runtime === null || record === void 0) return jsx(ResourceListSurface, { ...surface });
7992
+ return jsx(WritableLinkPanel, {
7993
+ surface,
7994
+ owner,
7995
+ link,
7996
+ storage,
7997
+ record,
7998
+ ownerKey,
7999
+ placement: own.linkActions
8000
+ });
8001
+ }
8002
+ function WritableLinkPanel(props) {
8003
+ const { surface, owner, link, storage, record, ownerKey, placement } = props;
8004
+ const displayField = isRecord(storage.targetDef) && typeof storage.targetDef.displayField === "string" ? storage.targetDef.displayField : storage.targetValueField;
8005
+ const { linkEditing, element } = useLinkPanelWrites({
8006
+ owner,
8007
+ junction: storage.junction,
8008
+ target: storage.target,
8009
+ link,
8010
+ targetKey: storage.targetKey,
8011
+ valueField: storage.targetValueField,
8012
+ junctionIdField: storage.junctionIdField,
8013
+ displayField,
8014
+ scope: storage.pickerScope ?? "context",
8015
+ record,
8016
+ ownerKey,
8017
+ placement
8018
+ });
8019
+ return jsxs(Fragment$1, { children: [jsx(ResourceListSurface, {
8020
+ ...surface,
8021
+ ...linkEditing === void 0 ? {} : { linkEditing }
8022
+ }), element] });
8023
+ }
8024
+ //#endregion
6875
8025
  //#region src/blocks/route-list-context.ts
6876
8026
  const RouteListContext = createContext(null);
6877
8027
  function useRouteList() {
@@ -6879,36 +8029,43 @@ function useRouteList() {
6879
8029
  }
6880
8030
  //#endregion
6881
8031
  //#region src/blocks/related-list-view.tsx
6882
- function RelatedListView({ props }) {
8032
+ function RelatedListView({ props, empty }) {
6883
8033
  const own = props;
6884
8034
  const route = useRouteList();
6885
8035
  const frame = usePlacementFrame();
6886
- const resource = resourceKey(own.resource);
8036
+ const app = useApp();
8037
+ const linked = own.link !== void 0;
8038
+ const resource = linked ? linkTarget(app.registry, frame?.owner, own.link) ?? "" : resourceKey(own.resource);
6887
8039
  const whereKey = JSON.stringify(own.where ?? []);
6888
- const fixedFilters = useMemo(() => own.where === void 0 ? void 0 : own.where.map(({ field, op, value }) => ({
6889
- field,
6890
- op,
6891
- value
6892
- })), [whereKey]);
8040
+ const fixedFilters = useMemo(() => own.where === void 0 || own.where.length === 0 ? void 0 : [whereToFilter(own.where)], [whereKey]);
6893
8041
  const columnsKey = JSON.stringify(own.columns ?? null);
6894
8042
  const columns = useMemo(() => own.columns, [columnsKey]);
6895
8043
  const routeScreen = useIsRouteScreen();
6896
8044
  const inQuick = useQuickEntry() !== null;
6897
8045
  useMemoryNotice(frame, routeScreen || inQuick, route !== null);
6898
- const t = useOptionalT();
6899
8046
  const permissions = useCtx((c) => c.permissions);
6900
- const app = useApp();
6901
8047
  const gate = useEntryGate();
6902
8048
  const quick = useQuick();
6903
8049
  const ownerDef = frame?.owner === void 0 ? void 0 : app.registry.get(frame.owner);
6904
8050
  const granted = ownerDef === void 0 ? "unknown" : grantedPlacementKeys(ownerDef);
6905
8051
  const trusted = frame !== null && !frame.nested && granted !== "unknown" && !granted.has(frame.key);
6906
- const onCreate = route === null && trusted && gate(resource, "create").state === "open" && frame !== null ? () => void quick.create(resource, { defaults: placementSeeds(frame.where ?? [], own.where ?? [], frame.closure ?? {}) }) : void 0;
6907
- useDeepLinkOpen(frame?.key ?? null, resource, route === null && frame !== null && !frame.nested && routeScreen && (own.open ?? "quick") === "quick");
6908
- const unbound = own.where?.some((entry) => entry.value === null || entry.value === void 0) === true;
8052
+ const onCreate = route === null && !linked && trusted && gate(resource, "create").state === "open" && own.create !== false && (own.create === void 0 || actionWhenHolds(own.create, frame?.sources ?? {})) && frame !== null ? () => void quick.create(resource, { defaults: placementSeeds(frame.where ?? [], own.where ?? [], frame.closure ?? {}) }) : void 0;
8053
+ useDeepLinkOpen(frame?.key ?? null, resource, route === null && !linked && frame !== null && !frame.nested && routeScreen && (own.open ?? "quick") === "quick");
8054
+ const unbound = own.where !== void 0 && hasNullRef(own.where);
8055
+ if (linked) return frame?.owner === void 0 ? jsx(UnboundRelatedList, {
8056
+ resource,
8057
+ title: own.title,
8058
+ ...empty === void 0 ? {} : { empty }
8059
+ }) : jsx(LinkPanel, {
8060
+ link: own.link,
8061
+ owner: frame.owner,
8062
+ own,
8063
+ ...empty === void 0 ? {} : { empty }
8064
+ });
6909
8065
  if (route !== null) return jsx(ResourceListSurface, {
6910
8066
  resource,
6911
8067
  variant: "route",
8068
+ layoutId: "route",
6912
8069
  ...route.meta === void 0 ? {} : { meta: route.meta },
6913
8070
  ...route.renderCell === void 0 ? {} : { renderCell: route.renderCell },
6914
8071
  ...route.emptyState === void 0 ? {} : { emptyState: route.emptyState }
@@ -6919,31 +8076,32 @@ function RelatedListView({ props }) {
6919
8076
  "data-testid": `list-undecided:${resource}`,
6920
8077
  children: own.view === "cards" ? jsx(RelatedCardsSkeleton, {}) : jsx(RelatedTimelineSkeleton, {})
6921
8078
  });
6922
- if (unbound) return jsxs("div", {
6923
- className: "flex flex-col gap-2",
6924
- "data-testid": `list-unbound:${resource}`,
6925
- children: [own.title === void 0 ? null : jsx("h3", {
6926
- className: "text-sm font-semibold",
6927
- children: own.title
6928
- }), jsx("p", {
6929
- className: "text-sm text-muted-foreground",
6930
- children: t("table.empty", { defaultValue: "No records" })
6931
- })]
8079
+ if (unbound) return jsx(UnboundRelatedList, {
8080
+ resource,
8081
+ title: own.title,
8082
+ ...empty === void 0 ? {} : { empty }
6932
8083
  });
6933
8084
  const state = frame?.state === "url" && routeScreen && !frame.nested ? { url: frame.key } : "memory";
8085
+ const scope = placementScope(own.scope, frame?.owner);
8086
+ const count = own.count === false ? "off" : own.count;
8087
+ const layoutId = placementLayoutId(frame);
6934
8088
  return jsx(ResourceListSurface, {
6935
8089
  resource,
6936
8090
  variant: "placement",
6937
8091
  state,
6938
8092
  ...fixedFilters === void 0 ? {} : { fixedFilters },
6939
- ...own.pageSize === void 0 ? {} : { pageSize: own.pageSize },
8093
+ ...own.initial === void 0 ? {} : { initial: own.initial },
6940
8094
  ...own.search === void 0 ? {} : { search: own.search },
6941
8095
  ...columns === void 0 ? {} : { columns },
6942
8096
  open: own.open ?? "quick",
6943
8097
  ...own.view === void 0 ? {} : { view: own.view },
6944
8098
  ...isItemHandle(own.item) ? { item: own.item } : {},
6945
8099
  ...frame === null ? {} : { placementKey: frame.key },
8100
+ ...layoutId === void 0 ? {} : { layoutId },
6946
8101
  ...own.title === void 0 ? {} : { title: own.title },
8102
+ ...empty === void 0 ? {} : { empty },
8103
+ scope,
8104
+ ...count === void 0 ? {} : { count },
6947
8105
  ...onCreate === void 0 ? {} : { onCreate }
6948
8106
  });
6949
8107
  }
@@ -6976,7 +8134,7 @@ function Missing({ text }) {
6976
8134
  children: text ?? t("table.empty", { defaultValue: "No records" })
6977
8135
  });
6978
8136
  }
6979
- function RelatedRecordView({ props }) {
8137
+ function RelatedRecordView({ props, empty }) {
6980
8138
  const own = props;
6981
8139
  const app = useApp();
6982
8140
  const resource = resourceKey(own.resource);
@@ -7010,15 +8168,15 @@ function RelatedRecordView({ props }) {
7010
8168
  capabilityFor,
7011
8169
  access
7012
8170
  ]);
7013
- if (id === null) return jsx(Missing, { text: own.missing });
8171
+ if (id === null) return jsx(Missing, { text: own.missing ?? empty });
7014
8172
  if (result === void 0) {
7015
- if (query.error != null) return jsx(Missing, { text: own.missing });
8173
+ if (query.error != null) return jsx(Missing, { text: own.missing ?? empty });
7016
8174
  return jsx(Skeleton, {
7017
8175
  "data-buro-skeleton": "",
7018
8176
  className: "h-9 w-full"
7019
8177
  });
7020
8178
  }
7021
- if (dtoRow === void 0) return jsx(Missing, { text: own.missing });
8179
+ if (dtoRow === void 0) return jsx(Missing, { text: own.missing ?? empty });
7022
8180
  if (projected.status === "pending" || projected.value === null) return jsx(Skeleton, {
7023
8181
  "data-buro-skeleton": "",
7024
8182
  className: "h-9 w-full"
@@ -7048,16 +8206,24 @@ function RelatedRecordView({ props }) {
7048
8206
  className: "flex min-w-0 flex-col gap-0.5",
7049
8207
  children: [jsx("dt", {
7050
8208
  className: FIELD_LABEL_SHOW,
7051
- children: resolveFieldLabel(caps, name, field?.label)
8209
+ children: resolveFieldLabel(caps, name, declaredText("field", field, "label"))
7052
8210
  }), jsx("dd", {
7053
8211
  className: "min-w-0 break-words text-sm",
7054
- children: jsx(ResolvedFieldDisplay, {
7055
- resource: own.resource,
7056
- name,
7057
- field,
8212
+ children: jsx(FieldValueFrame, {
8213
+ field: {
8214
+ ...field ?? {},
8215
+ name
8216
+ },
7058
8217
  value,
7059
- record: row,
7060
- fallback: toDisplayText(value).text
8218
+ surface: "display",
8219
+ children: jsx(ResolvedFieldDisplay, {
8220
+ resource: own.resource,
8221
+ name,
8222
+ field,
8223
+ value,
8224
+ record: row,
8225
+ fallback: toDisplayText(value).text
8226
+ })
7061
8227
  })
7062
8228
  })]
7063
8229
  }, name);
@@ -7128,28 +8294,39 @@ function Chrome({ node, size, state, children }) {
7128
8294
  });
7129
8295
  }
7130
8296
  function isAuthError(error) {
7131
- return isDataError(error) && error.kind === "auth";
8297
+ return normalizeDataError(error).kind === "auth";
7132
8298
  }
7133
- function FailedPlacement({ node, size, message, retry }) {
8299
+ const PLACE_NODE_MESSAGES = {
8300
+ failed: "This section could not be loaded.",
8301
+ sessionExpired: "Your session has expired. Sign in again to continue.",
8302
+ notImplemented: "This section is not available."
8303
+ };
8304
+ function FailedPlacement({ node, size, message, retry, error }) {
7134
8305
  const t = useOptionalT();
8306
+ const chromeT = useChromeT();
8307
+ const fallback = {
8308
+ key: `block.${message}`,
8309
+ default: PLACE_NODE_MESSAGES[message]
8310
+ };
7135
8311
  return jsx(Chrome, {
7136
8312
  node,
7137
8313
  size,
7138
8314
  state: "failed",
7139
- children: jsxs("div", {
8315
+ children: message === "notImplemented" ? jsxs("div", {
7140
8316
  role: "alert",
7141
8317
  className: "flex flex-wrap items-center gap-2 rounded-md border border-dashed px-3 py-2 text-sm text-muted-foreground",
7142
- children: [jsx("span", { children: t(`block.${message}`, { defaultValue: {
7143
- failed: "This section could not be loaded.",
7144
- sessionExpired: "Your session has expired. Sign in again to continue.",
7145
- notImplemented: "This section is not available."
7146
- }[message] }) }), retry === void 0 ? null : jsx(Button, {
8318
+ children: [jsx("span", { children: t(fallback.key, { defaultValue: fallback.default }) }), retry === void 0 ? null : jsx(Button, {
7147
8319
  type: "button",
7148
8320
  variant: "outline",
7149
8321
  size: "sm",
7150
8322
  onClick: retry,
7151
- children: t("actions.retry", { defaultValue: "Retry" })
8323
+ children: chromeT("actions.retry", { defaultValue: "Retry" })
7152
8324
  })]
8325
+ }) : jsx(LoadError, {
8326
+ surface: "block",
8327
+ error,
8328
+ ...retry === void 0 ? {} : { retry },
8329
+ fallback
7153
8330
  })
7154
8331
  });
7155
8332
  }
@@ -7229,7 +8406,8 @@ function ReadyPlacement({ node, size, placement, implementation }) {
7229
8406
  nested,
7230
8407
  ...Array.isArray(declaredWhere) ? { where: declaredWhere } : {},
7231
8408
  closure: compiled.sourceClosure,
7232
- ...owner === void 0 ? {} : { owner }
8409
+ ...owner === void 0 ? {} : { owner },
8410
+ sources: controller.sources
7233
8411
  }), [
7234
8412
  node.key,
7235
8413
  node.id,
@@ -7237,7 +8415,8 @@ function ReadyPlacement({ node, size, placement, implementation }) {
7237
8415
  nested,
7238
8416
  declaredWhere,
7239
8417
  compiled.sourceClosure,
7240
- owner
8418
+ owner,
8419
+ controller.sources
7241
8420
  ]);
7242
8421
  return jsx(PlacementBoundary, {
7243
8422
  node,
@@ -7295,6 +8474,7 @@ function PlaceNode({ node }) {
7295
8474
  node,
7296
8475
  size,
7297
8476
  message: isAuthError(placement.error) ? "sessionExpired" : "failed",
8477
+ error: placement.error,
7298
8478
  ...placement.retry === void 0 ? {} : { retry: placement.retry }
7299
8479
  });
7300
8480
  if (implementation === void 0) return jsx(FailedPlacement, {
@@ -7310,6 +8490,6 @@ function PlaceNode({ node }) {
7310
8490
  });
7311
8491
  }
7312
8492
  //#endregion
7313
- export { Tabs as $, ListColumns as A, ShellProvider as At, RouteScreenClaimContext as B, ListFilters as C, AppFlowButton as Ct, ExportButton as D, ResolvedFieldDisplay as Dt, ListExport as E, i18nPrefixToRef$1 as Et, ExportDialog as F, CursorLoadMore as G, useIsRouteScreen as H, DeclarativeForm as I, resolveUpdater as J, ListErrorState as K, ScreenTree as L, DataTableViewOptions as M, usePaletteState as Mt, ListBulkBar as N, useShell as Nt, ListCreate as O, lowerLegacyField as Ot, BulkActionToolbar as P, Checkbox as Q, useTabsUrlState as R, ListFlows as S, DeepLinkScope as St, ListSearchInput as T, FieldDisplay as Tt, ListBody as U, RouteScreenContext as V, ResourceList as W, useBuroDataTable as X, tanStackSortingToBuro as Y, DataTable as Z, useFrameChrome as _, ActionOverflow as _t, isBuiltinRecordView as a, RELATED_GRID_CLASS as at, SavedViewsDropdown as b, downloadArtifact as bt, ChangeTypeButton as c, ShowPageHeader as ct, UndeclaredTypeNotice as d, typography as dt, TabsContent as et, CopyId as f, isShowAvailable as ft, FrameChromeContext as g, showTarget as gt, RecordTitle as h, recordRouteAvailable as ht, RouteListContext as i, useItemDrawn as it, ColumnsToggle as j, defaultUserMapper as jt, CreateButton as k, toDisplayText as kt, HeaderTypeNotice as l, DocumentTitleProvider as lt, RecordActions as m, recordReturnTarget as mt, viewOf as n, TabsTrigger as nt, resolveFrameView as o, EditPageHeader as ot, FormActions as p, listTarget as pt, buroSortingToTanStack as q, shadcnBlockImplementations as r, RelatedItem as rt, shadcnFrameViews as s, PageHeader as st, selectImplementation as t, TabsList as tt, TypeChromeDrawnContext as u, useDocumentTitle as ut, ListControls as v, useShownItemActions as vt, FilterRegion as w, Field as wt, ListLive as x, EnumBadge as xt, ListSavedViews as y, useExportRunWatcher as yt, QuickScreenClaimContext as z };
8493
+ export { CursorLoadMore as $, ListSearchInput as A, EnumBadge as At, ExportDialog as B, usePaletteState as Bt, ListControls as C, recordReturnTarget as Ct, ListFlows as D, useShownItemActions as Dt, ListLive as E, ActionOverflow as Et, ListColumns as F, i18nPrefixToRef$1 as Ft, RouteScreenClaimContext as G, ScreenTree as H, ColumnsToggle as I, FieldValueFrame as It, FormControlProvider as J, RouteScreenContext as K, DataTableViewOptions as L, toDisplayText as Lt, ExportButton as M, AppFlowButton as Mt, ListCreate as N, Field as Nt, ListFilters as O, useExportRunWatcher as Ot, CreateButton as P, FieldDisplay as Pt, ResourceList as Q, ListBulkBar as R, ShellProvider as Rt, useFrameChrome as S, listTarget as St, SavedViewsDropdown as T, showTarget as Tt, useTabsUrlState as U, DeclarativeForm as V, useShell as Vt, QuickScreenClaimContext as W, useFormControlOf as X, useFormControl as Y, ListBody as Z, CopyId as _, resolveResourceTitle as _t, peekFieldDefinition as a, DataTable as at, RecordTitle as b, typography as bt, peekRow as c, TabsContent as ct, resolveFrameView as d, RelatedItem as dt, ListErrorState as et, shadcnFrameViews as f, useItemDrawn as ft, UndeclaredTypeNotice as g, ShowPageHeader as gt, TypeChromeDrawnContext as h, PageHeader as ht, RouteListContext as i, useBuroDataTable as it, ListExport as j, DeepLinkScope as jt, FilterRegion as k, downloadArtifact as kt, useRelationPicker as l, TabsList as lt, HeaderTypeNotice as m, EditPageHeader as mt, viewOf as n, resolveUpdater as nt, peekLeafAllowed as o, Checkbox as ot, ChangeTypeButton as p, RELATED_GRID_CLASS as pt, useIsRouteScreen as q, shadcnBlockImplementations as r, tanStackSortingToBuro as rt, peekRecord as s, Tabs as st, selectImplementation as t, buroSortingToTanStack as tt, isBuiltinRecordView as u, TabsTrigger as ut, FormActions as v, DocumentTitleProvider as vt, ListSavedViews as w, recordRouteAvailable as wt, FrameChromeContext as x, isShowAvailable as xt, RecordActions as y, useDocumentTitle as yt, BulkActionToolbar as z, defaultUserMapper as zt };
7314
8494
 
7315
- //# sourceMappingURL=place-node--GdsP688.js.map
8495
+ //# sourceMappingURL=place-node-CfzQvh4Y.js.map