@usetheo/ui 0.12.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 (52) hide show
  1. package/CHANGELOG.md +94 -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-GXBFGWQN.js +81 -0
  8. package/dist/chunk-GXBFGWQN.js.map +1 -0
  9. package/dist/chunk-I32I36LW.js +113 -0
  10. package/dist/chunk-I32I36LW.js.map +1 -0
  11. package/dist/chunk-JPTPIZ5V.js +120 -0
  12. package/dist/chunk-JPTPIZ5V.js.map +1 -0
  13. package/dist/{chunk-TO3UAT6O.js → chunk-K7PYLTMP.js} +3 -3
  14. package/dist/{chunk-TO3UAT6O.js.map → chunk-K7PYLTMP.js.map} +1 -1
  15. package/dist/{chunk-R2PAGRDP.js → chunk-MYEHGDC2.js} +2 -2
  16. package/dist/{chunk-R2PAGRDP.js.map → chunk-MYEHGDC2.js.map} +1 -1
  17. package/dist/{chunk-IPEYGWA7.js → chunk-PTHRL242.js} +4 -4
  18. package/dist/{chunk-IPEYGWA7.js.map → chunk-PTHRL242.js.map} +1 -1
  19. package/dist/chunk-RC5XME4T.js +33 -0
  20. package/dist/chunk-RC5XME4T.js.map +1 -0
  21. package/dist/chunk-UK27KR35.js +73 -0
  22. package/dist/chunk-UK27KR35.js.map +1 -0
  23. package/dist/chunk-UOMQPIB4.js +48 -0
  24. package/dist/chunk-UOMQPIB4.js.map +1 -0
  25. package/dist/{chunk-TNBJ36XJ.js → chunk-XZKEGEPT.js} +4 -4
  26. package/dist/{chunk-TNBJ36XJ.js.map → chunk-XZKEGEPT.js.map} +1 -1
  27. package/dist/components.css +1 -1
  28. package/dist/composites/agent-editor/index.js +2 -2
  29. package/dist/composites/rule-editor/index.js +3 -3
  30. package/dist/composites/skill-editor/index.js +3 -3
  31. package/dist/composites/stability-bundle-viewer/index.js +4 -0
  32. package/dist/composites/stability-bundle-viewer/index.js.map +1 -0
  33. package/dist/index.d.ts +162 -1
  34. package/dist/index.js +44 -36
  35. package/dist/index.js.map +1 -1
  36. package/dist/primitives/branch-indicator/index.js +4 -0
  37. package/dist/primitives/branch-indicator/index.js.map +1 -0
  38. package/dist/primitives/channel-card/index.js +4 -0
  39. package/dist/primitives/channel-card/index.js.map +1 -0
  40. package/dist/primitives/export-chat-dialog/index.js +4 -0
  41. package/dist/primitives/export-chat-dialog/index.js.map +1 -0
  42. package/dist/primitives/gateway-status-indicator/index.js +4 -0
  43. package/dist/primitives/gateway-status-indicator/index.js.map +1 -0
  44. package/dist/primitives/pin-input/index.js +1 -1
  45. package/dist/primitives/run-status-pill/index.js +4 -0
  46. package/dist/primitives/run-status-pill/index.js.map +1 -0
  47. package/dist/primitives/thinking-level-selector/index.js +4 -0
  48. package/dist/primitives/thinking-level-selector/index.js.map +1 -0
  49. package/dist/primitives/update-banner/index.js +4 -0
  50. package/dist/primitives/update-banner/index.js.map +1 -0
  51. package/package.json +123 -99
  52. package/registry/r/pin-input.json +1 -1
package/CHANGELOG.md CHANGED
@@ -7,6 +7,100 @@ 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
+
10
104
  ## [0.12.0] - 2026-05-28
11
105
 
