@usetheo/ui 0.12.0-next.0 → 0.13.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 (57) hide show
  1. package/CHANGELOG.md +111 -0
  2. package/README.md +20 -19
  3. package/dist/chunk-6ZQKEY54.js +149 -0
  4. package/dist/chunk-6ZQKEY54.js.map +1 -0
  5. package/dist/chunk-AVPHVQZS.js +73 -0
  6. package/dist/chunk-AVPHVQZS.js.map +1 -0
  7. package/dist/{chunk-ZNILW4G5.js → chunk-DW247T3Q.js} +2 -2
  8. package/dist/{chunk-ZNILW4G5.js.map → chunk-DW247T3Q.js.map} +1 -1
  9. package/dist/chunk-GXBFGWQN.js +81 -0
  10. package/dist/chunk-GXBFGWQN.js.map +1 -0
  11. package/dist/chunk-I32I36LW.js +113 -0
  12. package/dist/chunk-I32I36LW.js.map +1 -0
  13. package/dist/chunk-JPTPIZ5V.js +120 -0
  14. package/dist/chunk-JPTPIZ5V.js.map +1 -0
  15. package/dist/{chunk-TO3UAT6O.js → chunk-K7PYLTMP.js} +3 -3
  16. package/dist/{chunk-TO3UAT6O.js.map → chunk-K7PYLTMP.js.map} +1 -1
  17. package/dist/{chunk-R2PAGRDP.js → chunk-MYEHGDC2.js} +2 -2
  18. package/dist/{chunk-R2PAGRDP.js.map → chunk-MYEHGDC2.js.map} +1 -1
  19. package/dist/{chunk-IPEYGWA7.js → chunk-PTHRL242.js} +4 -4
  20. package/dist/{chunk-IPEYGWA7.js.map → chunk-PTHRL242.js.map} +1 -1
  21. package/dist/chunk-RC5XME4T.js +33 -0
  22. package/dist/chunk-RC5XME4T.js.map +1 -0
  23. package/dist/chunk-UK27KR35.js +73 -0
  24. package/dist/chunk-UK27KR35.js.map +1 -0
  25. package/dist/chunk-UOMQPIB4.js +48 -0
  26. package/dist/chunk-UOMQPIB4.js.map +1 -0
  27. package/dist/{chunk-TNBJ36XJ.js → chunk-XZKEGEPT.js} +4 -4
  28. package/dist/{chunk-TNBJ36XJ.js.map → chunk-XZKEGEPT.js.map} +1 -1
  29. package/dist/components.css +1 -1
  30. package/dist/composites/agent-editor/index.js +2 -2
  31. package/dist/composites/data-table/index.js +1 -1
  32. package/dist/composites/rule-editor/index.js +3 -3
  33. package/dist/composites/skill-editor/index.js +3 -3
  34. package/dist/composites/stability-bundle-viewer/index.js +4 -0
  35. package/dist/composites/stability-bundle-viewer/index.js.map +1 -0
  36. package/dist/index.d.ts +162 -1
  37. package/dist/index.js +44 -36
  38. package/dist/index.js.map +1 -1
  39. package/dist/preset-v3-legacy.js.map +1 -1
  40. package/dist/primitives/branch-indicator/index.js +4 -0
  41. package/dist/primitives/branch-indicator/index.js.map +1 -0
  42. package/dist/primitives/channel-card/index.js +4 -0
  43. package/dist/primitives/channel-card/index.js.map +1 -0
  44. package/dist/primitives/export-chat-dialog/index.js +4 -0
  45. package/dist/primitives/export-chat-dialog/index.js.map +1 -0
  46. package/dist/primitives/gateway-status-indicator/index.js +4 -0
  47. package/dist/primitives/gateway-status-indicator/index.js.map +1 -0
  48. package/dist/primitives/pin-input/index.js +1 -1
  49. package/dist/primitives/run-status-pill/index.js +4 -0
  50. package/dist/primitives/run-status-pill/index.js.map +1 -0
  51. package/dist/primitives/thinking-level-selector/index.js +4 -0
  52. package/dist/primitives/thinking-level-selector/index.js.map +1 -0
  53. package/dist/primitives/update-banner/index.js +4 -0
  54. package/dist/primitives/update-banner/index.js.map +1 -0
  55. package/package.json +126 -92
  56. package/registry/r/data-table.json +1 -1
  57. package/registry/r/pin-input.json +1 -1
package/CHANGELOG.md CHANGED
@@ -7,6 +7,117 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.13.0] - 2026-05-29
11
+
12
+ **Minor (additive only — zero breaking changes).**
13
+
14
+ Seven new components shipped as Phase 1 of the `theokit-ui-parity` plan
15
+ (`.claude/knowledge-base/plans/theokit-ui-parity-plan.md` v1.1). All seven
16
+ mirror OpenClaw Control UI patterns the framework lacked, designed to be
17
+ composable into any theokit app via `@usetheo/ui` barrel.
18
+
19
+ ### Added — Phase 2 component
20
+
21
+ - **`<ChannelCard>`** (primitive, `agent/`) — inbound gateway connection
22
+ surface (Telegram, Discord, Slack, WhatsApp, Webhook, MCP). 4 statuses
23
+ (`disconnected | connecting | connected | error`) with the toggle button
24
+ reflecting the current state: `connected` shows "Disconnect", others show
25
+ "Connect", `connecting` keeps the button disabled (transient state guard).
26
+ Closed `ChannelPlatform` enum prevents silent typos at the backend
27
+ boundary. 7 Vitest tests including vitest-axe. Ladle story with 5
28
+ variants (Connected/Disconnected/Connecting/ErrorState/MCP). Consumed by
29
+ the dogfood-app `/channels` page end-to-end against the
30
+ `server/routes/channels.ts` registry.
31
+
32
+ ### Added — Phase 1 components (44/44 Vitest GREEN)
33
+
34
+ - **`<ThinkingLevelSelector>`** (primitive, `agent/`) — multi-state combobox
35
+ for LLM reasoning budget. Mirrors OpenClaw thinking selector. Values:
36
+ `"inherited" | "off" | "minimal" | "low" | "medium" | "high" | "xhigh"`.
37
+ - **`<RunStatusPill>`** (primitive, `agent/`) — compact status indicator
38
+ for Run/Task lifecycle. 6 states mirror SDK `Task` enum (D362):
39
+ `queued | in_progress | finished | error | cancelled | interrupted`.
40
+ - **`<BranchIndicator>`** (primitive, `agent/`) — small "×N" pill that
41
+ shows when a run was retried/branched. Returns `null` for `< 2` or
42
+ non-integer (EC-10 guard).
43
+ - **`<GatewayStatusIndicator>`** (primitive, `infra/`) — live
44
+ connection-status dot. 4 statuses (online/offline/degraded/reconnecting)
45
+ × 2 variants (compact/labeled) + optional latency text.
46
+ - **`<UpdateBanner>`** (primitive, `infra/`) — top-of-app alert for newer
47
+ version. Dismiss persistence is consumer's responsibility (EC-16).
48
+ - **`<ExportChatDialog>`** (primitive, `agent/`) — modal exporting chat in
49
+ `markdown | json | jsonl | sharegpt`. Async-aware (disables buttons
50
+ during pending export).
51
+ - **`<StabilityBundleViewer>`** (composite, `infra/`) — crash bundle JSON
52
+ inspector. Sections collapse independently. EC-9 absorbed: handles
53
+ missing optional sections gracefully.
54
+
55
+ ### Added — Phase 0 tooling
56
+
57
+ - **`scripts/inventory-components.mjs`** — walks
58
+ `src/components/{primitives,composites}/<name>/` producing
59
+ `component-inventory.json`. CI drift-gate seed.
60
+ - **`scripts/generate-missing-stories.mjs`** — gerador + CI check mode.
61
+ EC-5/D12 absorbed: ships `kebabToPascal(name)` helper with 9 Vitest
62
+ cases + match-confirmation via `export\s+...` regex.
63
+ - **`wrangler.toml` + `.github/workflows/deploy-ladle.yml`** —
64
+ Cloudflare Pages deploy config for the Ladle component catalog.
65
+ Workflow contains `pnpm ladle:build` step BEFORE deploy (EC-14).
66
+ - New package scripts: `inventory`, `stories:check`, `stories:generate`,
67
+ `stories:test`.
68
+
69
+ ### Changed — Build pipeline hardening
70
+
71
+ - **`scripts/regen-subpath-exports.ts`** now delegates to `sync-exports.ts`
72
+ for the final write. Previously the post-build step sorted ALL exports
73
+ alphabetically while `sync-exports.buildExports` produced the canonical
74
+ `BASE → sorted components → ISOLATED` order — meaning `pnpm build &&
75
+ pnpm quality:structure` regressed every time. The validator and the
76
+ build now share a single source of truth.
77
+ - **`scripts/sync-exports.ts`** now post-formats `package.json` via
78
+ `biome format --write` after writing. `JSON.stringify(_, _, 2)` always
79
+ expands short arrays like `sideEffects` and `onlyBuiltDependencies`,
80
+ while `pnpm format:check` (Biome) expects them inline; the two were
81
+ fighting after every sync.
82
+ - **`tailwind.config.ts`** dropped the `satisfies Config` clause. The
83
+ preset (`src/styles/tailwind-preset.ts`) resolves the v4 `Config` type
84
+ via the `src/styles/node_modules/tailwindcss` symlink to v4.3.0, while
85
+ the root config would resolve v3.4.19 — the v3/v4 type seam mismatched
86
+ on `darkMode` (`DarkModeStrategy` vs `Partial<DarkModeConfig>`).
87
+ Runtime correctness is enforced by `pnpm dogfood:v4-zero-config`.
88
+ - **`biome.json`** override extended to cover `scripts/**/*.mjs` so the
89
+ `noConsole` rule does not block tool scripts (it was already exempted
90
+ for `.ts`).
91
+ - **`pin-input.tsx`** — removed stale `// biome-ignore lint/suspicious/noArrayIndexKey`
92
+ comment now flagged as unused suppression.
93
+ - **`export-chat-dialog.tsx` + `run-status-pill.tsx` + `thinking-level-selector.tsx`** —
94
+ added precise `biome-ignore` comments documenting the `a11y/useSemanticElements`
95
+ and `suspicious/noConsole` exceptions, with the reason inline.
96
+
97
+ ### Documentation
98
+
99
+ - 7 new component pages under `theo-opendocs/content/theoui/{agent,infra}/`.
100
+ - New `ChannelCard` page under `theo-opendocs/content/theoui/agent/channel-card.mdx`
101
+ with live preview (`ComponentPreview` + `PropsTable`) deployed to
102
+ `https://channel-card.theo-opendocs.pages.dev/`.
103
+
104
+ ## [0.12.0] - 2026-05-28
105
+
106
+ **Stable release — promoted from `0.12.0-next.0` after dogfood validation + cross-repo contract gates landed.**
107
+
108
+ First `@usetheo/ui` release on the npm `latest` tag without a `-next.*` pre-release suffix. Content is the union of everything shipped in `0.12.0-next.0` (2026-05-25) plus the additions below from `[Unreleased]` consolidation. **Zero breaking changes vs `0.12.0-next.0`** — consumers on `^0.12.0-next.0` upgrade transparently.
109
+
110
+ ### Added (dogfood-fixes-and-coverage-expansion T4.1, 2026-05-28)
111
+
112
+ - **`scripts/validate-exports.mjs`** (NEW) — FAANG-grade pre-publish gate com 6 runtime checks: (1) exports['.'] declared, (2) type:module consistency D13, (3) import condition file exists + dynamic import runtime, (4) require condition runtime check (skip se ESM-only), (5) ESM-only intentional notice, (6) TODOS subpath exports validados (não só `.`). Bloqueia `npm publish` se shape OR comportamento regridir.
113
+ - **`prepublishOnly`** hook estendido: `pnpm build && pnpm test:contract && node scripts/validate-exports.mjs`. EC-8 fix do edge case review FAANG-grade.
114
+ - **`validate:exports`** script standalone — pode rodar manual `pnpm validate:exports`.
115
+
116
+ ### Added (cross-repo-integration-coesao, 2026-05-28)
117
+
118
+ - **Contract test mirror** — `tests/contract/theokit-consumer.test.ts` (6 BDD `it()`) validates `dist/vite-plugin.js` shape (default export factory + Plugin/Plugin[] return + `name: string`) and presence of `dist/preset.css`, `dist/styles.css`, `dist/fonts.css`. Roda via novo `pnpm test:contract`. Bloqueia publish via `prepublishOnly: pnpm build && pnpm test:contract`. Cumpre [ADR 0001](docs/adr/0001-vite-plugin-subpath-export-contract.md) (cross-repo contract). Plano coordenador no monorepo theokit-tools: [cross-repo-integration-coesao-plan.md](../.claude/knowledge-base/plans/cross-repo-integration-coesao-plan.md) T1.3.
119
+ - **`vitest.config.ts`** include estendido para cobrir `tests/**/*.{test,spec}.ts` (era só `src/`).
120
+
10
121
  ## [0.12.0-next.0] - 2026-05-25
