@tangle-network/sandbox-ui 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/README.md +68 -0
  2. package/dist/auth.d.ts +57 -0
  3. package/dist/auth.js +14 -0
  4. package/dist/branding-DCi5VEik.d.ts +13 -0
  5. package/dist/button-BidTtuRS.d.ts +15 -0
  6. package/dist/chat.d.ts +121 -0
  7. package/dist/chat.js +25 -0
  8. package/dist/chunk-2UHPE5T7.js +201 -0
  9. package/dist/chunk-4EIWPJMJ.js +545 -0
  10. package/dist/chunk-6MQIDUPA.js +502 -0
  11. package/dist/chunk-B26TQ7SA.js +47 -0
  12. package/dist/chunk-E6FS7R4X.js +109 -0
  13. package/dist/chunk-GRYHFH5O.js +110 -0
  14. package/dist/chunk-HMND7JPA.js +868 -0
  15. package/dist/chunk-HRMUF35V.js +19 -0
  16. package/dist/chunk-HYEAX3DC.js +822 -0
  17. package/dist/chunk-KMXV7DDX.js +174 -0
  18. package/dist/chunk-KYY2X6LY.js +318 -0
  19. package/dist/chunk-L6ZDH5F4.js +334 -0
  20. package/dist/chunk-LTFK464G.js +103 -0
  21. package/dist/chunk-M34OA6PQ.js +233 -0
  22. package/dist/chunk-M6VLC32S.js +219 -0
  23. package/dist/chunk-MCGKDCOR.js +173 -0
  24. package/dist/chunk-NI2EI43H.js +294 -0
  25. package/dist/chunk-OU4TRNQZ.js +173 -0
  26. package/dist/chunk-QD4QE5P5.js +40 -0
  27. package/dist/chunk-QSQBDR3N.js +180 -0
  28. package/dist/chunk-RQHJBTEU.js +10 -0
  29. package/dist/chunk-U62G5TS7.js +472 -0
  30. package/dist/chunk-ZOL2TR5M.js +475 -0
  31. package/dist/dashboard.d.ts +111 -0
  32. package/dist/dashboard.js +26 -0
  33. package/dist/editor.d.ts +196 -0
  34. package/dist/editor.js +713 -0
  35. package/dist/expanded-tool-detail-OkXGqTHe.d.ts +52 -0
  36. package/dist/files.d.ts +66 -0
  37. package/dist/files.js +11 -0
  38. package/dist/hooks.d.ts +22 -0
  39. package/dist/hooks.js +107 -0
  40. package/dist/index.d.ts +107 -0
  41. package/dist/index.js +551 -0
  42. package/dist/markdown.d.ts +55 -0
  43. package/dist/markdown.js +17 -0
  44. package/dist/pages.d.ts +89 -0
  45. package/dist/pages.js +1181 -0
  46. package/dist/parts-CyGkM6Fp.d.ts +50 -0
  47. package/dist/primitives.d.ts +189 -0
  48. package/dist/primitives.js +161 -0
  49. package/dist/run-CtFZ6s-D.d.ts +41 -0
  50. package/dist/run.d.ts +14 -0
  51. package/dist/run.js +29 -0
  52. package/dist/sidecar-CFU2W9j1.d.ts +8 -0
  53. package/dist/stores.d.ts +28 -0
  54. package/dist/stores.js +49 -0
  55. package/dist/terminal.d.ts +44 -0
  56. package/dist/terminal.js +160 -0
  57. package/dist/tool-call-feed-D5Ume-Pt.d.ts +66 -0
  58. package/dist/tool-display-BvsVW_Ur.d.ts +32 -0
  59. package/dist/types.d.ts +6 -0
  60. package/dist/types.js +0 -0
  61. package/dist/usage-chart-DINgSVL5.d.ts +60 -0
  62. package/dist/use-sidecar-auth-Bb0-w3lX.d.ts +339 -0
  63. package/dist/utils.d.ts +28 -0
  64. package/dist/utils.js +28 -0
  65. package/dist/workspace.d.ts +113 -0
  66. package/dist/workspace.js +15 -0
  67. package/package.json +174 -0
  68. package/src/styles/globals.css +230 -0
  69. package/src/styles/tokens.css +73 -0
  70. package/tailwind.config.cjs +99 -0
