@xemahq/ui-kernel 0.11.1 → 0.11.2

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 (234) hide show
  1. package/package.json +1 -2
  2. package/src/index.ts +0 -17
  3. package/src/lib/biome-host/agent-validation.ts +0 -239
  4. package/src/lib/biome-host/biome-builders.ts +0 -112
  5. package/src/lib/biome-host/biome-mode.ts +0 -6
  6. package/src/lib/biome-host/biome-navigation.ts +0 -37
  7. package/src/lib/biome-host/biome-registry.ts +0 -299
  8. package/src/lib/biome-host/biome-scope-hooks.ts +0 -41
  9. package/src/lib/biome-host/biome-scope.tsx +0 -52
  10. package/src/lib/biome-host/biome-scoped-query.ts +0 -134
  11. package/src/lib/biome-host/create-biome-orval-config.ts +0 -82
  12. package/src/lib/biome-host/define-web-biome.spec.ts +0 -221
  13. package/src/lib/biome-host/define-web-biome.ts +0 -222
  14. package/src/lib/biome-host/errors.ts +0 -220
  15. package/src/lib/biome-host/frontend-biome.ts +0 -256
  16. package/src/lib/biome-host/host-bridge.ts +0 -281
  17. package/src/lib/biome-host/host-sources.ts +0 -54
  18. package/src/lib/biome-host/i18n-hooks.ts +0 -51
  19. package/src/lib/biome-host/i18n-port.test.ts +0 -50
  20. package/src/lib/biome-host/i18n-port.ts +0 -96
  21. package/src/lib/biome-host/index.ts +0 -35
  22. package/src/lib/biome-host/realtime-hooks.ts +0 -75
  23. package/src/lib/biome-host/realtime-port.ts +0 -115
  24. package/src/lib/biome-host/session-contributions.ts +0 -296
  25. package/src/lib/biome-host/use-mutation-with-error-toast.ts +0 -90
  26. package/src/lib/biome-host/use-page-state.ts +0 -248
  27. package/src/lib/capabilities/capability-context.ts +0 -25
  28. package/src/lib/capabilities/capability-provider.tsx +0 -73
  29. package/src/lib/capabilities/index.ts +0 -17
  30. package/src/lib/capabilities/types.ts +0 -69
  31. package/src/lib/capabilities/use-capability.ts +0 -79
  32. package/src/lib/contribution-registry/contribution-registry.spec.ts +0 -141
  33. package/src/lib/contribution-registry/contribution-registry.ts +0 -204
  34. package/src/lib/contribution-registry/index.ts +0 -9
  35. package/src/lib/contribution-registry/renderable-registry.ts +0 -142
  36. package/src/lib/system-bus/capability-invoke.ts +0 -92
  37. package/src/lib/system-bus/deeplink.ts +0 -210
  38. package/src/lib/system-bus/enums.ts +0 -86
  39. package/src/lib/system-bus/host-ports.ts +0 -93
  40. package/src/lib/system-bus/index.ts +0 -16
  41. package/src/lib/system-bus/intent-registry.ts +0 -109
  42. package/src/lib/system-bus/intents.ts +0 -113
  43. package/src/lib/system-bus/palette.ts +0 -78
  44. package/src/lib/system-bus/system-bus-builder.ts +0 -159
  45. package/src/lib/system-bus/system-bus.ts +0 -37
  46. package/src/lib/system-bus/windows.ts +0 -51
  47. package/src/offline/id.ts +0 -16
  48. package/src/offline/index.ts +0 -54
  49. package/src/offline/mutation.ts +0 -75
  50. package/src/offline/offline-status-badge.tsx +0 -51
  51. package/src/offline/online-controller.ts +0 -105
  52. package/src/offline/outbox.test.ts +0 -172
  53. package/src/offline/outbox.ts +0 -348
  54. package/src/offline/react-hooks.ts +0 -40
  55. package/src/offline/react.tsx +0 -32
  56. package/src/offline/storage.ts +0 -112
  57. package/src/offline/types.ts +0 -77
  58. package/src/org-db/components/DbResultTable.tsx +0 -176
  59. package/src/org-db/index.ts +0 -1
  60. package/src/pdf/index.ts +0 -11
  61. package/src/registry/index.ts +0 -9
  62. package/src/registry/lib/agent-validation-host.ts +0 -36
  63. package/src/registry/lib/biome-slot.tsx +0 -50
  64. package/src/registry/lib/biomes-enabled-context.ts +0 -20
  65. package/src/registry/lib/extension-points.ts +0 -134
  66. package/src/registry/lib/primitives/SessionEventCard.tsx +0 -164
  67. package/src/registry/lib/primitives/SessionEventTimeline.tsx +0 -95
  68. package/src/registry/lib/primitives/SessionMutationBar.tsx +0 -76
  69. package/src/registry/lib/primitives/SessionTabbedDrawer.tsx +0 -165
  70. package/src/registry/lib/primitives/index.ts +0 -18
  71. package/src/registry/lib/session-context-builder.ts +0 -84
  72. package/src/registry/lib/session-context-provider.tsx +0 -33
  73. package/src/registry/lib/session-context.ts +0 -21
  74. package/src/registry/lib/session-selectors.ts +0 -272
  75. package/src/session/comments/CommentRail.tsx +0 -164
  76. package/src/session/index.ts +0 -3
  77. package/src/session/lib/cn.ts +0 -11
  78. package/src/session/shell/SessionWorkspaceShell.tsx +0 -147
  79. package/src/session-kit/approvals/ApprovalButton.tsx +0 -89
  80. package/src/session-kit/approvals/ApprovalCard.tsx +0 -341
  81. package/src/session-kit/approvals/ApprovalsCenter.tsx +0 -336
  82. package/src/session-kit/approvals/CapabilityApprovalStickyBar.tsx +0 -118
  83. package/src/session-kit/approvals/Obligation.tsx +0 -111
  84. package/src/session-kit/approvals/ScopedBody.tsx +0 -392
  85. package/src/session-kit/approvals/approval-icons.ts +0 -31
  86. package/src/session-kit/approvals/approval-model.ts +0 -204
  87. package/src/session-kit/approvals/index.ts +0 -22
  88. package/src/session-kit/approvals/obligation-display.ts +0 -100
  89. package/src/session-kit/approvals/risk-accent.ts +0 -47
  90. package/src/session-kit/approvals/scope-icons.ts +0 -19
  91. package/src/session-kit/combobox/Combobox.tsx +0 -358
  92. package/src/session-kit/combobox/use-click-outside.ts +0 -21
  93. package/src/session-kit/display/ContextHeader.tsx +0 -148
  94. package/src/session-kit/display/FileDiffCard.tsx +0 -140
  95. package/src/session-kit/display/MD.tsx +0 -162
  96. package/src/session-kit/display/MessageTurn.tsx +0 -157
  97. package/src/session-kit/display/ThinkingPanel.tsx +0 -81
  98. package/src/session-kit/display/TodoChecklist.tsx +0 -120
  99. package/src/session-kit/display/TokenMeter.tsx +0 -89
  100. package/src/session-kit/display/ToolStrip.tsx +0 -278
  101. package/src/session-kit/display/TypingDots.tsx +0 -24
  102. package/src/session-kit/index.ts +0 -44
  103. package/src/session-kit/lib/enums.ts +0 -66
  104. package/src/session-kit/lib/portal-accent.ts +0 -30
  105. package/src/session-kit/lib/status-dot.ts +0 -68
  106. package/src/session-kit/primitives/Avatar.tsx +0 -44
  107. package/src/session-kit/primitives/PortalGlyph.tsx +0 -51
  108. package/src/session-kit/primitives/RiskPill.tsx +0 -95
  109. package/src/session-kit/primitives/ScopeDot.tsx +0 -47
  110. package/src/session-kit/primitives/Segmented.tsx +0 -71
  111. package/src/ui/branding/app-branding-context.ts +0 -20
  112. package/src/ui/branding/app-branding-provider.tsx +0 -123
  113. package/src/ui/branding/sanitize.ts +0 -89
  114. package/src/ui/breakpoints.ts +0 -14
  115. package/src/ui/capture/document-preview.tsx +0 -166
  116. package/src/ui/capture/field-spec-to-zod.test.ts +0 -81
  117. package/src/ui/capture/field-spec-to-zod.ts +0 -174
  118. package/src/ui/capture/field-spec-validate.ts +0 -236
  119. package/src/ui/capture/field-spec.test.ts +0 -49
  120. package/src/ui/capture/field-spec.ts +0 -274
  121. package/src/ui/capture/index.ts +0 -51
  122. package/src/ui/capture/schema-field-input.tsx +0 -620
  123. package/src/ui/capture/schema-form.tsx +0 -149
  124. package/src/ui/capture/submit-values.test.ts +0 -70
  125. package/src/ui/capture/submit-values.ts +0 -63
  126. package/src/ui/capture/use-prefill.ts +0 -186
  127. package/src/ui/capture/use-schema-validation.ts +0 -145
  128. package/src/ui/capture/use-wizard.ts +0 -180
  129. package/src/ui/capture/value-format.ts +0 -32
  130. package/src/ui/capture/wizard.tsx +0 -105
  131. package/src/ui/charts/bar-chart.tsx +0 -139
  132. package/src/ui/charts/chart-frame.tsx +0 -125
  133. package/src/ui/charts/chart-types.ts +0 -52
  134. package/src/ui/charts/chart-utils.ts +0 -190
  135. package/src/ui/charts/charts.test.tsx +0 -204
  136. package/src/ui/charts/donut-chart.tsx +0 -129
  137. package/src/ui/charts/index.ts +0 -27
  138. package/src/ui/charts/line-chart.tsx +0 -202
  139. package/src/ui/charts/sparkline.tsx +0 -109
  140. package/src/ui/chrome/AsyncBoundary.tsx +0 -74
  141. package/src/ui/chrome/EmptyState.tsx +0 -257
  142. package/src/ui/chrome/ErrorCard.tsx +0 -63
  143. package/src/ui/chrome/LoadingState.tsx +0 -61
  144. package/src/ui/chrome/PageHeader.tsx +0 -137
  145. package/src/ui/chrome/RecordCardList.tsx +0 -98
  146. package/src/ui/chrome/StateCard.tsx +0 -150
  147. package/src/ui/chrome/confirm-delete-dialog.tsx +0 -59
  148. package/src/ui/chrome/page-shell.spec.tsx +0 -117
  149. package/src/ui/chrome/page-shell.tsx +0 -221
  150. package/src/ui/chrome/scope-badge.tsx +0 -40
  151. package/src/ui/chrome/status-badge.tsx +0 -79
  152. package/src/ui/cn.ts +0 -32
  153. package/src/ui/design-tokens.ts +0 -372
  154. package/src/ui/hooks/use-debounced-value.ts +0 -16
  155. package/src/ui/hooks/use-is-mobile.ts +0 -28
  156. package/src/ui/index.ts +0 -110
  157. package/src/ui/pdf/pdf-preview.tsx +0 -122
  158. package/src/ui/primitives/alert-dialog.tsx +0 -104
  159. package/src/ui/primitives/async-combobox.tsx +0 -182
  160. package/src/ui/primitives/badge-variants.ts +0 -21
  161. package/src/ui/primitives/badge.tsx +0 -25
  162. package/src/ui/primitives/button-variants.ts +0 -31
  163. package/src/ui/primitives/button.tsx +0 -29
  164. package/src/ui/primitives/card.tsx +0 -43
  165. package/src/ui/primitives/checkbox.tsx +0 -26
  166. package/src/ui/primitives/collapsible.tsx +0 -9
  167. package/src/ui/primitives/dialog.tsx +0 -103
  168. package/src/ui/primitives/dropdown-menu.tsx +0 -179
  169. package/src/ui/primitives/form-stepper.tsx +0 -94
  170. package/src/ui/primitives/form-stepper.types.ts +0 -15
  171. package/src/ui/primitives/input.tsx +0 -22
  172. package/src/ui/primitives/label.tsx +0 -17
  173. package/src/ui/primitives/overflow-tabs.tsx +0 -331
  174. package/src/ui/primitives/popover.tsx +0 -33
  175. package/src/ui/primitives/preview-device-frame.tsx +0 -213
  176. package/src/ui/primitives/radio-group.tsx +0 -36
  177. package/src/ui/primitives/resizable.tsx +0 -67
  178. package/src/ui/primitives/scroll-area.tsx +0 -38
  179. package/src/ui/primitives/select.tsx +0 -143
  180. package/src/ui/primitives/separator.tsx +0 -20
  181. package/src/ui/primitives/sheet.tsx +0 -107
  182. package/src/ui/primitives/skeleton.tsx +0 -99
  183. package/src/ui/primitives/switch.tsx +0 -27
  184. package/src/ui/primitives/table.tsx +0 -72
  185. package/src/ui/primitives/tabs.tsx +0 -53
  186. package/src/ui/primitives/tag-multi-select.tsx +0 -261
  187. package/src/ui/primitives/textarea.tsx +0 -21
  188. package/src/ui/primitives/tooltip.tsx +0 -30
  189. package/src/ui/primitives/url-preview-chrome.tsx +0 -103
  190. package/src/vista/GenericCatalogueSurface.tsx +0 -179
  191. package/src/vista/SurfaceFallbackCard.tsx +0 -109
  192. package/src/vista/VistaPanel.tsx +0 -163
  193. package/src/vista/form-payload.spec.ts +0 -139
  194. package/src/vista/html/CustomUiFrame.spec.tsx +0 -127
  195. package/src/vista/html/CustomUiFrame.tsx +0 -206
  196. package/src/vista/html/custom-ui-frame-bridge.spec.ts +0 -191
  197. package/src/vista/html/custom-ui-frame-bridge.ts +0 -98
  198. package/src/vista/html/custom-ui-frame-contract.ts +0 -88
  199. package/src/vista/html/custom-ui-intents.spec.ts +0 -135
  200. package/src/vista/html/custom-ui-intents.ts +0 -155
  201. package/src/vista/html/custom-ui-srcdoc.spec.ts +0 -151
  202. package/src/vista/html/custom-ui-srcdoc.ts +0 -193
  203. package/src/vista/html/index.ts +0 -13
  204. package/src/vista/index.ts +0 -52
  205. package/src/vista/surface-catalogue.spec.ts +0 -140
  206. package/src/vista/surface-catalogue.ts +0 -90
  207. package/src/vista/surface-icons.ts +0 -57
  208. package/src/vista/surface-registry.ts +0 -56
  209. package/src/vista/surfaces/ChartSurface.tsx +0 -34
  210. package/src/vista/surfaces/FilesSurface.tsx +0 -202
  211. package/src/vista/surfaces/FormSurface.tsx +0 -380
  212. package/src/vista/surfaces/HtmlSurface.tsx +0 -43
  213. package/src/vista/surfaces/TableSurface.tsx +0 -100
  214. package/src/vista/surfaces/UrlSurface.tsx +0 -40
  215. package/src/vista/surfaces/blob-surfaces.tsx +0 -139
  216. package/src/vista/surfaces/elicitation-transport.spec.ts +0 -369
  217. package/src/vista/surfaces/elicitation-transport.ts +0 -367
  218. package/src/vista/surfaces/file-surface-utils.ts +0 -69
  219. package/src/vista/surfaces/index.ts +0 -24
  220. package/src/vista/surfaces/register-surfaces.tsx +0 -139
  221. package/src/vista/surfaces/text-surfaces.tsx +0 -132
  222. package/src/vista/use-vista-tabs.ts +0 -196
  223. package/src/vista/vista-code-renderer-context.ts +0 -16
  224. package/src/vista/vista-code-renderer.tsx +0 -35
  225. package/src/vista/vista-file-context.ts +0 -14
  226. package/src/vista/vista-file-source.tsx +0 -141
  227. package/src/vista/vista-form-context.ts +0 -20
  228. package/src/vista/vista-form-host.tsx +0 -48
  229. package/src/vista/vista-model.ts +0 -137
  230. package/src/vista/vista-open-context.tsx +0 -27
  231. package/src/vista/vista-open-state.ts +0 -11
  232. package/src/vista/vista-payloads.ts +0 -635
  233. package/src/vista/vista-tabs-manifest.ts +0 -138
  234. 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
- });