11
122
 
12
123
  Minor (additive, zero breaking change) — ships two LLM-facing artifacts
package/README.md CHANGED
@@ -13,8 +13,8 @@ A React component library built for AI agent surfaces and cloud dashboards. **10
13
13
  <!-- BEGIN:counts -->
14
14
  [![license](https://img.shields.io/badge/license-Apache--2.0-7C3AED?style=flat-square)](./LICENSE)
15
15
  [![react](https://img.shields.io/badge/react-18+-7C3AED?style=flat-square&logo=react&logoColor=white)](https://react.dev)
16
- [![tests](https://img.shields.io/badge/tests-1344%20passing-success?style=flat-square)](#quality-gates)
17
- [![components](https://img.shields.io/badge/components-139-7C3AED?style=flat-square)](#component-catalog)
16
+ [![tests](https://img.shields.io/badge/tests-1384%20passing-success?style=flat-square)](#quality-gates)
17
+ [![components](https://img.shields.io/badge/components-147-7C3AED?style=flat-square)](#component-catalog)
18
18
  [![shadcn](https://img.shields.io/badge/shadcn-compatible-000?style=flat-square)](https://ui.shadcn.com/docs/registry)
19
19
  <!-- END:counts -->
20
20
 
@@ -143,33 +143,34 @@ The skill lives at [`skills/theo-ui/`](./skills/theo-ui/) and is installable via
143
143
  ## Component catalog
144
144
 
145
145
  <!-- BEGIN:component-catalog-intro -->
146
- **139 components**, organized by mechanical rule: a *primitive* imports no other `@usetheo/ui` component; a *composite* does.
146
+ **147 components**, organized by mechanical rule: a *primitive* imports no other `@usetheo/ui` component; a *composite* does.
147
147
  <!-- END:component-catalog-intro -->
148
148
 
149
149
  <details>
150
150
  <summary>
151
151
  <!-- BEGIN:primitives-count -->
152
- **Primitives** (92) — building blocks
152
+ **Primitives** (99) — building blocks
153
153
  <!-- END:primitives-count -->
154
154
  </summary>
155
155
 
156
156
  <!-- BEGIN:primitives -->
157
157
  `ActionBar` · `AgentErrorCard` · `AgentEvent` · `AgentHandoff` · `AgentProfile` · `AgentStartingState`
158
158
  `AgentStreaming` · `Alert` · `ArtifactPreview` · `AttachmentChip` · `AuditLogEntry` · `AutoCompactNotice`
159
- `Avatar` · `Badge` · `BrowserControls` · `BuildLogStream` · `Button` · `CapabilityIndicator`
160
- `Card` · `ChatThread` · `Checkbox` · `ContextCard` · `ContextWindowBar` · `CopyButton`
161
- `CostMeter` · `CreatedFilesCard` · `CronJobCard` · `DangerZone` · `Dialog` · `DiffViewer`
162
- `DropdownMenu` · `EmptyState` · `FolderContextCard` · `FolderSelector` · `FormField` · `HookConfig`
163
- `HookEventLog` · `Input` · `IntentSelector` · `Label` · `LaneBoard` · `LoginSplit`
164
- `MCPServerCard` · `MemoryEditor` · `MentionMenu` · `MetricsPanel` · `ModelCard` · `ModelSelector`
165
- `Pagination` · `PermissionMatrix` · `PinInput` · `PlanBadge` · `Progress` · `ProgressChecklist`
166
- `ProjectSwitcher` · `QuickActionChips` · `RadioGroup` · `RecentFoldersList` · `RuleCard` · `RunStats`
167
- `RunningTasksPanel` · `ScrollArea` · `Select` · `SessionListItem` · `SessionTimeline` · `Sheet`
168
- `Sidebar` · `Skeleton` · `SkillCard` · `SocialAuthRow` · `StatTile` · `StatusDot`
169
- `StepsRail` · `SubAgentDispatch` · `Switch` · `SystemPromptEditor` · `Table` · `Tabs`
170
- `TaskNode` · `TaskPlan` · `TerminalPanel` · `Textarea` · `Timestamp` · `Toast`
159
+ `Avatar` · `Badge` · `BranchIndicator` · `BrowserControls` · `BuildLogStream` · `Button`
160
+ `CapabilityIndicator` · `Card` · `ChannelCard` · `ChatThread` · `Checkbox` · `ContextCard`
161
+ `ContextWindowBar` · `CopyButton` · `CostMeter` · `CreatedFilesCard` · `CronJobCard` · `DangerZone`
162
+ `Dialog` · `DiffViewer` · `DropdownMenu` · `EmptyState` · `ExportChatDialog` · `FolderContextCard`
163
+ `FolderSelector` · `FormField` · `GatewayStatusIndicator` · `HookConfig` · `HookEventLog` · `Input`
164
+ `IntentSelector` · `Label` · `LaneBoard` · `LoginSplit` · `MCPServerCard` · `MemoryEditor`
165
+ `MentionMenu` · `MetricsPanel` · `ModelCard` · `ModelSelector` · `Pagination` · `PermissionMatrix`
166
+ `PinInput` · `PlanBadge` · `Progress` · `ProgressChecklist` · `ProjectSwitcher` · `QuickActionChips`
167
+ `RadioGroup` · `RecentFoldersList` · `RuleCard` · `RunStats` · `RunStatusPill` · `RunningTasksPanel`
168
+ `ScrollArea` · `Select` · `SessionListItem` · `SessionTimeline` · `Sheet` · `Sidebar`
169
+ `Skeleton` · `SkillCard` · `SocialAuthRow` · `StatTile` · `StatusDot` · `StepsRail`
170
+ `SubAgentDispatch` · `Switch` · `SystemPromptEditor` · `Table` · `Tabs` · `TaskNode`
171
+ `TaskPlan` · `TerminalPanel` · `Textarea` · `ThinkingLevelSelector` · `Timestamp` · `Toast`
171
172
  `Toaster` · `TokenUsageChart` · `ToolCall` · `ToolCallCard` · `ToolResult` · `ToolsList`
172
- `Tooltip` · `TopNav`
173
+ `Tooltip` · `TopNav` · `UpdateBanner`
173
174
  <!-- END:primitives -->
174
175
 
175
176
  </details>
@@ -177,12 +178,12 @@ The skill lives at [`skills/theo-ui/`](./skills/theo-ui/) and is installable via
177
178
  <details>
178
179
  <summary>
179
180
  <!-- BEGIN:composites-count -->
180
- **Composites** (47) — assembled flows
181
+ **Composites** (48) — assembled flows
181
182
  <!-- END:composites-count -->
182
183
  </summary>
183
184
 
184
185
  <!-- BEGIN:composites -->
185
- `AccountMenu` · `AgentComposer` · `AgentEditor` · `AgentStream` · `AgentTimeline` · `ApprovalCard` · `ChatComposer` · `ChatMessage` · `ChatMessageAction` · `ChatMessageActions` · `ChatMessageBranch` · `ChatMessageBranchContent` · `ChatMessageBranchNext` · `ChatMessageBranchPage` · `ChatMessageBranchPrevious` · `ChatMessageBranchSelector` · `ChatMessageContent` · `ChatMessageResponse` · `ChatMessageRoot` · `ChatMessageToolbar` · `CodeBlock` · `CommandPalette` · `ConfirmDialog` · `CronJobsList` · `DataPart` · `DataTable` · `DeploymentRow` · `DomainConfig` · `EnvVarEditor` · `FilePart` · `MCPServerList` · `PageShell` · `PermissionModal` · `PreviewEnvCard` · `PreviewPanel` · `ProjectCard` · `ReasoningPart` · `RollbackUI` · `RuleEditor` · `SkillEditor` · `SkillsList` · `SourceDocumentPart` · `SourceUrlPart` · `TaskHeader` · `TextPart` · `ToolCallPart` · `UsageMeter`
186
+ `AccountMenu` · `AgentComposer` · `AgentEditor` · `AgentStream` · `AgentTimeline` · `ApprovalCard` · `ChatComposer` · `ChatMessage` · `ChatMessageAction` · `ChatMessageActions` · `ChatMessageBranch` · `ChatMessageBranchContent` · `ChatMessageBranchNext` · `ChatMessageBranchPage` · `ChatMessageBranchPrevious` · `ChatMessageBranchSelector` · `ChatMessageContent` · `ChatMessageResponse` · `ChatMessageRoot` · `ChatMessageToolbar` · `CodeBlock` · `CommandPalette` · `ConfirmDialog` · `CronJobsList` · `DataPart` · `DataTable` · `DeploymentRow` · `DomainConfig` · `EnvVarEditor` · `FilePart` · `MCPServerList` · `PageShell` · `PermissionModal` · `PreviewEnvCard` · `PreviewPanel` · `ProjectCard` · `ReasoningPart` · `RollbackUI` · `RuleEditor` · `SkillEditor` · `SkillsList` · `SourceDocumentPart` · `SourceUrlPart` · `StabilityBundleViewer` · `TaskHeader` · `TextPart` · `ToolCallPart` · `UsageMeter`
186
187
  <!-- END:composites -->
187
188
 
188
189
  </details>
@@ -0,0 +1,149 @@
1
+ import { cn } from './chunk-ZSRJCIWF.js';
2
+ import { Info, AlertTriangle, AlertOctagon } from 'lucide-react';
3
+ import { forwardRef, useState } from 'react';
4
+ import { jsxs, jsx } from 'react/jsx-runtime';
5
+
6
+ var SEVERITY_META = {
7
+ fatal: {
8
+ icon: AlertOctagon,
9
+ className: "text-destructive",
10
+ label: "Fatal"
11
+ },
12
+ error: {
13
+ icon: AlertTriangle,
14
+ className: "text-amber-600 dark:text-amber-400",
15
+ label: "Error"
16
+ },
17
+ warn: {
18
+ icon: Info,
19
+ className: "text-blue-600 dark:text-blue-400",
20
+ label: "Warning"
21
+ }
22
+ };
23
+ function CollapsibleSection({
24
+ title,
25
+ testId,
26
+ defaultOpen = true,
27
+ children
28
+ }) {
29
+ const [open, setOpen] = useState(defaultOpen);
30
+ return /* @__PURE__ */ jsxs("section", { className: "rounded border border-border", "data-testid": testId, children: [
31
+ /* @__PURE__ */ jsxs(
32
+ "button",
33
+ {
34
+ type: "button",
35
+ onClick: () => setOpen((v) => !v),
36
+ className: "flex w-full items-center justify-between px-3 py-2 text-left font-medium text-sm hover:bg-muted/30",
37
+ "aria-expanded": open,
38
+ "data-testid": `${testId}-toggle`,
39
+ children: [
40
+ /* @__PURE__ */ jsx("span", { children: title }),
41
+ /* @__PURE__ */ jsx("span", { "aria-hidden": true, children: open ? "\u25BE" : "\u25B8" })
42
+ ]
43
+ }
44
+ ),
45
+ open && /* @__PURE__ */ jsx("div", { className: "border-border border-t px-3 py-2", children })
46
+ ] });
47
+ }
48
+ var StabilityBundleViewer = forwardRef(
49
+ ({ bundle, onCopy, className, "data-testid": dataTestId }, ref) => {
50
+ const meta = SEVERITY_META[bundle.severity] ?? SEVERITY_META.error;
51
+ const Icon = meta.icon;
52
+ const envEntries = bundle.env !== void 0 ? Object.entries(bundle.env) : [];
53
+ const configEntries = bundle.config !== void 0 ? Object.entries(bundle.config) : [];
54
+ const metadataEntries = bundle.metadata !== void 0 ? Object.entries(bundle.metadata) : [];
55
+ return /* @__PURE__ */ jsxs(
56
+ "div",
57
+ {
58
+ ref,
59
+ className: cn("flex flex-col gap-3", className),
60
+ "data-testid": dataTestId ?? "stability-bundle-viewer",
61
+ children: [
62
+ /* @__PURE__ */ jsxs("header", { className: "flex items-start gap-3 rounded border border-border bg-card p-3", children: [
63
+ /* @__PURE__ */ jsx(Icon, { className: cn("size-5", meta.className), "aria-hidden": true }),
64
+ /* @__PURE__ */ jsxs("div", { className: "flex-1", children: [
65
+ /* @__PURE__ */ jsxs("div", { className: "flex items-baseline gap-2", children: [
66
+ /* @__PURE__ */ jsx(
67
+ "span",
68
+ {
69
+ className: cn("font-semibold text-sm", meta.className),
70
+ "data-testid": "stability-severity",
71
+ children: meta.label
72
+ }
73
+ ),
74
+ /* @__PURE__ */ jsx(
75
+ "time",
76
+ {
77
+ className: "text-muted-foreground text-xs",
78
+ dateTime: bundle.timestamp,
79
+ "data-testid": "stability-timestamp",
80
+ children: bundle.timestamp
81
+ }
82
+ )
83
+ ] }),
84
+ /* @__PURE__ */ jsx("p", { className: "mt-1 text-sm", "data-testid": "stability-summary", children: bundle.summary })
85
+ ] }),
86
+ onCopy !== void 0 && /* @__PURE__ */ jsx(
87
+ "button",
88
+ {
89
+ type: "button",
90
+ onClick: onCopy,
91
+ className: "rounded border border-border px-2 py-1 text-xs hover:bg-muted/30",
92
+ "data-testid": "stability-copy",
93
+ children: "Copy"
94
+ }
95
+ )
96
+ ] }),
97
+ bundle.error !== void 0 && /* @__PURE__ */ jsxs(CollapsibleSection, { title: "Error", testId: "stability-section-error", children: [
98
+ /* @__PURE__ */ jsxs("p", { className: "font-mono text-sm", children: [
99
+ /* @__PURE__ */ jsx("strong", { children: bundle.error.name }),
100
+ ": ",
101
+ bundle.error.message
102
+ ] }),
103
+ bundle.error.stack !== void 0 && bundle.error.stack.length > 0 && /* @__PURE__ */ jsx(
104
+ "pre",
105
+ {
106
+ className: "mt-2 overflow-x-auto rounded bg-muted/30 p-2 text-xs",
107
+ "data-testid": "stability-stack",
108
+ children: bundle.error.stack
109
+ }
110
+ )
111
+ ] }),
112
+ envEntries.length > 0 && /* @__PURE__ */ jsx(
113
+ CollapsibleSection,
114
+ {
115
+ title: `Environment (${envEntries.length})`,
116
+ testId: "stability-section-env",
117
+ defaultOpen: false,
118
+ children: /* @__PURE__ */ jsxs("table", { className: "w-full text-xs", children: [
119
+ /* @__PURE__ */ jsx("thead", { className: "text-muted-foreground", children: /* @__PURE__ */ jsxs("tr", { children: [
120
+ /* @__PURE__ */ jsx("th", { className: "text-left", children: "Key" }),
121
+ /* @__PURE__ */ jsx("th", { className: "text-left", children: "Value" })
122
+ ] }) }),
123
+ /* @__PURE__ */ jsx("tbody", { children: envEntries.map(([k, v]) => /* @__PURE__ */ jsxs("tr", { className: "border-border/40 border-t", children: [
124
+ /* @__PURE__ */ jsx("td", { className: "py-1 pr-2 font-mono", children: k }),
125
+ /* @__PURE__ */ jsx("td", { className: "py-1 font-mono", children: v })
126
+ ] }, k)) })
127
+ ] })
128
+ }
129
+ ),
130
+ configEntries.length > 0 && /* @__PURE__ */ jsx(CollapsibleSection, { title: "Config", testId: "stability-section-config", defaultOpen: false, children: /* @__PURE__ */ jsx("pre", { className: "overflow-x-auto rounded bg-muted/30 p-2 text-xs", children: JSON.stringify(bundle.config, null, 2) }) }),
131
+ metadataEntries.length > 0 && /* @__PURE__ */ jsx(
132
+ CollapsibleSection,
133
+ {
134
+ title: "Metadata",
135
+ testId: "stability-section-metadata",
136
+ defaultOpen: false,
137
+ children: /* @__PURE__ */ jsx("pre", { className: "overflow-x-auto rounded bg-muted/30 p-2 text-xs", children: JSON.stringify(bundle.metadata, null, 2) })
138
+ }
139
+ )
140
+ ]
141
+ }
142
+ );
143
+ }
144
+ );
145
+ StabilityBundleViewer.displayName = "StabilityBundleViewer";
146
+
147
+ export { StabilityBundleViewer };
148
+ //# sourceMappingURL=chunk-6ZQKEY54.js.map
149
+ //# sourceMappingURL=chunk-6ZQKEY54.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/components/composites/stability-bundle-viewer/stability-bundle-viewer.tsx"],"names":[],"mappings":"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","file":"chunk-6ZQKEY54.js","sourcesContent":["import { AlertOctagon, AlertTriangle, Info, type LucideIcon } from \"lucide-react\";\nimport { forwardRef, useState } from \"react\";\n\nimport { cn } from \"../../../lib/cn.js\";\n\n/**\n * StabilityBundleViewer — inspector for a stability-bundle JSON produced by a\n * server crash. Sections (error, env, config, metadata) collapse independently.\n *\n * EC-9: handles bundles missing optional sections gracefully. Renderer DOES\n * NOT redact env values — bundle writer is expected to redact at emit-time.\n */\n\nexport type StabilitySeverity = \"fatal\" | \"error\" | \"warn\";\n\nexport interface StabilityBundle {\n timestamp: string;\n severity: StabilitySeverity;\n summary: string;\n error?: { name: string; message: string; stack?: string };\n env?: Record<string, string>;\n config?: Record<string, unknown>;\n metadata?: Record<string, unknown>;\n}\n\nexport interface StabilityBundleViewerProps {\n bundle: StabilityBundle;\n onCopy?: () => void;\n className?: string;\n \"data-testid\"?: string;\n}\n\nconst SEVERITY_META: Record<\n StabilitySeverity,\n { icon: LucideIcon; className: string; label: string }\n> = {\n fatal: {\n icon: AlertOctagon,\n className: \"text-destructive\",\n label: \"Fatal\",\n },\n error: {\n icon: AlertTriangle,\n className: \"text-amber-600 dark:text-amber-400\",\n label: \"Error\",\n },\n warn: {\n icon: Info,\n className: \"text-blue-600 dark:text-blue-400\",\n label: \"Warning\",\n },\n};\n\ninterface CollapsibleSectionProps {\n title: string;\n testId: string;\n defaultOpen?: boolean;\n children: React.ReactNode;\n}\n\nfunction CollapsibleSection({\n title,\n testId,\n defaultOpen = true,\n children,\n}: CollapsibleSectionProps) {\n const [open, setOpen] = useState(defaultOpen);\n return (\n <section className=\"rounded border border-border\" data-testid={testId}>\n <button\n type=\"button\"\n onClick={() => setOpen((v) => !v)}\n className=\"flex w-full items-center justify-between px-3 py-2 text-left font-medium text-sm hover:bg-muted/30\"\n aria-expanded={open}\n data-testid={`${testId}-toggle`}\n >\n <span>{title}</span>\n <span aria-hidden>{open ? \"▾\" : \"▸\"}</span>\n </button>\n {open && <div className=\"border-border border-t px-3 py-2\">{children}</div>}\n </section>\n );\n}\n\nexport const StabilityBundleViewer = forwardRef<HTMLDivElement, StabilityBundleViewerProps>(\n ({ bundle, onCopy, className, \"data-testid\": dataTestId }, ref) => {\n const meta = SEVERITY_META[bundle.severity] ?? SEVERITY_META.error;\n const Icon = meta.icon;\n const envEntries = bundle.env !== undefined ? Object.entries(bundle.env) : [];\n const configEntries = bundle.config !== undefined ? Object.entries(bundle.config) : [];\n const metadataEntries = bundle.metadata !== undefined ? Object.entries(bundle.metadata) : [];\n\n return (\n <div\n ref={ref}\n className={cn(\"flex flex-col gap-3\", className)}\n data-testid={dataTestId ?? \"stability-bundle-viewer\"}\n >\n <header className=\"flex items-start gap-3 rounded border border-border bg-card p-3\">\n <Icon className={cn(\"size-5\", meta.className)} aria-hidden />\n <div className=\"flex-1\">\n <div className=\"flex items-baseline gap-2\">\n <span\n className={cn(\"font-semibold text-sm\", meta.className)}\n data-testid=\"stability-severity\"\n >\n {meta.label}\n </span>\n <time\n className=\"text-muted-foreground text-xs\"\n dateTime={bundle.timestamp}\n data-testid=\"stability-timestamp\"\n >\n {bundle.timestamp}\n </time>\n </div>\n <p className=\"mt-1 text-sm\" data-testid=\"stability-summary\">\n {bundle.summary}\n </p>\n </div>\n {onCopy !== undefined && (\n <button\n type=\"button\"\n onClick={onCopy}\n className=\"rounded border border-border px-2 py-1 text-xs hover:bg-muted/30\"\n data-testid=\"stability-copy\"\n >\n Copy\n </button>\n )}\n </header>\n\n {bundle.error !== undefined && (\n <CollapsibleSection title=\"Error\" testId=\"stability-section-error\">\n <p className=\"font-mono text-sm\">\n <strong>{bundle.error.name}</strong>: {bundle.error.message}\n </p>\n {bundle.error.stack !== undefined && bundle.error.stack.length > 0 && (\n <pre\n className=\"mt-2 overflow-x-auto rounded bg-muted/30 p-2 text-xs\"\n data-testid=\"stability-stack\"\n >\n {bundle.error.stack}\n </pre>\n )}\n </CollapsibleSection>\n )}\n\n {envEntries.length > 0 && (\n <CollapsibleSection\n title={`Environment (${envEntries.length})`}\n testId=\"stability-section-env\"\n defaultOpen={false}\n >\n <table className=\"w-full text-xs\">\n <thead className=\"text-muted-foreground\">\n <tr>\n <th className=\"text-left\">Key</th>\n <th className=\"text-left\">Value</th>\n </tr>\n </thead>\n <tbody>\n {envEntries.map(([k, v]) => (\n <tr key={k} className=\"border-border/40 border-t\">\n <td className=\"py-1 pr-2 font-mono\">{k}</td>\n <td className=\"py-1 font-mono\">{v}</td>\n </tr>\n ))}\n </tbody>\n </table>\n </CollapsibleSection>\n )}\n\n {configEntries.length > 0 && (\n <CollapsibleSection title=\"Config\" testId=\"stability-section-config\" defaultOpen={false}>\n <pre className=\"overflow-x-auto rounded bg-muted/30 p-2 text-xs\">\n {JSON.stringify(bundle.config, null, 2)}\n </pre>\n </CollapsibleSection>\n )}\n\n {metadataEntries.length > 0 && (\n <CollapsibleSection\n title=\"Metadata\"\n testId=\"stability-section-metadata\"\n defaultOpen={false}\n >\n <pre className=\"overflow-x-auto rounded bg-muted/30 p-2 text-xs\">\n {JSON.stringify(bundle.metadata, null, 2)}\n </pre>\n </CollapsibleSection>\n )}\n </div>\n );\n },\n);\nStabilityBundleViewer.displayName = \"StabilityBundleViewer\";\n"]}
@@ -0,0 +1,73 @@
1
+ import { cn } from './chunk-ZSRJCIWF.js';
2
+ import { forwardRef } from 'react';
3
+ import { jsxs, jsx } from 'react/jsx-runtime';
4
+
5
+ var STATUS_META = {
6
+ online: {
7
+ dot: "bg-emerald-500",
8
+ label: "Online",
9
+ aria: "Gateway online"
10
+ },
11
+ offline: {
12
+ dot: "bg-red-500",
13
+ label: "Offline",
14
+ aria: "Gateway offline"
15
+ },
16
+ degraded: {
17
+ dot: "bg-amber-500",
18
+ label: "Degraded",
19
+ aria: "Gateway degraded"
20
+ },
21
+ reconnecting: {
22
+ dot: "bg-blue-500",
23
+ label: "Reconnecting\u2026",
24
+ aria: "Gateway reconnecting"
25
+ }
26
+ };
27
+ function formatLatency(ms) {
28
+ if (ms === void 0) return null;
29
+ if (!Number.isFinite(ms) || ms < 0) return null;
30
+ if (ms < 1) return "<1ms";
31
+ if (ms < 1e3) return `${Math.round(ms)}ms`;
32
+ return `${(ms / 1e3).toFixed(1)}s`;
33
+ }
34
+ var GatewayStatusIndicator = forwardRef(
35
+ ({ status, latencyMs, variant = "labeled", className, "data-testid": dataTestId, ...rest }, ref) => {
36
+ const meta = STATUS_META[status];
37
+ const latency = formatLatency(latencyMs);
38
+ return /* @__PURE__ */ jsxs(
39
+ "span",
40
+ {
41
+ ref,
42
+ role: "img",
43
+ "aria-label": meta.aria,
44
+ className: cn("inline-flex items-center gap-2 font-medium text-xs", className),
45
+ "data-testid": dataTestId ?? "gateway-status-indicator",
46
+ "data-status": status,
47
+ ...rest,
48
+ children: [
49
+ /* @__PURE__ */ jsx(
50
+ "span",
51
+ {
52
+ className: cn(
53
+ "inline-block size-2 rounded-full",
54
+ meta.dot,
55
+ status === "reconnecting" && "animate-pulse"
56
+ ),
57
+ "aria-hidden": true
58
+ }
59
+ ),
60
+ variant === "labeled" && /* @__PURE__ */ jsxs("span", { className: "text-foreground", children: [
61
+ meta.label,
62
+ latency !== null && /* @__PURE__ */ jsx("span", { className: "ml-1 text-muted-foreground", "data-testid": "gateway-latency", children: latency })
63
+ ] })
64
+ ]
65
+ }
66
+ );
67
+ }
68
+ );
69
+ GatewayStatusIndicator.displayName = "GatewayStatusIndicator";
70
+
71
+ export { GatewayStatusIndicator };
72
+ //# sourceMappingURL=chunk-AVPHVQZS.js.map
73
+ //# sourceMappingURL=chunk-AVPHVQZS.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/components/primitives/gateway-status-indicator/gateway-status-indicator.tsx"],"names":[],"mappings":";;;;AAyBA,IAAM,WAAA,GAAmF;AAAA,EACvF,MAAA,EAAQ;AAAA,IACN,GAAA,EAAK,gBAAA;AAAA,IACL,KAAA,EAAO,QAAA;AAAA,IACP,IAAA,EAAM;AAAA,GACR;AAAA,EACA,OAAA,EAAS;AAAA,IACP,GAAA,EAAK,YAAA;AAAA,IACL,KAAA,EAAO,SAAA;AAAA,IACP,IAAA,EAAM;AAAA,GACR;AAAA,EACA,QAAA,EAAU;AAAA,IACR,GAAA,EAAK,cAAA;AAAA,IACL,KAAA,EAAO,UAAA;AAAA,IACP,IAAA,EAAM;AAAA,GACR;AAAA,EACA,YAAA,EAAc;AAAA,IACZ,GAAA,EAAK,aAAA;AAAA,IACL,KAAA,EAAO,oBAAA;AAAA,IACP,IAAA,EAAM;AAAA;AAEV,CAAA;AAEA,SAAS,cAAc,EAAA,EAAuC;AAC5D,EAAA,IAAI,EAAA,KAAO,QAAW,OAAO,IAAA;AAC7B,EAAA,IAAI,CAAC,MAAA,CAAO,QAAA,CAAS,EAAE,CAAA,IAAK,EAAA,GAAK,GAAG,OAAO,IAAA;AAC3C,EAAA,IAAI,EAAA,GAAK,GAAG,OAAO,MAAA;AACnB,EAAA,IAAI,KAAK,GAAA,EAAM,OAAO,GAAG,IAAA,CAAK,KAAA,CAAM,EAAE,CAAC,CAAA,EAAA,CAAA;AACvC,EAAA,OAAO,CAAA,EAAA,CAAI,EAAA,GAAK,GAAA,EAAM,OAAA,CAAQ,CAAC,CAAC,CAAA,CAAA,CAAA;AAClC;AAEO,IAAM,sBAAA,GAAyB,UAAA;AAAA,EACpC,CACE,EAAE,MAAA,EAAQ,SAAA,EAAW,OAAA,GAAU,SAAA,EAAW,SAAA,EAAW,aAAA,EAAe,UAAA,EAAY,GAAG,IAAA,EAAK,EACxF,GAAA,KACG;AACH,IAAA,MAAM,IAAA,GAAO,YAAY,MAAM,CAAA;AAC/B,IAAA,MAAM,OAAA,GAAU,cAAc,SAAS,CAAA;AACvC,IAAA,uBACE,IAAA;AAAA,MAAC,MAAA;AAAA,MAAA;AAAA,QACC,GAAA;AAAA,QACA,IAAA,EAAK,KAAA;AAAA,QACL,cAAY,IAAA,CAAK,IAAA;AAAA,QACjB,SAAA,EAAW,EAAA,CAAG,oDAAA,EAAsD,SAAS,CAAA;AAAA,QAC7E,eAAa,UAAA,IAAc,0BAAA;AAAA,QAC3B,aAAA,EAAa,MAAA;AAAA,QACZ,GAAG,IAAA;AAAA,QAEJ,QAAA,EAAA;AAAA,0BAAA,GAAA;AAAA,YAAC,MAAA;AAAA,YAAA;AAAA,cACC,SAAA,EAAW,EAAA;AAAA,gBACT,kCAAA;AAAA,gBACA,IAAA,CAAK,GAAA;AAAA,gBACL,WAAW,cAAA,IAAkB;AAAA,eAC/B;AAAA,cACA,aAAA,EAAW;AAAA;AAAA,WACb;AAAA,UACC,OAAA,KAAY,SAAA,oBACX,IAAA,CAAC,MAAA,EAAA,EAAK,WAAU,iBAAA,EACb,QAAA,EAAA;AAAA,YAAA,IAAA,CAAK,KAAA;AAAA,YACL,OAAA,KAAY,wBACX,GAAA,CAAC,MAAA,EAAA,EAAK,WAAU,4BAAA,EAA6B,aAAA,EAAY,mBACtD,QAAA,EAAA,OAAA,EACH;AAAA,WAAA,EAEJ;AAAA;AAAA;AAAA,KAEJ;AAAA,EAEJ;AACF;AACA,sBAAA,CAAuB,WAAA,GAAc,wBAAA","file":"chunk-AVPHVQZS.js","sourcesContent":["import { type HTMLAttributes, forwardRef } from \"react\";\n\nimport { cn } from \"../../../lib/cn.js\";\n\n/**\n * GatewayStatusIndicator — live connection-status dot for a gateway/server.\n *\n * Variants:\n * - compact : colored dot only (sidebar footer use)\n * - labeled : dot + label + optional latency text\n *\n * `reconnecting` state animates with `animate-pulse` and respects\n * `prefers-reduced-motion` automatically (Tailwind defaults).\n */\n\nexport type GatewayStatus = \"online\" | \"offline\" | \"degraded\" | \"reconnecting\";\n\nexport interface GatewayStatusIndicatorProps\n extends Omit<HTMLAttributes<HTMLSpanElement>, \"title\"> {\n status: GatewayStatus;\n latencyMs?: number;\n variant?: \"compact\" | \"labeled\";\n \"data-testid\"?: string;\n}\n\nconst STATUS_META: Record<GatewayStatus, { dot: string; label: string; aria: string }> = {\n online: {\n dot: \"bg-emerald-500\",\n label: \"Online\",\n aria: \"Gateway online\",\n },\n offline: {\n dot: \"bg-red-500\",\n label: \"Offline\",\n aria: \"Gateway offline\",\n },\n degraded: {\n dot: \"bg-amber-500\",\n label: \"Degraded\",\n aria: \"Gateway degraded\",\n },\n reconnecting: {\n dot: \"bg-blue-500\",\n label: \"Reconnecting…\",\n aria: \"Gateway reconnecting\",\n },\n};\n\nfunction formatLatency(ms: number | undefined): string | null {\n if (ms === undefined) return null;\n if (!Number.isFinite(ms) || ms < 0) return null;\n if (ms < 1) return \"<1ms\";\n if (ms < 1000) return `${Math.round(ms)}ms`;\n return `${(ms / 1000).toFixed(1)}s`;\n}\n\nexport const GatewayStatusIndicator = forwardRef<HTMLSpanElement, GatewayStatusIndicatorProps>(\n (\n { status, latencyMs, variant = \"labeled\", className, \"data-testid\": dataTestId, ...rest },\n ref,\n ) => {\n const meta = STATUS_META[status];\n const latency = formatLatency(latencyMs);\n return (\n <span\n ref={ref}\n role=\"img\"\n aria-label={meta.aria}\n className={cn(\"inline-flex items-center gap-2 font-medium text-xs\", className)}\n data-testid={dataTestId ?? \"gateway-status-indicator\"}\n data-status={status}\n {...rest}\n >\n <span\n className={cn(\n \"inline-block size-2 rounded-full\",\n meta.dot,\n status === \"reconnecting\" && \"animate-pulse\",\n )}\n aria-hidden\n />\n {variant === \"labeled\" && (\n <span className=\"text-foreground\">\n {meta.label}\n {latency !== null && (\n <span className=\"ml-1 text-muted-foreground\" data-testid=\"gateway-latency\">\n {latency}\n </span>\n )}\n </span>\n )}\n </span>\n );\n },\n);\nGatewayStatusIndicator.displayName = \"GatewayStatusIndicator\";\n"]}
@@ -195,5 +195,5 @@ function DataTable(props) {
195
195
  }
196
196
 
197
197
  export { DataTable };
198
- //# sourceMappingURL=chunk-ZNILW4G5.js.map
199
- //# sourceMappingURL=chunk-ZNILW4G5.js.map
198
+ //# sourceMappingURL=chunk-DW247T3Q.js.map
199
+ //# sourceMappingURL=chunk-DW247T3Q.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/components/composites/data-table/data-table.tsx"],"names":["_"],"mappings":"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","file":"chunk-ZNILW4G5.js","sourcesContent":["import { ChevronDown, ChevronRight, MoreHorizontal } from \"lucide-react\";\nimport { Fragment, useMemo, useState } from \"react\";\nimport type { ReactNode } from \"react\";\nimport { cn } from \"../../../lib/cn.js\";\nimport { DropdownMenu } from \"../../primitives/dropdown-menu/index.js\";\nimport { EmptyState } from \"../../primitives/empty-state/index.js\";\nimport { Pagination } from \"../../primitives/pagination/index.js\";\nimport { Skeleton } from \"../../primitives/skeleton/index.js\";\nimport { Table } from \"../../primitives/table/index.js\";\n\n/**\n * DataTable — generic, sortable, expandable composite over `<Table>`.\n *\n * Adds operator-grade entity-list patterns on top of the plain Table\n * primitive: sortable headers, sticky header, expandable rows\n * (multi-row by default), row action menus (Dropdown), client-side\n * pagination, loading skeleton rows, empty state. Both sort and\n * pagination support controlled OR uncontrolled mode (consumer\n * passes onSortChange / onPageChange to take over state).\n *\n * @example\n * <DataTable\n * columns={[\n * { key: \"name\", label: \"Name\", sortable: true },\n * { key: \"status\", label: \"Status\" },\n * ]}\n * data={domains}\n * rowKey={(d) => d.id}\n * expandable={(d) => d.status === \"pending\" ? <DnsRecords domain={d} /> : null}\n * rowActions={(d) => (\n * <>\n * <DropdownMenu.Item onSelect={() => editDomain(d)}>Edit</DropdownMenu.Item>\n * <DropdownMenu.Item onSelect={() => deleteDomain(d)}>Delete</DropdownMenu.Item>\n * </>\n * )}\n * />\n */\nexport interface DataTableColumn<T> {\n key: string;\n label: ReactNode;\n align?: \"left\" | \"center\" | \"right\";\n sortable?: boolean;\n width?: string;\n render?: (row: T) => ReactNode;\n className?: string;\n}\n\nexport interface DataTableSort {\n key: string;\n direction: \"asc\" | \"desc\";\n}\n\nexport interface DataTableProps<T> {\n data: T[];\n columns: DataTableColumn<T>[];\n rowKey: (row: T) => string;\n stickyHeader?: boolean;\n expandable?: (row: T) => ReactNode | null;\n expandMode?: \"single\" | \"multiple\";\n rowActions?: (row: T) => ReactNode;\n pagination?: {\n pageSize: number;\n controlledPage?: number;\n onPageChange?: (page: number) => void;\n } | null;\n defaultSort?: DataTableSort;\n sort?: DataTableSort | null;\n onSortChange?: (sort: DataTableSort | null) => void;\n loading?: boolean;\n emptyState?: ReactNode;\n className?: string;\n}\n\nfunction compareValues(a: unknown, b: unknown): number {\n if (a === b) return 0;\n if (a === null || a === undefined) return -1;\n if (b === null || b === undefined) return 1;\n if (typeof a === \"number\" && typeof b === \"number\") return a - b;\n return String(a).localeCompare(String(b));\n}\n\nfunction DataTable<T>(props: DataTableProps<T>): ReactNode {\n const {\n data,\n columns,\n rowKey,\n stickyHeader = true,\n expandable,\n expandMode = \"multiple\",\n rowActions,\n pagination,\n defaultSort,\n sort: controlledSort,\n onSortChange,\n loading = false,\n emptyState,\n className,\n } = props;\n\n const isControlledSort = onSortChange !== undefined;\n const [uncontrolledSort, setUncontrolledSort] = useState<DataTableSort | null>(\n defaultSort ?? null,\n );\n const sort = isControlledSort ? (controlledSort ?? null) : uncontrolledSort;\n\n const isControlledPage = pagination?.controlledPage !== undefined;\n const [uncontrolledPage, setUncontrolledPage] = useState(0);\n const currentPage = isControlledPage ? (pagination?.controlledPage ?? 0) : uncontrolledPage;\n\n // EC-9: clamp pageSize to >= 1 to avoid divide-by-zero / infinite render\n const effectivePageSize = Math.max(1, pagination?.pageSize ?? 10);\n\n const [expanded, setExpanded] = useState<Set<string>>(new Set());\n\n function handleSort(columnKey: string) {\n // Cycle: none → asc → desc → none\n let nextSort: DataTableSort | null;\n if (sort?.key !== columnKey) {\n nextSort = { key: columnKey, direction: \"asc\" };\n } else if (sort.direction === \"asc\") {\n nextSort = { key: columnKey, direction: \"desc\" };\n } else {\n nextSort = null;\n }\n if (isControlledSort) {\n onSortChange?.(nextSort);\n } else {\n setUncontrolledSort(nextSort);\n // EC-8: sort change resets pagination to page 0\n if (!isControlledPage) setUncontrolledPage(0);\n }\n }\n\n function handlePageChange(page: number) {\n // Pagination uses 1-indexed; internal state 0-indexed\n const zeroIdx = page - 1;\n if (isControlledPage) {\n pagination?.onPageChange?.(zeroIdx);\n } else {\n setUncontrolledPage(zeroIdx);\n }\n }\n\n function toggleExpand(key: string) {\n if (expandMode === \"single\") {\n setExpanded((prev) => (prev.has(key) ? new Set() : new Set([key])));\n } else {\n setExpanded((prev) => {\n const next = new Set(prev);\n if (next.has(key)) {\n next.delete(key);\n } else {\n next.add(key);\n }\n return next;\n });\n }\n }\n\n // Apply client-side sort in uncontrolled mode\n const sortedData = useMemo(() => {\n if (isControlledSort || sort === null) return data;\n const col = columns.find((c) => c.key === sort.key);\n if (!col) return data;\n const sorted = [...data].sort((a, b) => {\n const aVal = col.render\n ? null\n : (a as Record<string, unknown>)[sort.key as keyof T as string];\n const bVal = col.render\n ? null\n : (b as Record<string, unknown>)[sort.key as keyof T as string];\n const cmp = compareValues(aVal, bVal);\n return sort.direction === \"asc\" ? cmp : -cmp;\n });\n return sorted;\n }, [data, sort, isControlledSort, columns]);\n\n // Apply client-side pagination in uncontrolled mode\n const visibleData = useMemo(() => {\n if (!pagination) return sortedData;\n if (isControlledPage) return sortedData; // consumer pre-sliced\n const start = currentPage * effectivePageSize;\n return sortedData.slice(start, start + effectivePageSize);\n }, [sortedData, pagination, isControlledPage, currentPage, effectivePageSize]);\n\n // EC-1 fix: compute colSpan accounting for chevron + actions columns\n const extraCols = (expandable ? 1 : 0) + (rowActions ? 1 : 0);\n const expandedColSpan = columns.length + extraCols;\n const totalCols = columns.length + extraCols;\n\n // Loading state (EC-7: loading > empty)\n if (loading) {\n return (\n <div className={cn(\"w-full\", className)}>\n <Table>\n <Table.Header className={stickyHeader ? \"sticky top-0 bg-card\" : undefined}>\n <Table.Row>\n {expandable ? (\n <Table.HeaderCell>\n <span className=\"sr-only\">Expand</span>\n </Table.HeaderCell>\n ) : null}\n {columns.map((col) => (\n <Table.HeaderCell key={col.key} align={col.align}>\n {col.label}\n </Table.HeaderCell>\n ))}\n {rowActions ? (\n <Table.HeaderCell>\n <span className=\"sr-only\">Actions</span>\n </Table.HeaderCell>\n ) : null}\n </Table.Row>\n </Table.Header>\n <Table.Body>\n {Array.from({ length: 5 }, (_, i) => (\n // biome-ignore lint/suspicious/noArrayIndexKey: skeleton rows are positional placeholders\n <Table.Row key={`skeleton-${i}`}>\n {Array.from({ length: totalCols }, (_, j) => (\n // biome-ignore lint/suspicious/noArrayIndexKey: skeleton cells are positional placeholders\n <Table.Cell key={`s-${i}-${j}`}>\n <Skeleton className=\"h-4 w-full\" />\n </Table.Cell>\n ))}\n </Table.Row>\n ))}\n </Table.Body>\n </Table>\n </div>\n );\n }\n\n // Empty state (after loading check)\n if (sortedData.length === 0) {\n return (\n <div className={cn(\"w-full\", className)}>\n {emptyState ?? <EmptyState title=\"No data\" description=\"There's nothing here yet.\" />}\n </div>\n );\n }\n\n const totalPages = pagination ? Math.ceil(sortedData.length / effectivePageSize) : 1;\n\n return (\n <div className={cn(\"w-full\", className)}>\n <Table>\n <Table.Header className={stickyHeader ? \"sticky top-0 z-10 bg-card\" : undefined}>\n <Table.Row>\n {expandable ? (\n <Table.HeaderCell>\n <span className=\"sr-only\">Expand</span>\n </Table.HeaderCell>\n ) : null}\n {columns.map((col) => {\n const isSortable = col.sortable === true;\n const isActive = sort?.key === col.key;\n return (\n <Table.HeaderCell\n key={col.key}\n align={col.align}\n onSort={isSortable ? () => handleSort(col.key) : undefined}\n sortDirection={isSortable ? (isActive ? sort?.direction : \"none\") : undefined}\n style={col.width ? { width: col.width } : undefined}\n >\n {col.label}\n </Table.HeaderCell>\n );\n })}\n {rowActions ? (\n <Table.HeaderCell>\n <span className=\"sr-only\">Actions</span>\n </Table.HeaderCell>\n ) : null}\n </Table.Row>\n </Table.Header>\n <Table.Body>\n {visibleData.map((row) => {\n const key = rowKey(row);\n const expandedContent = expandable ? expandable(row) : null;\n const isExpandable = expandedContent !== null && expandedContent !== undefined;\n const isExpanded = expanded.has(key);\n return (\n <Fragment key={key}>\n <Table.Row>\n {expandable ? (\n <Table.Cell>\n {isExpandable ? (\n <button\n type=\"button\"\n onClick={() => toggleExpand(key)}\n aria-expanded={isExpanded}\n aria-controls={`expanded-${key}`}\n aria-label={isExpanded ? \"Collapse row\" : \"Expand row\"}\n className=\"inline-flex items-center justify-center rounded-md p-0.5 hover:bg-muted\"\n >\n {isExpanded ? (\n <ChevronDown aria-hidden=\"true\" className=\"size-4\" />\n ) : (\n <ChevronRight aria-hidden=\"true\" className=\"size-4\" />\n )}\n </button>\n ) : null}\n </Table.Cell>\n ) : null}\n {columns.map((col) => (\n <Table.Cell key={col.key} align={col.align} className={col.className}>\n {col.render\n ? col.render(row)\n : String((row as Record<string, unknown>)[col.key] ?? \"\")}\n </Table.Cell>\n ))}\n {rowActions ? (\n <Table.Cell align=\"right\">\n <DropdownMenu>\n <DropdownMenu.Trigger\n aria-label=\"Row actions\"\n className={cn(\n \"inline-flex size-7 items-center justify-center rounded-md\",\n \"text-muted-foreground hover:bg-muted hover:text-foreground\",\n \"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring\",\n )}\n >\n <MoreHorizontal aria-hidden=\"true\" className=\"size-4\" />\n </DropdownMenu.Trigger>\n <DropdownMenu.Content align=\"end\">{rowActions(row)}</DropdownMenu.Content>\n </DropdownMenu>\n </Table.Cell>\n ) : null}\n </Table.Row>\n {isExpanded && isExpandable ? (\n <tr id={`expanded-${key}`}>\n <td colSpan={expandedColSpan} className=\"bg-muted/30 p-4\">\n {expandedContent}\n </td>\n </tr>\n ) : null}\n </Fragment>\n );\n })}\n </Table.Body>\n </Table>\n {pagination && totalPages > 1 ? (\n <div className=\"mt-4 flex items-center justify-end\">\n <Pagination\n currentPage={currentPage + 1}\n totalPages={totalPages}\n onPageChange={handlePageChange}\n />\n </div>\n ) : null}\n </div>\n );\n}\n\nexport { DataTable };\n"]}
1
+ {"version":3,"sources":["../src/components/composites/data-table/data-table.tsx"],"names":["_"],"mappings":"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","file":"chunk-DW247T3Q.js","sourcesContent":["import { ChevronDown, ChevronRight, MoreHorizontal } from \"lucide-react\";\nimport { Fragment, useMemo, useState } from \"react\";\nimport type { ReactNode } from \"react\";\nimport { cn } from \"../../../lib/cn.js\";\nimport { DropdownMenu } from \"../../primitives/dropdown-menu/index.js\";\nimport { EmptyState } from \"../../primitives/empty-state/index.js\";\nimport { Pagination } from \"../../primitives/pagination/index.js\";\nimport { Skeleton } from \"../../primitives/skeleton/index.js\";\nimport { Table } from \"../../primitives/table/index.js\";\n\n/**\n * DataTable — generic, sortable, expandable composite over `<Table>`.\n *\n * Adds operator-grade entity-list patterns on top of the plain Table\n * primitive: sortable headers, sticky header, expandable rows\n * (multi-row by default), row action menus (Dropdown), client-side\n * pagination, loading skeleton rows, empty state. Both sort and\n * pagination support controlled OR uncontrolled mode (consumer\n * passes onSortChange / onPageChange to take over state).\n *\n * @example\n * <DataTable\n * columns={[\n * { key: \"name\", label: \"Name\", sortable: true },\n * { key: \"status\", label: \"Status\" },\n * ]}\n * data={domains}\n * rowKey={(d) => d.id}\n * expandable={(d) => d.status === \"pending\" ? <DnsRecords domain={d} /> : null}\n * rowActions={(d) => (\n * <>\n * <DropdownMenu.Item onSelect={() => editDomain(d)}>Edit</DropdownMenu.Item>\n * <DropdownMenu.Item onSelect={() => deleteDomain(d)}>Delete</DropdownMenu.Item>\n * </>\n * )}\n * />\n */\nexport interface DataTableColumn<T> {\n key: string;\n label: ReactNode;\n align?: \"left\" | \"center\" | \"right\";\n sortable?: boolean;\n width?: string;\n render?: (row: T) => ReactNode;\n className?: string;\n}\n\nexport interface DataTableSort {\n key: string;\n direction: \"asc\" | \"desc\";\n}\n\nexport interface DataTableProps<T> {\n data: T[];\n columns: DataTableColumn<T>[];\n rowKey: (row: T) => string;\n stickyHeader?: boolean;\n expandable?: (row: T) => ReactNode | null;\n expandMode?: \"single\" | \"multiple\";\n rowActions?: (row: T) => ReactNode;\n pagination?: {\n pageSize: number;\n controlledPage?: number;\n onPageChange?: (page: number) => void;\n } | null;\n defaultSort?: DataTableSort;\n sort?: DataTableSort | null;\n onSortChange?: (sort: DataTableSort | null) => void;\n loading?: boolean;\n emptyState?: ReactNode;\n className?: string;\n}\n\nfunction compareValues(a: unknown, b: unknown): number {\n if (a === b) return 0;\n if (a === null || a === undefined) return -1;\n if (b === null || b === undefined) return 1;\n if (typeof a === \"number\" && typeof b === \"number\") return a - b;\n return String(a).localeCompare(String(b));\n}\n\nfunction DataTable<T>(props: DataTableProps<T>): ReactNode {\n const {\n data,\n columns,\n rowKey,\n stickyHeader = true,\n expandable,\n expandMode = \"multiple\",\n rowActions,\n pagination,\n defaultSort,\n sort: controlledSort,\n onSortChange,\n loading = false,\n emptyState,\n className,\n } = props;\n\n const isControlledSort = onSortChange !== undefined;\n const [uncontrolledSort, setUncontrolledSort] = useState<DataTableSort | null>(\n defaultSort ?? null,\n );\n const sort = isControlledSort ? (controlledSort ?? null) : uncontrolledSort;\n\n const isControlledPage = pagination?.controlledPage !== undefined;\n const [uncontrolledPage, setUncontrolledPage] = useState(0);\n const currentPage = isControlledPage ? (pagination?.controlledPage ?? 0) : uncontrolledPage;\n\n // EC-9: clamp pageSize to >= 1 to avoid divide-by-zero / infinite render\n const effectivePageSize = Math.max(1, pagination?.pageSize ?? 10);\n\n const [expanded, setExpanded] = useState<Set<string>>(new Set());\n\n function handleSort(columnKey: string) {\n // Cycle: none → asc → desc → none\n let nextSort: DataTableSort | null;\n if (sort?.key !== columnKey) {\n nextSort = { key: columnKey, direction: \"asc\" };\n } else if (sort.direction === \"asc\") {\n nextSort = { key: columnKey, direction: \"desc\" };\n } else {\n nextSort = null;\n }\n if (isControlledSort) {\n onSortChange?.(nextSort);\n } else {\n setUncontrolledSort(nextSort);\n // EC-8: sort change resets pagination to page 0\n if (!isControlledPage) setUncontrolledPage(0);\n }\n }\n\n function handlePageChange(page: number) {\n // Pagination uses 1-indexed; internal state 0-indexed\n const zeroIdx = page - 1;\n if (isControlledPage) {\n pagination?.onPageChange?.(zeroIdx);\n } else {\n setUncontrolledPage(zeroIdx);\n }\n }\n\n function toggleExpand(key: string) {\n if (expandMode === \"single\") {\n setExpanded((prev) => (prev.has(key) ? new Set() : new Set([key])));\n } else {\n setExpanded((prev) => {\n const next = new Set(prev);\n if (next.has(key)) {\n next.delete(key);\n } else {\n next.add(key);\n }\n return next;\n });\n }\n }\n\n // Apply client-side sort in uncontrolled mode\n const sortedData = useMemo(() => {\n if (isControlledSort || sort === null) return data;\n const col = columns.find((c) => c.key === sort.key);\n if (!col) return data;\n const sorted = [...data].sort((a, b) => {\n const aVal = col.render\n ? null\n : (a as Record<string, unknown>)[sort.key as keyof T as string];\n const bVal = col.render\n ? null\n : (b as Record<string, unknown>)[sort.key as keyof T as string];\n const cmp = compareValues(aVal, bVal);\n return sort.direction === \"asc\" ? cmp : -cmp;\n });\n return sorted;\n }, [data, sort, isControlledSort, columns]);\n\n // Apply client-side pagination in uncontrolled mode\n const visibleData = useMemo(() => {\n if (!pagination) return sortedData;\n if (isControlledPage) return sortedData; // consumer pre-sliced\n const start = currentPage * effectivePageSize;\n return sortedData.slice(start, start + effectivePageSize);\n }, [sortedData, pagination, isControlledPage, currentPage, effectivePageSize]);\n\n // EC-1 fix: compute colSpan accounting for chevron + actions columns\n const extraCols = (expandable ? 1 : 0) + (rowActions ? 1 : 0);\n const expandedColSpan = columns.length + extraCols;\n const totalCols = columns.length + extraCols;\n\n // Loading state (EC-7: loading > empty)\n if (loading) {\n return (\n <div className={cn(\"w-full\", className)}>\n <Table>\n <Table.Header className={stickyHeader ? \"sticky top-0 bg-card\" : undefined}>\n <Table.Row>\n {expandable ? (\n <Table.HeaderCell>\n <span className=\"sr-only\">Expand</span>\n </Table.HeaderCell>\n ) : null}\n {columns.map((col) => (\n <Table.HeaderCell key={col.key} align={col.align}>\n {col.label}\n </Table.HeaderCell>\n ))}\n {rowActions ? (\n <Table.HeaderCell>\n <span className=\"sr-only\">Actions</span>\n </Table.HeaderCell>\n ) : null}\n </Table.Row>\n </Table.Header>\n <Table.Body>\n {Array.from({ length: 5 }, (_, i) => (\n // biome-ignore lint/suspicious/noArrayIndexKey: skeleton rows are positional placeholders\n <Table.Row key={`skeleton-${i}`}>\n {Array.from({ length: totalCols }, (_, j) => (\n // biome-ignore lint/suspicious/noArrayIndexKey: skeleton cells are positional placeholders\n <Table.Cell key={`s-${i}-${j}`}>\n <Skeleton className=\"h-4 w-full\" />\n </Table.Cell>\n ))}\n </Table.Row>\n ))}\n </Table.Body>\n </Table>\n </div>\n );\n }\n\n // Empty state (after loading check)\n if (sortedData.length === 0) {\n return (\n <div className={cn(\"w-full\", className)}>\n {emptyState ?? <EmptyState title=\"No data\" description=\"There's nothing here yet.\" />}\n </div>\n );\n }\n\n const totalPages = pagination ? Math.ceil(sortedData.length / effectivePageSize) : 1;\n\n return (\n <div className={cn(\"w-full\", className)}>\n <Table>\n <Table.Header className={stickyHeader ? \"sticky top-0 z-10 bg-card\" : undefined}>\n <Table.Row>\n {expandable ? (\n <Table.HeaderCell>\n <span className=\"sr-only\">Expand</span>\n </Table.HeaderCell>\n ) : null}\n {columns.map((col) => {\n const isSortable = col.sortable === true;\n const isActive = sort?.key === col.key;\n return (\n <Table.HeaderCell\n key={col.key}\n align={col.align}\n onSort={isSortable ? () => handleSort(col.key) : undefined}\n sortDirection={isSortable ? (isActive ? sort?.direction : \"none\") : undefined}\n style={col.width ? { width: col.width } : undefined}\n >\n {col.label}\n </Table.HeaderCell>\n );\n })}\n {rowActions ? (\n <Table.HeaderCell>\n <span className=\"sr-only\">Actions</span>\n </Table.HeaderCell>\n ) : null}\n </Table.Row>\n </Table.Header>\n <Table.Body>\n {visibleData.map((row) => {\n const key = rowKey(row);\n const expandedContent = expandable ? expandable(row) : null;\n const isExpandable = expandedContent !== null && expandedContent !== undefined;\n const isExpanded = expanded.has(key);\n return (\n <Fragment key={key}>\n <Table.Row>\n {expandable ? (\n <Table.Cell>\n {isExpandable ? (\n <button\n type=\"button\"\n onClick={() => toggleExpand(key)}\n aria-expanded={isExpanded}\n aria-controls={`expanded-${key}`}\n aria-label={isExpanded ? \"Collapse row\" : \"Expand row\"}\n className=\"inline-flex items-center justify-center rounded-md p-0.5 hover:bg-muted\"\n >\n {isExpanded ? (\n <ChevronDown aria-hidden=\"true\" className=\"size-4\" />\n ) : (\n <ChevronRight aria-hidden=\"true\" className=\"size-4\" />\n )}\n </button>\n ) : null}\n </Table.Cell>\n ) : null}\n {columns.map((col) => (\n <Table.Cell key={col.key} align={col.align} className={col.className}>\n {col.render\n ? col.render(row)\n : String((row as Record<string, unknown>)[col.key] ?? \"\")}\n </Table.Cell>\n ))}\n {rowActions ? (\n <Table.Cell align=\"right\">\n <DropdownMenu>\n <DropdownMenu.Trigger\n aria-label=\"Row actions\"\n className={cn(\n \"inline-flex size-7 items-center justify-center rounded-md\",\n \"text-muted-foreground hover:bg-muted hover:text-foreground\",\n \"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring\",\n )}\n >\n <MoreHorizontal aria-hidden=\"true\" className=\"size-4\" />\n </DropdownMenu.Trigger>\n <DropdownMenu.Content align=\"end\">{rowActions(row)}</DropdownMenu.Content>\n </DropdownMenu>\n </Table.Cell>\n ) : null}\n </Table.Row>\n {isExpanded && isExpandable ? (\n <tr id={`expanded-${key}`}>\n <td colSpan={expandedColSpan} className=\"bg-muted/30 p-4\">\n {expandedContent}\n </td>\n </tr>\n ) : null}\n </Fragment>\n );\n })}\n </Table.Body>\n </Table>\n {pagination && totalPages > 1 ? (\n <div className=\"mt-4 flex items-center justify-end\">\n <Pagination\n currentPage={currentPage + 1}\n totalPages={totalPages}\n onPageChange={handlePageChange}\n />\n </div>\n ) : null}\n </div>\n );\n}\n\nexport { DataTable };\n"]}