@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.
- package/CHANGELOG.md +94 -0
- package/README.md +20 -19
- package/dist/chunk-6ZQKEY54.js +149 -0
- package/dist/chunk-6ZQKEY54.js.map +1 -0
- package/dist/chunk-AVPHVQZS.js +73 -0
- package/dist/chunk-AVPHVQZS.js.map +1 -0
- package/dist/chunk-GXBFGWQN.js +81 -0
- package/dist/chunk-GXBFGWQN.js.map +1 -0
- package/dist/chunk-I32I36LW.js +113 -0
- package/dist/chunk-I32I36LW.js.map +1 -0
- package/dist/chunk-JPTPIZ5V.js +120 -0
- package/dist/chunk-JPTPIZ5V.js.map +1 -0
- package/dist/{chunk-TO3UAT6O.js → chunk-K7PYLTMP.js} +3 -3
- package/dist/{chunk-TO3UAT6O.js.map → chunk-K7PYLTMP.js.map} +1 -1
- package/dist/{chunk-R2PAGRDP.js → chunk-MYEHGDC2.js} +2 -2
- package/dist/{chunk-R2PAGRDP.js.map → chunk-MYEHGDC2.js.map} +1 -1
- package/dist/{chunk-IPEYGWA7.js → chunk-PTHRL242.js} +4 -4
- package/dist/{chunk-IPEYGWA7.js.map → chunk-PTHRL242.js.map} +1 -1
- package/dist/chunk-RC5XME4T.js +33 -0
- package/dist/chunk-RC5XME4T.js.map +1 -0
- package/dist/chunk-UK27KR35.js +73 -0
- package/dist/chunk-UK27KR35.js.map +1 -0
- package/dist/chunk-UOMQPIB4.js +48 -0
- package/dist/chunk-UOMQPIB4.js.map +1 -0
- package/dist/{chunk-TNBJ36XJ.js → chunk-XZKEGEPT.js} +4 -4
- package/dist/{chunk-TNBJ36XJ.js.map → chunk-XZKEGEPT.js.map} +1 -1
- package/dist/components.css +1 -1
- package/dist/composites/agent-editor/index.js +2 -2
- package/dist/composites/rule-editor/index.js +3 -3
- package/dist/composites/skill-editor/index.js +3 -3
- package/dist/composites/stability-bundle-viewer/index.js +4 -0
- package/dist/composites/stability-bundle-viewer/index.js.map +1 -0
- package/dist/index.d.ts +162 -1
- package/dist/index.js +44 -36
- package/dist/index.js.map +1 -1
- package/dist/primitives/branch-indicator/index.js +4 -0
- package/dist/primitives/branch-indicator/index.js.map +1 -0
- package/dist/primitives/channel-card/index.js +4 -0
- package/dist/primitives/channel-card/index.js.map +1 -0
- package/dist/primitives/export-chat-dialog/index.js +4 -0
- package/dist/primitives/export-chat-dialog/index.js.map +1 -0
- package/dist/primitives/gateway-status-indicator/index.js +4 -0
- package/dist/primitives/gateway-status-indicator/index.js.map +1 -0
- package/dist/primitives/pin-input/index.js +1 -1
- package/dist/primitives/run-status-pill/index.js +4 -0
- package/dist/primitives/run-status-pill/index.js.map +1 -0
- package/dist/primitives/thinking-level-selector/index.js +4 -0
- package/dist/primitives/thinking-level-selector/index.js.map +1 -0
- package/dist/primitives/update-banner/index.js +4 -0
- package/dist/primitives/update-banner/index.js.map +1 -0
- package/package.json +123 -99
- 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)
|
|
15
15
|
[](https://react.dev)
|
|
16
|
-
[](#quality-gates)
|
|
17
|
+
[](#component-catalog)
|
|
18
18
|
[](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
|
-
**
|
|
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** (
|
|
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` · `
|
|
160
|
-
`
|
|
161
|
-
`
|
|
162
|
-
`
|
|
163
|
-
`
|
|
164
|
-
`
|
|
165
|
-
`
|
|
166
|
-
`
|
|
167
|
-
`
|
|
168
|
-
`
|
|
169
|
-
`
|
|
170
|
-
`
|
|
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** (
|
|
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"]}
|