12
106
  **Stable release — promoted from `0.12.0-next.0` after dogfood validation + cross-repo contract gates landed.**
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"]}
@@ -0,0 +1,81 @@
1
+ import { cn } from './chunk-ZSRJCIWF.js';
2
+ import { ArrowUpCircle, X } from 'lucide-react';
3
+ import { forwardRef } from 'react';
4
+ import { jsxs, jsx } from 'react/jsx-runtime';
5
+
6
+ var UpdateBanner = forwardRef(
7
+ ({
8
+ currentVersion,
9
+ latestVersion,
10
+ onUpdate,
11
+ onDismiss,
12
+ severity = "info",
13
+ className,
14
+ "data-testid": dataTestId,
15
+ ...rest
16
+ }, ref) => {
17
+ return /* @__PURE__ */ jsxs(
18
+ "div",
19
+ {
20
+ ref,
21
+ role: "alert",
22
+ "aria-live": "polite",
23
+ className: cn(
24
+ "flex items-center justify-between gap-3 border-b px-4 py-2 text-sm",
25
+ severity === "warn" ? "border-amber-500/40 bg-amber-500/10 text-amber-700 dark:text-amber-300" : "border-primary/40 bg-primary/10 text-primary",
26
+ className
27
+ ),
28
+ "data-testid": dataTestId ?? "update-banner",
29
+ "data-severity": severity,
30
+ ...rest,
31
+ children: [
32
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
33
+ /* @__PURE__ */ jsx(ArrowUpCircle, { className: "size-4", "aria-hidden": true }),
34
+ /* @__PURE__ */ jsxs("span", { children: [
35
+ "Update available: ",
36
+ /* @__PURE__ */ jsxs("strong", { "data-testid": "update-banner-latest", children: [
37
+ "v",
38
+ latestVersion
39
+ ] }),
40
+ " ",
41
+ "(running ",
42
+ /* @__PURE__ */ jsxs("span", { "data-testid": "update-banner-current", children: [
43
+ "v",
44
+ currentVersion
45
+ ] }),
46
+ ")."
47
+ ] })
48
+ ] }),
49
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
50
+ /* @__PURE__ */ jsx(
51
+ "button",
52
+ {
53
+ type: "button",
54
+ onClick: onUpdate,
55
+ className: "rounded-md border border-current px-2 py-1 font-medium text-xs hover:bg-current/10",
56
+ "data-testid": "update-banner-update-button",
57
+ children: "Update now"
58
+ }
59
+ ),
60
+ onDismiss !== void 0 && /* @__PURE__ */ jsx(
61
+ "button",
62
+ {
63
+ type: "button",
64
+ onClick: onDismiss,
65
+ className: "rounded-md p-1 hover:bg-current/10",
66
+ "aria-label": "Dismiss update banner",
67
+ "data-testid": "update-banner-dismiss",
68
+ children: /* @__PURE__ */ jsx(X, { className: "size-4", "aria-hidden": true })
69
+ }
70
+ )
71
+ ] })
72
+ ]
73
+ }
74
+ );
75
+ }
76
+ );
77
+ UpdateBanner.displayName = "UpdateBanner";
78
+
79
+ export { UpdateBanner };
80
+ //# sourceMappingURL=chunk-GXBFGWQN.js.map
81
+ //# sourceMappingURL=chunk-GXBFGWQN.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/components/primitives/update-banner/update-banner.tsx"],"names":[],"mappings":";;;;;AAqBO,IAAM,YAAA,GAAe,UAAA;AAAA,EAC1B,CACE;AAAA,IACE,cAAA;AAAA,IACA,aAAA;AAAA,IACA,QAAA;AAAA,IACA,SAAA;AAAA,IACA,QAAA,GAAW,MAAA;AAAA,IACX,SAAA;AAAA,IACA,aAAA,EAAe,UAAA;AAAA,IACf,GAAG;AAAA,KAEL,GAAA,KACG;AACH,IAAA,uBACE,IAAA;AAAA,MAAC,KAAA;AAAA,MAAA;AAAA,QACC,GAAA;AAAA,QACA,IAAA,EAAK,OAAA;AAAA,QACL,WAAA,EAAU,QAAA;AAAA,QACV,SAAA,EAAW,EAAA;AAAA,UACT,oEAAA;AAAA,UACA,QAAA,KAAa,SACT,wEAAA,GACA,8CAAA;AAAA,UACJ;AAAA,SACF;AAAA,QACA,eAAa,UAAA,IAAc,eAAA;AAAA,QAC3B,eAAA,EAAe,QAAA;AAAA,QACd,GAAG,IAAA;AAAA,QAEJ,QAAA,EAAA;AAAA,0BAAA,IAAA,CAAC,KAAA,EAAA,EAAI,WAAU,yBAAA,EACb,QAAA,EAAA;AAAA,4BAAA,GAAA,CAAC,aAAA,EAAA,EAAc,SAAA,EAAU,QAAA,EAAS,aAAA,EAAW,IAAA,EAAC,CAAA;AAAA,iCAC7C,MAAA,EAAA,EAAK,QAAA,EAAA;AAAA,cAAA,oBAAA;AAAA,8BACc,IAAA,CAAC,QAAA,EAAA,EAAO,aAAA,EAAY,sBAAA,EAAuB,QAAA,EAAA;AAAA,gBAAA,GAAA;AAAA,gBAAE;AAAA,eAAA,EAAc,CAAA;AAAA,cAAU,GAAA;AAAA,cAAI,WAAA;AAAA,8BAClF,IAAA,CAAC,MAAA,EAAA,EAAK,aAAA,EAAY,uBAAA,EAAwB,QAAA,EAAA;AAAA,gBAAA,GAAA;AAAA,gBAAE;AAAA,eAAA,EAAe,CAAA;AAAA,cAAO;AAAA,aAAA,EAC7E;AAAA,WAAA,EACF,CAAA;AAAA,0BACA,IAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,yBAAA,EACb,QAAA,EAAA;AAAA,4BAAA,GAAA;AAAA,cAAC,QAAA;AAAA,cAAA;AAAA,gBACC,IAAA,EAAK,QAAA;AAAA,gBACL,OAAA,EAAS,QAAA;AAAA,gBACT,SAAA,EAAU,oFAAA;AAAA,gBACV,aAAA,EAAY,6BAAA;AAAA,gBACb,QAAA,EAAA;AAAA;AAAA,aAED;AAAA,YACC,cAAc,MAAA,oBACb,GAAA;AAAA,cAAC,QAAA;AAAA,cAAA;AAAA,gBACC,IAAA,EAAK,QAAA;AAAA,gBACL,OAAA,EAAS,SAAA;AAAA,gBACT,SAAA,EAAU,oCAAA;AAAA,gBACV,YAAA,EAAW,uBAAA;AAAA,gBACX,aAAA,EAAY,uBAAA;AAAA,gBAEZ,QAAA,kBAAA,GAAA,CAAC,CAAA,EAAA,EAAE,SAAA,EAAU,QAAA,EAAS,eAAW,IAAA,EAAC;AAAA;AAAA;AACpC,WAAA,EAEJ;AAAA;AAAA;AAAA,KACF;AAAA,EAEJ;AACF;AACA,YAAA,CAAa,WAAA,GAAc,cAAA","file":"chunk-GXBFGWQN.js","sourcesContent":["import { ArrowUpCircle, X } from \"lucide-react\";\nimport { type HTMLAttributes, forwardRef } from \"react\";\n\nimport { cn } from \"../../../lib/cn.js\";\n\n/**\n * UpdateBanner — top-of-app alert when a newer version is available.\n *\n * Controlled. Dismiss persistence (localStorage/cookie/etc) is the consumer's\n * responsibility (per EC-16 DOCUMENT). Component just fires `onDismiss`.\n */\n\nexport interface UpdateBannerProps extends Omit<HTMLAttributes<HTMLDivElement>, \"title\"> {\n currentVersion: string;\n latestVersion: string;\n onUpdate: () => void;\n onDismiss?: () => void;\n severity?: \"info\" | \"warn\";\n \"data-testid\"?: string;\n}\n\nexport const UpdateBanner = forwardRef<HTMLDivElement, UpdateBannerProps>(\n (\n {\n currentVersion,\n latestVersion,\n onUpdate,\n onDismiss,\n severity = \"info\",\n className,\n \"data-testid\": dataTestId,\n ...rest\n },\n ref,\n ) => {\n return (\n <div\n ref={ref}\n role=\"alert\"\n aria-live=\"polite\"\n className={cn(\n \"flex items-center justify-between gap-3 border-b px-4 py-2 text-sm\",\n severity === \"warn\"\n ? \"border-amber-500/40 bg-amber-500/10 text-amber-700 dark:text-amber-300\"\n : \"border-primary/40 bg-primary/10 text-primary\",\n className,\n )}\n data-testid={dataTestId ?? \"update-banner\"}\n data-severity={severity}\n {...rest}\n >\n <div className=\"flex items-center gap-2\">\n <ArrowUpCircle className=\"size-4\" aria-hidden />\n <span>\n Update available: <strong data-testid=\"update-banner-latest\">v{latestVersion}</strong>{\" \"}\n (running <span data-testid=\"update-banner-current\">v{currentVersion}</span>).\n </span>\n </div>\n <div className=\"flex items-center gap-2\">\n <button\n type=\"button\"\n onClick={onUpdate}\n className=\"rounded-md border border-current px-2 py-1 font-medium text-xs hover:bg-current/10\"\n data-testid=\"update-banner-update-button\"\n >\n Update now\n </button>\n {onDismiss !== undefined && (\n <button\n type=\"button\"\n onClick={onDismiss}\n className=\"rounded-md p-1 hover:bg-current/10\"\n aria-label=\"Dismiss update banner\"\n data-testid=\"update-banner-dismiss\"\n >\n <X className=\"size-4\" aria-hidden />\n </button>\n )}\n </div>\n </div>\n );\n },\n);\nUpdateBanner.displayName = \"UpdateBanner\";\n"]}