@@ -0,0 +1,174 @@
1
+ import {
2
+ getToolCategory
3
+ } from "./chunk-QSQBDR3N.js";
4
+
5
+ // src/hooks/use-run-groups.ts
6
+ import { useMemo } from "react";
7
+ function computeRunStats(messages, partMap) {
8
+ const stats = {
9
+ toolCount: 0,
10
+ messageCount: messages.length,
11
+ thinkingDurationMs: 0,
12
+ textPartCount: 0,
13
+ toolCategories: /* @__PURE__ */ new Set()
14
+ };
15
+ for (const msg of messages) {
16
+ const parts = partMap[msg.id] ?? [];
17
+ for (const part of parts) {
18
+ switch (part.type) {
19
+ case "tool":
20
+ stats.toolCount++;
21
+ stats.toolCategories.add(getToolCategory(part.tool));
22
+ break;
23
+ case "text":
24
+ if (!part.synthetic) stats.textPartCount++;
25
+ break;
26
+ case "reasoning": {
27
+ const start = part.time?.start;
28
+ const end = part.time?.end;
29
+ if (start && end) stats.thinkingDurationMs += end - start;
30
+ break;
31
+ }
32
+ }
33
+ }
34
+ }
35
+ return stats;
36
+ }
37
+ function getLastTextContent(messages, partMap) {
38
+ for (let i = messages.length - 1; i >= 0; i--) {
39
+ const parts = partMap[messages[i].id] ?? [];
40
+ for (let j = parts.length - 1; j >= 0; j--) {
41
+ const part = parts[j];
42
+ if (part.type === "text" && !part.synthetic && part.text.trim()) {
43
+ return part.text.trim();
44
+ }
45
+ }
46
+ }
47
+ return null;
48
+ }
49
+ function useRunGroups({
50
+ messages,
51
+ partMap,
52
+ isStreaming
53
+ }) {
54
+ return useMemo(() => {
55
+ const groups = [];
56
+ let currentRunMessages = [];
57
+ function flushRun(streaming) {
58
+ if (currentRunMessages.length === 0) return;
59
+ const msgs = [...currentRunMessages];
60
+ const stats = computeRunStats(msgs, partMap);
61
+ const summaryText = getLastTextContent(msgs, partMap);
62
+ const isComplete = !streaming;
63
+ groups.push({
64
+ type: "run",
65
+ run: {
66
+ id: msgs[0].id,
67
+ messages: msgs,
68
+ isComplete,
69
+ isStreaming: streaming,
70
+ stats,
71
+ summaryText,
72
+ finalTextPart: null
73
+ }
74
+ });
75
+ currentRunMessages = [];
76
+ }
77
+ for (let i = 0; i < messages.length; i++) {
78
+ const msg = messages[i];
79
+ if (msg.role === "user") {
80
+ flushRun(false);
81
+ groups.push({ type: "user", message: msg });
82
+ } else {
83
+ currentRunMessages.push(msg);
84
+ }
85
+ }
86
+ if (currentRunMessages.length > 0) {
87
+ flushRun(isStreaming);
88
+ }
89
+ return groups;
90
+ }, [messages, partMap, isStreaming]);
91
+ }
92
+
93
+ // src/hooks/use-run-collapse-state.ts
94
+ import { useCallback, useEffect, useRef, useState } from "react";
95
+ var AUTO_COLLAPSE_DELAY = 1e3;
96
+ function useRunCollapseState(runs) {
97
+ const [collapsedMap, setCollapsedMap] = useState({});
98
+ const userOverrides = useRef(/* @__PURE__ */ new Set());
99
+ const completedRuns = useRef(/* @__PURE__ */ new Set());
100
+ useEffect(() => {
101
+ const timers = [];
102
+ for (const run of runs) {
103
+ if (run.isComplete && !completedRuns.current.has(run.id)) {
104
+ completedRuns.current.add(run.id);
105
+ if (userOverrides.current.has(run.id)) continue;
106
+ const timer = setTimeout(() => {
107
+ setCollapsedMap((prev) => ({ ...prev, [run.id]: true }));
108
+ }, AUTO_COLLAPSE_DELAY);
109
+ timers.push(timer);
110
+ }
111
+ }
112
+ return () => timers.forEach(clearTimeout);
113
+ }, [runs]);
114
+ const isCollapsed = useCallback(
115
+ (runId) => {
116
+ return collapsedMap[runId] ?? false;
117
+ },
118
+ [collapsedMap]
119
+ );
120
+ const toggleCollapse = useCallback((runId) => {
121
+ userOverrides.current.add(runId);
122
+ setCollapsedMap((prev) => ({ ...prev, [runId]: !prev[runId] }));
123
+ }, []);
124
+ return { isCollapsed, toggleCollapse };
125
+ }
126
+
127
+ // src/hooks/use-auto-scroll.ts
128
+ import {
129
+ useCallback as useCallback2,
130
+ useEffect as useEffect2,
131
+ useRef as useRef2,
132
+ useState as useState2
133
+ } from "react";
134
+ var BOTTOM_THRESHOLD = 40;
135
+ function useAutoScroll(containerRef, deps = []) {
136
+ const [isAtBottom, setIsAtBottom] = useState2(true);
137
+ const userScrolledUp = useRef2(false);
138
+ const checkBottom = useCallback2(() => {
139
+ const el = containerRef.current;
140
+ if (!el) return true;
141
+ return el.scrollHeight - el.scrollTop - el.clientHeight < BOTTOM_THRESHOLD;
142
+ }, [containerRef]);
143
+ useEffect2(() => {
144
+ const el = containerRef.current;
145
+ if (!el) return;
146
+ const onScroll = () => {
147
+ const atBottom = checkBottom();
148
+ setIsAtBottom(atBottom);
149
+ userScrolledUp.current = !atBottom;
150
+ };
151
+ el.addEventListener("scroll", onScroll, { passive: true });
152
+ return () => el.removeEventListener("scroll", onScroll);
153
+ }, [containerRef, checkBottom]);
154
+ useEffect2(() => {
155
+ if (userScrolledUp.current) return;
156
+ const el = containerRef.current;
157
+ if (!el) return;
158
+ el.scrollTop = el.scrollHeight;
159
+ }, deps);
160
+ const scrollToBottom = useCallback2(() => {
161
+ const el = containerRef.current;
162
+ if (!el) return;
163
+ userScrolledUp.current = false;
164
+ el.scrollTo({ top: el.scrollHeight, behavior: "smooth" });
165
+ setIsAtBottom(true);
166
+ }, [containerRef]);
167
+ return { isAtBottom, scrollToBottom };
168
+ }
169
+
170
+ export {
171
+ useRunGroups,
172
+ useRunCollapseState,
173
+ useAutoScroll
174
+ };
@@ -0,0 +1,318 @@
1
+ import {
2
+ cn
3
+ } from "./chunk-RQHJBTEU.js";
4
+
5
+ // src/workspace/workspace-layout.tsx
6
+ import { useState } from "react";
7
+ import { PanelLeftClose, PanelLeftOpen, PanelRightClose, PanelRightOpen } from "lucide-react";
8
+ import { jsx, jsxs } from "react/jsx-runtime";
9
+ function WorkspaceLayout({
10
+ left,
11
+ leftHeader,
12
+ center,
13
+ centerHeader,
14
+ centerFooter,
15
+ right,
16
+ rightHeader,
17
+ bottom,
18
+ defaultLeftOpen = true,
19
+ defaultRightOpen = false,
20
+ defaultBottomOpen = false,
21
+ className
22
+ }) {
23
+ const [leftOpen, setLeftOpen] = useState(defaultLeftOpen);
24
+ const [rightOpen, setRightOpen] = useState(defaultRightOpen);
25
+ const [bottomOpen, setBottomOpen] = useState(defaultBottomOpen);
26
+ return /* @__PURE__ */ jsx("div", { className: cn("flex flex-col h-screen bg-[var(--bg-root)] text-[var(--text-primary)] font-[var(--font-sans)]", className), children: /* @__PURE__ */ jsxs("div", { className: "flex flex-1 min-h-0", children: [
27
+ left && leftOpen && /* @__PURE__ */ jsxs("aside", { className: "w-64 shrink-0 border-r border-[var(--border-subtle)] flex flex-col bg-[var(--bg-dark)]", children: [
28
+ leftHeader && /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between px-3 py-2 border-b border-[var(--border-subtle)] shrink-0", children: [
29
+ leftHeader,
30
+ /* @__PURE__ */ jsx(
31
+ "button",
32
+ {
33
+ onClick: () => setLeftOpen(false),
34
+ className: "p-1 rounded-[var(--radius-sm)] hover:bg-[var(--bg-hover)] text-[var(--text-muted)]",
35
+ children: /* @__PURE__ */ jsx(PanelLeftClose, { className: "h-4 w-4" })
36
+ }
37
+ )
38
+ ] }),
39
+ /* @__PURE__ */ jsx("div", { className: "flex-1 overflow-auto py-1", children: left })
40
+ ] }),
41
+ /* @__PURE__ */ jsxs("main", { className: "flex-1 flex flex-col min-w-0", children: [
42
+ (centerHeader || left) && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 px-3 py-2 border-b border-[var(--border-subtle)] shrink-0 bg-[var(--bg-dark)]", children: [
43
+ left && !leftOpen && /* @__PURE__ */ jsx(
44
+ "button",
45
+ {
46
+ onClick: () => setLeftOpen(true),
47
+ className: "p-1 rounded-[var(--radius-sm)] hover:bg-[var(--bg-hover)] text-[var(--text-muted)]",
48
+ children: /* @__PURE__ */ jsx(PanelLeftOpen, { className: "h-4 w-4" })
49
+ }
50
+ ),
51
+ /* @__PURE__ */ jsx("div", { className: "flex-1 min-w-0", children: centerHeader }),
52
+ right && !rightOpen && /* @__PURE__ */ jsx(
53
+ "button",
54
+ {
55
+ onClick: () => setRightOpen(true),
56
+ className: "p-1 rounded-[var(--radius-sm)] hover:bg-[var(--bg-hover)] text-[var(--text-muted)]",
57
+ children: /* @__PURE__ */ jsx(PanelRightOpen, { className: "h-4 w-4" })
58
+ }
59
+ )
60
+ ] }),
61
+ /* @__PURE__ */ jsx("div", { className: "flex-1 overflow-auto", children: center }),
62
+ bottom && bottomOpen && /* @__PURE__ */ jsx("div", { className: "border-t border-[var(--border-subtle)] bg-[var(--bg-card)] max-h-48 overflow-auto", children: bottom }),
63
+ centerFooter && /* @__PURE__ */ jsx("div", { className: "border-t border-[var(--border-subtle)] shrink-0 bg-[var(--bg-dark)]", children: centerFooter })
64
+ ] }),
65
+ right && rightOpen && /* @__PURE__ */ jsxs("aside", { className: "w-[480px] shrink-0 border-l border-[var(--border-subtle)] flex flex-col bg-[var(--bg-dark)]", children: [
66
+ rightHeader !== void 0 ? /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between px-3 py-2 border-b border-[var(--border-subtle)] shrink-0", children: [
67
+ rightHeader,
68
+ /* @__PURE__ */ jsx(
69
+ "button",
70
+ {
71
+ onClick: () => setRightOpen(false),
72
+ className: "p-1 rounded-[var(--radius-sm)] hover:bg-[var(--bg-hover)] text-[var(--text-muted)]",
73
+ children: /* @__PURE__ */ jsx(PanelRightClose, { className: "h-4 w-4" })
74
+ }
75
+ )
76
+ ] }) : /* @__PURE__ */ jsx("div", { className: "flex justify-end px-3 py-2 border-b border-[var(--border-subtle)] shrink-0", children: /* @__PURE__ */ jsx(
77
+ "button",
78
+ {
79
+ onClick: () => setRightOpen(false),
80
+ className: "p-1 rounded-[var(--radius-sm)] hover:bg-[var(--bg-hover)] text-[var(--text-muted)]",
81
+ children: /* @__PURE__ */ jsx(PanelRightClose, { className: "h-4 w-4" })
82
+ }
83
+ ) }),
84
+ /* @__PURE__ */ jsx("div", { className: "flex-1 overflow-auto", children: right })
85
+ ] })
86
+ ] }) });
87
+ }
88
+
89
+ // src/workspace/status-bar.tsx
90
+ import { Zap, FileText, X } from "lucide-react";
91
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
92
+ var STATUS_LABELS = {
93
+ connected: { label: "", color: "bg-[var(--code-success)]" },
94
+ connecting: { label: "Connecting...", color: "bg-[var(--code-number)]" },
95
+ disconnected: { label: "Disconnected", color: "bg-[var(--code-error)]" },
96
+ provisioning: { label: "Provisioning...", color: "bg-[var(--code-number)]" }
97
+ };
98
+ function StatusBar({
99
+ modelLabel,
100
+ onModelClick,
101
+ credits,
102
+ contextBadges = [],
103
+ onRemoveBadge,
104
+ status = "connected",
105
+ className
106
+ }) {
107
+ const statusInfo = STATUS_LABELS[status];
108
+ return /* @__PURE__ */ jsxs2(
109
+ "div",
110
+ {
111
+ className: cn(
112
+ "flex items-center gap-3 px-4 py-1.5 border-t border-[var(--border-subtle)] bg-[var(--bg-dark)] text-xs",
113
+ className
114
+ ),
115
+ children: [
116
+ modelLabel && /* @__PURE__ */ jsxs2(
117
+ "button",
118
+ {
119
+ onClick: onModelClick,
120
+ className: "inline-flex items-center gap-1.5 px-2 py-0.5 rounded-[var(--radius-full)] border border-[var(--border-subtle)] text-[var(--text-muted)] hover:border-[var(--border-accent)] hover:text-[var(--text-secondary)] transition-colors",
121
+ children: [
122
+ /* @__PURE__ */ jsx2("span", { className: cn("w-1.5 h-1.5 rounded-full", statusInfo.color) }),
123
+ modelLabel
124
+ ]
125
+ }
126
+ ),
127
+ statusInfo.label && /* @__PURE__ */ jsx2("span", { className: "text-[var(--text-muted)]", children: statusInfo.label }),
128
+ contextBadges.map((badge) => /* @__PURE__ */ jsxs2(
129
+ "span",
130
+ {
131
+ className: "inline-flex items-center gap-1 px-2 py-0.5 rounded-[var(--radius-full)] border border-[var(--border-accent)] text-[var(--text-secondary)] bg-[var(--border-accent)]/5",
132
+ children: [
133
+ /* @__PURE__ */ jsx2(FileText, { className: "h-3 w-3" }),
134
+ badge.label,
135
+ badge.count !== void 0 && /* @__PURE__ */ jsx2("span", { className: "text-[var(--text-muted)]", children: badge.count }),
136
+ onRemoveBadge && /* @__PURE__ */ jsx2(
137
+ "button",
138
+ {
139
+ onClick: () => onRemoveBadge(badge.id),
140
+ className: "hover:text-[var(--text-primary)] transition-colors",
141
+ children: /* @__PURE__ */ jsx2(X, { className: "h-2.5 w-2.5" })
142
+ }
143
+ )
144
+ ]
145
+ },
146
+ badge.id
147
+ )),
148
+ /* @__PURE__ */ jsx2("div", { className: "flex-1" }),
149
+ credits !== void 0 && /* @__PURE__ */ jsxs2("span", { className: "inline-flex items-center gap-1 text-[var(--text-muted)]", children: [
150
+ /* @__PURE__ */ jsx2(Zap, { className: "h-3 w-3" }),
151
+ credits.toLocaleString(),
152
+ " credits"
153
+ ] })
154
+ ]
155
+ }
156
+ );
157
+ }
158
+
159
+ // src/workspace/status-banner.tsx
160
+ import { Loader2, AlertCircle, CheckCircle, Wifi } from "lucide-react";
161
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
162
+ var BANNER_STYLES = {
163
+ provisioning: { bg: "bg-[var(--brand-cool)]/5", border: "border-[var(--brand-cool)]/20", icon: Loader2 },
164
+ connecting: { bg: "bg-[var(--code-number)]/5", border: "border-[var(--code-number)]/20", icon: Wifi },
165
+ error: { bg: "bg-[var(--code-error)]/5", border: "border-[var(--code-error)]/20", icon: AlertCircle },
166
+ success: { bg: "bg-[var(--code-success)]/5", border: "border-[var(--code-success)]/20", icon: CheckCircle },
167
+ info: { bg: "bg-[var(--bg-elevated)]", border: "border-[var(--border-default)]", icon: AlertCircle }
168
+ };
169
+ function StatusBanner({ type, message, detail, onDismiss, className }) {
170
+ const style = BANNER_STYLES[type];
171
+ const Icon = style.icon;
172
+ const isAnimated = type === "provisioning" || type === "connecting";
173
+ return /* @__PURE__ */ jsxs3("div", { className: cn("flex items-center gap-3 px-4 py-2 border-b text-sm", style.bg, style.border, className), children: [
174
+ /* @__PURE__ */ jsx3(Icon, { className: cn("h-4 w-4 shrink-0", isAnimated && "animate-spin") }),
175
+ /* @__PURE__ */ jsx3("span", { className: "text-[var(--text-secondary)]", children: message }),
176
+ detail && /* @__PURE__ */ jsx3("span", { className: "text-[var(--text-muted)] text-xs", children: detail }),
177
+ onDismiss && /* @__PURE__ */ jsx3("button", { onClick: onDismiss, className: "ml-auto text-[var(--text-muted)] hover:text-[var(--text-secondary)] text-xs", children: "Dismiss" })
178
+ ] });
179
+ }
180
+
181
+ // src/workspace/audit-results.tsx
182
+ import { useState as useState2 } from "react";
183
+ import { CheckCircle as CheckCircle2, XCircle, ChevronRight, Shield } from "lucide-react";
184
+ import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
185
+ function AuditResults({ forms, crossFormChecks = [], overallScore, className }) {
186
+ const totalPassed = forms.reduce((s, f) => s + f.passed, 0);
187
+ const totalChecks = forms.reduce((s, f) => s + f.passed + f.failed, 0);
188
+ return /* @__PURE__ */ jsxs4("div", { className: cn("space-y-3 p-3", className), children: [
189
+ /* @__PURE__ */ jsxs4("div", { className: "flex items-center gap-3 px-3 py-2 rounded-[var(--radius-md)] bg-[var(--bg-input)] border border-[var(--border-subtle)]", children: [
190
+ /* @__PURE__ */ jsx4(Shield, { className: cn("h-5 w-5", totalChecks === totalPassed ? "text-[var(--code-success)]" : "text-[var(--code-number)]") }),
191
+ /* @__PURE__ */ jsxs4("div", { children: [
192
+ /* @__PURE__ */ jsxs4("div", { className: "text-sm font-semibold text-[var(--text-primary)]", children: [
193
+ totalPassed,
194
+ "/",
195
+ totalChecks,
196
+ " checks passed"
197
+ ] }),
198
+ overallScore !== void 0 && /* @__PURE__ */ jsxs4("div", { className: "text-xs text-[var(--text-muted)]", children: [
199
+ "Score: ",
200
+ overallScore,
201
+ "/100"
202
+ ] })
203
+ ] })
204
+ ] }),
205
+ forms.map((form) => /* @__PURE__ */ jsx4(FormAuditCard, { form }, form.formId)),
206
+ crossFormChecks.length > 0 && /* @__PURE__ */ jsxs4("div", { children: [
207
+ /* @__PURE__ */ jsx4("div", { className: "text-xs font-semibold text-[var(--text-muted)] uppercase tracking-wider mb-1", children: "Cross-Form Checks" }),
208
+ crossFormChecks.map((check, i) => /* @__PURE__ */ jsx4(CheckRow, { check }, i))
209
+ ] })
210
+ ] });
211
+ }
212
+ function FormAuditCard({ form }) {
213
+ const [expanded, setExpanded] = useState2(form.failed > 0);
214
+ const allPassed = form.failed === 0 && form.found;
215
+ return /* @__PURE__ */ jsxs4("div", { className: "rounded-[var(--radius-md)] border border-[var(--border-subtle)] overflow-hidden", children: [
216
+ /* @__PURE__ */ jsxs4(
217
+ "button",
218
+ {
219
+ onClick: () => setExpanded(!expanded),
220
+ className: "flex items-center gap-2 w-full px-3 py-2 text-left hover:bg-[var(--bg-hover)] transition-colors",
221
+ children: [
222
+ allPassed ? /* @__PURE__ */ jsx4(CheckCircle2, { className: "h-4 w-4 text-[var(--code-success)] shrink-0" }) : !form.found ? /* @__PURE__ */ jsx4(XCircle, { className: "h-4 w-4 text-[var(--code-error)] shrink-0" }) : /* @__PURE__ */ jsx4(XCircle, { className: "h-4 w-4 text-[var(--code-number)] shrink-0" }),
223
+ /* @__PURE__ */ jsx4("span", { className: "text-sm font-medium text-[var(--text-primary)] flex-1", children: form.formName || form.formId }),
224
+ /* @__PURE__ */ jsxs4("span", { className: cn("text-xs tabular-nums", allPassed ? "text-[var(--code-success)]" : "text-[var(--text-muted)]"), children: [
225
+ form.passed,
226
+ "/",
227
+ form.passed + form.failed
228
+ ] }),
229
+ /* @__PURE__ */ jsx4(ChevronRight, { className: cn("h-3 w-3 text-[var(--text-muted)] transition-transform", expanded && "rotate-90") })
230
+ ]
231
+ }
232
+ ),
233
+ expanded && /* @__PURE__ */ jsx4("div", { className: "border-t border-[var(--border-subtle)] px-3 py-1.5 space-y-0.5", children: form.checks.map((check, i) => /* @__PURE__ */ jsx4(CheckRow, { check }, i)) })
234
+ ] });
235
+ }
236
+ function CheckRow({ check }) {
237
+ return /* @__PURE__ */ jsxs4("div", { className: "flex items-center gap-2 py-1 text-xs", children: [
238
+ check.passed ? /* @__PURE__ */ jsx4(CheckCircle2, { className: "h-3 w-3 text-[var(--code-success)] shrink-0" }) : /* @__PURE__ */ jsx4(XCircle, { className: "h-3 w-3 text-[var(--code-error)] shrink-0" }),
239
+ /* @__PURE__ */ jsx4("span", { className: "text-[var(--text-secondary)] flex-1 truncate", children: check.label }),
240
+ /* @__PURE__ */ jsx4("span", { className: "text-[var(--text-muted)] tabular-nums shrink-0", children: check.passed ? String(check.actual ?? check.expected) : `${check.actual ?? "missing"} \u2260 ${check.expected}` })
241
+ ] });
242
+ }
243
+
244
+ // src/workspace/terminal-panel.tsx
245
+ import { useRef, useEffect } from "react";
246
+ import { Terminal as TerminalIcon, ChevronDown, ChevronUp, X as X2 } from "lucide-react";
247
+ import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
248
+ var LINE_COLORS = {
249
+ command: "text-[var(--code-function)]",
250
+ stdout: "text-[var(--text-secondary)]",
251
+ stderr: "text-[var(--code-error)]",
252
+ system: "text-[var(--text-muted)]"
253
+ };
254
+ var LINE_PREFIXES = {
255
+ command: "$ ",
256
+ stdout: "",
257
+ stderr: "",
258
+ system: "# "
259
+ };
260
+ function TerminalPanel({
261
+ lines,
262
+ title = "Terminal",
263
+ isCollapsed = false,
264
+ onToggle,
265
+ onClose,
266
+ maxHeight = 200,
267
+ className
268
+ }) {
269
+ const scrollRef = useRef(null);
270
+ useEffect(() => {
271
+ if (!isCollapsed && scrollRef.current) {
272
+ scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
273
+ }
274
+ }, [lines, isCollapsed]);
275
+ return /* @__PURE__ */ jsxs5("div", { className: cn("border-t border-[var(--border-subtle)] bg-[var(--bg-card)]", className), children: [
276
+ /* @__PURE__ */ jsxs5(
277
+ "button",
278
+ {
279
+ onClick: onToggle,
280
+ className: "flex items-center gap-2 w-full px-3 py-1.5 text-xs text-[var(--text-muted)] hover:text-[var(--text-secondary)] transition-colors",
281
+ children: [
282
+ /* @__PURE__ */ jsx5(TerminalIcon, { className: "h-3.5 w-3.5" }),
283
+ /* @__PURE__ */ jsx5("span", { className: "font-medium", children: title }),
284
+ lines.length > 0 && /* @__PURE__ */ jsx5("span", { className: "px-1.5 py-0.5 rounded-[var(--radius-full)] bg-[var(--bg-input)] text-[10px] tabular-nums", children: lines.length }),
285
+ /* @__PURE__ */ jsx5("div", { className: "flex-1" }),
286
+ onClose && /* @__PURE__ */ jsx5(X2, { className: "h-3 w-3 hover:text-[var(--text-primary)]", onClick: (e) => {
287
+ e.stopPropagation();
288
+ onClose();
289
+ } }),
290
+ isCollapsed ? /* @__PURE__ */ jsx5(ChevronUp, { className: "h-3 w-3" }) : /* @__PURE__ */ jsx5(ChevronDown, { className: "h-3 w-3" })
291
+ ]
292
+ }
293
+ ),
294
+ !isCollapsed && /* @__PURE__ */ jsxs5(
295
+ "div",
296
+ {
297
+ ref: scrollRef,
298
+ className: "overflow-auto px-3 pb-2 font-[var(--font-mono)] text-xs leading-[1.6]",
299
+ style: { maxHeight },
300
+ children: [
301
+ lines.map((line) => /* @__PURE__ */ jsxs5("div", { className: cn("whitespace-pre-wrap", LINE_COLORS[line.type]), children: [
302
+ /* @__PURE__ */ jsx5("span", { className: "text-[var(--text-muted)] select-none", children: LINE_PREFIXES[line.type] }),
303
+ line.text
304
+ ] }, line.id)),
305
+ lines.length === 0 && /* @__PURE__ */ jsx5("div", { className: "text-[var(--text-muted)] py-2", children: "No output yet" })
306
+ ]
307
+ }
308
+ )
309
+ ] });
310
+ }
311
+
312
+ export {
313
+ WorkspaceLayout,
314
+ StatusBar,
315
+ StatusBanner,
316
+ AuditResults,
317
+ TerminalPanel
318
+ };