dsh-tool-stats 0.5.0 → 0.5.1
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 +23 -0
- package/lib/client/view.js +2 -2
- package/lib/stats.web.js +4 -4
- package/lib/stats.web.js.map +2 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,28 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.5.1
|
|
4
|
+
|
|
5
|
+
**Fixed: the previous release only covered `ui.tsx`.**
|
|
6
|
+
|
|
7
|
+
The 0.5.0 fix replaced the invented tokens in `ui.tsx` but missed
|
|
8
|
+
`view.tsx`, which still read `--error`, `--warning`, `--success` and
|
|
9
|
+
`--accent` with hardcoded fallbacks. dsh defines none of those, so the
|
|
10
|
+
fallbacks applied in every theme.
|
|
11
|
+
|
|
12
|
+
Fixed here, 17 sites across the seven plugin panels:
|
|
13
|
+
|
|
14
|
+
- `var(--error, #e53935)` → `var(--dsw-alias-state-error-primary)`
|
|
15
|
+
- `var(--warning, #ed6c02)` → `var(--dsw-alias-state-warn-primary)`
|
|
16
|
+
- `var(--success, #2e7d32)` → `var(--dsw-alias-state-success-primary)`
|
|
17
|
+
- `var(--accent, #4B8BBE)` → `var(--dsw-alias-brand-primary)`
|
|
18
|
+
|
|
19
|
+
Also replaced literal colours in the same files (alert card tints, dimmed text,
|
|
20
|
+
code-block background) with the matching dsh tokens.
|
|
21
|
+
|
|
22
|
+
The guard test now scans **every `.ts`/`.tsx` under `src/client/`** instead of
|
|
23
|
+
only `ui.tsx` — that narrow scope is exactly how these 17 sites slipped through.
|
|
24
|
+
An added assertion fails if `view.tsx` stops being scanned.
|
|
25
|
+
|
|
3
26
|
## 0.5.0
|
|
4
27
|
|
|
5
28
|
**Fixed: panels did not follow the host theme.**
|
package/lib/client/view.js
CHANGED
|
@@ -37,11 +37,11 @@ function ToolStatsPanelInner({ t }) {
|
|
|
37
37
|
}, [t, toast, reload]);
|
|
38
38
|
const header = (_jsxs("header", { style: { display: 'flex', alignItems: 'center', gap: 12, marginBottom: 4 }, children: [_jsxs("strong", { style: { fontSize: 15 }, children: ["\uD83D\uDD27 ", t('title')] }), _jsx("span", { style: { flex: 1 } }), _jsx("a", { href: EXPORT_PATH, download: true, children: _jsx(Button, { variant: "secondary", size: "sm", children: t('export') }) }), _jsx(Button, { variant: "danger", size: "sm", onClick: () => setConfirmClear(true), children: t('clear') }), _jsx(Button, { variant: "secondary", size: "sm", onClick: reload, children: t('refresh') })] }));
|
|
39
39
|
if (error !== null)
|
|
40
|
-
return _jsxs("div", { style: { maxWidth: 820 }, children: [header, _jsx(Card, { children: _jsxs("p", { role: "alert", style: { margin: 0, fontSize: 13, color: 'var(--error
|
|
40
|
+
return _jsxs("div", { style: { maxWidth: 820 }, children: [header, _jsx(Card, { children: _jsxs("p", { role: "alert", style: { margin: 0, fontSize: 13, color: 'var(--dsw-alias-state-error-primary)' }, children: [t('failed'), ": ", error] }) })] });
|
|
41
41
|
if (payload === null)
|
|
42
42
|
return _jsxs("div", { style: { maxWidth: 820 }, children: [header, _jsx("div", { style: { display: 'flex', justifyContent: 'center', padding: 40 }, children: _jsx(Spinner, { size: 28 }) })] });
|
|
43
43
|
const o = payload.overview;
|
|
44
|
-
return (_jsxs("div", { style: { display: 'flex', flexDirection: 'column', gap: 14, maxWidth: 820 }, children: [header, payload.alerts.length > 0 && (_jsx("div", { style: { display: 'flex', flexDirection: 'column', gap: 6 }, children: payload.alerts.map((a, i) => (_jsx(Card, { style: { borderColor: a.level === 'error' ? 'var(--error
|
|
44
|
+
return (_jsxs("div", { style: { display: 'flex', flexDirection: 'column', gap: 14, maxWidth: 820 }, children: [header, payload.alerts.length > 0 && (_jsx("div", { style: { display: 'flex', flexDirection: 'column', gap: 6 }, children: payload.alerts.map((a, i) => (_jsx(Card, { style: { borderColor: a.level === 'error' ? 'var(--dsw-alias-state-error-primary)' : 'var(--dsw-alias-state-warn-primary)' }, children: _jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: 8 }, children: [_jsx(Badge, { color: a.level === 'error' ? 'error' : 'warning', children: a.level === 'error' ? '🔴' : '🟡' }), _jsx("strong", { children: a.tool }), _jsx("span", { style: { fontSize: 12 }, children: a.message })] }) }, i))) })), _jsxs("div", { style: { display: 'flex', gap: 10, flexWrap: 'wrap' }, children: [_jsx(StatCard, { value: o.totalCalls, label: t('totalCalls') }), _jsx(StatCard, { value: o.totalFailures, label: t('totalFailures'), color: o.totalFailures > 0 ? 'var(--dsw-alias-state-error-primary)' : undefined }), _jsx(StatCard, { value: `${Math.round(o.overallFailureRate * 100)}%`, label: t('failRate') }), _jsx(StatCard, { value: o.activeSessions, label: t('sessions') }), _jsx(StatCard, { value: o.avgLatencyMs, unit: "ms", label: t('avgLatency') })] }), payload.tools.length === 0 ? _jsx(EmptyState, { icon: "\uD83D\uDCED", message: t('empty') }) : (_jsxs(_Fragment, { children: [_jsx(SectionTitle, { icon: "\uD83D\uDCCA", children: t('tools') }), _jsx(Card, { padding: 0, children: _jsxs("table", { style: tableStyles.table, children: [_jsx("thead", { children: _jsxs("tr", { children: [_jsx("th", { style: tableStyles.th, children: t('tool') }), _jsx("th", { style: tableStyles.th, children: t('calls') }), _jsx("th", { style: tableStyles.th, children: t('failRate') }), _jsx("th", { style: tableStyles.th, children: "p50" }), _jsx("th", { style: tableStyles.th, children: "p95" })] }) }), _jsx("tbody", { children: payload.tools.map((row) => (_jsxs("tr", { style: tableStyles.clickRow, onClick: () => setToolModal(row.tool), children: [_jsx("td", { style: tableStyles.td, children: _jsx("code", { style: { fontSize: 11 }, children: row.tool }) }), _jsx("td", { style: tableStyles.td, children: row.invocations }), _jsxs("td", { style: { ...tableStyles.td, color: row.failureRate > 0.3 ? 'var(--dsw-alias-state-error-primary)' : undefined }, children: [Math.round(row.failureRate * 100), "%"] }), _jsxs("td", { style: tableStyles.td, children: [row.p50Latency, "ms"] }), _jsxs("td", { style: tableStyles.td, children: [row.p95Latency, "ms"] })] }, row.tool))) })] }) })] })), payload.deadTools.length > 0 && (_jsxs(_Fragment, { children: [_jsx(SectionTitle, { icon: "\uD83D\uDC80", children: t('deadTools') }), _jsx(Card, { children: payload.deadTools.map((row) => (_jsxs("div", { style: { marginBottom: 4 }, children: [_jsx("code", { style: { fontSize: 11 }, children: row.tool }), " \u2014 ", row.sessionsSinceLastUse, " ", t('sessionsUnused')] }, row.tool))) })] })), payload.failingTools.length > 0 && (_jsxs(_Fragment, { children: [_jsx(SectionTitle, { icon: "\u26A0\uFE0F", children: t('failingTools') }), _jsx(Card, { children: payload.failingTools.map((row) => (_jsxs("div", { style: { marginBottom: 4 }, children: [_jsx("code", { style: { fontSize: 11 }, children: row.tool }), " \u00B7 ", Math.round(row.failureRate * 100), "%", row.lastError ? ` — ${row.lastError.slice(0, 60)}` : ''] }, row.tool))) })] })), payload.recommendations.length > 0 && (_jsxs(_Fragment, { children: [_jsx(SectionTitle, { icon: "\uD83D\uDCA1", children: t('recommendations') }), _jsx(Card, { children: payload.recommendations.map((row, i) => _jsx("div", { style: { marginBottom: 4 }, children: row.message }, i)) })] })), toolModal !== null && _jsx(ToolModal, { tool: toolModal, t: t, onClose: () => setToolModal(null) }), confirmClear && _jsx(ConfirmDialog, { title: t('clear'), message: t('confirmClear'), confirmLabel: t('clear'), danger: true, onConfirm: handleClear, onClose: () => setConfirmClear(false) })] }));
|
|
45
45
|
}
|
|
46
46
|
export function ToolStatsPanel({ t }) {
|
|
47
47
|
return _jsx(ToastProvider, { children: _jsx(ToolStatsPanelInner, { t: t }) });
|
package/lib/stats.web.js
CHANGED
|
@@ -609,7 +609,7 @@ function ToolStatsPanelInner({ t }) {
|
|
|
609
609
|
] });
|
|
610
610
|
if (error !== null) return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { maxWidth: 820 }, children: [
|
|
611
611
|
header,
|
|
612
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Card, { children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("p", { role: "alert", style: { margin: 0, fontSize: 13, color: "var(--error
|
|
612
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Card, { children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("p", { role: "alert", style: { margin: 0, fontSize: 13, color: "var(--dsw-alias-state-error-primary)" }, children: [
|
|
613
613
|
t("failed"),
|
|
614
614
|
": ",
|
|
615
615
|
error
|
|
@@ -622,14 +622,14 @@ function ToolStatsPanelInner({ t }) {
|
|
|
622
622
|
const o = payload.overview;
|
|
623
623
|
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { display: "flex", flexDirection: "column", gap: 14, maxWidth: 820 }, children: [
|
|
624
624
|
header,
|
|
625
|
-
payload.alerts.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { display: "flex", flexDirection: "column", gap: 6 }, children: payload.alerts.map((a, i) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Card, { style: { borderColor: a.level === "error" ? "var(--error
|
|
625
|
+
payload.alerts.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { display: "flex", flexDirection: "column", gap: 6 }, children: payload.alerts.map((a, i) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Card, { style: { borderColor: a.level === "error" ? "var(--dsw-alias-state-error-primary)" : "var(--dsw-alias-state-warn-primary)" }, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: 8 }, children: [
|
|
626
626
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Badge, { color: a.level === "error" ? "error" : "warning", children: a.level === "error" ? "\u{1F534}" : "\u{1F7E1}" }),
|
|
627
627
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("strong", { children: a.tool }),
|
|
628
628
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: { fontSize: 12 }, children: a.message })
|
|
629
629
|
] }) }, i)) }),
|
|
630
630
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { display: "flex", gap: 10, flexWrap: "wrap" }, children: [
|
|
631
631
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(StatCard, { value: o.totalCalls, label: t("totalCalls") }),
|
|
632
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(StatCard, { value: o.totalFailures, label: t("totalFailures"), color: o.totalFailures > 0 ? "var(--error
|
|
632
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(StatCard, { value: o.totalFailures, label: t("totalFailures"), color: o.totalFailures > 0 ? "var(--dsw-alias-state-error-primary)" : void 0 }),
|
|
633
633
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(StatCard, { value: `${Math.round(o.overallFailureRate * 100)}%`, label: t("failRate") }),
|
|
634
634
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(StatCard, { value: o.activeSessions, label: t("sessions") }),
|
|
635
635
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(StatCard, { value: o.avgLatencyMs, unit: "ms", label: t("avgLatency") })
|
|
@@ -647,7 +647,7 @@ function ToolStatsPanelInner({ t }) {
|
|
|
647
647
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("tbody", { children: payload.tools.map((row) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("tr", { style: tableStyles.clickRow, onClick: () => setToolModal(row.tool), children: [
|
|
648
648
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("td", { style: tableStyles.td, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("code", { style: { fontSize: 11 }, children: row.tool }) }),
|
|
649
649
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("td", { style: tableStyles.td, children: row.invocations }),
|
|
650
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("td", { style: { ...tableStyles.td, color: row.failureRate > 0.3 ? "var(--error
|
|
650
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("td", { style: { ...tableStyles.td, color: row.failureRate > 0.3 ? "var(--dsw-alias-state-error-primary)" : void 0 }, children: [
|
|
651
651
|
Math.round(row.failureRate * 100),
|
|
652
652
|
"%"
|
|
653
653
|
] }),
|
package/lib/stats.web.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/client/index.tsx", "../src/client/locales.ts", "../src/client/view.tsx", "../src/client/ui.tsx"],
|
|
4
|
-
"sourcesContent": ["/**\n * Browser half of dsh-tool-stats: its page inside Settings.\n *\n * Two services are injected by their runtime identity, not by the packages that provide\n * them: the settings shell is `slots`, the dictionary registry is `locale`. The label and\n * every string inside the component are translated through this plugin's own namespace,\n * which is why the page follows the UI language instead of the plugin author's.\n *\n * @module client\n */\n\nimport type { ComponentType } from 'react'\n\nimport { NS, en, zh } from './locales.js'\nimport { ToolStatsPanel } from './view.js'\n\nexport const inject = ['slots', 'locale']\n\ninterface SlotsService {\n inject(name: string, register: () => void): void\n register(\n options: { name: string; id: string; order: number; label: () => string; locale: string },\n component: ComponentType<{ t: (key: string, params?: Record<string, unknown>) => string }>,\n ): unknown\n}\n\ninterface ClientContext {\n slots: SlotsService\n locale: {\n register(ns: string, dicts: { zh: unknown; en: unknown }): () => void\n bind(ns: string): (key: string, params?: Record<string, unknown>) => string\n }\n effect(callback: () => unknown, label?: string): unknown\n}\n\nexport function apply(ctx: ClientContext): void {\n // `zh` is the key-set source of truth, matching the official client plugins.\n ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'dsh-tool-stats: dictionaries')\n\n ctx.slots.inject('settings.section', () => ctx.slots.register(\n {\n name: 'settings.section',\n id: 'tool-stats',\n order: 42,\n label: () => ctx.locale.bind(NS)('nav'),\n locale: NS,\n },\n ToolStatsPanel,\n ))\n}\n", "/**\n * Dictionaries for the Tool Stats page.\n *\n * `zh` is the key-set source of truth, as in the official client plugins, and `en` is\n * typed against it: a key translated in one language but not the other fails the build\n * instead of silently rendering the raw key.\n *\n * @module client/locales\n */\n\n/** Dictionary namespace owned by this plugin. */\nexport const NS = 'toolStats'\n\n/** Simplified Chinese dictionary (the key-set source of truth). */\nexport const zh = {\n 'nav': '\u5DE5\u5177\u7EDF\u8BA1',\n 'title': '\u5DE5\u5177\u7EDF\u8BA1',\n 'tool': '\u5DE5\u5177',\n 'calls': '\u8C03\u7528\u6B21\u6570',\n 'failRate': '\u5931\u8D25\u7387',\n 'deadTools': '\u4ECE\u672A\u8C03\u7528\u7684\u5DE5\u5177',\n 'failingTools': '\u9AD8\u5931\u8D25\u7387\u5DE5\u5177',\n 'recommendations': '\u5EFA\u8BAE',\n 'empty': '\u8FD8\u6CA1\u6709\u8BB0\u5F55\u5230\u5DE5\u5177\u8C03\u7528\u3002',\n 'refresh': '\u5237\u65B0',\n 'loading': '\u6B63\u5728\u52A0\u8F7D\u2026',\n 'failed': '\u52A0\u8F7D\u5931\u8D25',\n 'retry': '\u91CD\u8BD5',\n 'totalCalls': '\u603B\u8C03\u7528',\n 'totalFailures': '\u603B\u5931\u8D25',\n 'sessions': '\u4F1A\u8BDD\u6570',\n 'avgLatency': '\u5E73\u5747\u5EF6\u8FDF',\n 'export': '\u5BFC\u51FA CSV',\n 'clear': '\u6E05\u7A7A',\n 'confirmClear': '\u786E\u5B9A\u6E05\u7A7A\u6240\u6709\u8BB0\u5F55\uFF1F',\n 'errorBreakdown': '\u9519\u8BEF\u5206\u7C7B',\n 'recentCalls': '\u6700\u8FD1\u8C03\u7528',\n 'sessionsUnused': '\u4F1A\u8BDD\u672A\u7528',\n}\n\n/** English dictionary, checked complete against the zh key set. */\nexport const en: typeof zh = {\n 'nav': 'Tool Stats',\n 'title': 'Tool Stats',\n 'tool': 'Tool',\n 'calls': 'Calls',\n 'failRate': 'Failure',\n 'deadTools': 'Dead tools',\n 'failingTools': 'Failing tools',\n 'recommendations': 'Recommendations',\n 'empty': 'No tool calls recorded yet.',\n 'refresh': 'Refresh',\n 'loading': 'Loading\u2026',\n 'failed': 'Failed to load',\n 'retry': 'Retry',\n 'totalCalls': 'Total Calls',\n 'totalFailures': 'Total Failures',\n 'sessions': 'Sessions',\n 'avgLatency': 'Avg Latency',\n 'export': 'Export CSV',\n 'clear': 'Clear',\n 'confirmClear': 'Clear all recorded data?',\n 'errorBreakdown': 'Error Breakdown',\n 'recentCalls': 'Recent Calls',\n 'sessionsUnused': 'sessions unused',\n}\n", "/**\n * Pure rendering half of the toolStats page. Uses shared UI kit.\n * @module client/view\n */\n\nimport { useCallback, useState } from 'react'\nimport type { ReactNode } from 'react'\n\nimport type { PanelPayload, ToolDetail } from '../types.js'\nimport {\n Badge, Button, Card, ConfirmDialog, EmptyState, Modal, SectionTitle,\n Spinner, StatCard, ToastProvider, tableStyles, usePanel, useToast,\n} from './ui.js'\n\nexport type Translate = (key: string, params?: Record<string, unknown>) => string\nexport interface PanelProps { t: Translate }\n\nconst PANEL_PATH = '/api/stats.panel'\nconst DETAIL_PATH = '/api/stats.detail'\nconst EXPORT_PATH = '/api/stats.export'\nconst CLEAR_PATH = '/api/stats.clear'\n\nfunction ToolModal({ tool, t, onClose }: { tool: string; t: Translate; onClose: () => void }): ReactNode {\n const [detail, setDetail] = useState<ToolDetail | null>(null)\n const [loading, setLoading] = useState(true)\n\n useCallback(() => {\n setLoading(true)\n fetch(`${DETAIL_PATH}?tool=${encodeURIComponent(tool)}`)\n .then(async (r) => { if (r.ok) setDetail(await r.json() as ToolDetail) })\n .finally(() => setLoading(false))\n }, [tool])()\n\n return (\n <Modal title={`${t('tool')}: ${tool}`} onClose={onClose} width={600}>\n {loading ? <div style={{ display: 'flex', justifyContent: 'center', padding: 20 }}><Spinner size={24} /></div>\n : detail === null ? <EmptyState message={t('notFound')} />\n : (\n <div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>\n {detail.errorBreakdown.length > 0 && (\n <Card title={t('errorBreakdown')}>\n {detail.errorBreakdown.map((e, i) => (\n <div key={i} style={{ display: 'flex', gap: 8, alignItems: 'center', marginBottom: 4 }}>\n <Badge color=\"error\">\u00D7{e.count}</Badge>\n <span style={{ fontSize: 12 }}>{e.message}</span>\n </div>\n ))}\n </Card>\n )}\n <Card title={t('recentCalls')}>\n <table style={tableStyles.table}>\n <tbody>\n {detail.recentCalls.slice(0, 15).map((c, i) => (\n <tr key={i}>\n <td style={tableStyles.td}>{c.success ? <Badge color=\"success\">\u2713</Badge> : <Badge color=\"error\">\u2717</Badge>}</td>\n <td style={tableStyles.td}>{c.latencyMs}ms</td>\n <td style={tableStyles.td}>{new Date(c.timestamp).toLocaleTimeString()}</td>\n <td style={{ ...tableStyles.td, fontSize: 10, opacity: 0.6 }}>{c.errorMessage?.slice(0, 60)}</td>\n </tr>\n ))}\n </tbody>\n </table>\n </Card>\n </div>\n )}\n </Modal>\n )\n}\n\nfunction ToolStatsPanelInner({ t }: PanelProps): ReactNode {\n const { payload, error, reload } = usePanel<PanelPayload>(PANEL_PATH)\n const toast = useToast()\n const [toolModal, setToolModal] = useState<string | null>(null)\n const [confirmClear, setConfirmClear] = useState(false)\n\n const handleClear = useCallback(async () => {\n const r = await fetch(CLEAR_PATH, { method: 'POST' })\n if (r.ok) { toast('success', t('cleared')); reload() }\n }, [t, toast, reload])\n\n const header = (\n <header style={{ display: 'flex', alignItems: 'center', gap: 12, marginBottom: 4 }}>\n <strong style={{ fontSize: 15 }}>\uD83D\uDD27 {t('title')}</strong>\n <span style={{ flex: 1 }} />\n <a href={EXPORT_PATH} download><Button variant=\"secondary\" size=\"sm\">{t('export')}</Button></a>\n <Button variant=\"danger\" size=\"sm\" onClick={() => setConfirmClear(true)}>{t('clear')}</Button>\n <Button variant=\"secondary\" size=\"sm\" onClick={reload}>{t('refresh')}</Button>\n </header>\n )\n\n if (error !== null) return <div style={{ maxWidth: 820 }}>{header}<Card><p role=\"alert\" style={{ margin: 0, fontSize: 13, color: 'var(--error, #e53935)' }}>{t('failed')}: {error}</p></Card></div>\n if (payload === null) return <div style={{ maxWidth: 820 }}>{header}<div style={{ display: 'flex', justifyContent: 'center', padding: 40 }}><Spinner size={28} /></div></div>\n\n const o = payload.overview\n return (\n <div style={{ display: 'flex', flexDirection: 'column', gap: 14, maxWidth: 820 }}>\n {header}\n\n {payload.alerts.length > 0 && (\n <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>\n {payload.alerts.map((a, i) => (\n <Card key={i} style={{ borderColor: a.level === 'error' ? 'var(--error, #e53935)' : 'var(--warning, #ed6c02)' }}>\n <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>\n <Badge color={a.level === 'error' ? 'error' : 'warning'}>{a.level === 'error' ? '\uD83D\uDD34' : '\uD83D\uDFE1'}</Badge>\n <strong>{a.tool}</strong><span style={{ fontSize: 12 }}>{a.message}</span>\n </div>\n </Card>\n ))}\n </div>\n )}\n\n <div style={{ display: 'flex', gap: 10, flexWrap: 'wrap' }}>\n <StatCard value={o.totalCalls} label={t('totalCalls')} />\n <StatCard value={o.totalFailures} label={t('totalFailures')} color={o.totalFailures > 0 ? 'var(--error, #e53935)' : undefined as unknown as string} />\n <StatCard value={`${Math.round(o.overallFailureRate * 100)}%`} label={t('failRate')} />\n <StatCard value={o.activeSessions} label={t('sessions')} />\n <StatCard value={o.avgLatencyMs} unit=\"ms\" label={t('avgLatency')} />\n </div>\n\n {payload.tools.length === 0 ? <EmptyState icon=\"\uD83D\uDCED\" message={t('empty')} /> : (\n <>\n <SectionTitle icon=\"\uD83D\uDCCA\">{t('tools')}</SectionTitle>\n <Card padding={0}>\n <table style={tableStyles.table}>\n <thead><tr><th style={tableStyles.th}>{t('tool')}</th><th style={tableStyles.th}>{t('calls')}</th><th style={tableStyles.th}>{t('failRate')}</th><th style={tableStyles.th}>p50</th><th style={tableStyles.th}>p95</th></tr></thead>\n <tbody>\n {payload.tools.map((row) => (\n <tr key={row.tool} style={tableStyles.clickRow} onClick={() => setToolModal(row.tool)}>\n <td style={tableStyles.td}><code style={{ fontSize: 11 }}>{row.tool}</code></td>\n <td style={tableStyles.td}>{row.invocations}</td>\n <td style={{ ...tableStyles.td, color: row.failureRate > 0.3 ? 'var(--error, #e53935)' : undefined }}>{Math.round(row.failureRate * 100)}%</td>\n <td style={tableStyles.td}>{row.p50Latency}ms</td>\n <td style={tableStyles.td}>{row.p95Latency}ms</td>\n </tr>\n ))}\n </tbody>\n </table>\n </Card>\n </>\n )}\n\n {payload.deadTools.length > 0 && (\n <>\n <SectionTitle icon=\"\uD83D\uDC80\">{t('deadTools')}</SectionTitle>\n <Card>{payload.deadTools.map((row) => (\n <div key={row.tool} style={{ marginBottom: 4 }}><code style={{ fontSize: 11 }}>{row.tool}</code> \u2014 {row.sessionsSinceLastUse} {t('sessionsUnused')}</div>\n ))}</Card>\n </>\n )}\n\n {payload.failingTools.length > 0 && (\n <>\n <SectionTitle icon=\"\u26A0\uFE0F\">{t('failingTools')}</SectionTitle>\n <Card>{payload.failingTools.map((row) => (\n <div key={row.tool} style={{ marginBottom: 4 }}><code style={{ fontSize: 11 }}>{row.tool}</code> \u00B7 {Math.round(row.failureRate * 100)}%{row.lastError ? ` \u2014 ${row.lastError.slice(0, 60)}` : ''}</div>\n ))}</Card>\n </>\n )}\n\n {payload.recommendations.length > 0 && (\n <>\n <SectionTitle icon=\"\uD83D\uDCA1\">{t('recommendations')}</SectionTitle>\n <Card>{payload.recommendations.map((row, i) => <div key={i} style={{ marginBottom: 4 }}>{row.message}</div>)}</Card>\n </>\n )}\n\n {toolModal !== null && <ToolModal tool={toolModal} t={t} onClose={() => setToolModal(null)} />}\n {confirmClear && <ConfirmDialog title={t('clear')} message={t('confirmClear')} confirmLabel={t('clear')} danger onConfirm={handleClear} onClose={() => setConfirmClear(false)} />}\n </div>\n )\n}\n\nexport function ToolStatsPanel({ t }: PanelProps): ReactNode {\n return <ToastProvider><ToolStatsPanelInner t={t} /></ToastProvider>\n}\n", "/**\n * Shared UI component kit for dsh plugin panels.\n *\n * Self-contained: imports nothing but React. All styling is inline + CSS custom\n * properties so the components follow the host theme. A single <style> tag injects\n * keyframes the first time any component mounts.\n *\n * @module client/ui\n */\n\nimport {\n createContext,\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useState,\n} from 'react'\nimport type {\n ButtonHTMLAttributes,\n CSSProperties,\n InputHTMLAttributes,\n ReactNode,\n SelectHTMLAttributes,\n} from 'react'\n\n// \u2500\u2500\u2500 Theme tokens \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n/**\n * Host theme tokens.\n *\n * Every name here is a real dsh token, read out of the shipped stylesheets\n * (`@deepseek-ai/dsh-client-ui-theme`), which define them on `body`. An earlier\n * version used invented names \u2014 `--accent`, `--border`, `--bg-primary`,\n * `--text-primary` and friends \u2014 that dsh does not define at all. The\n * hardcoded fallbacks then applied in every theme, so each panel rendered its\n * own fixed palette instead of following the host, and the modal and input\n * surfaces stayed white in dark mode.\n *\n * No fallbacks on purpose: if a name were ever missing, the declaration becomes\n * invalid at computed-value time and the property inherits, which degrades\n * gracefully, whereas a hardcoded fallback bakes in a color that is wrong in one\n * of the two themes. `tests/ui-tokens.test.mjs` enforces the name list.\n */\nconst T = {\n accent: 'var(--dsw-alias-brand-primary)',\n accentHover: 'var(--dsw-alias-button-primary-hover)',\n inverted: 'var(--dsw-alias-label-primary-inverted)',\n error: 'var(--dsw-alias-state-error-primary)',\n success: 'var(--dsw-alias-state-success-primary)',\n warning: 'var(--dsw-alias-state-warn-primary)',\n bg: 'var(--dsw-alias-bg-layer-2)',\n bgHover: 'var(--dsw-alias-interactive-bg-hover)',\n border: 'var(--dsw-alias-border-l2)',\n text: 'var(--dsw-alias-label-primary)',\n muted: 'var(--dsw-alias-label-tertiary)',\n surface: 'var(--dsw-alias-bg-layer-1)',\n mask: 'var(--dsw-alias-bg-mask-1)',\n}\n\n// \u2500\u2500\u2500 Style injection (once) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nlet injected = false\nfunction ensureStyles(): void {\n if (injected || typeof document === 'undefined') return\n injected = true\n const el = document.createElement('style')\n el.textContent = `\n@keyframes dsh-fade-in{from{opacity:0}to{opacity:1}}\n@keyframes dsh-slide-up{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}}\n@keyframes dsh-spin{to{transform:rotate(360deg)}}\n.dsh-modal-overlay{animation:dsh-fade-in .15s ease}\n.dsh-modal-body{animation:dsh-slide-up .2s ease}\n.dsh-toast{animation:dsh-slide-up .2s ease}\n.dsh-spin{animation:dsh-spin .6s linear infinite}\n `\n document.head.appendChild(el)\n}\n\n// \u2500\u2500\u2500 Modal \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nexport function Modal({\n title,\n onClose,\n children,\n footer,\n width = 520,\n}: {\n title: string\n onClose: () => void\n children: ReactNode\n footer?: ReactNode\n width?: number\n}): ReactNode {\n useEffect(() => {\n ensureStyles()\n const handler = (e: KeyboardEvent): void => {\n if (e.key === 'Escape') onClose()\n }\n document.addEventListener('keydown', handler)\n return () => document.removeEventListener('keydown', handler)\n }, [onClose])\n\n return (\n <div\n className=\"dsh-modal-overlay\"\n style={{\n position: 'fixed',\n inset: 0,\n zIndex: 1000,\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n background: T.mask,\n backdropFilter: 'blur(3px)',\n WebkitBackdropFilter: 'blur(3px)',\n }}\n onClick={onClose}\n >\n <div\n className=\"dsh-modal-body\"\n style={{\n background: T.surface,\n borderRadius: 14,\n boxShadow: 'var(--dsw-elevation-prominent)',\n width: `min(92vw, ${width}px)`,\n maxHeight: '85vh',\n display: 'flex',\n flexDirection: 'column',\n overflow: 'hidden',\n }}\n onClick={(e) => e.stopPropagation()}\n >\n <div\n style={{\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'space-between',\n padding: '16px 22px',\n borderBottom: `1px solid ${T.border}`,\n }}\n >\n <h3 style={{ margin: 0, fontSize: 15, fontWeight: 600 }}>{title}</h3>\n <button\n type=\"button\"\n onClick={onClose}\n style={{\n background: 'none',\n border: 'none',\n cursor: 'pointer',\n fontSize: 20,\n color: T.muted,\n padding: '0 4px',\n lineHeight: 1,\n borderRadius: 4,\n }}\n aria-label=\"Close\"\n >\n \u00D7\n </button>\n </div>\n <div style={{ padding: 22, overflow: 'auto', flex: 1 }}>{children}</div>\n {footer !== undefined && (\n <div\n style={{\n padding: '14px 22px',\n borderTop: `1px solid ${T.border}`,\n display: 'flex',\n justifyContent: 'flex-end',\n gap: 10,\n background: T.bg,\n }}\n >\n {footer}\n </div>\n )}\n </div>\n </div>\n )\n}\n\n// \u2500\u2500\u2500 ConfirmDialog \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nexport function ConfirmDialog({\n title,\n message,\n confirmLabel,\n cancelLabel = 'Cancel',\n onConfirm,\n onClose,\n danger,\n}: {\n title: string\n message: string\n confirmLabel: string\n cancelLabel?: string\n onConfirm: () => void\n onClose: () => void\n danger?: boolean\n}): ReactNode {\n return (\n <Modal\n title={title}\n onClose={onClose}\n width={420}\n footer={\n <>\n <Button variant=\"secondary\" onClick={onClose}>\n {cancelLabel}\n </Button>\n <Button\n variant={danger === true ? 'danger' : 'primary'}\n onClick={() => {\n onConfirm()\n onClose()\n }}\n >\n {confirmLabel}\n </Button>\n </>\n }\n >\n <p style={{ margin: 0, fontSize: 14, lineHeight: 1.6 }}>{message}</p>\n </Modal>\n )\n}\n\n// \u2500\u2500\u2500 Button \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nexport function Button({\n variant = 'secondary',\n size = 'md',\n children,\n style,\n ...rest\n}: {\n variant?: 'primary' | 'secondary' | 'danger' | 'ghost'\n size?: 'sm' | 'md'\n children: ReactNode\n style?: CSSProperties\n} & ButtonHTMLAttributes<HTMLButtonElement>): ReactNode {\n const base: CSSProperties = {\n cursor: 'pointer',\n borderRadius: 7,\n fontWeight: 500,\n transition: 'all 0.15s ease',\n border: 'none',\n display: 'inline-flex',\n alignItems: 'center',\n gap: 4,\n fontSize: size === 'sm' ? 12 : 13,\n padding: size === 'sm' ? '5px 12px' : '8px 18px',\n fontFamily: 'inherit',\n }\n const variants: Record<string, CSSProperties> = {\n primary: { background: T.accent, color: T.inverted },\n secondary: {\n background: T.bg,\n color: T.text,\n border: `1px solid ${T.border}`,\n },\n danger: {\n background: 'transparent',\n color: T.error,\n border: `1px solid ${T.error}`,\n },\n ghost: { background: 'transparent', color: T.muted, border: 'none' },\n }\n return (\n <button type=\"button\" style={{ ...base, ...variants[variant], ...style }} {...rest}>\n {children}\n </button>\n )\n}\n\n// \u2500\u2500\u2500 Card \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nexport function Card({\n title,\n icon,\n children,\n actions,\n style,\n padding = 14,\n}: {\n title?: string\n icon?: string\n children: ReactNode\n actions?: ReactNode\n style?: CSSProperties\n padding?: number\n}): ReactNode {\n return (\n <div\n style={{\n borderRadius: 10,\n border: `1px solid ${T.border}`,\n background: T.bg,\n overflow: 'hidden',\n ...style,\n }}\n >\n {(title !== undefined || actions !== undefined) && (\n <div\n style={{\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'space-between',\n padding: '12px 16px',\n borderBottom: title !== undefined ? `1px solid ${T.border}` : 'none',\n }}\n >\n <span style={{ fontSize: 13, fontWeight: 600, display: 'flex', alignItems: 'center', gap: 6 }}>\n {icon !== undefined && <span>{icon}</span>}\n {title}\n </span>\n {actions}\n </div>\n )}\n <div style={{ padding }}>{children}</div>\n </div>\n )\n}\n\n// \u2500\u2500\u2500 StatCard \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nexport function StatCard({\n value,\n label,\n color,\n unit,\n}: {\n value: string | number\n label: string\n color?: string\n unit?: string\n}): ReactNode {\n return (\n <div\n style={{\n flex: 1,\n minWidth: 90,\n textAlign: 'center',\n padding: '14px 10px',\n borderRadius: 10,\n border: `1px solid ${T.border}`,\n background: T.bg,\n }}\n >\n <div style={{ fontSize: 24, fontWeight: 700, color: color ?? T.text, lineHeight: 1.2 }}>\n {value}\n {unit !== undefined && (\n <span style={{ fontSize: 13, fontWeight: 500, opacity: 0.7, marginLeft: 2 }}>{unit}</span>\n )}\n </div>\n <div style={{ fontSize: 11, color: T.muted, marginTop: 4 }}>{label}</div>\n </div>\n )\n}\n\n// \u2500\u2500\u2500 Badge \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nexport function Badge({\n children,\n color = 'default',\n}: {\n children: ReactNode\n color?: 'default' | 'success' | 'error' | 'warning' | 'info'\n}): ReactNode {\n const colors: Record<string, { bg: string; fg: string }> = {\n default: { bg: T.bgHover, fg: T.muted },\n success: { bg: 'var(--dsw-alias-state-success-tertiary)', fg: T.success },\n error: { bg: 'var(--dsw-alias-state-error-secondary)', fg: T.error },\n warning: { bg: 'var(--dsw-alias-state-warn-tertiary)', fg: T.warning },\n info: { bg: 'var(--dsw-alias-state-business-tertiary)', fg: T.accent },\n }\n const c = colors[color]\n return (\n <span\n style={{\n display: 'inline-block',\n padding: '2px 9px',\n borderRadius: 10,\n fontSize: 11,\n fontWeight: 600,\n background: c.bg,\n color: c.fg,\n }}\n >\n {children}\n </span>\n )\n}\n\n// \u2500\u2500\u2500 Input \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nexport function Input({\n style,\n ...rest\n}: { style?: CSSProperties } & InputHTMLAttributes<HTMLInputElement>): ReactNode {\n return (\n <input\n {...rest}\n style={{\n fontSize: 13,\n padding: '8px 12px',\n borderRadius: 7,\n border: `1px solid ${T.border}`,\n background: T.surface,\n color: T.text,\n width: '100%',\n boxSizing: 'border-box',\n fontFamily: 'inherit',\n outline: 'none',\n transition: 'border-color 0.15s',\n ...style,\n }}\n />\n )\n}\n\n// \u2500\u2500\u2500 Select \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nexport function Select({\n style,\n children,\n ...rest\n}: {\n style?: CSSProperties\n children: ReactNode\n} & SelectHTMLAttributes<HTMLSelectElement>): ReactNode {\n return (\n <select\n {...rest}\n style={{\n fontSize: 13,\n padding: '8px 12px',\n borderRadius: 7,\n border: `1px solid ${T.border}`,\n background: T.surface,\n color: T.text,\n cursor: 'pointer',\n fontFamily: 'inherit',\n outline: 'none',\n ...style,\n }}\n >\n {children}\n </select>\n )\n}\n\n// \u2500\u2500\u2500 Textarea \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nexport function Textarea({\n style,\n ...rest\n}: { style?: CSSProperties } & React.TextareaHTMLAttributes<HTMLTextAreaElement>): ReactNode {\n return (\n <textarea\n {...rest}\n style={{\n fontSize: 13,\n padding: '8px 12px',\n borderRadius: 7,\n border: `1px solid ${T.border}`,\n background: T.surface,\n color: T.text,\n width: '100%',\n boxSizing: 'border-box',\n fontFamily: 'inherit',\n outline: 'none',\n resize: 'vertical',\n lineHeight: 1.5,\n ...style,\n }}\n />\n )\n}\n\n// \u2500\u2500\u2500 Toast \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\ninterface ToastItem {\n id: number\n type: 'success' | 'error' | 'info'\n message: string\n}\n\nconst ToastContext = createContext<(type: ToastItem['type'], message: string) => void>(\n () => {},\n)\n\nexport function ToastProvider({ children }: { children: ReactNode }): ReactNode {\n const [toasts, setToasts] = useState<ToastItem[]>([])\n const show = useCallback((type: ToastItem['type'], message: string) => {\n const id = Date.now() + Math.random()\n setToasts((t) => [...t, { id, type, message }])\n setTimeout(() => setToasts((t) => t.filter((x) => x.id !== id)), 3500)\n }, [])\n return (\n <ToastContext.Provider value={show}>\n {children}\n {toasts.length > 0 && (\n <div\n style={{\n position: 'fixed',\n bottom: 24,\n right: 24,\n zIndex: 2000,\n display: 'flex',\n flexDirection: 'column',\n gap: 8,\n }}\n >\n {toasts.map((t) => (\n <div\n key={t.id}\n className=\"dsh-toast\"\n style={{\n padding: '12px 18px',\n borderRadius: 8,\n fontSize: 13,\n fontWeight: 500,\n boxShadow: 'var(--dsw-elevation-panel)',\n color: T.inverted,\n background:\n t.type === 'success'\n ? T.success\n : t.type === 'error'\n ? T.error\n : T.accent,\n maxWidth: 360,\n }}\n >\n {t.message}\n </div>\n ))}\n </div>\n )}\n </ToastContext.Provider>\n )\n}\n\nexport function useToast(): (type: ToastItem['type'], message: string) => void {\n return useContext(ToastContext)\n}\n\n// \u2500\u2500\u2500 Spinner \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nexport function Spinner({ size = 16 }: { size?: number }): ReactNode {\n ensureStyles()\n return (\n <span\n className=\"dsh-spin\"\n style={{\n display: 'inline-block',\n width: size,\n height: size,\n border: `2px solid ${T.border}`,\n borderTopColor: T.accent,\n borderRadius: '50%',\n }}\n />\n )\n}\n\n// \u2500\u2500\u2500 EmptyState \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nexport function EmptyState({\n icon,\n message,\n}: {\n icon?: string\n message: string\n}): ReactNode {\n return (\n <div\n style={{\n textAlign: 'center',\n padding: '36px 16px',\n color: T.muted,\n }}\n >\n {icon !== undefined && (\n <div style={{ fontSize: 36, marginBottom: 10, opacity: 0.4 }}>{icon}</div>\n )}\n <div style={{ fontSize: 13 }}>{message}</div>\n </div>\n )\n}\n\n// \u2500\u2500\u2500 SectionTitle \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nexport function SectionTitle({\n icon,\n children,\n actions,\n}: {\n icon?: string\n children: ReactNode\n actions?: ReactNode\n}): ReactNode {\n return (\n <div\n style={{\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'space-between',\n marginTop: 6,\n marginBottom: 8,\n }}\n >\n <span\n style={{\n fontSize: 14,\n fontWeight: 600,\n display: 'flex',\n alignItems: 'center',\n gap: 6,\n }}\n >\n {icon !== undefined && <span>{icon}</span>}\n {children}\n </span>\n {actions}\n </div>\n )\n}\n\n// \u2500\u2500\u2500 Table helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nexport const tableStyles = {\n table: {\n borderCollapse: 'collapse' as const,\n width: '100%' as const,\n },\n th: {\n textAlign: 'left' as const,\n padding: '8px 12px 8px 0',\n fontWeight: 600 as const,\n fontSize: 12 as const,\n color: T.muted,\n borderBottom: `1px solid ${T.border}`,\n },\n td: {\n padding: '8px 12px 8px 0',\n fontSize: 13 as const,\n borderBottom: `1px solid ${T.border}`,\n },\n clickRow: { cursor: 'pointer' as const },\n}\n\n// \u2500\u2500\u2500 CodeBlock \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nexport function CodeBlock({\n children,\n maxHeight = 200,\n style,\n}: {\n children: string\n maxHeight?: number\n style?: CSSProperties\n}): ReactNode {\n return (\n <pre\n style={{\n whiteSpace: 'pre-wrap',\n wordBreak: 'break-word',\n fontSize: 12,\n maxHeight,\n overflow: 'auto',\n padding: 10,\n borderRadius: 7,\n background: T.bg,\n border: `1px solid ${T.border}`,\n margin: 0,\n ...style,\n }}\n >\n {children}\n </pre>\n )\n}\n\n// \u2500\u2500\u2500 ProgressBar \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nexport function ProgressBar({\n pct,\n color,\n height = 18,\n}: {\n pct: number\n color?: string\n height?: number\n}): ReactNode {\n return (\n <div\n style={{\n width: '100%',\n height,\n background: T.bgHover,\n borderRadius: 5,\n overflow: 'hidden',\n position: 'relative',\n }}\n >\n <div\n style={{\n width: `${Math.min(pct, 100)}%`,\n height: '100%',\n background: color ?? T.accent,\n borderRadius: 5,\n transition: 'width 0.3s ease',\n }}\n />\n </div>\n )\n}\n\n// \u2500\u2500\u2500 Field label \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nexport function Field({\n label,\n children,\n hint,\n}: {\n label: string\n children: ReactNode\n hint?: string\n}): ReactNode {\n return (\n <div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}>\n <label style={{ fontSize: 12, fontWeight: 600, color: T.text }}>{label}</label>\n {children}\n {hint !== undefined && (\n <span style={{ fontSize: 11, color: T.muted }}>{hint}</span>\n )}\n </div>\n )\n}\n\n// \u2500\u2500\u2500 usePanel hook (shared fetch logic) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nexport function usePanel<P>(path: string): {\n payload: P | null\n error: string | null\n reload: () => void\n} {\n const [payload, setPayload] = useState<P | null>(null)\n const [error, setError] = useState<string | null>(null)\n const [tick, setTick] = useState(0)\n const reload = useCallback(() => setTick((v) => v + 1), [])\n\n useEffect(() => {\n const c = new AbortController()\n setError(null)\n fetch(path, { signal: c.signal })\n .then(async (r) => {\n if (!r.ok) throw new Error(String(r.status))\n return r.json() as Promise<P>\n })\n .then((p) => {\n if (!c.signal.aborted) setPayload(p)\n })\n .catch((e: unknown) => {\n if (!c.signal.aborted) setPayload(null), setError(e instanceof Error ? e.message : String(e))\n })\n return () => c.abort()\n }, [tick, path])\n\n return useMemo(() => ({ payload, error, reload }), [payload, error, reload])\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["/**\n * Browser half of dsh-tool-stats: its page inside Settings.\n *\n * Two services are injected by their runtime identity, not by the packages that provide\n * them: the settings shell is `slots`, the dictionary registry is `locale`. The label and\n * every string inside the component are translated through this plugin's own namespace,\n * which is why the page follows the UI language instead of the plugin author's.\n *\n * @module client\n */\n\nimport type { ComponentType } from 'react'\n\nimport { NS, en, zh } from './locales.js'\nimport { ToolStatsPanel } from './view.js'\n\nexport const inject = ['slots', 'locale']\n\ninterface SlotsService {\n inject(name: string, register: () => void): void\n register(\n options: { name: string; id: string; order: number; label: () => string; locale: string },\n component: ComponentType<{ t: (key: string, params?: Record<string, unknown>) => string }>,\n ): unknown\n}\n\ninterface ClientContext {\n slots: SlotsService\n locale: {\n register(ns: string, dicts: { zh: unknown; en: unknown }): () => void\n bind(ns: string): (key: string, params?: Record<string, unknown>) => string\n }\n effect(callback: () => unknown, label?: string): unknown\n}\n\nexport function apply(ctx: ClientContext): void {\n // `zh` is the key-set source of truth, matching the official client plugins.\n ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'dsh-tool-stats: dictionaries')\n\n ctx.slots.inject('settings.section', () => ctx.slots.register(\n {\n name: 'settings.section',\n id: 'tool-stats',\n order: 42,\n label: () => ctx.locale.bind(NS)('nav'),\n locale: NS,\n },\n ToolStatsPanel,\n ))\n}\n", "/**\n * Dictionaries for the Tool Stats page.\n *\n * `zh` is the key-set source of truth, as in the official client plugins, and `en` is\n * typed against it: a key translated in one language but not the other fails the build\n * instead of silently rendering the raw key.\n *\n * @module client/locales\n */\n\n/** Dictionary namespace owned by this plugin. */\nexport const NS = 'toolStats'\n\n/** Simplified Chinese dictionary (the key-set source of truth). */\nexport const zh = {\n 'nav': '\u5DE5\u5177\u7EDF\u8BA1',\n 'title': '\u5DE5\u5177\u7EDF\u8BA1',\n 'tool': '\u5DE5\u5177',\n 'calls': '\u8C03\u7528\u6B21\u6570',\n 'failRate': '\u5931\u8D25\u7387',\n 'deadTools': '\u4ECE\u672A\u8C03\u7528\u7684\u5DE5\u5177',\n 'failingTools': '\u9AD8\u5931\u8D25\u7387\u5DE5\u5177',\n 'recommendations': '\u5EFA\u8BAE',\n 'empty': '\u8FD8\u6CA1\u6709\u8BB0\u5F55\u5230\u5DE5\u5177\u8C03\u7528\u3002',\n 'refresh': '\u5237\u65B0',\n 'loading': '\u6B63\u5728\u52A0\u8F7D\u2026',\n 'failed': '\u52A0\u8F7D\u5931\u8D25',\n 'retry': '\u91CD\u8BD5',\n 'totalCalls': '\u603B\u8C03\u7528',\n 'totalFailures': '\u603B\u5931\u8D25',\n 'sessions': '\u4F1A\u8BDD\u6570',\n 'avgLatency': '\u5E73\u5747\u5EF6\u8FDF',\n 'export': '\u5BFC\u51FA CSV',\n 'clear': '\u6E05\u7A7A',\n 'confirmClear': '\u786E\u5B9A\u6E05\u7A7A\u6240\u6709\u8BB0\u5F55\uFF1F',\n 'errorBreakdown': '\u9519\u8BEF\u5206\u7C7B',\n 'recentCalls': '\u6700\u8FD1\u8C03\u7528',\n 'sessionsUnused': '\u4F1A\u8BDD\u672A\u7528',\n}\n\n/** English dictionary, checked complete against the zh key set. */\nexport const en: typeof zh = {\n 'nav': 'Tool Stats',\n 'title': 'Tool Stats',\n 'tool': 'Tool',\n 'calls': 'Calls',\n 'failRate': 'Failure',\n 'deadTools': 'Dead tools',\n 'failingTools': 'Failing tools',\n 'recommendations': 'Recommendations',\n 'empty': 'No tool calls recorded yet.',\n 'refresh': 'Refresh',\n 'loading': 'Loading\u2026',\n 'failed': 'Failed to load',\n 'retry': 'Retry',\n 'totalCalls': 'Total Calls',\n 'totalFailures': 'Total Failures',\n 'sessions': 'Sessions',\n 'avgLatency': 'Avg Latency',\n 'export': 'Export CSV',\n 'clear': 'Clear',\n 'confirmClear': 'Clear all recorded data?',\n 'errorBreakdown': 'Error Breakdown',\n 'recentCalls': 'Recent Calls',\n 'sessionsUnused': 'sessions unused',\n}\n", "/**\n * Pure rendering half of the toolStats page. Uses shared UI kit.\n * @module client/view\n */\n\nimport { useCallback, useState } from 'react'\nimport type { ReactNode } from 'react'\n\nimport type { PanelPayload, ToolDetail } from '../types.js'\nimport {\n Badge, Button, Card, ConfirmDialog, EmptyState, Modal, SectionTitle,\n Spinner, StatCard, ToastProvider, tableStyles, usePanel, useToast,\n} from './ui.js'\n\nexport type Translate = (key: string, params?: Record<string, unknown>) => string\nexport interface PanelProps { t: Translate }\n\nconst PANEL_PATH = '/api/stats.panel'\nconst DETAIL_PATH = '/api/stats.detail'\nconst EXPORT_PATH = '/api/stats.export'\nconst CLEAR_PATH = '/api/stats.clear'\n\nfunction ToolModal({ tool, t, onClose }: { tool: string; t: Translate; onClose: () => void }): ReactNode {\n const [detail, setDetail] = useState<ToolDetail | null>(null)\n const [loading, setLoading] = useState(true)\n\n useCallback(() => {\n setLoading(true)\n fetch(`${DETAIL_PATH}?tool=${encodeURIComponent(tool)}`)\n .then(async (r) => { if (r.ok) setDetail(await r.json() as ToolDetail) })\n .finally(() => setLoading(false))\n }, [tool])()\n\n return (\n <Modal title={`${t('tool')}: ${tool}`} onClose={onClose} width={600}>\n {loading ? <div style={{ display: 'flex', justifyContent: 'center', padding: 20 }}><Spinner size={24} /></div>\n : detail === null ? <EmptyState message={t('notFound')} />\n : (\n <div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>\n {detail.errorBreakdown.length > 0 && (\n <Card title={t('errorBreakdown')}>\n {detail.errorBreakdown.map((e, i) => (\n <div key={i} style={{ display: 'flex', gap: 8, alignItems: 'center', marginBottom: 4 }}>\n <Badge color=\"error\">\u00D7{e.count}</Badge>\n <span style={{ fontSize: 12 }}>{e.message}</span>\n </div>\n ))}\n </Card>\n )}\n <Card title={t('recentCalls')}>\n <table style={tableStyles.table}>\n <tbody>\n {detail.recentCalls.slice(0, 15).map((c, i) => (\n <tr key={i}>\n <td style={tableStyles.td}>{c.success ? <Badge color=\"success\">\u2713</Badge> : <Badge color=\"error\">\u2717</Badge>}</td>\n <td style={tableStyles.td}>{c.latencyMs}ms</td>\n <td style={tableStyles.td}>{new Date(c.timestamp).toLocaleTimeString()}</td>\n <td style={{ ...tableStyles.td, fontSize: 10, opacity: 0.6 }}>{c.errorMessage?.slice(0, 60)}</td>\n </tr>\n ))}\n </tbody>\n </table>\n </Card>\n </div>\n )}\n </Modal>\n )\n}\n\nfunction ToolStatsPanelInner({ t }: PanelProps): ReactNode {\n const { payload, error, reload } = usePanel<PanelPayload>(PANEL_PATH)\n const toast = useToast()\n const [toolModal, setToolModal] = useState<string | null>(null)\n const [confirmClear, setConfirmClear] = useState(false)\n\n const handleClear = useCallback(async () => {\n const r = await fetch(CLEAR_PATH, { method: 'POST' })\n if (r.ok) { toast('success', t('cleared')); reload() }\n }, [t, toast, reload])\n\n const header = (\n <header style={{ display: 'flex', alignItems: 'center', gap: 12, marginBottom: 4 }}>\n <strong style={{ fontSize: 15 }}>\uD83D\uDD27 {t('title')}</strong>\n <span style={{ flex: 1 }} />\n <a href={EXPORT_PATH} download><Button variant=\"secondary\" size=\"sm\">{t('export')}</Button></a>\n <Button variant=\"danger\" size=\"sm\" onClick={() => setConfirmClear(true)}>{t('clear')}</Button>\n <Button variant=\"secondary\" size=\"sm\" onClick={reload}>{t('refresh')}</Button>\n </header>\n )\n\n if (error !== null) return <div style={{ maxWidth: 820 }}>{header}<Card><p role=\"alert\" style={{ margin: 0, fontSize: 13, color: 'var(--dsw-alias-state-error-primary)' }}>{t('failed')}: {error}</p></Card></div>\n if (payload === null) return <div style={{ maxWidth: 820 }}>{header}<div style={{ display: 'flex', justifyContent: 'center', padding: 40 }}><Spinner size={28} /></div></div>\n\n const o = payload.overview\n return (\n <div style={{ display: 'flex', flexDirection: 'column', gap: 14, maxWidth: 820 }}>\n {header}\n\n {payload.alerts.length > 0 && (\n <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>\n {payload.alerts.map((a, i) => (\n <Card key={i} style={{ borderColor: a.level === 'error' ? 'var(--dsw-alias-state-error-primary)' : 'var(--dsw-alias-state-warn-primary)' }}>\n <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>\n <Badge color={a.level === 'error' ? 'error' : 'warning'}>{a.level === 'error' ? '\uD83D\uDD34' : '\uD83D\uDFE1'}</Badge>\n <strong>{a.tool}</strong><span style={{ fontSize: 12 }}>{a.message}</span>\n </div>\n </Card>\n ))}\n </div>\n )}\n\n <div style={{ display: 'flex', gap: 10, flexWrap: 'wrap' }}>\n <StatCard value={o.totalCalls} label={t('totalCalls')} />\n <StatCard value={o.totalFailures} label={t('totalFailures')} color={o.totalFailures > 0 ? 'var(--dsw-alias-state-error-primary)' : undefined as unknown as string} />\n <StatCard value={`${Math.round(o.overallFailureRate * 100)}%`} label={t('failRate')} />\n <StatCard value={o.activeSessions} label={t('sessions')} />\n <StatCard value={o.avgLatencyMs} unit=\"ms\" label={t('avgLatency')} />\n </div>\n\n {payload.tools.length === 0 ? <EmptyState icon=\"\uD83D\uDCED\" message={t('empty')} /> : (\n <>\n <SectionTitle icon=\"\uD83D\uDCCA\">{t('tools')}</SectionTitle>\n <Card padding={0}>\n <table style={tableStyles.table}>\n <thead><tr><th style={tableStyles.th}>{t('tool')}</th><th style={tableStyles.th}>{t('calls')}</th><th style={tableStyles.th}>{t('failRate')}</th><th style={tableStyles.th}>p50</th><th style={tableStyles.th}>p95</th></tr></thead>\n <tbody>\n {payload.tools.map((row) => (\n <tr key={row.tool} style={tableStyles.clickRow} onClick={() => setToolModal(row.tool)}>\n <td style={tableStyles.td}><code style={{ fontSize: 11 }}>{row.tool}</code></td>\n <td style={tableStyles.td}>{row.invocations}</td>\n <td style={{ ...tableStyles.td, color: row.failureRate > 0.3 ? 'var(--dsw-alias-state-error-primary)' : undefined }}>{Math.round(row.failureRate * 100)}%</td>\n <td style={tableStyles.td}>{row.p50Latency}ms</td>\n <td style={tableStyles.td}>{row.p95Latency}ms</td>\n </tr>\n ))}\n </tbody>\n </table>\n </Card>\n </>\n )}\n\n {payload.deadTools.length > 0 && (\n <>\n <SectionTitle icon=\"\uD83D\uDC80\">{t('deadTools')}</SectionTitle>\n <Card>{payload.deadTools.map((row) => (\n <div key={row.tool} style={{ marginBottom: 4 }}><code style={{ fontSize: 11 }}>{row.tool}</code> \u2014 {row.sessionsSinceLastUse} {t('sessionsUnused')}</div>\n ))}</Card>\n </>\n )}\n\n {payload.failingTools.length > 0 && (\n <>\n <SectionTitle icon=\"\u26A0\uFE0F\">{t('failingTools')}</SectionTitle>\n <Card>{payload.failingTools.map((row) => (\n <div key={row.tool} style={{ marginBottom: 4 }}><code style={{ fontSize: 11 }}>{row.tool}</code> \u00B7 {Math.round(row.failureRate * 100)}%{row.lastError ? ` \u2014 ${row.lastError.slice(0, 60)}` : ''}</div>\n ))}</Card>\n </>\n )}\n\n {payload.recommendations.length > 0 && (\n <>\n <SectionTitle icon=\"\uD83D\uDCA1\">{t('recommendations')}</SectionTitle>\n <Card>{payload.recommendations.map((row, i) => <div key={i} style={{ marginBottom: 4 }}>{row.message}</div>)}</Card>\n </>\n )}\n\n {toolModal !== null && <ToolModal tool={toolModal} t={t} onClose={() => setToolModal(null)} />}\n {confirmClear && <ConfirmDialog title={t('clear')} message={t('confirmClear')} confirmLabel={t('clear')} danger onConfirm={handleClear} onClose={() => setConfirmClear(false)} />}\n </div>\n )\n}\n\nexport function ToolStatsPanel({ t }: PanelProps): ReactNode {\n return <ToastProvider><ToolStatsPanelInner t={t} /></ToastProvider>\n}\n", "/**\n * Shared UI component kit for dsh plugin panels.\n *\n * Self-contained: imports nothing but React. All styling is inline + CSS custom\n * properties so the components follow the host theme. A single <style> tag injects\n * keyframes the first time any component mounts.\n *\n * @module client/ui\n */\n\nimport {\n createContext,\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useState,\n} from 'react'\nimport type {\n ButtonHTMLAttributes,\n CSSProperties,\n InputHTMLAttributes,\n ReactNode,\n SelectHTMLAttributes,\n} from 'react'\n\n// \u2500\u2500\u2500 Theme tokens \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n/**\n * Host theme tokens.\n *\n * Every name here is a real dsh token, read out of the shipped stylesheets\n * (`@deepseek-ai/dsh-client-ui-theme`), which define them on `body`. An earlier\n * version used invented names \u2014 `--accent`, `--border`, `--bg-primary`,\n * `--text-primary` and friends \u2014 that dsh does not define at all. The\n * hardcoded fallbacks then applied in every theme, so each panel rendered its\n * own fixed palette instead of following the host, and the modal and input\n * surfaces stayed white in dark mode.\n *\n * No fallbacks on purpose: if a name were ever missing, the declaration becomes\n * invalid at computed-value time and the property inherits, which degrades\n * gracefully, whereas a hardcoded fallback bakes in a color that is wrong in one\n * of the two themes. `tests/ui-tokens.test.mjs` enforces the name list.\n */\nconst T = {\n accent: 'var(--dsw-alias-brand-primary)',\n accentHover: 'var(--dsw-alias-button-primary-hover)',\n inverted: 'var(--dsw-alias-label-primary-inverted)',\n error: 'var(--dsw-alias-state-error-primary)',\n success: 'var(--dsw-alias-state-success-primary)',\n warning: 'var(--dsw-alias-state-warn-primary)',\n bg: 'var(--dsw-alias-bg-layer-2)',\n bgHover: 'var(--dsw-alias-interactive-bg-hover)',\n border: 'var(--dsw-alias-border-l2)',\n text: 'var(--dsw-alias-label-primary)',\n muted: 'var(--dsw-alias-label-tertiary)',\n surface: 'var(--dsw-alias-bg-layer-1)',\n mask: 'var(--dsw-alias-bg-mask-1)',\n}\n\n// \u2500\u2500\u2500 Style injection (once) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nlet injected = false\nfunction ensureStyles(): void {\n if (injected || typeof document === 'undefined') return\n injected = true\n const el = document.createElement('style')\n el.textContent = `\n@keyframes dsh-fade-in{from{opacity:0}to{opacity:1}}\n@keyframes dsh-slide-up{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}}\n@keyframes dsh-spin{to{transform:rotate(360deg)}}\n.dsh-modal-overlay{animation:dsh-fade-in .15s ease}\n.dsh-modal-body{animation:dsh-slide-up .2s ease}\n.dsh-toast{animation:dsh-slide-up .2s ease}\n.dsh-spin{animation:dsh-spin .6s linear infinite}\n `\n document.head.appendChild(el)\n}\n\n// \u2500\u2500\u2500 Modal \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nexport function Modal({\n title,\n onClose,\n children,\n footer,\n width = 520,\n}: {\n title: string\n onClose: () => void\n children: ReactNode\n footer?: ReactNode\n width?: number\n}): ReactNode {\n useEffect(() => {\n ensureStyles()\n const handler = (e: KeyboardEvent): void => {\n if (e.key === 'Escape') onClose()\n }\n document.addEventListener('keydown', handler)\n return () => document.removeEventListener('keydown', handler)\n }, [onClose])\n\n return (\n <div\n className=\"dsh-modal-overlay\"\n style={{\n position: 'fixed',\n inset: 0,\n zIndex: 1000,\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n background: T.mask,\n backdropFilter: 'blur(3px)',\n WebkitBackdropFilter: 'blur(3px)',\n }}\n onClick={onClose}\n >\n <div\n className=\"dsh-modal-body\"\n style={{\n background: T.surface,\n borderRadius: 14,\n boxShadow: 'var(--dsw-elevation-prominent)',\n width: `min(92vw, ${width}px)`,\n maxHeight: '85vh',\n display: 'flex',\n flexDirection: 'column',\n overflow: 'hidden',\n }}\n onClick={(e) => e.stopPropagation()}\n >\n <div\n style={{\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'space-between',\n padding: '16px 22px',\n borderBottom: `1px solid ${T.border}`,\n }}\n >\n <h3 style={{ margin: 0, fontSize: 15, fontWeight: 600 }}>{title}</h3>\n <button\n type=\"button\"\n onClick={onClose}\n style={{\n background: 'none',\n border: 'none',\n cursor: 'pointer',\n fontSize: 20,\n color: T.muted,\n padding: '0 4px',\n lineHeight: 1,\n borderRadius: 4,\n }}\n aria-label=\"Close\"\n >\n \u00D7\n </button>\n </div>\n <div style={{ padding: 22, overflow: 'auto', flex: 1 }}>{children}</div>\n {footer !== undefined && (\n <div\n style={{\n padding: '14px 22px',\n borderTop: `1px solid ${T.border}`,\n display: 'flex',\n justifyContent: 'flex-end',\n gap: 10,\n background: T.bg,\n }}\n >\n {footer}\n </div>\n )}\n </div>\n </div>\n )\n}\n\n// \u2500\u2500\u2500 ConfirmDialog \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nexport function ConfirmDialog({\n title,\n message,\n confirmLabel,\n cancelLabel = 'Cancel',\n onConfirm,\n onClose,\n danger,\n}: {\n title: string\n message: string\n confirmLabel: string\n cancelLabel?: string\n onConfirm: () => void\n onClose: () => void\n danger?: boolean\n}): ReactNode {\n return (\n <Modal\n title={title}\n onClose={onClose}\n width={420}\n footer={\n <>\n <Button variant=\"secondary\" onClick={onClose}>\n {cancelLabel}\n </Button>\n <Button\n variant={danger === true ? 'danger' : 'primary'}\n onClick={() => {\n onConfirm()\n onClose()\n }}\n >\n {confirmLabel}\n </Button>\n </>\n }\n >\n <p style={{ margin: 0, fontSize: 14, lineHeight: 1.6 }}>{message}</p>\n </Modal>\n )\n}\n\n// \u2500\u2500\u2500 Button \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nexport function Button({\n variant = 'secondary',\n size = 'md',\n children,\n style,\n ...rest\n}: {\n variant?: 'primary' | 'secondary' | 'danger' | 'ghost'\n size?: 'sm' | 'md'\n children: ReactNode\n style?: CSSProperties\n} & ButtonHTMLAttributes<HTMLButtonElement>): ReactNode {\n const base: CSSProperties = {\n cursor: 'pointer',\n borderRadius: 7,\n fontWeight: 500,\n transition: 'all 0.15s ease',\n border: 'none',\n display: 'inline-flex',\n alignItems: 'center',\n gap: 4,\n fontSize: size === 'sm' ? 12 : 13,\n padding: size === 'sm' ? '5px 12px' : '8px 18px',\n fontFamily: 'inherit',\n }\n const variants: Record<string, CSSProperties> = {\n primary: { background: T.accent, color: T.inverted },\n secondary: {\n background: T.bg,\n color: T.text,\n border: `1px solid ${T.border}`,\n },\n danger: {\n background: 'transparent',\n color: T.error,\n border: `1px solid ${T.error}`,\n },\n ghost: { background: 'transparent', color: T.muted, border: 'none' },\n }\n return (\n <button type=\"button\" style={{ ...base, ...variants[variant], ...style }} {...rest}>\n {children}\n </button>\n )\n}\n\n// \u2500\u2500\u2500 Card \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nexport function Card({\n title,\n icon,\n children,\n actions,\n style,\n padding = 14,\n}: {\n title?: string\n icon?: string\n children: ReactNode\n actions?: ReactNode\n style?: CSSProperties\n padding?: number\n}): ReactNode {\n return (\n <div\n style={{\n borderRadius: 10,\n border: `1px solid ${T.border}`,\n background: T.bg,\n overflow: 'hidden',\n ...style,\n }}\n >\n {(title !== undefined || actions !== undefined) && (\n <div\n style={{\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'space-between',\n padding: '12px 16px',\n borderBottom: title !== undefined ? `1px solid ${T.border}` : 'none',\n }}\n >\n <span style={{ fontSize: 13, fontWeight: 600, display: 'flex', alignItems: 'center', gap: 6 }}>\n {icon !== undefined && <span>{icon}</span>}\n {title}\n </span>\n {actions}\n </div>\n )}\n <div style={{ padding }}>{children}</div>\n </div>\n )\n}\n\n// \u2500\u2500\u2500 StatCard \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nexport function StatCard({\n value,\n label,\n color,\n unit,\n}: {\n value: string | number\n label: string\n color?: string\n unit?: string\n}): ReactNode {\n return (\n <div\n style={{\n flex: 1,\n minWidth: 90,\n textAlign: 'center',\n padding: '14px 10px',\n borderRadius: 10,\n border: `1px solid ${T.border}`,\n background: T.bg,\n }}\n >\n <div style={{ fontSize: 24, fontWeight: 700, color: color ?? T.text, lineHeight: 1.2 }}>\n {value}\n {unit !== undefined && (\n <span style={{ fontSize: 13, fontWeight: 500, opacity: 0.7, marginLeft: 2 }}>{unit}</span>\n )}\n </div>\n <div style={{ fontSize: 11, color: T.muted, marginTop: 4 }}>{label}</div>\n </div>\n )\n}\n\n// \u2500\u2500\u2500 Badge \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nexport function Badge({\n children,\n color = 'default',\n}: {\n children: ReactNode\n color?: 'default' | 'success' | 'error' | 'warning' | 'info'\n}): ReactNode {\n const colors: Record<string, { bg: string; fg: string }> = {\n default: { bg: T.bgHover, fg: T.muted },\n success: { bg: 'var(--dsw-alias-state-success-tertiary)', fg: T.success },\n error: { bg: 'var(--dsw-alias-state-error-secondary)', fg: T.error },\n warning: { bg: 'var(--dsw-alias-state-warn-tertiary)', fg: T.warning },\n info: { bg: 'var(--dsw-alias-state-business-tertiary)', fg: T.accent },\n }\n const c = colors[color]\n return (\n <span\n style={{\n display: 'inline-block',\n padding: '2px 9px',\n borderRadius: 10,\n fontSize: 11,\n fontWeight: 600,\n background: c.bg,\n color: c.fg,\n }}\n >\n {children}\n </span>\n )\n}\n\n// \u2500\u2500\u2500 Input \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nexport function Input({\n style,\n ...rest\n}: { style?: CSSProperties } & InputHTMLAttributes<HTMLInputElement>): ReactNode {\n return (\n <input\n {...rest}\n style={{\n fontSize: 13,\n padding: '8px 12px',\n borderRadius: 7,\n border: `1px solid ${T.border}`,\n background: T.surface,\n color: T.text,\n width: '100%',\n boxSizing: 'border-box',\n fontFamily: 'inherit',\n outline: 'none',\n transition: 'border-color 0.15s',\n ...style,\n }}\n />\n )\n}\n\n// \u2500\u2500\u2500 Select \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nexport function Select({\n style,\n children,\n ...rest\n}: {\n style?: CSSProperties\n children: ReactNode\n} & SelectHTMLAttributes<HTMLSelectElement>): ReactNode {\n return (\n <select\n {...rest}\n style={{\n fontSize: 13,\n padding: '8px 12px',\n borderRadius: 7,\n border: `1px solid ${T.border}`,\n background: T.surface,\n color: T.text,\n cursor: 'pointer',\n fontFamily: 'inherit',\n outline: 'none',\n ...style,\n }}\n >\n {children}\n </select>\n )\n}\n\n// \u2500\u2500\u2500 Textarea \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nexport function Textarea({\n style,\n ...rest\n}: { style?: CSSProperties } & React.TextareaHTMLAttributes<HTMLTextAreaElement>): ReactNode {\n return (\n <textarea\n {...rest}\n style={{\n fontSize: 13,\n padding: '8px 12px',\n borderRadius: 7,\n border: `1px solid ${T.border}`,\n background: T.surface,\n color: T.text,\n width: '100%',\n boxSizing: 'border-box',\n fontFamily: 'inherit',\n outline: 'none',\n resize: 'vertical',\n lineHeight: 1.5,\n ...style,\n }}\n />\n )\n}\n\n// \u2500\u2500\u2500 Toast \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\ninterface ToastItem {\n id: number\n type: 'success' | 'error' | 'info'\n message: string\n}\n\nconst ToastContext = createContext<(type: ToastItem['type'], message: string) => void>(\n () => {},\n)\n\nexport function ToastProvider({ children }: { children: ReactNode }): ReactNode {\n const [toasts, setToasts] = useState<ToastItem[]>([])\n const show = useCallback((type: ToastItem['type'], message: string) => {\n const id = Date.now() + Math.random()\n setToasts((t) => [...t, { id, type, message }])\n setTimeout(() => setToasts((t) => t.filter((x) => x.id !== id)), 3500)\n }, [])\n return (\n <ToastContext.Provider value={show}>\n {children}\n {toasts.length > 0 && (\n <div\n style={{\n position: 'fixed',\n bottom: 24,\n right: 24,\n zIndex: 2000,\n display: 'flex',\n flexDirection: 'column',\n gap: 8,\n }}\n >\n {toasts.map((t) => (\n <div\n key={t.id}\n className=\"dsh-toast\"\n style={{\n padding: '12px 18px',\n borderRadius: 8,\n fontSize: 13,\n fontWeight: 500,\n boxShadow: 'var(--dsw-elevation-panel)',\n color: T.inverted,\n background:\n t.type === 'success'\n ? T.success\n : t.type === 'error'\n ? T.error\n : T.accent,\n maxWidth: 360,\n }}\n >\n {t.message}\n </div>\n ))}\n </div>\n )}\n </ToastContext.Provider>\n )\n}\n\nexport function useToast(): (type: ToastItem['type'], message: string) => void {\n return useContext(ToastContext)\n}\n\n// \u2500\u2500\u2500 Spinner \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nexport function Spinner({ size = 16 }: { size?: number }): ReactNode {\n ensureStyles()\n return (\n <span\n className=\"dsh-spin\"\n style={{\n display: 'inline-block',\n width: size,\n height: size,\n border: `2px solid ${T.border}`,\n borderTopColor: T.accent,\n borderRadius: '50%',\n }}\n />\n )\n}\n\n// \u2500\u2500\u2500 EmptyState \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nexport function EmptyState({\n icon,\n message,\n}: {\n icon?: string\n message: string\n}): ReactNode {\n return (\n <div\n style={{\n textAlign: 'center',\n padding: '36px 16px',\n color: T.muted,\n }}\n >\n {icon !== undefined && (\n <div style={{ fontSize: 36, marginBottom: 10, opacity: 0.4 }}>{icon}</div>\n )}\n <div style={{ fontSize: 13 }}>{message}</div>\n </div>\n )\n}\n\n// \u2500\u2500\u2500 SectionTitle \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nexport function SectionTitle({\n icon,\n children,\n actions,\n}: {\n icon?: string\n children: ReactNode\n actions?: ReactNode\n}): ReactNode {\n return (\n <div\n style={{\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'space-between',\n marginTop: 6,\n marginBottom: 8,\n }}\n >\n <span\n style={{\n fontSize: 14,\n fontWeight: 600,\n display: 'flex',\n alignItems: 'center',\n gap: 6,\n }}\n >\n {icon !== undefined && <span>{icon}</span>}\n {children}\n </span>\n {actions}\n </div>\n )\n}\n\n// \u2500\u2500\u2500 Table helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nexport const tableStyles = {\n table: {\n borderCollapse: 'collapse' as const,\n width: '100%' as const,\n },\n th: {\n textAlign: 'left' as const,\n padding: '8px 12px 8px 0',\n fontWeight: 600 as const,\n fontSize: 12 as const,\n color: T.muted,\n borderBottom: `1px solid ${T.border}`,\n },\n td: {\n padding: '8px 12px 8px 0',\n fontSize: 13 as const,\n borderBottom: `1px solid ${T.border}`,\n },\n clickRow: { cursor: 'pointer' as const },\n}\n\n// \u2500\u2500\u2500 CodeBlock \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nexport function CodeBlock({\n children,\n maxHeight = 200,\n style,\n}: {\n children: string\n maxHeight?: number\n style?: CSSProperties\n}): ReactNode {\n return (\n <pre\n style={{\n whiteSpace: 'pre-wrap',\n wordBreak: 'break-word',\n fontSize: 12,\n maxHeight,\n overflow: 'auto',\n padding: 10,\n borderRadius: 7,\n background: T.bg,\n border: `1px solid ${T.border}`,\n margin: 0,\n ...style,\n }}\n >\n {children}\n </pre>\n )\n}\n\n// \u2500\u2500\u2500 ProgressBar \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nexport function ProgressBar({\n pct,\n color,\n height = 18,\n}: {\n pct: number\n color?: string\n height?: number\n}): ReactNode {\n return (\n <div\n style={{\n width: '100%',\n height,\n background: T.bgHover,\n borderRadius: 5,\n overflow: 'hidden',\n position: 'relative',\n }}\n >\n <div\n style={{\n width: `${Math.min(pct, 100)}%`,\n height: '100%',\n background: color ?? T.accent,\n borderRadius: 5,\n transition: 'width 0.3s ease',\n }}\n />\n </div>\n )\n}\n\n// \u2500\u2500\u2500 Field label \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nexport function Field({\n label,\n children,\n hint,\n}: {\n label: string\n children: ReactNode\n hint?: string\n}): ReactNode {\n return (\n <div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}>\n <label style={{ fontSize: 12, fontWeight: 600, color: T.text }}>{label}</label>\n {children}\n {hint !== undefined && (\n <span style={{ fontSize: 11, color: T.muted }}>{hint}</span>\n )}\n </div>\n )\n}\n\n// \u2500\u2500\u2500 usePanel hook (shared fetch logic) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nexport function usePanel<P>(path: string): {\n payload: P | null\n error: string | null\n reload: () => void\n} {\n const [payload, setPayload] = useState<P | null>(null)\n const [error, setError] = useState<string | null>(null)\n const [tick, setTick] = useState(0)\n const reload = useCallback(() => setTick((v) => v + 1), [])\n\n useEffect(() => {\n const c = new AbortController()\n setError(null)\n fetch(path, { signal: c.signal })\n .then(async (r) => {\n if (!r.ok) throw new Error(String(r.status))\n return r.json() as Promise<P>\n })\n .then((p) => {\n if (!c.signal.aborted) setPayload(p)\n })\n .catch((e: unknown) => {\n if (!c.signal.aborted) setPayload(null), setError(e instanceof Error ? e.message : String(e))\n })\n return () => c.abort()\n }, [tick, path])\n\n return useMemo(() => ({ payload, error, reload }), [payload, error, reload])\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["import_react", "import_jsx_runtime"]
|
|
7
7
|
}
|