@xemahq/ui-kernel 0.11.1 → 0.12.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 (257) hide show
  1. package/dist/errors/http-error-status.d.ts +17 -0
  2. package/dist/errors/http-error-status.d.ts.map +1 -0
  3. package/dist/errors/http-error-status.js +59 -0
  4. package/dist/errors/http-error-status.js.map +1 -0
  5. package/dist/errors/index.d.ts +2 -0
  6. package/dist/errors/index.d.ts.map +1 -0
  7. package/dist/errors/index.js +2 -0
  8. package/dist/errors/index.js.map +1 -0
  9. package/dist/index.d.ts +1 -0
  10. package/dist/index.d.ts.map +1 -1
  11. package/dist/index.js +1 -0
  12. package/dist/index.js.map +1 -1
  13. package/dist/lib/biome-host/errors.d.ts.map +1 -1
  14. package/dist/lib/biome-host/errors.js +1 -19
  15. package/dist/lib/biome-host/errors.js.map +1 -1
  16. package/dist/offline/index.d.ts.map +1 -1
  17. package/dist/offline/index.js.map +1 -1
  18. package/dist/offline/online-controller.d.ts.map +1 -1
  19. package/dist/offline/online-controller.js +6 -1
  20. package/dist/offline/online-controller.js.map +1 -1
  21. package/dist/offline/outbox.d.ts.map +1 -1
  22. package/dist/offline/outbox.js +2 -24
  23. package/dist/offline/outbox.js.map +1 -1
  24. package/package.json +1 -2
  25. package/src/index.ts +0 -17
  26. package/src/lib/biome-host/agent-validation.ts +0 -239
  27. package/src/lib/biome-host/biome-builders.ts +0 -112
  28. package/src/lib/biome-host/biome-mode.ts +0 -6
  29. package/src/lib/biome-host/biome-navigation.ts +0 -37
  30. package/src/lib/biome-host/biome-registry.ts +0 -299
  31. package/src/lib/biome-host/biome-scope-hooks.ts +0 -41
  32. package/src/lib/biome-host/biome-scope.tsx +0 -52
  33. package/src/lib/biome-host/biome-scoped-query.ts +0 -134
  34. package/src/lib/biome-host/create-biome-orval-config.ts +0 -82
  35. package/src/lib/biome-host/define-web-biome.spec.ts +0 -221
  36. package/src/lib/biome-host/define-web-biome.ts +0 -222
  37. package/src/lib/biome-host/errors.ts +0 -220
  38. package/src/lib/biome-host/frontend-biome.ts +0 -256
  39. package/src/lib/biome-host/host-bridge.ts +0 -281
  40. package/src/lib/biome-host/host-sources.ts +0 -54
  41. package/src/lib/biome-host/i18n-hooks.ts +0 -51
  42. package/src/lib/biome-host/i18n-port.test.ts +0 -50
  43. package/src/lib/biome-host/i18n-port.ts +0 -96
  44. package/src/lib/biome-host/index.ts +0 -35
  45. package/src/lib/biome-host/realtime-hooks.ts +0 -75
  46. package/src/lib/biome-host/realtime-port.ts +0 -115
  47. package/src/lib/biome-host/session-contributions.ts +0 -296
  48. package/src/lib/biome-host/use-mutation-with-error-toast.ts +0 -90
  49. package/src/lib/biome-host/use-page-state.ts +0 -248
  50. package/src/lib/capabilities/capability-context.ts +0 -25
  51. package/src/lib/capabilities/capability-provider.tsx +0 -73
  52. package/src/lib/capabilities/index.ts +0 -17
  53. package/src/lib/capabilities/types.ts +0 -69
  54. package/src/lib/capabilities/use-capability.ts +0 -79
  55. package/src/lib/contribution-registry/contribution-registry.spec.ts +0 -141
  56. package/src/lib/contribution-registry/contribution-registry.ts +0 -204
  57. package/src/lib/contribution-registry/index.ts +0 -9
  58. package/src/lib/contribution-registry/renderable-registry.ts +0 -142
  59. package/src/lib/system-bus/capability-invoke.ts +0 -92
  60. package/src/lib/system-bus/deeplink.ts +0 -210
  61. package/src/lib/system-bus/enums.ts +0 -86
  62. package/src/lib/system-bus/host-ports.ts +0 -93
  63. package/src/lib/system-bus/index.ts +0 -16
  64. package/src/lib/system-bus/intent-registry.ts +0 -109
  65. package/src/lib/system-bus/intents.ts +0 -113
  66. package/src/lib/system-bus/palette.ts +0 -78
  67. package/src/lib/system-bus/system-bus-builder.ts +0 -159
  68. package/src/lib/system-bus/system-bus.ts +0 -37
  69. package/src/lib/system-bus/windows.ts +0 -51
  70. package/src/offline/id.ts +0 -16
  71. package/src/offline/index.ts +0 -54
  72. package/src/offline/mutation.ts +0 -75
  73. package/src/offline/offline-status-badge.tsx +0 -51
  74. package/src/offline/online-controller.ts +0 -105
  75. package/src/offline/outbox.test.ts +0 -172
  76. package/src/offline/outbox.ts +0 -348
  77. package/src/offline/react-hooks.ts +0 -40
  78. package/src/offline/react.tsx +0 -32
  79. package/src/offline/storage.ts +0 -112
  80. package/src/offline/types.ts +0 -77
  81. package/src/org-db/components/DbResultTable.tsx +0 -176
  82. package/src/org-db/index.ts +0 -1
  83. package/src/pdf/index.ts +0 -11
  84. package/src/registry/index.ts +0 -9
  85. package/src/registry/lib/agent-validation-host.ts +0 -36
  86. package/src/registry/lib/biome-slot.tsx +0 -50
  87. package/src/registry/lib/biomes-enabled-context.ts +0 -20
  88. package/src/registry/lib/extension-points.ts +0 -134
  89. package/src/registry/lib/primitives/SessionEventCard.tsx +0 -164
  90. package/src/registry/lib/primitives/SessionEventTimeline.tsx +0 -95
  91. package/src/registry/lib/primitives/SessionMutationBar.tsx +0 -76
  92. package/src/registry/lib/primitives/SessionTabbedDrawer.tsx +0 -165
  93. package/src/registry/lib/primitives/index.ts +0 -18
  94. package/src/registry/lib/session-context-builder.ts +0 -84
  95. package/src/registry/lib/session-context-provider.tsx +0 -33
  96. package/src/registry/lib/session-context.ts +0 -21
  97. package/src/registry/lib/session-selectors.ts +0 -272
  98. package/src/session/comments/CommentRail.tsx +0 -164
  99. package/src/session/index.ts +0 -3
  100. package/src/session/lib/cn.ts +0 -11
  101. package/src/session/shell/SessionWorkspaceShell.tsx +0 -147
  102. package/src/session-kit/approvals/ApprovalButton.tsx +0 -89
  103. package/src/session-kit/approvals/ApprovalCard.tsx +0 -341
  104. package/src/session-kit/approvals/ApprovalsCenter.tsx +0 -336
  105. package/src/session-kit/approvals/CapabilityApprovalStickyBar.tsx +0 -118
  106. package/src/session-kit/approvals/Obligation.tsx +0 -111
  107. package/src/session-kit/approvals/ScopedBody.tsx +0 -392
  108. package/src/session-kit/approvals/approval-icons.ts +0 -31
  109. package/src/session-kit/approvals/approval-model.ts +0 -204
  110. package/src/session-kit/approvals/index.ts +0 -22
  111. package/src/session-kit/approvals/obligation-display.ts +0 -100
  112. package/src/session-kit/approvals/risk-accent.ts +0 -47
  113. package/src/session-kit/approvals/scope-icons.ts +0 -19
  114. package/src/session-kit/combobox/Combobox.tsx +0 -358
  115. package/src/session-kit/combobox/use-click-outside.ts +0 -21
  116. package/src/session-kit/display/ContextHeader.tsx +0 -148
  117. package/src/session-kit/display/FileDiffCard.tsx +0 -140
  118. package/src/session-kit/display/MD.tsx +0 -162
  119. package/src/session-kit/display/MessageTurn.tsx +0 -157
  120. package/src/session-kit/display/ThinkingPanel.tsx +0 -81
  121. package/src/session-kit/display/TodoChecklist.tsx +0 -120
  122. package/src/session-kit/display/TokenMeter.tsx +0 -89
  123. package/src/session-kit/display/ToolStrip.tsx +0 -278
  124. package/src/session-kit/display/TypingDots.tsx +0 -24
  125. package/src/session-kit/index.ts +0 -44
  126. package/src/session-kit/lib/enums.ts +0 -66
  127. package/src/session-kit/lib/portal-accent.ts +0 -30
  128. package/src/session-kit/lib/status-dot.ts +0 -68
  129. package/src/session-kit/primitives/Avatar.tsx +0 -44
  130. package/src/session-kit/primitives/PortalGlyph.tsx +0 -51
  131. package/src/session-kit/primitives/RiskPill.tsx +0 -95
  132. package/src/session-kit/primitives/ScopeDot.tsx +0 -47
  133. package/src/session-kit/primitives/Segmented.tsx +0 -71
  134. package/src/ui/branding/app-branding-context.ts +0 -20
  135. package/src/ui/branding/app-branding-provider.tsx +0 -123
  136. package/src/ui/branding/sanitize.ts +0 -89
  137. package/src/ui/breakpoints.ts +0 -14
  138. package/src/ui/capture/document-preview.tsx +0 -166
  139. package/src/ui/capture/field-spec-to-zod.test.ts +0 -81
  140. package/src/ui/capture/field-spec-to-zod.ts +0 -174
  141. package/src/ui/capture/field-spec-validate.ts +0 -236
  142. package/src/ui/capture/field-spec.test.ts +0 -49
  143. package/src/ui/capture/field-spec.ts +0 -274
  144. package/src/ui/capture/index.ts +0 -51
  145. package/src/ui/capture/schema-field-input.tsx +0 -620
  146. package/src/ui/capture/schema-form.tsx +0 -149
  147. package/src/ui/capture/submit-values.test.ts +0 -70
  148. package/src/ui/capture/submit-values.ts +0 -63
  149. package/src/ui/capture/use-prefill.ts +0 -186
  150. package/src/ui/capture/use-schema-validation.ts +0 -145
  151. package/src/ui/capture/use-wizard.ts +0 -180
  152. package/src/ui/capture/value-format.ts +0 -32
  153. package/src/ui/capture/wizard.tsx +0 -105
  154. package/src/ui/charts/bar-chart.tsx +0 -139
  155. package/src/ui/charts/chart-frame.tsx +0 -125
  156. package/src/ui/charts/chart-types.ts +0 -52
  157. package/src/ui/charts/chart-utils.ts +0 -190
  158. package/src/ui/charts/charts.test.tsx +0 -204
  159. package/src/ui/charts/donut-chart.tsx +0 -129
  160. package/src/ui/charts/index.ts +0 -27
  161. package/src/ui/charts/line-chart.tsx +0 -202
  162. package/src/ui/charts/sparkline.tsx +0 -109
  163. package/src/ui/chrome/AsyncBoundary.tsx +0 -74
  164. package/src/ui/chrome/EmptyState.tsx +0 -257
  165. package/src/ui/chrome/ErrorCard.tsx +0 -63
  166. package/src/ui/chrome/LoadingState.tsx +0 -61
  167. package/src/ui/chrome/PageHeader.tsx +0 -137
  168. package/src/ui/chrome/RecordCardList.tsx +0 -98
  169. package/src/ui/chrome/StateCard.tsx +0 -150
  170. package/src/ui/chrome/confirm-delete-dialog.tsx +0 -59
  171. package/src/ui/chrome/page-shell.spec.tsx +0 -117
  172. package/src/ui/chrome/page-shell.tsx +0 -221
  173. package/src/ui/chrome/scope-badge.tsx +0 -40
  174. package/src/ui/chrome/status-badge.tsx +0 -79
  175. package/src/ui/cn.ts +0 -32
  176. package/src/ui/design-tokens.ts +0 -372
  177. package/src/ui/hooks/use-debounced-value.ts +0 -16
  178. package/src/ui/hooks/use-is-mobile.ts +0 -28
  179. package/src/ui/index.ts +0 -110
  180. package/src/ui/pdf/pdf-preview.tsx +0 -122
  181. package/src/ui/primitives/alert-dialog.tsx +0 -104
  182. package/src/ui/primitives/async-combobox.tsx +0 -182
  183. package/src/ui/primitives/badge-variants.ts +0 -21
  184. package/src/ui/primitives/badge.tsx +0 -25
  185. package/src/ui/primitives/button-variants.ts +0 -31
  186. package/src/ui/primitives/button.tsx +0 -29
  187. package/src/ui/primitives/card.tsx +0 -43
  188. package/src/ui/primitives/checkbox.tsx +0 -26
  189. package/src/ui/primitives/collapsible.tsx +0 -9
  190. package/src/ui/primitives/dialog.tsx +0 -103
  191. package/src/ui/primitives/dropdown-menu.tsx +0 -179
  192. package/src/ui/primitives/form-stepper.tsx +0 -94
  193. package/src/ui/primitives/form-stepper.types.ts +0 -15
  194. package/src/ui/primitives/input.tsx +0 -22
  195. package/src/ui/primitives/label.tsx +0 -17
  196. package/src/ui/primitives/overflow-tabs.tsx +0 -331
  197. package/src/ui/primitives/popover.tsx +0 -33
  198. package/src/ui/primitives/preview-device-frame.tsx +0 -213
  199. package/src/ui/primitives/radio-group.tsx +0 -36
  200. package/src/ui/primitives/resizable.tsx +0 -67
  201. package/src/ui/primitives/scroll-area.tsx +0 -38
  202. package/src/ui/primitives/select.tsx +0 -143
  203. package/src/ui/primitives/separator.tsx +0 -20
  204. package/src/ui/primitives/sheet.tsx +0 -107
  205. package/src/ui/primitives/skeleton.tsx +0 -99
  206. package/src/ui/primitives/switch.tsx +0 -27
  207. package/src/ui/primitives/table.tsx +0 -72
  208. package/src/ui/primitives/tabs.tsx +0 -53
  209. package/src/ui/primitives/tag-multi-select.tsx +0 -261
  210. package/src/ui/primitives/textarea.tsx +0 -21
  211. package/src/ui/primitives/tooltip.tsx +0 -30
  212. package/src/ui/primitives/url-preview-chrome.tsx +0 -103
  213. package/src/vista/GenericCatalogueSurface.tsx +0 -179
  214. package/src/vista/SurfaceFallbackCard.tsx +0 -109
  215. package/src/vista/VistaPanel.tsx +0 -163
  216. package/src/vista/form-payload.spec.ts +0 -139
  217. package/src/vista/html/CustomUiFrame.spec.tsx +0 -127
  218. package/src/vista/html/CustomUiFrame.tsx +0 -206
  219. package/src/vista/html/custom-ui-frame-bridge.spec.ts +0 -191
  220. package/src/vista/html/custom-ui-frame-bridge.ts +0 -98
  221. package/src/vista/html/custom-ui-frame-contract.ts +0 -88
  222. package/src/vista/html/custom-ui-intents.spec.ts +0 -135
  223. package/src/vista/html/custom-ui-intents.ts +0 -155
  224. package/src/vista/html/custom-ui-srcdoc.spec.ts +0 -151
  225. package/src/vista/html/custom-ui-srcdoc.ts +0 -193
  226. package/src/vista/html/index.ts +0 -13
  227. package/src/vista/index.ts +0 -52
  228. package/src/vista/surface-catalogue.spec.ts +0 -140
  229. package/src/vista/surface-catalogue.ts +0 -90
  230. package/src/vista/surface-icons.ts +0 -57
  231. package/src/vista/surface-registry.ts +0 -56
  232. package/src/vista/surfaces/ChartSurface.tsx +0 -34
  233. package/src/vista/surfaces/FilesSurface.tsx +0 -202
  234. package/src/vista/surfaces/FormSurface.tsx +0 -380
  235. package/src/vista/surfaces/HtmlSurface.tsx +0 -43
  236. package/src/vista/surfaces/TableSurface.tsx +0 -100
  237. package/src/vista/surfaces/UrlSurface.tsx +0 -40
  238. package/src/vista/surfaces/blob-surfaces.tsx +0 -139
  239. package/src/vista/surfaces/elicitation-transport.spec.ts +0 -369
  240. package/src/vista/surfaces/elicitation-transport.ts +0 -367
  241. package/src/vista/surfaces/file-surface-utils.ts +0 -69
  242. package/src/vista/surfaces/index.ts +0 -24
  243. package/src/vista/surfaces/register-surfaces.tsx +0 -139
  244. package/src/vista/surfaces/text-surfaces.tsx +0 -132
  245. package/src/vista/use-vista-tabs.ts +0 -196
  246. package/src/vista/vista-code-renderer-context.ts +0 -16
  247. package/src/vista/vista-code-renderer.tsx +0 -35
  248. package/src/vista/vista-file-context.ts +0 -14
  249. package/src/vista/vista-file-source.tsx +0 -141
  250. package/src/vista/vista-form-context.ts +0 -20
  251. package/src/vista/vista-form-host.tsx +0 -48
  252. package/src/vista/vista-model.ts +0 -137
  253. package/src/vista/vista-open-context.tsx +0 -27
  254. package/src/vista/vista-open-state.ts +0 -11
  255. package/src/vista/vista-payloads.ts +0 -635
  256. package/src/vista/vista-tabs-manifest.ts +0 -138
  257. package/src/vista/vista-tools.ts +0 -232
@@ -1,61 +0,0 @@
1
- import { Loader2 } from 'lucide-react';
2
-
3
- import { cn } from '../cn';
4
- import { Skeleton } from '../primitives/skeleton';
5
-
6
- export type LoadingStateVariant = 'spinner' | 'skeleton-list' | 'skeleton-card';
7
-
8
- interface LoadingStateProps {
9
- /**
10
- * `spinner` (default): centred spinner + optional label. `skeleton-list`:
11
- * N skeleton rows. `skeleton-card`: a single card-shaped skeleton block.
12
- */
13
- variant?: LoadingStateVariant;
14
- /** Accessible label / caption (spinner variant). */
15
- label?: string;
16
- /** Row count for `skeleton-list` (default 4). */
17
- rows?: number;
18
- className?: string;
19
- }
20
-
21
- /**
22
- * Canonical loading affordance shared across biomes. Replaces the per-biome
23
- * ad-hoc spinners and inline skeleton stacks.
24
- */
25
- export default function LoadingState({
26
- variant = 'spinner',
27
- label = 'Loading…',
28
- rows = 4,
29
- className,
30
- }: Readonly<LoadingStateProps>) {
31
- if (variant === 'skeleton-list') {
32
- return (
33
- <div className={cn('space-y-3', className)} role="status" aria-label={label}>
34
- {Array.from({ length: rows }).map((_, i) => (
35
- <Skeleton key={i} className="h-12 w-full rounded-xl" />
36
- ))}
37
- </div>
38
- );
39
- }
40
-
41
- if (variant === 'skeleton-card') {
42
- return (
43
- <div className={cn('space-y-3', className)} role="status" aria-label={label}>
44
- <Skeleton className="h-40 w-full rounded-2xl" />
45
- <Skeleton className="h-4 w-2/3 rounded" />
46
- <Skeleton className="h-4 w-1/2 rounded" />
47
- </div>
48
- );
49
- }
50
-
51
- return (
52
- <div
53
- className={cn('flex flex-col items-center justify-center py-14 text-center', className)}
54
- role="status"
55
- aria-label={label}
56
- >
57
- <Loader2 className="h-6 w-6 animate-spin text-ink-3" />
58
- {label && <p className="mt-3 text-body-2 text-ink-3">{label}</p>}
59
- </div>
60
- );
61
- }
@@ -1,137 +0,0 @@
1
- // ═══════════════════════════════════════════════════════════════════════════
2
- // ── PageHeader — INVISIBLE topbar registration ──
3
- //
4
- // PageHeader does NOT render a visible header bar. It registers the page's
5
- // title, description, eyebrow, help-actions, status pill, count, and
6
- // primary action buttons into the shared page-meta store. The global
7
- // `AppTopbar` reads from that store and renders them in the topbar
8
- // (mac-style — the topbar IS the page chrome).
9
- //
10
- // Why invisible: stacking a second header row beneath the topbar wastes
11
- // vertical space and competes with the topbar for attention. Pages get
12
- // the canvas back; the topbar carries context.
13
- //
14
- // When you need a thin in-page row (filter dropdowns, search input,
15
- // selection-mode toolbar), use `<PageToolbar>` from this same file —
16
- // that's a real visible row but carries NO title (the topbar still owns
17
- // it), so there's no visual duplication.
18
- //
19
- // Host-agnostic: reaches the page-meta store through the kernel `HostBridge`
20
- // singleton (NOT a host import), so it bundles cleanly into any biome remote.
21
- // ═══════════════════════════════════════════════════════════════════════════
22
-
23
- import { useMemo, type ReactNode } from 'react';
24
-
25
- import { useHostBridge, type PageBackTarget } from '../../lib/biome-host';
26
- import { cn } from '../cn';
27
-
28
- interface PageHeaderProps {
29
- readonly title: string;
30
- /**
31
- * Short secondary line. Renders in the topbar info popover (long-form
32
- * `topbarDescription` wins for the popover if both are set) AND
33
- * inline next to the title as `topbarMeta` when it's a string.
34
- */
35
- readonly description?: string;
36
- /**
37
- * Long-form text rendered ONLY in the topbar's info popover (the
38
- * (i) button). Use when `description` is a counts line and you still
39
- * want a richer popover blurb.
40
- */
41
- readonly topbarDescription?: string;
42
- /** Mono caption rendered before the title in the topbar info popover. */
43
- readonly eyebrow?: string;
44
- /** Bullet list of "what you can do here" shown in the topbar info popover. */
45
- readonly helpActions?: readonly string[];
46
- /**
47
- * Right-aligned topbar action cluster. Use small h-7 buttons / icon
48
- * buttons. Anything that needs more space (multi-input filter rows,
49
- * search inputs) belongs in a `<PageToolbar>` inside the page body
50
- * — not here.
51
- */
52
- readonly actions?: ReactNode;
53
- /**
54
- * Leftmost back affordance. Renders as an icon-only button before
55
- * the title in the topbar. Pages with a parent context (a detail
56
- * page under a list, etc.) set this so the user can return to where
57
- * they came from. See `app/page-meta.ts` for the full layout
58
- * contract — back always lives on the left, never inside the page
59
- * body.
60
- */
61
- readonly backTo?: PageBackTarget;
62
- }
63
-
64
- /**
65
- * Invisible primitive — registers page meta with the topbar. Renders
66
- * nothing visible. Mount once at the top of a page component.
67
- */
68
- export default function PageHeader({
69
- title,
70
- description,
71
- topbarDescription,
72
- eyebrow,
73
- helpActions,
74
- actions,
75
- backTo,
76
- }: Readonly<PageHeaderProps>) {
77
- // `topbarActions` is a ReactNode — wrap in useMemo so React doesn't see
78
- // a new identity every render and re-notify the store needlessly. Same
79
- // for the meta line.
80
- const topbarActions = useMemo(() => actions, [actions]);
81
- const topbarMeta = useMemo<ReactNode>(
82
- () => (description ? <span>{description}</span> : null),
83
- [description],
84
- );
85
-
86
- // Reach the host page-meta store through the shared bridge singleton — NOT
87
- // a module-level import — so this component bundles cleanly into an MF
88
- // remote and still drives the one host topbar store.
89
- useHostBridge().pageMeta.usePageMeta({
90
- title,
91
- eyebrow,
92
- description: topbarDescription ?? description,
93
- actions: helpActions,
94
- topbarActions,
95
- topbarMeta,
96
- backTo,
97
- });
98
- return null;
99
- }
100
-
101
- // ═══════════════════════════════════════════════════════════════════════════
102
- // ── PageToolbar — thin in-page row for filters / search / sub-tabs ──
103
- //
104
- // Use when the page needs a horizontal row of controls that don't fit
105
- // in the topbar (multi-select filter, search input, bulk-selection
106
- // toolbar). NEVER include the page title — that's the topbar's job.
107
- //
108
- // Default height is `h-9` to match the topbar visual rhythm without
109
- // looking like a duplicated header. Background is the same paper tone
110
- // as the page so it reads as a tool strip, not a banner.
111
- // ═══════════════════════════════════════════════════════════════════════════
112
-
113
- interface PageToolbarProps {
114
- /** Left cluster — filters, search, etc. */
115
- readonly children?: ReactNode;
116
- /** Right cluster — usually `null`; the primary action lives in the topbar. */
117
- readonly trailing?: ReactNode;
118
- readonly className?: string;
119
- }
120
-
121
- export function PageToolbar({
122
- children,
123
- trailing,
124
- className,
125
- }: Readonly<PageToolbarProps>) {
126
- return (
127
- <div
128
- className={cn(
129
- 'flex shrink-0 items-center gap-2 border-b border-rule/50 bg-paper px-4 py-1.5 sm:px-6',
130
- className,
131
- )}
132
- >
133
- <div className="flex min-w-0 flex-1 flex-wrap items-center gap-2">{children}</div>
134
- {trailing && <div className="flex shrink-0 items-center gap-1.5">{trailing}</div>}
135
- </div>
136
- );
137
- }
@@ -1,98 +0,0 @@
1
- import { cn } from '../cn';
2
-
3
- import type { ReactNode } from 'react';
4
-
5
- /**
6
- * One label/value row inside a record card — mirrors one table column.
7
- *
8
- * `key` exists because a card's rows are keyed by the COLUMN, and a column's
9
- * identity is its field key, not its user-facing label (two columns may carry
10
- * the same label). It defaults to `label` for the common case where the label
11
- * is already unique.
12
- */
13
- export interface RecordCardField {
14
- readonly key?: string;
15
- readonly label: string;
16
- readonly value: ReactNode;
17
- /**
18
- * `data-*` attributes for the row element — e.g. the same hook the equivalent
19
- * table cell carries, so a click-to-context resolver lands on the identical
20
- * node at every viewport width. Deliberately `data-*` only: styling stays in
21
- * this primitive, so a caller cannot re-open the drift it closed.
22
- */
23
- readonly rowAttributes?: Readonly<Record<`data-${string}`, string>>;
24
- }
25
-
26
- export interface RecordCardListProps<T> {
27
- readonly items: readonly T[];
28
- readonly getKey: (item: T) => string;
29
- /** Card header rendered above the field list (title, status, row toggle). */
30
- readonly renderHeader?: (item: T) => ReactNode;
31
- readonly getFields: (item: T) => readonly RecordCardField[];
32
- /** Row actions, rendered right-aligned below a divider. */
33
- readonly renderActions?: (item: T) => ReactNode;
34
- /** Free block appended after the actions (expanded detail). */
35
- readonly renderDetail?: (item: T) => ReactNode;
36
- /** Extra classes for the list element. */
37
- readonly className?: string;
38
- }
39
-
40
- /**
41
- * Small-viewport (below `md`) layout for a record list: one stacked card per
42
- * record with the table's columns rendered as label/value rows, instead of a
43
- * horizontal-scroll-only table.
44
- *
45
- * This is the ONE definition of that card chrome — spacing, border, divider,
46
- * label/value alignment. Callers name their columns and their actions; they do
47
- * not restate the chrome. Deliberately NOT parameterised by card class: a
48
- * per-caller class hook is how the three copies this replaced drifted apart in
49
- * the first place.
50
- */
51
- export function RecordCardList<T>(props: Readonly<RecordCardListProps<T>>) {
52
- const {
53
- items,
54
- getKey,
55
- renderHeader,
56
- getFields,
57
- renderActions,
58
- renderDetail,
59
- className,
60
- } = props;
61
-
62
- return (
63
- <ul className={cn('space-y-2', className)}>
64
- {items.map((item) => {
65
- const actions = renderActions?.(item);
66
- const detail = renderDetail?.(item);
67
- return (
68
- <li
69
- key={getKey(item)}
70
- className="space-y-2 rounded-lg border border-rule bg-paper-elev p-3"
71
- >
72
- {renderHeader?.(item)}
73
- <dl className="space-y-1.5">
74
- {getFields(item).map((field) => (
75
- <div
76
- key={field.key ?? field.label}
77
- {...field.rowAttributes}
78
- className="flex justify-between gap-3 text-sm"
79
- >
80
- <dt className="shrink-0 text-ink-3">{field.label}</dt>
81
- <dd className="text-right text-ink-2">{field.value}</dd>
82
- </div>
83
- ))}
84
- </dl>
85
- {actions ? (
86
- <div className="flex items-center justify-end gap-2 border-t border-rule/50 pt-2">
87
- {actions}
88
- </div>
89
- ) : null}
90
- {detail}
91
- </li>
92
- );
93
- })}
94
- </ul>
95
- );
96
- }
97
-
98
- export default RecordCardList;
@@ -1,150 +0,0 @@
1
- /**
2
- * Unified state display card for empty, error-simple, and error-detailed states.
3
- * Replaces ErrorCard, ErrorDisplay, and EmptyState components.
4
- * Supports collapsible error details and structured field extraction.
5
- */
6
- import { ChevronDown, ChevronRight } from 'lucide-react';
7
- import React, { useState } from 'react';
8
-
9
-
10
- import { cn } from '../cn';
11
-
12
- interface FieldEntry {
13
- label: string;
14
- value: string | React.ReactNode;
15
- }
16
-
17
- interface StateCardProps {
18
- /** State variant: 'empty' | 'error-simple' | 'error-detailed' */
19
- variant: 'empty' | 'error-simple' | 'error-detailed';
20
- /** Icon React element */
21
- icon: React.ReactNode;
22
- /** Primary title */
23
- title: string;
24
- /** Description or error message */
25
- description?: string;
26
- /** Optional action button */
27
- action?: {
28
- label: string;
29
- onClick: () => void;
30
- };
31
- /** Optional error details (for error-detailed variant) */
32
- errorDetails?: {
33
- fields?: FieldEntry[];
34
- stackTrace?: string;
35
- requestId?: string;
36
- };
37
- /** Optional CSS class */
38
- className?: string;
39
- }
40
-
41
- /**
42
- * Renders a state card (empty/error) with consistent appearance.
43
- * Supports collapsible error details for debugging.
44
- */
45
- export const StateCard: React.FC<StateCardProps> = ({
46
- variant,
47
- icon,
48
- title,
49
- description,
50
- action,
51
- errorDetails,
52
- className = '',
53
- }) => {
54
- const [detailsExpanded, setDetailsExpanded] = useState(false);
55
-
56
- const isError = variant.startsWith('error');
57
- const isDetailed = variant === 'error-detailed';
58
-
59
- return (
60
- <div className={cn(
61
- 'flex flex-col items-center justify-center py-14 px-6 rounded-xl border-2',
62
- isError ? 'border-destructive/20 bg-destructive/[0.03]' : 'border-dashed border-border/50 bg-paper-elev/60',
63
- className,
64
- )}>
65
- <div className={cn(
66
- 'h-14 w-14 rounded-2xl flex items-center justify-center mb-5',
67
- isError ? 'bg-destructive/10' : 'bg-primary/10',
68
- )}>
69
- <span className={isError ? 'text-destructive' : 'text-primary'}>{icon}</span>
70
- </div>
71
-
72
- <h2 className="text-subtitle font-semibold text-ink mb-2 text-center">
73
- {title}
74
- </h2>
75
-
76
- {description && (
77
- <p className="text-body-1 text-ink-3 max-w-md text-center mb-4 leading-relaxed">
78
- {description}
79
- </p>
80
- )}
81
-
82
- {/* Error details section */}
83
- {isDetailed && errorDetails && (
84
- <div className="mt-4 w-full max-w-md">
85
- <button
86
- type="button"
87
- onClick={() => setDetailsExpanded(!detailsExpanded)}
88
- className="text-body-1 font-medium text-destructive hover:text-destructive flex items-center gap-1.5 mb-2"
89
- >
90
- {detailsExpanded ? <ChevronDown className="h-4 w-4" /> : <ChevronRight className="h-4 w-4" />}
91
- Error Details
92
- </button>
93
-
94
- {detailsExpanded && (
95
- <div className="p-4 bg-card border border-destructive/20 rounded-xl space-y-3">
96
- {/* Structured fields */}
97
- {errorDetails.fields && errorDetails.fields.length > 0 && (
98
- <div className="space-y-1.5">
99
- {errorDetails.fields.map((field, idx) => (
100
- <div key={idx} className="text-body-1">
101
- <span className="font-semibold text-destructive">{field.label}</span>
102
- <span className="text-ink/70">: {field.value}</span>
103
- </div>
104
- ))}
105
- </div>
106
- )}
107
-
108
- {/* Stack trace */}
109
- {errorDetails.stackTrace && (
110
- <details className="text-body-1">
111
- <summary className="font-medium text-destructive cursor-pointer mb-1">
112
- Stack Trace
113
- </summary>
114
- <pre className="bg-destructive/5 p-3 rounded-lg overflow-auto max-h-32 text-destructive whitespace-pre-wrap break-words text-body-1 font-mono">
115
- {errorDetails.stackTrace}
116
- </pre>
117
- </details>
118
- )}
119
-
120
- {/* Request ID */}
121
- {errorDetails.requestId && (
122
- <div className="text-body-1 pt-2 border-t border-destructive/15">
123
- <span className="font-medium text-ink-3">Request ID</span>
124
- <br />
125
- <code className="text-body-1 text-ink/70 font-mono break-all">{errorDetails.requestId}</code>
126
- </div>
127
- )}
128
- </div>
129
- )}
130
- </div>
131
- )}
132
-
133
- {/* Action button */}
134
- {action && (
135
- <button
136
- type="button"
137
- onClick={action.onClick}
138
- className={cn(
139
- 'mt-6 px-5 py-2.5 rounded-lg font-medium transition-colors h-10',
140
- isError
141
- ? 'bg-destructive text-destructive-foreground hover:bg-destructive/90'
142
- : 'bg-primary text-primary-foreground hover:bg-primary/90',
143
- )}
144
- >
145
- {action.label}
146
- </button>
147
- )}
148
- </div>
149
- );
150
- };
@@ -1,59 +0,0 @@
1
- import { Loader2 } from 'lucide-react';
2
-
3
- import {
4
- AlertDialog,
5
- AlertDialogAction,
6
- AlertDialogCancel,
7
- AlertDialogContent,
8
- AlertDialogDescription,
9
- AlertDialogFooter,
10
- AlertDialogHeader,
11
- AlertDialogTitle,
12
- } from '../primitives/alert-dialog';
13
-
14
- export interface ConfirmDeleteDialogProps {
15
- open: boolean;
16
- onOpenChange: (open: boolean) => void;
17
- onConfirm: () => void;
18
- title?: string;
19
- description?: string;
20
- isPending?: boolean;
21
- /** Confirm-button label. Defaults to 'Delete' for the destructive case. */
22
- confirmLabel?: string;
23
- }
24
-
25
- /**
26
- * Reusable delete confirmation dialog.
27
- * Used across CRUD surfaces to prevent accidental destructive actions.
28
- */
29
- export default function ConfirmDeleteDialog({
30
- open,
31
- onOpenChange,
32
- onConfirm,
33
- title = 'Delete item',
34
- description = 'This action cannot be undone. Are you sure you want to continue?',
35
- isPending,
36
- confirmLabel = 'Delete',
37
- }: ConfirmDeleteDialogProps) {
38
- return (
39
- <AlertDialog open={open} onOpenChange={onOpenChange}>
40
- <AlertDialogContent>
41
- <AlertDialogHeader>
42
- <AlertDialogTitle>{title}</AlertDialogTitle>
43
- <AlertDialogDescription>{description}</AlertDialogDescription>
44
- </AlertDialogHeader>
45
- <AlertDialogFooter>
46
- <AlertDialogCancel disabled={isPending}>Cancel</AlertDialogCancel>
47
- <AlertDialogAction
48
- onClick={onConfirm}
49
- disabled={isPending}
50
- className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
51
- >
52
- {isPending && <Loader2 className="h-4 w-4 mr-1.5 animate-spin" />}
53
- {confirmLabel}
54
- </AlertDialogAction>
55
- </AlertDialogFooter>
56
- </AlertDialogContent>
57
- </AlertDialog>
58
- );
59
- }
@@ -1,117 +0,0 @@
1
- /**
2
- * An EMBEDDED PageShell must not touch the topbar.
3
- *
4
- * ── THE DEFECT ────────────────────────────────────────────────────────────
5
- *
6
- * `embedded` means "the host page already owns the topbar — render only my
7
- * body". But `usePageMeta` was called UNCONDITIONALLY, above the `embedded`
8
- * check, and the page-meta store is a STACK: "nested pages can push their own
9
- * meta; on unmount the outer meta is restored".
10
- *
11
- * So an embedded shell pushed its own `title`, `description`, `eyebrow`,
12
- * `backTo` and `topbarActions` OVER its host's — the precise opposite of what
13
- * embedding means. The host's title and back button silently became the
14
- * child's for as long as the child was mounted, and the page-meta contract
15
- * ("never render a back button inside the page body… move it into
16
- * usePageMeta") made that the ONLY back affordance on screen, so it pointed at
17
- * the wrong parent.
18
- *
19
- * A hook cannot be called conditionally, so the fix is structural: `PageShell`
20
- * calls no hooks and only chooses between two components. These tests pin the
21
- * OBSERVABLE property — whether meta is registered — not the file layout, so
22
- * any future refactor that reintroduces the push fails them.
23
- *
24
- * Runs under the host-web vitest mirror (this package carries no vitest dep).
25
- */
26
- import { FileQuestion } from 'lucide-react';
27
- import { renderToStaticMarkup } from 'react-dom/server';
28
- import { describe, expect, it, vi, beforeEach } from 'vitest';
29
-
30
-
31
- const registered: unknown[] = [];
32
-
33
- // The kernel reaches the page-meta store through the HostBridge singleton,
34
- // never a host import — so the bridge is the seam to observe.
35
- vi.mock('../../lib/biome-host/host-bridge', async () => {
36
- const actual = await vi.importActual<Record<string, unknown>>(
37
- '../../lib/biome-host/host-bridge',
38
- );
39
- return {
40
- ...actual,
41
- useHostBridge: () => ({
42
- pageMeta: {
43
- usePageMeta: (meta: unknown) => {
44
- registered.push(meta);
45
- },
46
- },
47
- }),
48
- };
49
- });
50
-
51
- const { default: PageShell } = await import('./page-shell');
52
-
53
- beforeEach(() => {
54
- registered.length = 0;
55
- });
56
-
57
- const META = {
58
- icon: FileQuestion,
59
- title: 'Child Title',
60
- description: 'Child description',
61
- eyebrow: 'Child eyebrow',
62
- backTo: { to: '/child-parent', label: 'Child parent' },
63
- } as const;
64
-
65
- describe('PageShell page-meta ownership', () => {
66
- it('registers NOTHING when embedded — the host owns the topbar', () => {
67
- // THE BUG, directly. Before the fix this registered the child's meta,
68
- // replacing the host's title and back button on a stack-based store.
69
- renderToStaticMarkup(
70
- <PageShell {...META} embedded>
71
- <p>body</p>
72
- </PageShell>,
73
- );
74
- expect(registered).toEqual([]);
75
- });
76
-
77
- it('registers its meta when NOT embedded — a full page owns its topbar', () => {
78
- // The other half: the fix must not silence the normal case.
79
- renderToStaticMarkup(
80
- <PageShell {...META}>
81
- <p>body</p>
82
- </PageShell>,
83
- );
84
- expect(registered).toHaveLength(1);
85
- expect(registered[0]).toMatchObject({
86
- title: 'Child Title',
87
- description: 'Child description',
88
- eyebrow: 'Child eyebrow',
89
- backTo: { to: '/child-parent' },
90
- });
91
- });
92
-
93
- it('still renders the embedded body — not registering is not "render nothing"', () => {
94
- const html = renderToStaticMarkup(
95
- <PageShell {...META} embedded>
96
- <p>embedded-body-marker</p>
97
- </PageShell>,
98
- );
99
- expect(html).toContain('embedded-body-marker');
100
- });
101
-
102
- it('renders the embedded sub-tab strip, which is the child’s own chrome', () => {
103
- // Embedding suppresses the TOPBAR meta, not the child's in-body tabs.
104
- const html = renderToStaticMarkup(
105
- <PageShell
106
- {...META}
107
- embedded
108
- tabs={[{ value: 'a', label: 'Alpha' }]}
109
- activeTab="a"
110
- >
111
- <p>body</p>
112
- </PageShell>,
113
- );
114
- expect(html).toContain('Alpha');
115
- expect(registered).toEqual([]);
116
- });
117
- });