authorgravatar for me@t3.ggTheo Browne <me@t3.gg> 2026-02-09 01:19:46-08:00
committergravatar for me@t3.ggTheo Browne <me@t3.gg> 2026-02-09 01:19:46-08:00
log377d21211a6b2873437e488590da13d81fa9e926
tree4ae28914083e29f210d790175b572d26ffdecee3
parentc51f252ff2007f7dbb0abd9a6b0af37476bbca9d
signature Commit is signed but in an unrecognized format.

Revert "light mode (#4)"

This reverts commit c51f252ff2007f7dbb0abd9a6b0af37476bbca9d.

52 files changed, 918 insertions(+), 4872 deletions(-)

apps/desktop/scripts/smoke-test.mjs+2-2
...@@ -13,7 +13,7 @@...@@ -13,7 +13,7 @@
13 * the first render). For production mode (no React DevTools message), we13 * the first render). For production mode (no React DevTools message), we
14 * instead check that no fatal errors appeared.14 * instead check that no fatal errors appeared.
15 */15 */
16import { execSync, spawn } from "node:child_process";16import { spawn, execSync } from "node:child_process";
17import { dirname, resolve } from "node:path";17import { dirname, resolve } from "node:path";
18import { fileURLToPath } from "node:url";18import { fileURLToPath } from "node:url";
1919
...@@ -75,7 +75,7 @@ child.on("exit", () => {...@@ -75,7 +75,7 @@ child.on("exit", () => {
75 for (const f of failures) {75 for (const f of failures) {
76 console.error(` • ${f}`);76 console.error(` • ${f}`);
77 }77 }
78 console.error(`\nFull output:\n${output}`);78 console.error("\nFull output:\n" + output);
79 process.exit(1);79 process.exit(1);
80 }80 }
8181
apps/renderer/components.json deleted-25
...@@ -1,25 +0,0 @@
1{
2 "$schema": "https://ui.shadcn.com/schema.json",
3 "style": "new-york",
4 "rsc": false,
5 "tsx": true,
6 "tailwind": {
7 "config": "",
8 "css": "src/index.css",
9 "baseColor": "zinc",
10 "cssVariables": true,
11 "prefix": ""
12 },
13 "iconLibrary": "lucide",
14 "rtl": false,
15 "aliases": {
16 "components": "#/components",
17 "utils": "#/lib/utils",
18 "ui": "#/components/ui",
19 "lib": "#/lib",
20 "hooks": "#/hooks"
21 },
22 "registries": {
23 "@coss": "https://coss.com/ui/r/{name}.json"
24 }
25}
apps/renderer/index.html+1-1
...@@ -5,7 +5,7 @@...@@ -5,7 +5,7 @@
5 <meta name="viewport" content="width=device-width, initial-scale=1.0" />5 <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6 <title>Electron Todo</title>6 <title>Electron Todo</title>
7 </head>7 </head>
8 <body>8 <body class="bg-slate-100">
9 <div id="root"></div>9 <div id="root"></div>
10 <script type="module" src="/src/main.tsx"></script>10 <script type="module" src="/src/main.tsx"></script>
11 </body>11 </body>
apps/renderer/package.json-5
...@@ -12,17 +12,12 @@...@@ -12,17 +12,12 @@
12 },12 },
13 "dependencies": {13 "dependencies": {
14 "@acme/contracts": "workspace:*",14 "@acme/contracts": "workspace:*",
15 "@base-ui/react": "^1.1.0",
16 "class-variance-authority": "^0.7.1",
17 "clsx": "^2.1.1",
18 "highlight.js": "^11.11.1",15 "highlight.js": "^11.11.1",
19 "lucide-react": "^0.563.0",
20 "react": "^19.0.0",16 "react": "^19.0.0",
21 "react-dom": "^19.0.0",17 "react-dom": "^19.0.0",
22 "react-markdown": "^10.1.0",18 "react-markdown": "^10.1.0",
23 "rehype-highlight": "^7.0.2",19 "rehype-highlight": "^7.0.2",
24 "remark-gfm": "^4.0.1",20 "remark-gfm": "^4.0.1",
25 "tailwind-merge": "^3.4.0",
26 "zod": "^3.24.1"21 "zod": "^3.24.1"
27 },22 },
28 "devDependencies": {23 "devDependencies": {
apps/renderer/src/App.tsx+3-3
...@@ -31,10 +31,10 @@ function Layout() {...@@ -31,10 +31,10 @@ function Layout() {
3131
32 if (!api) {32 if (!api) {
33 return (33 return (
34 <div className="flex h-screen flex-col bg-background text-foreground">34 <div className="flex h-screen flex-col bg-[#0c0c0c] text-[#e0e0e0]">
35 <div className="drag-region h-[52px] shrink-0" />35 <div className="drag-region h-[52px] shrink-0" />
36 <div className="flex flex-1 items-center justify-center">36 <div className="flex flex-1 items-center justify-center">
37 <p className="text-sm text-muted-foreground">37 <p className="text-sm text-[#a0a0a0]/60">
38 Native bridge unavailable. Launch through Electron.38 Native bridge unavailable. Launch through Electron.
39 </p>39 </p>
40 </div>40 </div>
...@@ -43,7 +43,7 @@ function Layout() {...@@ -43,7 +43,7 @@ function Layout() {
43 }43 }
4444
45 return (45 return (
46 <div className="flex h-screen overflow-hidden bg-background text-foreground">46 <div className="flex h-screen overflow-hidden bg-[#0c0c0c] text-[#e0e0e0]">
47 <EventRouter />47 <EventRouter />
48 <Sidebar />48 <Sidebar />
49 <ChatView />49 <ChatView />
apps/renderer/src/components/ChatMarkdown.tsx+1-1
...@@ -15,7 +15,7 @@ const markdownComponents: Components = {...@@ -15,7 +15,7 @@ const markdownComponents: Components = {
1515
16export default function ChatMarkdown({ text }: ChatMarkdownProps) {16export default function ChatMarkdown({ text }: ChatMarkdownProps) {
17 return (17 return (
18 <div className="chat-markdown text-sm leading-relaxed text-foreground/80">18 <div className="chat-markdown text-sm leading-relaxed text-[#d0d0d0]">
19 <ReactMarkdown19 <ReactMarkdown
20 remarkPlugins={[remarkGfm]}20 remarkPlugins={[remarkGfm]}
21 rehypePlugins={[[rehypeHighlight, { detect: true, ignoreMissing: true }]]}21 rehypePlugins={[[rehypeHighlight, { detect: true, ignoreMissing: true }]]}
apps/renderer/src/components/ChatView.tsx+754-255
...@@ -1,6 +1,7 @@...@@ -1,6 +1,7 @@
1import { ArrowUpIcon, LoaderCircleIcon, SquareIcon } from "lucide-react";1import type { ProviderApprovalDecision, ProviderEvent } from "@acme/contracts";
2import {2import {
3 type FormEvent,3 type FormEvent,
4 Fragment,
4 type KeyboardEvent,5 type KeyboardEvent,
5 useEffect,6 useEffect,
6 useMemo,7 useMemo,
...@@ -8,6 +9,7 @@ import {...@@ -8,6 +9,7 @@ import {
8 useState,9 useState,
9} from "react";10} from "react";
1011
12import { EDITORS, type EditorId } from "@acme/contracts";
11import {13import {
12 DEFAULT_MODEL,14 DEFAULT_MODEL,
13 DEFAULT_REASONING,15 DEFAULT_REASONING,
...@@ -19,44 +21,100 @@ import {...@@ -19,44 +21,100 @@ import {
19 derivePhase,21 derivePhase,
20 deriveTimelineEntries,22 deriveTimelineEntries,
21 deriveWorkLogEntries,23 deriveWorkLogEntries,
24 formatDuration,
22 formatElapsed,25 formatElapsed,
23 formatTimestamp,26 formatTimestamp,
24 readNativeApi,27 readNativeApi,
25} from "../session-logic";28} from "../session-logic";
26import { useStore } from "../store";29import { useStore } from "../store";
27import ChatMarkdown from "./ChatMarkdown";30import ChatMarkdown from "./ChatMarkdown";
28import { Button } from "./ui/button";
29import {
30 InputGroup,
31 InputGroupAddon,
32 InputGroupText,
33 InputGroupTextarea,
34} from "./ui/input-group";
35import {
36 Select,
37 SelectItem,
38 SelectPopup,
39 SelectTrigger,
40 SelectValue,
41} from "./ui/select";
4231
43function formatMessageMeta(createdAt: string, duration: string | null): string {32function formatMessageMeta(createdAt: string, duration: string | null): string {
44 if (!duration) return formatTimestamp(createdAt);33 if (!duration) return formatTimestamp(createdAt);
45 return `${formatTimestamp(createdAt)} • ${duration}`;34 return `${formatTimestamp(createdAt)} • ${duration}`;
46}35}
4736
48function statusLabel(phase: string): string {37const FILE_MANAGER_LABEL = navigator.platform.includes("Mac")
49 if (phase === "running") return "Thinking / working";38 ? "Finder"
50 if (phase === "connecting") return "Connecting";39 : navigator.platform.includes("Win")
51 if (phase === "ready") return "Ready";40 ? "Explorer"
52 return "Disconnected";41 : "Files";
42
43function editorLabel(editor: (typeof EDITORS)[number]): string {
44 return editor.command ? editor.label : FILE_MANAGER_LABEL;
53}45}
5446
47const LAST_EDITOR_KEY = "codething:last-editor";
48
55function workToneClass(tone: "thinking" | "tool" | "info" | "error"): string {49function workToneClass(tone: "thinking" | "tool" | "info" | "error"): string {
56 if (tone === "error") return "text-destructive/50";50 if (tone === "error") return "text-rose-300/50";
57 if (tone === "tool") return "text-muted-foreground";51 if (tone === "tool") return "text-[#8a8a8a]";
58 if (tone === "thinking") return "text-muted-foreground/70";52 if (tone === "thinking") return "text-[#707070]";
59 return "text-muted-foreground/60";53 return "text-[#606060]";
54}
55
56interface PendingApprovalCard {
57 requestId: string;
58 requestKind: "command" | "file-change";
59 createdAt: string;
60 detail?: string;
61}
62
63function asRecord(value: unknown): Record<string, unknown> | undefined {
64 if (!value || typeof value !== "object") return undefined;
65 return value as Record<string, unknown>;
66}
67
68function asString(value: unknown): string | undefined {
69 return typeof value === "string" ? value : undefined;
70}
71
72function approvalDetail(event: ProviderEvent): string | undefined {
73 const payload = asRecord(event.payload);
74 const command = asString(payload?.command);
75 if (command) return command;
76 return asString(payload?.reason);
77}
78
79function derivePendingApprovals(
80 events: ProviderEvent[],
81): PendingApprovalCard[] {
82 const pending = new Map<string, PendingApprovalCard>();
83 const ordered = [...events].reverse();
84
85 for (const event of ordered) {
86 if (
87 event.method === "session/closed" ||
88 event.method === "session/exited"
89 ) {
90 pending.clear();
91 continue;
92 }
93
94 const requestId =
95 event.requestId ?? asString(asRecord(event.payload)?.requestId);
96 if (!requestId) continue;
97
98 if (
99 event.kind === "request" &&
100 (event.requestKind === "command" || event.requestKind === "file-change")
101 ) {
102 const detail = approvalDetail(event);
103 pending.set(requestId, {
104 requestId,
105 requestKind: event.requestKind,
106 createdAt: event.createdAt,
107 ...(detail ? { detail } : {}),
108 });
109 continue;
110 }
111
112 if (event.method === "item/requestApproval/decision") {
113 pending.delete(requestId);
114 }
115 }
116
117 return Array.from(pending.values());
60}118}
61119
62export default function ChatView() {120export default function ChatView() {
...@@ -65,30 +123,46 @@ export default function ChatView() {...@@ -65,30 +123,46 @@ export default function ChatView() {
65 const [prompt, setPrompt] = useState("");123 const [prompt, setPrompt] = useState("");
66 const [isSending, setIsSending] = useState(false);124 const [isSending, setIsSending] = useState(false);
67 const [isConnecting, setIsConnecting] = useState(false);125 const [isConnecting, setIsConnecting] = useState(false);
126 const [isModelMenuOpen, setIsModelMenuOpen] = useState(false);
127 const [isEditorMenuOpen, setIsEditorMenuOpen] = useState(false);
128 const [lastEditor, setLastEditor] = useState<EditorId>(() => {
129 const stored = localStorage.getItem(LAST_EDITOR_KEY);
130 return EDITORS.some((e) => e.id === stored)
131 ? (stored as EditorId)
132 : EDITORS[0].id;
133 });
68 const [selectedEffort, setSelectedEffort] =134 const [selectedEffort, setSelectedEffort] =
69 useState<string>(DEFAULT_REASONING);135 useState<string>(DEFAULT_REASONING);
136 const [isSwitchingRuntimeMode, setIsSwitchingRuntimeMode] = useState(false);
137 const [respondingRequestIds, setRespondingRequestIds] = useState<string[]>(
138 [],
139 );
70 const [nowTick, setNowTick] = useState(() => Date.now());140 const [nowTick, setNowTick] = useState(() => Date.now());
71 const messagesEndRef = useRef<HTMLDivElement>(null);141 const messagesEndRef = useRef<HTMLDivElement>(null);
142 const textareaRef = useRef<HTMLTextAreaElement>(null);
143 const modelMenuRef = useRef<HTMLDivElement>(null);
144 const editorMenuRef = useRef<HTMLDivElement>(null);
72145
73 const activeThread = state.threads.find((t) => t.id === state.activeThreadId);146 const activeThread = state.threads.find((t) => t.id === state.activeThreadId);
74 const activeProject = state.projects.find(147 const activeProject = state.projects.find((p) => p.id === activeThread?.projectId);
75 (p) => p.id === activeThread?.projectId,
76 );
77 const selectedModel = resolveModelSlug(148 const selectedModel = resolveModelSlug(
78 activeThread?.model ?? activeProject?.model ?? DEFAULT_MODEL,149 activeThread?.model ?? activeProject?.model ?? DEFAULT_MODEL,
79 );150 );
80 const phase = derivePhase(activeThread?.session ?? null);151 const phase = derivePhase(activeThread?.session ?? null);
81 const isWorking = phase === "running" || isSending || isConnecting;152 const isWorking = phase === "running" || isSending || isConnecting;
82 const activeTurnId = activeThread?.session?.activeTurnId;
83 const nowIso = new Date(nowTick).toISOString();153 const nowIso = new Date(nowTick).toISOString();
84 const modelOptions = MODEL_OPTIONS;154 const modelOptions = MODEL_OPTIONS;
85 const workLogEntries = useMemo(155 const workLogEntries = useMemo(
86 () => deriveWorkLogEntries(activeThread?.events ?? [], activeTurnId),156 () => deriveWorkLogEntries(activeThread?.events ?? [], undefined),
87 [activeThread?.events, activeTurnId],157 [activeThread?.events],
158 );
159 const pendingApprovals = useMemo(
160 () => derivePendingApprovals(activeThread?.events ?? []),
161 [activeThread?.events],
88 );162 );
89 const assistantCompletionByItemId = useMemo(() => {163 const assistantCompletionByItemId = useMemo(() => {
90 const map = new Map<string, string>();164 const map = new Map<string, string>();
91 const ordered = [...(activeThread?.events ?? [])].reverse();165 const ordered = [...(activeThread?.events ?? [])].toReversed();
92 for (const event of ordered) {166 for (const event of ordered) {
93 if (event.method !== "item/completed") continue;167 if (event.method !== "item/completed") continue;
94 if (!event.itemId) continue;168 if (!event.itemId) continue;
...@@ -97,27 +171,113 @@ export default function ChatView() {...@@ -97,27 +171,113 @@ export default function ChatView() {
97 return map;171 return map;
98 }, [activeThread?.events]);172 }, [activeThread?.events]);
99 const timelineEntries = useMemo(173 const timelineEntries = useMemo(
100 () =>174 () => deriveTimelineEntries(activeThread?.messages ?? [], workLogEntries),
101 deriveTimelineEntries(175 [activeThread?.messages, workLogEntries],
102 activeThread?.messages ?? [],
103 isWorking ? workLogEntries : [],
104 ),
105 [activeThread?.messages, isWorking, workLogEntries],
106 );176 );
177 const completionSummary = useMemo(() => {
178 if (!activeThread?.latestTurnStartedAt) return null;
179 if (!activeThread.latestTurnCompletedAt) return null;
180 if (workLogEntries.length === 0) return null;
181
182 if (
183 typeof activeThread.latestTurnDurationMs === "number" &&
184 Number.isFinite(activeThread.latestTurnDurationMs) &&
185 activeThread.latestTurnDurationMs >= 0
186 ) {
187 return `Worked for ${formatDuration(activeThread.latestTurnDurationMs)}`;
188 }
189
190 const elapsed = formatElapsed(
191 activeThread.latestTurnStartedAt,
192 activeThread.latestTurnCompletedAt,
193 );
194 return elapsed ? `Worked for ${elapsed}` : null;
195 }, [
196 activeThread?.latestTurnStartedAt,
197 activeThread?.latestTurnCompletedAt,
198 activeThread?.latestTurnDurationMs,
199 workLogEntries.length,
200 ]);
201 const completionDividerBeforeEntryId = useMemo(() => {
202 if (!activeThread?.latestTurnStartedAt) return null;
203 if (!activeThread.latestTurnCompletedAt) return null;
204 if (workLogEntries.length === 0) return null;
205
206 const turnStartedAt = Date.parse(activeThread.latestTurnStartedAt);
207 if (Number.isNaN(turnStartedAt)) return null;
208
209 const entry = timelineEntries.find((timelineEntry) => {
210 if (timelineEntry.kind !== "message") return false;
211 if (timelineEntry.message.role !== "assistant") return false;
212 const messageAt = Date.parse(timelineEntry.message.createdAt);
213 return !Number.isNaN(messageAt) && messageAt >= turnStartedAt;
214 });
215 return entry?.id ?? null;
216 }, [
217 activeThread?.latestTurnStartedAt,
218 activeThread?.latestTurnCompletedAt,
219 timelineEntries,
220 workLogEntries.length,
221 ]);
222 const runtimeSessionConfig =
223 state.runtimeMode === "full-access"
224 ? ({
225 approvalPolicy: "never",
226 sandboxMode: "danger-full-access",
227 } as const)
228 : ({
229 approvalPolicy: "on-request",
230 sandboxMode: "workspace-write",
231 } as const);
232
233 const handleRuntimeModeChange = async (
234 mode: "approval-required" | "full-access",
235 ) => {
236 if (mode === state.runtimeMode) return;
237 dispatch({ type: "SET_RUNTIME_MODE", mode });
238 if (!api) return;
239
240 const sessionIds = state.threads
241 .map((t) => t.session)
242 .filter(
243 (s): s is NonNullable<typeof s> =>
244 s !== null && s.status !== "closed",
245 )
246 .map((s) => s.sessionId);
247
248 if (sessionIds.length === 0) return;
249
250 setIsSwitchingRuntimeMode(true);
251 try {
252 await Promise.all(
253 sessionIds.map((id) =>
254 api.providers.stopSession({ sessionId: id }).catch(() => undefined),
255 ),
256 );
257 } finally {
258 setIsSwitchingRuntimeMode(false);
259 }
260 };
107261
108 // Auto-scroll on new messages262 // Auto-scroll on new messages
109 const messageCount = activeThread?.messages.length ?? 0;263 const messageCount = activeThread?.messages.length ?? 0;
110 const workLogCount = workLogEntries.length;264 const workLogCount = workLogEntries.length;
111 // biome-ignore lint/correctness/useExhaustiveDependencies: trigger on message count change
112 useEffect(() => {265 useEffect(() => {
113 messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });266 messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
114 }, [messageCount]);267 }, [messageCount]);
115 // biome-ignore lint/correctness/useExhaustiveDependencies: auto-scroll while active work-log events stream in
116 useEffect(() => {268 useEffect(() => {
117 if (phase !== "running") return;269 if (phase !== "running") return;
118 messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });270 messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
119 }, [phase, workLogCount]);271 }, [phase, workLogCount]);
120272
273 // Auto-resize textarea
274 useEffect(() => {
275 const ta = textareaRef.current;
276 if (!ta) return;
277 ta.style.height = "auto";
278 ta.style.height = `${Math.min(ta.scrollHeight, 200)}px`;
279 }, [prompt]);
280
121 useEffect(() => {281 useEffect(() => {
122 if (phase !== "running") return;282 if (phase !== "running") return;
123 const timer = window.setInterval(() => {283 const timer = window.setInterval(() => {
...@@ -128,6 +288,63 @@ export default function ChatView() {...@@ -128,6 +288,63 @@ export default function ChatView() {
128 };288 };
129 }, [phase]);289 }, [phase]);
130290
291 useEffect(() => {
292 if (!isModelMenuOpen) return;
293
294 const handleClickOutside = (event: MouseEvent) => {
295 if (!modelMenuRef.current) return;
296 if (event.target instanceof Node && !modelMenuRef.current.contains(event.target)) {
297 setIsModelMenuOpen(false);
298 }
299 };
300
301 window.addEventListener("mousedown", handleClickOutside);
302 return () => {
303 window.removeEventListener("mousedown", handleClickOutside);
304 };
305 }, [isModelMenuOpen]);
306
307 useEffect(() => {
308 if (!isEditorMenuOpen) return;
309
310 const handleClickOutside = (event: MouseEvent) => {
311 if (!editorMenuRef.current) return;
312 if (
313 event.target instanceof Node &&
314 !editorMenuRef.current.contains(event.target)
315 ) {
316 setIsEditorMenuOpen(false);
317 }
318 };
319
320 window.addEventListener("mousedown", handleClickOutside);
321 return () => {
322 window.removeEventListener("mousedown", handleClickOutside);
323 };
324 }, [isEditorMenuOpen]);
325
326 // Cmd+O / Ctrl+O to open in last-used editor
327 useEffect(() => {
328 const handler = (e: globalThis.KeyboardEvent) => {
329 if (e.key === "o" && (e.metaKey || e.ctrlKey) && !e.shiftKey) {
330 if (api && activeProject) {
331 e.preventDefault();
332 void api.shell.openInEditor(activeProject.cwd, lastEditor);
333 }
334 }
335 };
336 window.addEventListener("keydown", handler);
337 return () => window.removeEventListener("keydown", handler);
338 }, [api, activeProject, lastEditor]);
339
340 const openInEditor = (editorId: EditorId) => {
341 if (!api || !activeProject) return;
342 void api.shell.openInEditor(activeProject.cwd, editorId);
343 setLastEditor(editorId);
344 localStorage.setItem(LAST_EDITOR_KEY, editorId);
345 setIsEditorMenuOpen(false);
346 };
347
131 const ensureSession = async (): Promise<string | null> => {348 const ensureSession = async (): Promise<string | null> => {
132 if (!api || !activeThread || !activeProject) return null;349 if (!api || !activeThread || !activeProject) return null;
133 if (activeThread.session && activeThread.session.status !== "closed") {350 if (activeThread.session && activeThread.session.status !== "closed") {
...@@ -140,6 +357,8 @@ export default function ChatView() {...@@ -140,6 +357,8 @@ export default function ChatView() {
140 provider: "codex",357 provider: "codex",
141 cwd: activeProject.cwd || undefined,358 cwd: activeProject.cwd || undefined,
142 model: selectedModel || undefined,359 model: selectedModel || undefined,
360 approvalPolicy: runtimeSessionConfig.approvalPolicy,
361 sandboxMode: runtimeSessionConfig.sandboxMode,
143 });362 });
144 dispatch({363 dispatch({
145 type: "UPDATE_SESSION",364 type: "UPDATE_SESSION",
...@@ -167,8 +386,7 @@ export default function ChatView() {...@@ -167,8 +386,7 @@ export default function ChatView() {
167386
168 // Auto-title from first message387 // Auto-title from first message
169 if (activeThread.messages.length === 0) {388 if (activeThread.messages.length === 0) {
170 const title =389 const title = trimmed.length > 50 ? `${trimmed.slice(0, 50)}...` : trimmed;
171 trimmed.length > 50 ? `${trimmed.slice(0, 50)}...` : trimmed;
172 dispatch({390 dispatch({
173 type: "SET_THREAD_TITLE",391 type: "SET_THREAD_TITLE",
174 threadId: activeThread.id,392 threadId: activeThread.id,
...@@ -219,6 +437,37 @@ export default function ChatView() {...@@ -219,6 +437,37 @@ export default function ChatView() {
219 });437 });
220 };438 };
221439
440 const onRespondToApproval = async (
441 requestId: string,
442 decision: ProviderApprovalDecision,
443 ) => {
444 if (!api || !activeThread?.session) return;
445
446 setRespondingRequestIds((existing) =>
447 existing.includes(requestId) ? existing : [...existing, requestId],
448 );
449 try {
450 await api.providers.respondToRequest({
451 sessionId: activeThread.session.sessionId,
452 requestId,
453 decision,
454 });
455 } catch (err) {
456 dispatch({
457 type: "SET_ERROR",
458 threadId: activeThread.id,
459 error:
460 err instanceof Error
461 ? err.message
462 : "Failed to submit approval decision.",
463 });
464 } finally {
465 setRespondingRequestIds((existing) =>
466 existing.filter((id) => id !== requestId),
467 );
468 }
469 };
470
222 const onModelSelect = (model: string) => {471 const onModelSelect = (model: string) => {
223 if (!activeThread) return;472 if (!activeThread) return;
224 dispatch({473 dispatch({
...@@ -226,6 +475,7 @@ export default function ChatView() {...@@ -226,6 +475,7 @@ export default function ChatView() {
226 threadId: activeThread.id,475 threadId: activeThread.id,
227 model: resolveModelSlug(model),476 model: resolveModelSlug(model),
228 });477 });
478 setIsModelMenuOpen(false);
229 };479 };
230480
231 const onKeyDown = (e: KeyboardEvent<HTMLTextAreaElement>) => {481 const onKeyDown = (e: KeyboardEvent<HTMLTextAreaElement>) => {
...@@ -238,13 +488,11 @@ export default function ChatView() {...@@ -238,13 +488,11 @@ export default function ChatView() {
238 // Empty state: no active thread488 // Empty state: no active thread
239 if (!activeThread) {489 if (!activeThread) {
240 return (490 return (
241 <div className="flex flex-1 flex-col bg-background text-muted-foreground/60">491 <div className="flex flex-1 flex-col bg-[#0c0c0c] text-[#a0a0a0]/40">
242 <div className="drag-region h-[52px] shrink-0" />492 <div className="drag-region h-[52px] shrink-0" />
243 <div className="flex flex-1 items-center justify-center">493 <div className="flex flex-1 items-center justify-center">
244 <div className="text-center">494 <div className="text-center">
245 <p className="text-sm">495 <p className="text-sm">Select a thread or create a new one to get started.</p>
246 Select a thread or create a new one to get started.
247 </p>
248 </div>496 </div>
249 </div>497 </div>
250 </div>498 </div>
...@@ -252,61 +500,55 @@ export default function ChatView() {...@@ -252,61 +500,55 @@ export default function ChatView() {
252 }500 }
253501
254 return (502 return (
255 <div className="flex flex-1 flex-col bg-background">503 <div className="flex flex-1 flex-col bg-[#0c0c0c]">
256 {/* Top bar */}504 {/* Top bar */}
257 <header className="drag-region flex items-center justify-between border-b px-5 pt-[28px] pb-3">505 <header className="drag-region flex items-center justify-between border-b border-white/8 px-5 pt-[28px] pb-3">
258 <div className="flex items-center gap-3">506 <div className="flex items-center gap-3">
259 <h2 className="text-sm font-medium text-foreground">507 <h2 className="text-sm font-medium text-[#e0e0e0]">
260 {activeThread.title}508 {activeThread.title}
261 </h2>509 </h2>
262 {activeProject && (
263 <span className="rounded-full bg-accent px-2 py-0.5 text-[10px] text-muted-foreground/70">
264 {activeProject.name}
265 </span>
266 )}
267 </div>510 </div>
268 <div className="flex items-center gap-3">511 <div className="flex items-center gap-3">
269 {/* Status indicator */}512 {/* Open in editor */}
270 <div513 {activeProject && (
271 className={`inline-flex items-center gap-2 rounded-full border px-2.5 py-1 text-[10px] ${514 <div className="relative" ref={editorMenuRef}>
272 phase === "running"515 <button
273 ? "border-sky-400/35 bg-sky-500/8 text-sky-700 dark:text-sky-100"516 type="button"
274 : phase === "connecting"517 className="rounded-md px-2 py-1 text-[10px] text-[#a0a0a0]/40 transition-colors duration-150 hover:text-[#a0a0a0]/60"
275 ? "border-amber-400/35 bg-amber-500/8 text-amber-700 dark:text-amber-100"518 onClick={() => setIsEditorMenuOpen((v) => !v)}
276 : phase === "ready"519 >
277 ? "border-emerald-400/35 bg-emerald-500/8 text-emerald-700 dark:text-emerald-100"520 Open in&hellip;
278 : "border-border bg-secondary text-muted-foreground"521 </button>
279 }`}522 {isEditorMenuOpen && (
280 >523 <div className="absolute right-0 top-full z-50 mt-1 min-w-[120px] rounded-md border border-white/[0.08] bg-[#1b1b1d] py-1 shadow-xl">
281 <span className="relative inline-flex h-2.5 w-2.5">524 {EDITORS.map((editor) => (
282 {(phase === "running" || phase === "connecting") && (525 <button
283 <span526 key={editor.id}
284 className={`absolute inline-flex h-full w-full animate-ping rounded-full ${527 type="button"
285 phase === "running" ? "bg-sky-300/70" : "bg-amber-300/70"528 className="flex w-full items-center gap-2 px-3 py-1.5 text-left text-[11px] text-[#e0e0e0] hover:bg-white/[0.06]"
286 }`}529 onClick={() => openInEditor(editor.id)}
287 />530 >
531 {editorLabel(editor)}
532 {editor.id === lastEditor && (
533 <kbd className="ml-auto text-[9px] text-[#a0a0a0]/40">
534 {navigator.platform.includes("Mac")
535 ? "\u2318O"
536 : "Ctrl+O"}
537 </kbd>
538 )}
539 </button>
540 ))}
541 </div>
288 )}542 )}
289 <span543 </div>
290 className={`relative inline-flex h-2.5 w-2.5 rounded-full ${544 )}
291 phase === "running"
292 ? "bg-sky-200"
293 : phase === "connecting"
294 ? "bg-amber-200"
295 : phase === "ready"
296 ? "bg-emerald-200"
297 : "bg-muted-foreground/40"
298 }`}
299 />
300 </span>
301 <span>{statusLabel(phase)}</span>
302 </div>
303 {/* Diff toggle */}545 {/* Diff toggle */}
304 <button546 <button
305 type="button"547 type="button"
306 className={`rounded-md px-2 py-1 text-[10px] transition-colors duration-150 ${548 className={`rounded-md px-2 py-1 text-[10px] transition-colors duration-150 ${
307 state.diffOpen549 state.diffOpen
308 ? "bg-accent text-accent-foreground"550 ? "bg-white/10 text-white"
309 : "text-muted-foreground/60 hover:text-muted-foreground"551 : "text-[#a0a0a0]/40 hover:text-[#a0a0a0]/60"
310 }`}552 }`}
311 onClick={() => dispatch({ type: "TOGGLE_DIFF" })}553 onClick={() => dispatch({ type: "TOGGLE_DIFF" })}
312 >554 >
...@@ -317,105 +559,192 @@ export default function ChatView() {...@@ -317,105 +559,192 @@ export default function ChatView() {
317559
318 {/* Error banner */}560 {/* Error banner */}
319 {activeThread.error && (561 {activeThread.error && (
320 <div className="mx-4 mt-3 rounded-lg border border-destructive/20 bg-destructive/10 px-3 py-2 text-xs text-destructive-foreground">562 <div className="mx-4 mt-3 rounded-lg border border-rose-400/20 bg-rose-900/20 px-3 py-2 text-xs text-rose-200">
321 {activeThread.error}563 {activeThread.error}
322 </div>564 </div>
323 )}565 )}
324566
567 {pendingApprovals.length > 0 && (
568 <div className="mx-4 mt-3 space-y-2">
569 {pendingApprovals.map((approval) => {
570 const isResponding = respondingRequestIds.includes(
571 approval.requestId,
572 );
573 return (
574 <div
575 key={approval.requestId}
576 className="rounded-lg border border-amber-300/20 bg-amber-500/[0.07] px-3 py-2"
577 >
578 <p className="text-xs font-medium text-amber-100">
579 {approval.requestKind === "command"
580 ? "Command approval requested"
581 : "File-change approval requested"}
582 </p>
583 {approval.detail && (
584 <p
585 className="mt-1 truncate font-mono text-[11px] text-amber-100/75"
586 title={approval.detail}
587 >
588 {approval.detail}
589 </p>
590 )}
591 <div className="mt-2 flex flex-wrap gap-1.5">
592 <button
593 type="button"
594 className="rounded-md border border-white/[0.15] bg-white/[0.08] px-2 py-1 text-[11px] text-[#e8e8e8] transition-colors duration-150 hover:bg-white/[0.13] disabled:cursor-not-allowed disabled:opacity-50"
595 disabled={isResponding}
596 onClick={() =>
597 void onRespondToApproval(approval.requestId, "accept")
598 }
599 >
600 Approve once
601 </button>
602 <button
603 type="button"
604 className="rounded-md border border-sky-300/30 bg-sky-500/[0.15] px-2 py-1 text-[11px] text-sky-100 transition-colors duration-150 hover:bg-sky-500/[0.22] disabled:cursor-not-allowed disabled:opacity-50"
605 disabled={isResponding}
606 onClick={() =>
607 void onRespondToApproval(
608 approval.requestId,
609 "acceptForSession",
610 )
611 }
612 >
613 Always allow this session
614 </button>
615 <button
616 type="button"
617 className="rounded-md border border-white/[0.15] px-2 py-1 text-[11px] text-[#d8d8d8] transition-colors duration-150 hover:bg-white/[0.06] disabled:cursor-not-allowed disabled:opacity-50"
618 disabled={isResponding}
619 onClick={() =>
620 void onRespondToApproval(approval.requestId, "decline")
621 }
622 >
623 Decline
624 </button>
625 <button
626 type="button"
627 className="rounded-md border border-rose-300/30 bg-rose-500/[0.12] px-2 py-1 text-[11px] text-rose-100 transition-colors duration-150 hover:bg-rose-500/[0.2] disabled:cursor-not-allowed disabled:opacity-50"
628 disabled={isResponding}
629 onClick={() =>
630 void onRespondToApproval(approval.requestId, "cancel")
631 }
632 >
633 Cancel turn
634 </button>
635 </div>
636 </div>
637 );
638 })}
639 </div>
640 )}
641
325 {/* Messages */}642 {/* Messages */}
326 <div className="flex-1 overflow-y-auto px-5 py-4">643 <div className="flex-1 overflow-y-auto px-5 py-4">
327 {activeThread.messages.length === 0 && !isWorking ? (644 {activeThread.messages.length === 0 && !isWorking ? (
328 <div className="flex h-full items-center justify-center">645 <div className="flex h-full items-center justify-center">
329 <p className="text-sm text-muted-foreground/40">646 <p className="text-sm text-[#a0a0a0]/30">Send a message to start the conversation.</p>
330 Send a message to start the conversation.
331 </p>
332 </div>647 </div>
333 ) : (648 ) : (
334 <div className="mx-auto max-w-3xl space-y-4">649 <div className="mx-auto max-w-3xl space-y-4">
335 {timelineEntries.map((timelineEntry) =>650 {timelineEntries.map((timelineEntry, index) => (
336 timelineEntry.kind === "work" ? (651 <Fragment key={timelineEntry.id}>
337 <div652 {timelineEntry.kind === "message" &&
338 key={timelineEntry.id}653 timelineEntry.message.role === "assistant" &&
339 className="border-l-2 border-border/60 pl-4 py-1"654 (completionDividerBeforeEntryId === timelineEntry.id ||
340 >655 timelineEntries[index - 1]?.kind === "work") && (
341 <p656 <div className="my-3 flex items-center gap-3">
342 className={`py-[2px] text-[12px] leading-relaxed ${workToneClass(timelineEntry.entry.tone)}`}657 <span className="h-px flex-1 bg-white/[0.1]" />
343 >658 <span className="rounded-full border border-white/[0.12] bg-[#121212] px-2.5 py-1 text-[10px] uppercase tracking-[0.14em] text-[#9a9a9a]/80">
344 {timelineEntry.entry.detail ? (659 {completionSummary
345 <>660 ? `Response • ${completionSummary}`
346 {timelineEntry.entry.label}661 : "Response"}
347 <span className="ml-1.5 font-mono text-[11px] opacity-60">662 </span>
348 {timelineEntry.entry.detail}663 <span className="h-px flex-1 bg-white/[0.1]" />
349 </span>
350 </>
351 ) : (
352 timelineEntry.entry.label
353 )}
354 </p>
355 </div>
356 ) : (
357 <div key={timelineEntry.id}>
358 {timelineEntry.message.role === "user" ? (
359 <div className="flex justify-end">
360 <div className="max-w-[80%] rounded-2xl rounded-br-sm border border-border bg-accent px-4 py-3">
361 <pre className="whitespace-pre-wrap wrap-break-word font-mono text-sm leading-relaxed text-foreground">
362 {timelineEntry.message.text}
363 </pre>
364 <p className="mt-1.5 text-right text-[10px] text-muted-foreground/40">
365 {formatTimestamp(timelineEntry.message.createdAt)}
366 </p>
367 </div>
368 </div>664 </div>
369 ) : (665 )}
370 <div className="border-l-2 border-border pl-4">666 {timelineEntry.kind === "work" ? (
371 <ChatMarkdown667 <div className="flex items-start gap-2 py-0.5 pl-1.5">
372 text={668 <span className="mt-[7px] h-1.5 w-1.5 shrink-0 rounded-full bg-white/[0.18]" />
373 timelineEntry.message.text ||669 <p
374 (timelineEntry.message.streaming670 className={`py-[2px] text-[11px] leading-relaxed ${workToneClass(timelineEntry.entry.tone)}`}
375 ? ""671 >
376 : "(empty response)")672 {timelineEntry.entry.detail ? (
377 }673 <>
378 />674 {timelineEntry.entry.label}
379 {timelineEntry.message.streaming && (675 <span
380 <div className="pt-1.5">676 className="ml-1.5 inline-block max-w-[70ch] truncate align-bottom font-mono text-[11px] opacity-60"
381 <span className="inline-flex items-center gap-2 rounded-full border border-sky-400/25 bg-sky-500/8 px-2 py-0.5 text-[10px] text-sky-700/90 dark:text-sky-100/90">677 title={timelineEntry.entry.detail}
382 <span className="inline-flex gap-1">678 >
383 <span className="h-1.5 w-1.5 animate-pulse rounded-full bg-sky-500/80 dark:bg-sky-100/80" />679 {timelineEntry.entry.detail}
384 <span className="h-1.5 w-1.5 animate-pulse rounded-full bg-sky-500/80 dark:bg-sky-100/80 [animation-delay:150ms]" />
385 <span className="h-1.5 w-1.5 animate-pulse rounded-full bg-sky-500/80 dark:bg-sky-100/80 [animation-delay:300ms]" />
386 </span>
387 <span>Thinking</span>
388 </span>680 </span>
389 </div>681 </>
682 ) : (
683 timelineEntry.entry.label
390 )}684 )}
391 <p className="mt-1.5 text-[10px] text-muted-foreground/40">685 </p>
392 {formatMessageMeta(686 </div>
393 timelineEntry.message.createdAt,687 ) : timelineEntry.message.role === "user" ? (
394 timelineEntry.message.streaming688 <div className="flex justify-end">
395 ? formatElapsed(689 <div className="max-w-[80%] rounded-2xl rounded-br-sm border border-white/[0.08] bg-white/[0.05] px-4 py-3">
396 timelineEntry.message.createdAt,690 <pre className="whitespace-pre-wrap break-words font-mono text-sm leading-relaxed text-[#e0e0e0]">
397 nowIso,691 {timelineEntry.message.text}
398 )692 </pre>
399 : formatElapsed(693 <p className="mt-1.5 text-right text-[10px] text-[#a0a0a0]/30">
400 timelineEntry.message.createdAt,694 {formatTimestamp(timelineEntry.message.createdAt)}
401 assistantCompletionByItemId.get(
402 timelineEntry.message.id,
403 ),
404 ),
405 )}
406 </p>695 </p>
407 </div>696 </div>
408 )}697 </div>
409 </div>698 ) : (
410 ),699 <div className="px-1 py-0.5">
411 )}700 <ChatMarkdown
701 text={
702 timelineEntry.message.text ||
703 (timelineEntry.message.streaming
704 ? ""
705 : "(empty response)")
706 }
707 />
708 {timelineEntry.message.streaming && (
709 <div className="pt-1.5">
710 <span className="inline-flex items-center gap-2 rounded-full border border-sky-400/25 bg-sky-500/[0.08] px-2 py-0.5 text-[10px] text-sky-100/90">
711 <span className="inline-flex gap-1">
712 <span className="h-1.5 w-1.5 animate-pulse rounded-full bg-sky-100/80" />
713 <span className="h-1.5 w-1.5 animate-pulse rounded-full bg-sky-100/80 [animation-delay:150ms]" />
714 <span className="h-1.5 w-1.5 animate-pulse rounded-full bg-sky-100/80 [animation-delay:300ms]" />
715 </span>
716 <span>Thinking</span>
717 </span>
718 </div>
719 )}
720 <p className="mt-1.5 text-[10px] text-[#a0a0a0]/30">
721 {formatMessageMeta(
722 timelineEntry.message.createdAt,
723 timelineEntry.message.streaming
724 ? formatElapsed(
725 timelineEntry.message.createdAt,
726 nowIso,
727 )
728 : formatElapsed(
729 timelineEntry.message.createdAt,
730 assistantCompletionByItemId.get(
731 timelineEntry.message.id,
732 ),
733 ),
734 )}
735 </p>
736 </div>
737 )}
738 </Fragment>
739 ))}
412 {isWorking && (740 {isWorking && (
413 <div className="border-l-2 border-border/60 pl-4 py-1">741 <div className="flex items-center gap-2 py-0.5 pl-1.5">
742 <span className="h-1.5 w-1.5 shrink-0 rounded-full bg-white/[0.18]" />
414 <div className="flex items-center pt-1">743 <div className="flex items-center pt-1">
415 <span className="inline-flex items-center gap-[3px]">744 <span className="inline-flex items-center gap-[3px]">
416 <span className="h-1 w-1 rounded-full bg-foreground/20 animate-pulse" />745 <span className="h-1 w-1 rounded-full bg-white/20 animate-pulse" />
417 <span className="h-1 w-1 rounded-full bg-foreground/20 animate-pulse [animation-delay:200ms]" />746 <span className="h-1 w-1 rounded-full bg-white/20 animate-pulse [animation-delay:200ms]" />
418 <span className="h-1 w-1 rounded-full bg-foreground/20 animate-pulse [animation-delay:400ms]" />747 <span className="h-1 w-1 rounded-full bg-white/20 animate-pulse [animation-delay:400ms]" />
419 </span>748 </span>
420 </div>749 </div>
421 </div>750 </div>
...@@ -428,101 +757,271 @@ export default function ChatView() {...@@ -428,101 +757,271 @@ export default function ChatView() {
428 {/* Input bar */}757 {/* Input bar */}
429 <div className="px-5 pb-4 pt-2">758 <div className="px-5 pb-4 pt-2">
430 <form onSubmit={onSend} className="mx-auto max-w-3xl">759 <form onSubmit={onSend} className="mx-auto max-w-3xl">
431 <InputGroup className="rounded-[20px] before:rounded-[19px] **:[textarea]:max-h-[200px]">760 <div className="group rounded-[20px] border border-white/8 bg-[#141416] transition-colors duration-200 focus-within:border-white/16">
432 <InputGroupTextarea761 {/* Textarea area */}
433 value={prompt}762 <div className="px-4 pt-4 pb-2">
434 onChange={(e) => setPrompt(e.target.value)}763 <textarea
435 onKeyDown={onKeyDown}764 ref={textareaRef}
436 placeholder={765 className="w-full resize-none bg-transparent text-[14px] leading-relaxed text-[#e0e0e0] placeholder:text-[#a0a0a0]/35 focus:outline-none"
437 phase === "disconnected"766 rows={2}
438 ? "Ask for follow-up changes"767 value={prompt}
439 : "Ask anything..."768 onChange={(e) => setPrompt(e.target.value)}
440 }769 onKeyDown={onKeyDown}
441 disabled={isSending || isConnecting}770 placeholder={
442 rows={2}771 phase === "disconnected" ? "Ask for follow-up changes" : "Ask anything..."
443 className="font-sans!"772 }
444 />773 disabled={isSending || isConnecting}
445 <InputGroupAddon align="block-end">774 />
446 {/* Model picker */}775 </div>
447 <Select
448 value={selectedModel}
449 onValueChange={(val) => val && onModelSelect(val)}
450 >
451 <SelectTrigger
452 size="sm"
453 className="w-auto min-w-0 border-0 shadow-none bg-transparent dark:bg-transparent before:hidden data-[popup-open]:bg-accent dark:data-[popup-open]:bg-accent"
454 >
455 <SelectValue />
456 </SelectTrigger>
457 <SelectPopup side="top" align="start">
458 {modelOptions.map((model) => (
459 <SelectItem key={model} value={model}>
460 {model}
461 </SelectItem>
462 ))}
463 </SelectPopup>
464 </Select>
465776
466 {/* Reasoning effort */}777 {/* Bottom toolbar */}
467 <Select778 <div className="flex items-center justify-between px-3 pb-3">
468 value={selectedEffort}779 <div className="flex items-center gap-1">
469 onValueChange={(val) => val && setSelectedEffort(val)}780 {/* Model picker */}
470 >781 <div className="relative" ref={modelMenuRef}>
471 <SelectTrigger782 <button
472 size="sm"783 type="button"
473 className="w-auto min-w-0 border-0 shadow-none bg-transparent dark:bg-transparent before:hidden data-[popup-open]:bg-accent dark:data-[popup-open]:bg-accent capitalize"784 className="inline-flex items-center gap-1.5 rounded-lg px-2.5 py-1.5 text-[13px] text-[#a0a0a0]/70 transition-colors duration-150 hover:bg-white/6 hover:text-[#d0d0d0]"
474 >785 onClick={() => setIsModelMenuOpen((open) => !open)}
475 <SelectValue />786 >
476 </SelectTrigger>787 <span className="max-w-[180px] truncate">{selectedModel}</span>
477 <SelectPopup side="top" align="start">788 <svg
478 {REASONING_OPTIONS.map((effort) => (789 width="10"
479 <SelectItem key={effort} value={effort}>790 height="10"
480 {effort.charAt(0).toUpperCase() + effort.slice(1)}791 viewBox="0 0 10 10"
481 {effort === DEFAULT_REASONING ? " (default)" : ""}792 fill="none"
482 </SelectItem>793 className="opacity-50"
483 ))}794 aria-hidden="true"
484 </SelectPopup>795 >
485 </Select>796 <path
797 d="M2.5 4L5 6.5L7.5 4"
798 stroke="currentColor"
799 strokeWidth="1.2"
800 strokeLinecap="round"
801 strokeLinejoin="round"
802 />
803 </svg>
804 </button>
805 {isModelMenuOpen && (
806 <div className="absolute bottom-full left-0 z-20 mb-2 w-[320px] rounded-2xl border border-white/10 bg-[#1b1b1d]/95 p-2 shadow-[0_16px_40px_rgba(0,0,0,0.55)] backdrop-blur">
807 <p className="px-2 py-1 text-[11px] text-[#a0a0a0]/70">Select model</p>
808 <div className="max-h-72 overflow-y-auto">
809 {modelOptions.map((model) => {
810 const isSelected = model === selectedModel;
811 return (
812 <button
813 key={model}
814 type="button"
815 className={`mb-0.5 flex w-full items-center justify-between gap-2 rounded-xl px-2 py-2 text-left font-mono text-sm transition-colors duration-150 ${
816 isSelected
817 ? "bg-white/8 text-white"
818 : "text-[#d4d4d4] hover:bg-white/5"
819 }`}
820 onClick={() => onModelSelect(model)}
821 >
822 <span className="truncate">{model}</span>
823 <span
824 className={`pt-0.5 text-sm ${
825 isSelected ? "text-white" : "text-transparent"
826 }`}
827 >
828 ✓
829 </span>
830 </button>
831 );
832 })}
833 </div>
834 </div>
835 )}
836 </div>
486837
487 {activeProject && (838 {/* Divider */}
488 <InputGroupText className="ml-auto text-muted-foreground/40">839 <div className="mx-0.5 h-4 w-px bg-white/8" />
489 {activeProject.name}
490 </InputGroupText>
491 )}
492840
493 {phase === "running" ? (841 {/* Reasoning effort */}
494 <Button842 <label
495 variant="destructive"843 className="inline-flex cursor-pointer items-center gap-1.5 rounded-lg px-2.5 py-1.5 text-[13px] text-[#a0a0a0]/70 transition-colors duration-150 hover:bg-white/6 hover:text-[#d0d0d0]"
496 size="icon-sm"844 htmlFor="reasoning-effort"
497 className="rounded-full"
498 onClick={() => void onInterrupt()}
499 aria-label="Stop generation"
500 >845 >
501 <SquareIcon className="size-3" />846 <span>{selectedEffort.charAt(0).toUpperCase() + selectedEffort.slice(1)}</span>
502 </Button>847 <select
503 ) : (848 id="reasoning-effort"
504 <Button849 className="absolute opacity-0 w-0 h-0"
505 type="submit"850 value={selectedEffort}
506 size="icon-sm"851 onChange={(event) => setSelectedEffort(event.target.value)}
507 className="rounded-full"852 >
508 disabled={isSending || isConnecting || !prompt.trim()}853 {REASONING_OPTIONS.map((effort) => (
509 aria-label={854 <option key={effort} value={effort} className="bg-[#1b1b1d]">
510 isConnecting855 {effort}
511 ? "Connecting"856 {effort === DEFAULT_REASONING ? " (default)" : ""}
512 : isSending857 </option>
513 ? "Sending"858 ))}
514 : "Send message"859 </select>
860 <svg
861 width="10"
862 height="10"
863 viewBox="0 0 10 10"
864 fill="none"
865 className="opacity-50"
866 aria-hidden="true"
867 >
868 <path
869 d="M2.5 4L5 6.5L7.5 4"
870 stroke="currentColor"
871 strokeWidth="1.2"
872 strokeLinecap="round"
873 strokeLinejoin="round"
874 />
875 </svg>
876 </label>
877
878 {/* Divider */}
879 <div className="mx-0.5 h-4 w-px bg-white/[0.08]" />
880
881 {/* Runtime mode toggle */}
882 <button
883 type="button"
884 className="inline-flex items-center gap-1.5 rounded-lg px-2.5 py-1.5 text-[13px] text-[#a0a0a0]/70 transition-colors duration-150 hover:bg-white/[0.06] hover:text-[#d0d0d0]"
885 disabled={isSwitchingRuntimeMode}
886 onClick={() =>
887 void handleRuntimeModeChange(
888 state.runtimeMode === "full-access"
889 ? "approval-required"
890 : "full-access",
891 )
892 }
893 title={
894 state.runtimeMode === "full-access"
895 ? "Full access — click to require approvals"
896 : "Approval required — click for full access"
515 }897 }
516 >898 >
517 {isConnecting || isSending ? (899 {state.runtimeMode === "full-access" ? (
518 <LoaderCircleIcon className="animate-spin" />900 <svg
901 width="14"
902 height="14"
903 viewBox="0 0 14 14"
904 fill="none"
905 aria-hidden="true"
906 >
907 <rect
908 x="2"
909 y="5.5"
910 width="10"
911 height="7"
912 rx="1.5"
913 stroke="currentColor"
914 strokeWidth="1.3"
915 />
916 <path
917 d="M9.5 5.5V4a2.5 2.5 0 0 0-5 0"
918 stroke="currentColor"
919 strokeWidth="1.3"
920 strokeLinecap="round"
921 />
922 </svg>
519 ) : (923 ) : (
520 <ArrowUpIcon />924 <svg
925 width="14"
926 height="14"
927 viewBox="0 0 14 14"
928 fill="none"
929 aria-hidden="true"
930 >
931 <rect
932 x="2"
933 y="5.5"
934 width="10"
935 height="7"
936 rx="1.5"
937 stroke="currentColor"
938 strokeWidth="1.3"
939 />
940 <path
941 d="M4.5 5.5V4a2.5 2.5 0 0 1 5 0v1.5"
942 stroke="currentColor"
943 strokeWidth="1.3"
944 strokeLinecap="round"
945 />
946 </svg>
521 )}947 )}
522 </Button>948 <span>
523 )}949 {state.runtimeMode === "full-access"
524 </InputGroupAddon>950 ? "Full access"
525 </InputGroup>951 : "Supervised"}
952 </span>
953 </button>
954 </div>
955
956 {/* Right side: send / stop button */}
957 <div className="flex items-center gap-2">
958 {phase === "running" ? (
959 <button
960 type="button"
961 className="flex h-8 w-8 items-center justify-center rounded-full bg-rose-500/90 text-white transition-all duration-150 hover:bg-rose-500 hover:scale-105"
962 onClick={() => void onInterrupt()}
963 aria-label="Stop generation"
964 >
965 <svg
966 width="12"
967 height="12"
968 viewBox="0 0 12 12"
969 fill="currentColor"
970 aria-hidden="true"
971 >
972 <rect x="2" y="2" width="8" height="8" rx="1.5" />
973 </svg>
974 </button>
975 ) : (
976 <button
977 type="submit"
978 className="flex h-8 w-8 items-center justify-center rounded-full bg-white/90 text-[#0c0c0c] transition-all duration-150 hover:bg-white hover:scale-105 disabled:opacity-30 disabled:hover:scale-100"
979 disabled={isSending || isConnecting || !prompt.trim()}
980 aria-label={
981 isConnecting ? "Connecting" : isSending ? "Sending" : "Send message"
982 }
983 >
984 {isConnecting || isSending ? (
985 <svg
986 width="14"
987 height="14"
988 viewBox="0 0 14 14"
989 fill="none"
990 className="animate-spin"
991 aria-hidden="true"
992 >
993 <circle
994 cx="7"
995 cy="7"
996 r="5.5"
997 stroke="currentColor"
998 strokeWidth="1.5"
999 strokeLinecap="round"
1000 strokeDasharray="20 12"
1001 />
1002 </svg>
1003 ) : (
1004 <svg
1005 width="14"
1006 height="14"
1007 viewBox="0 0 14 14"
1008 fill="none"
1009 aria-hidden="true"
1010 >
1011 <path
1012 d="M7 11.5V2.5M7 2.5L3 6.5M7 2.5L11 6.5"
1013 stroke="currentColor"
1014 strokeWidth="1.8"
1015 strokeLinecap="round"
1016 strokeLinejoin="round"
1017 />
1018 </svg>
1019 )}
1020 </button>
1021 )}
1022 </div>
1023 </div>
1024 </div>
526 </form>1025 </form>
527 </div>1026 </div>
528 </div>1027 </div>
apps/renderer/src/components/DiffPanel.tsx+10-18
...@@ -4,15 +4,13 @@ export default function DiffPanel() {...@@ -4,15 +4,13 @@ export default function DiffPanel() {
4 const { dispatch } = useStore();4 const { dispatch } = useStore();
55
6 return (6 return (
7 <aside className="flex h-full w-[400px] shrink-0 flex-col border-l border-border bg-card">7 <aside className="flex h-full w-[400px] shrink-0 flex-col border-l border-white/[0.08] bg-[#141414]">
8 {/* Header */}8 {/* Header */}
9 <div className="drag-region flex items-center justify-between border-b border-border px-4 pt-[28px] pb-3">9 <div className="drag-region flex items-center justify-between border-b border-white/[0.08] px-4 pt-[28px] pb-3">
10 <h3 className="text-xs font-medium text-foreground">10 <h3 className="text-xs font-medium text-[#e0e0e0]">Uncommitted changes</h3>
11 Uncommitted changes
12 </h3>
13 <button11 <button
14 type="button"12 type="button"
15 className="text-muted-foreground/60 transition-colors duration-150 hover:text-muted-foreground"13 className="text-[#a0a0a0]/40 transition-colors duration-150 hover:text-[#a0a0a0]/70"
16 onClick={() => dispatch({ type: "TOGGLE_DIFF" })}14 onClick={() => dispatch({ type: "TOGGLE_DIFF" })}
17 >15 >
18 <span className="text-sm">&times;</span>16 <span className="text-sm">&times;</span>
...@@ -22,36 +20,30 @@ export default function DiffPanel() {...@@ -22,36 +20,30 @@ export default function DiffPanel() {
22 {/* Placeholder content */}20 {/* Placeholder content */}
23 <div className="flex-1 overflow-y-auto p-4">21 <div className="flex-1 overflow-y-auto p-4">
24 <div className="space-y-3">22 <div className="space-y-3">
25 <div className="rounded-lg border border-border/80 bg-muted p-3">23 <div className="rounded-lg border border-white/[0.06] bg-white/[0.02] p-3">
26 <div className="mb-2 flex items-center gap-2">24 <div className="mb-2 flex items-center gap-2">
27 <span className="rounded bg-emerald-500/20 px-1.5 py-0.5 text-[9px] font-medium text-emerald-400">25 <span className="rounded bg-emerald-500/20 px-1.5 py-0.5 text-[9px] font-medium text-emerald-400">
28 M26 M
29 </span>27 </span>
30 <span className="font-mono text-xs text-muted-foreground/80">28 <span className="font-mono text-xs text-[#a0a0a0]/60">src/App.tsx</span>
31 src/App.tsx
32 </span>
33 </div>29 </div>
34 <div className="space-y-0.5 font-mono text-[11px]">30 <div className="space-y-0.5 font-mono text-[11px]">
35 <div className="text-red-400/60">- const oldValue = "hello";</div>31 <div className="text-red-400/60">- const oldValue = "hello";</div>
36 <div className="text-emerald-400/60">32 <div className="text-emerald-400/60">+ const newValue = "world";</div>
37 + const newValue = "world";
38 </div>
39 </div>33 </div>
40 </div>34 </div>
4135
42 <div className="rounded-lg border border-border/80 bg-muted p-3">36 <div className="rounded-lg border border-white/[0.06] bg-white/[0.02] p-3">
43 <div className="flex items-center gap-2">37 <div className="flex items-center gap-2">
44 <span className="rounded bg-blue-500/20 px-1.5 py-0.5 text-[9px] font-medium text-blue-400">38 <span className="rounded bg-blue-500/20 px-1.5 py-0.5 text-[9px] font-medium text-blue-400">
45 A39 A
46 </span>40 </span>
47 <span className="font-mono text-xs text-muted-foreground/80">41 <span className="font-mono text-xs text-[#a0a0a0]/60">src/utils.ts</span>
48 src/utils.ts
49 </span>
50 </div>42 </div>
51 </div>43 </div>
52 </div>44 </div>
5345
54 <p className="mt-6 text-center text-[10px] text-muted-foreground/30">46 <p className="mt-6 text-center text-[10px] text-[#a0a0a0]/25">
55 Diff integration coming soon47 Diff integration coming soon
56 </p>48 </p>
57 </div>49 </div>
apps/renderer/src/components/Sidebar.tsx+119-95
...@@ -1,11 +1,10 @@...@@ -1,11 +1,10 @@
1import { MonitorIcon, MoonIcon, SunIcon } from "lucide-react";1import { useCallback, useEffect, useMemo, useState } from "react";
2import { useMemo, useState } from "react";2import { DEFAULT_MODEL } from "../model-logic";
3import { useTheme } from "../hooks/useTheme";
4import { DEFAULT_MODEL, MODEL_OPTIONS, resolveModelSlug } from "../model-logic";
5import { readNativeApi } from "../session-logic";3import { readNativeApi } from "../session-logic";
6import { useStore } from "../store";4import { useStore } from "../store";
7import type { Project } from "../types";5import type { Project } from "../types";
86
7
9function formatRelativeTime(iso: string): string {8function formatRelativeTime(iso: string): string {
10 const diff = Date.now() - new Date(iso).getTime();9 const diff = Date.now() - new Date(iso).getTime();
11 const minutes = Math.floor(diff / 60_000);10 const minutes = Math.floor(diff / 60_000);
...@@ -16,53 +15,58 @@ function formatRelativeTime(iso: string): string {...@@ -16,53 +15,58 @@ function formatRelativeTime(iso: string): string {
16 return `${Math.floor(hours / 24)}d ago`;15 return `${Math.floor(hours / 24)}d ago`;
17}16}
1817
19const THEME_CYCLE = { system: "light", light: "dark", dark: "system" } as const;18function threadStatusLabel(
19 status: "connecting" | "ready" | "running" | "error" | "closed" | undefined,
20): "Working" | "Connecting" | null {
21 if (status === "running") return "Working";
22 if (status === "connecting") return "Connecting";
23 return null;
24}
2025
21export default function Sidebar() {26export default function Sidebar() {
22 const { state, dispatch } = useStore();27 const { state, dispatch } = useStore();
23 const api = useMemo(() => readNativeApi(), []);28 const api = useMemo(() => readNativeApi(), []);
24 const { theme, setTheme } = useTheme();
25 const [addingProject, setAddingProject] = useState(false);29 const [addingProject, setAddingProject] = useState(false);
26 const [newCwd, setNewCwd] = useState("");30 const [newCwd, setNewCwd] = useState("");
27 const [newModel, setNewModel] = useState(DEFAULT_MODEL);
28 const [isPickingFolder, setIsPickingFolder] = useState(false);31 const [isPickingFolder, setIsPickingFolder] = useState(false);
2932
30 const handleAddProject = () => {33 const handleAddProject = () => {
31 const cwd = newCwd.trim();34 const cwd = newCwd.trim();
32 if (!cwd) return;35 if (!cwd) return;
33 const name = cwd.split("/").filter(Boolean).pop() ?? "project";36 const name = cwd.split("/").filter(Boolean).pop() ?? "project";
34 const normalizedModel = resolveModelSlug(newModel);
35 const project: Project = {37 const project: Project = {
36 id: crypto.randomUUID(),38 id: crypto.randomUUID(),
37 name,39 name,
38 cwd,40 cwd,
39 model: normalizedModel,41 model: DEFAULT_MODEL,
40 expanded: true,42 expanded: true,
41 };43 };
42 dispatch({ type: "ADD_PROJECT", project });44 dispatch({ type: "ADD_PROJECT", project });
43 setNewCwd("");45 setNewCwd("");
44 setNewModel(DEFAULT_MODEL);
45 setAddingProject(false);46 setAddingProject(false);
46 };47 };
4748
48 const handleNewThread = (projectId: string) => {49 const handleNewThread = useCallback(
49 dispatch({50 (projectId: string) => {
50 type: "ADD_THREAD",51 dispatch({
51 thread: {52 type: "ADD_THREAD",
52 id: crypto.randomUUID(),53 thread: {
53 projectId,54 id: crypto.randomUUID(),
54 title: "New thread",55 projectId,
55 model:56 title: "New thread",
56 state.projects.find((p) => p.id === projectId)?.model ??57 model:
57 DEFAULT_MODEL,58 state.projects.find((p) => p.id === projectId)?.model ??
58 session: null,59 DEFAULT_MODEL,
59 messages: [],60 session: null,
60 events: [],61 messages: [],
61 error: null,62 events: [],
62 createdAt: new Date().toISOString(),63 error: null,
63 },64 createdAt: new Date().toISOString(),
64 });65 },
65 };66 });
67 },
68 [dispatch, state.projects],
69 );
6670
67 const handlePickFolder = async () => {71 const handlePickFolder = async () => {
68 if (!api || isPickingFolder) return;72 if (!api || isPickingFolder) return;
...@@ -76,40 +80,62 @@ export default function Sidebar() {...@@ -76,40 +80,62 @@ export default function Sidebar() {
76 }80 }
77 };81 };
7882
83 useEffect(() => {
84 const onWindowKeyDown = (event: KeyboardEvent) => {
85 const isNewThreadShortcut =
86 event.metaKey &&
87 event.shiftKey &&
88 !event.ctrlKey &&
89 !event.altKey &&
90 event.key.toLowerCase() === "o";
91 if (!isNewThreadShortcut) return;
92
93 const target = event.target;
94 if (target instanceof HTMLElement) {
95 const tagName = target.tagName;
96 if (
97 target.isContentEditable ||
98 tagName === "INPUT" ||
99 tagName === "TEXTAREA" ||
100 tagName === "SELECT"
101 ) {
102 return;
103 }
104 }
105
106 const activeThread = state.threads.find(
107 (t) => t.id === state.activeThreadId,
108 );
109 const projectId = activeThread?.projectId ?? state.projects[0]?.id;
110 if (!projectId) return;
111
112 event.preventDefault();
113 handleNewThread(projectId);
114 };
115
116 window.addEventListener("keydown", onWindowKeyDown);
117 return () => {
118 window.removeEventListener("keydown", onWindowKeyDown);
119 };
120 }, [handleNewThread, state.activeThreadId, state.projects, state.threads]);
121
79 return (122 return (
80 <aside className="sidebar flex h-full w-[260px] shrink-0 flex-col border-r border-border bg-card">123 <aside className="sidebar flex h-full w-[260px] shrink-0 flex-col border-r border-white/8 bg-[#141414]">
81 {/* Drag region / traffic light space */}124 {/* Drag region / traffic light space */}
82 <div className="drag-region h-[52px] shrink-0" />125 <div className="drag-region h-[52px] shrink-0" />
83 {/* Branding */}126 {/* Branding */}
84 <div className="flex items-center gap-2 px-4 pb-4">127 <div className="flex items-center gap-2 px-4 pb-4">
85 <div className="flex h-7 w-7 items-center justify-center rounded-lg bg-primary text-xs font-bold text-primary-foreground">128 <div className="flex h-7 w-7 items-center justify-center rounded-lg bg-white text-xs font-bold text-[#0c0c0c]">
86 CT129 CT
87 </div>130 </div>
88 <span className="flex-1 text-sm font-semibold tracking-tight text-foreground">131 <span className="text-sm font-semibold tracking-tight text-[#e0e0e0]">CodeThing</span>
89 CodeThing
90 </span>
91 <button
92 type="button"
93 className="rounded-md p-1.5 text-muted-foreground/80 transition-colors duration-150 hover:bg-accent hover:text-muted-foreground"
94 onClick={() => setTheme(THEME_CYCLE[theme])}
95 aria-label={`Theme: ${theme}`}
96 title={`Theme: ${theme}`}
97 >
98 {theme === "system" ? (
99 <MonitorIcon className="size-3.5" />
100 ) : theme === "light" ? (
101 <SunIcon className="size-3.5" />
102 ) : (
103 <MoonIcon className="size-3.5" />
104 )}
105 </button>
106 </div>132 </div>
107133
108 {/* New thread (global) */}134 {/* New thread (global) */}
109 <div className="px-3 pb-3">135 <div className="px-3 pb-3">
110 <button136 <button
111 type="button"137 type="button"
112 className="flex w-full items-center gap-2 rounded-lg border border-border bg-secondary px-3 py-2 text-xs text-muted-foreground transition-colors duration-150 hover:bg-accent"138 className="flex w-full items-center gap-2 rounded-lg border border-white/8 bg-white/4 px-3 py-2 text-xs text-[#a0a0a0]/80 transition-colors duration-150 hover:bg-white/[0.07]"
113 onClick={() => {139 onClick={() => {
114 if (state.projects.length === 0) {140 if (state.projects.length === 0) {
115 setAddingProject(true);141 setAddingProject(true);
...@@ -119,7 +145,7 @@ export default function Sidebar() {...@@ -119,7 +145,7 @@ export default function Sidebar() {
119 if (firstProject) handleNewThread(firstProject.id);145 if (firstProject) handleNewThread(firstProject.id);
120 }}146 }}
121 >147 >
122 <span className="text-foreground">+</span>148 <span className="text-white">+</span>
123 New thread149 New thread
124 </button>150 </button>
125 </div>151 </div>
...@@ -127,43 +153,46 @@ export default function Sidebar() {...@@ -127,43 +153,46 @@ export default function Sidebar() {
127 {/* Project list */}153 {/* Project list */}
128 <nav className="flex-1 overflow-y-auto px-2">154 <nav className="flex-1 overflow-y-auto px-2">
129 {state.projects.map((project) => {155 {state.projects.map((project) => {
130 const threads = state.threads.filter(156 const threads = state.threads
131 (t) => t.projectId === project.id,157 .filter((t) => t.projectId === project.id)
132 );158 .sort((a, b) => {
159 const byDate =
160 new Date(b.createdAt).getTime() -
161 new Date(a.createdAt).getTime();
162 if (byDate !== 0) return byDate;
163 return b.id.localeCompare(a.id);
164 });
133 return (165 return (
134 <div key={project.id} className="mb-1">166 <div key={project.id} className="mb-1">
135 {/* Project header */}167 {/* Project header */}
136 <button168 <button
137 type="button"169 type="button"
138 className="flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-left transition-colors duration-150 hover:bg-accent"170 className="flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-left transition-colors duration-150 hover:bg-white/5"
139 onClick={() =>171 onClick={() => dispatch({ type: "TOGGLE_PROJECT", projectId: project.id })}
140 dispatch({ type: "TOGGLE_PROJECT", projectId: project.id })
141 }
142 >172 >
143 <span className="text-[10px] text-muted-foreground/70">173 <span className="text-[10px] text-[#a0a0a0]/50">
144 {project.expanded ? "â–¼" : "â–¶"}174 {project.expanded ? "â–¼" : "â–¶"}
145 </span>175 </span>
146 <span className="flex-1 truncate text-xs font-medium text-foreground/90">176 <span className="flex-1 truncate text-xs font-medium text-[#e0e0e0]/90">
147 {project.name}177 {project.name}
148 </span>178 </span>
149 <span className="text-[10px] text-muted-foreground/60">179 <span className="text-[10px] text-[#a0a0a0]/40">{threads.length}</span>
150 {threads.length}
151 </span>
152 </button>180 </button>
153181
154 {/* Threads */}182 {/* Threads */}
155 {project.expanded && (183 {project.expanded && (
156 <div className="ml-2 border-l border-border/80 pl-2">184 <div className="ml-2 border-l border-white/6 pl-2">
157 {threads.map((thread) => {185 {threads.map((thread) => {
158 const isActive = state.activeThreadId === thread.id;186 const isActive = state.activeThreadId === thread.id;
187 const threadStatus = threadStatusLabel(
188 thread.session?.status,
189 );
159 return (190 return (
160 <button191 <button
161 key={thread.id}192 key={thread.id}
162 type="button"193 type="button"
163 className={`flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-left transition-colors duration-150 ${194 className={`flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-left transition-colors duration-150 ${
164 isActive195 isActive ? "bg-white/10 text-white" : "text-[#a0a0a0]/70 hover:bg-white/4"
165 ? "bg-accent text-foreground"
166 : "text-muted-foreground hover:bg-secondary"
167 }`}196 }`}
168 onClick={() =>197 onClick={() =>
169 dispatch({198 dispatch({
...@@ -172,10 +201,20 @@ export default function Sidebar() {...@@ -172,10 +201,20 @@ export default function Sidebar() {
172 })201 })
173 }202 }
174 >203 >
175 <span className="flex-1 truncate text-xs">204 <div className="flex min-w-0 flex-1 items-center gap-1.5">
176 {thread.title}205 {threadStatus && (
177 </span>206 <span className="inline-flex items-center gap-1 text-[10px] text-[#7ed8ff]/80">
178 <span className="shrink-0 text-[10px] text-muted-foreground/40">207 <span className="h-1.5 w-1.5 animate-pulse rounded-full bg-sky-300/80" />
208 <span className="hidden xl:inline">
209 {threadStatus}
210 </span>
211 </span>
212 )}
213 <span className="min-w-0 flex-1 truncate text-xs">
214 {thread.title}
215 </span>
216 </div>
217 <span className="shrink-0 text-[10px] text-[#a0a0a0]/30">
179 {formatRelativeTime(thread.createdAt)}218 {formatRelativeTime(thread.createdAt)}
180 </span>219 </span>
181 </button>220 </button>
...@@ -185,7 +224,7 @@ export default function Sidebar() {...@@ -185,7 +224,7 @@ export default function Sidebar() {
185 {/* New thread within project */}224 {/* New thread within project */}
186 <button225 <button
187 type="button"226 type="button"
188 className="flex w-full items-center gap-1 px-2 py-1 text-[10px] text-muted-foreground/60 transition-colors duration-150 hover:text-muted-foreground/80"227 className="flex w-full items-center gap-1 px-2 py-1 text-[10px] text-[#a0a0a0]/40 transition-colors duration-150 hover:text-[#a0a0a0]/60"
189 onClick={() => handleNewThread(project.id)}228 onClick={() => handleNewThread(project.id)}
190 >229 >
191 <span>+</span> New thread230 <span>+</span> New thread
...@@ -197,7 +236,7 @@ export default function Sidebar() {...@@ -197,7 +236,7 @@ export default function Sidebar() {
197 })}236 })}
198237
199 {state.projects.length === 0 && !addingProject && (238 {state.projects.length === 0 && !addingProject && (
200 <div className="px-2 pt-4 text-center text-xs text-muted-foreground/60">239 <div className="px-2 pt-4 text-center text-xs text-[#a0a0a0]/40">
201 No projects yet.240 No projects yet.
202 <br />241 <br />
203 Add one to get started.242 Add one to get started.
...@@ -207,12 +246,12 @@ export default function Sidebar() {...@@ -207,12 +246,12 @@ export default function Sidebar() {
207246
208 {/* Add project form */}247 {/* Add project form */}
209 {addingProject ? (248 {addingProject ? (
210 <div className="border-t border-border p-3">249 <div className="border-t border-white/8 p-3">
211 <p className="mb-2 text-[10px] font-medium uppercase tracking-wider text-muted-foreground/70">250 <p className="mb-2 text-[10px] font-medium uppercase tracking-wider text-[#a0a0a0]/50">
212 Add project251 Add project
213 </p>252 </p>
214 <input253 <input
215 className="mb-2 w-full rounded-md border border-border bg-secondary px-2 py-1.5 font-mono text-xs text-foreground placeholder:text-muted-foreground/40 focus:border-ring focus:outline-none"254 className="mb-2 w-full rounded-md border border-white/10 bg-white/4 px-2 py-1.5 font-mono text-xs text-[#e0e0e0] placeholder:text-[#a0a0a0]/30 focus:border-white/30 focus:outline-none"
216 placeholder="/path/to/project"255 placeholder="/path/to/project"
217 value={newCwd}256 value={newCwd}
218 onChange={(e) => setNewCwd(e.target.value)}257 onChange={(e) => setNewCwd(e.target.value)}
...@@ -224,39 +263,24 @@ export default function Sidebar() {...@@ -224,39 +263,24 @@ export default function Sidebar() {
224 {api && (263 {api && (
225 <button264 <button
226 type="button"265 type="button"
227 className="mb-2 flex w-full items-center justify-center rounded-md border border-border px-2 py-1.5 text-xs text-muted-foreground transition-colors duration-150 hover:bg-secondary disabled:cursor-not-allowed disabled:opacity-60"266 className="mb-2 flex w-full items-center justify-center rounded-md border border-white/10 px-2 py-1.5 text-xs text-[#a0a0a0]/70 transition-colors duration-150 hover:bg-white/4 disabled:cursor-not-allowed disabled:opacity-60"
228 onClick={() => void handlePickFolder()}267 onClick={() => void handlePickFolder()}
229 disabled={isPickingFolder}268 disabled={isPickingFolder}
230 >269 >
231 {isPickingFolder ? "Picking folder..." : "Browse for folder"}270 {isPickingFolder ? "Picking folder..." : "Browse for folder"}
232 </button>271 </button>
233 )}272 )}
234 <select
235 className="mb-2 w-full rounded-md border border-border bg-secondary px-2 py-1.5 font-mono text-xs text-foreground placeholder:text-muted-foreground/40 focus:border-ring focus:outline-none"
236 value={newModel}
237 onChange={(e) => setNewModel(e.target.value)}
238 onKeyDown={(e) => {
239 if (e.key === "Enter") handleAddProject();
240 if (e.key === "Escape") setAddingProject(false);
241 }}
242 >
243 {MODEL_OPTIONS.map((model) => (
244 <option key={model} value={model} className="bg-card">
245 {model}
246 </option>
247 ))}
248 </select>
249 <div className="flex gap-2">273 <div className="flex gap-2">
250 <button274 <button
251 type="button"275 type="button"
252 className="flex-1 rounded-md bg-primary px-2 py-1 text-xs font-medium text-primary-foreground transition-colors duration-150 hover:bg-primary/90"276 className="flex-1 rounded-md bg-white px-2 py-1 text-xs font-medium text-[#0c0c0c] transition-colors duration-150 hover:bg-white/90"
253 onClick={handleAddProject}277 onClick={handleAddProject}
254 >278 >
255 Add279 Add
256 </button>280 </button>
257 <button281 <button
258 type="button"282 type="button"
259 className="flex-1 rounded-md border border-border px-2 py-1 text-xs text-muted-foreground/80 transition-colors duration-150 hover:bg-secondary"283 className="flex-1 rounded-md border border-white/10 px-2 py-1 text-xs text-[#a0a0a0]/60 transition-colors duration-150 hover:bg-white/4"
260 onClick={() => setAddingProject(false)}284 onClick={() => setAddingProject(false)}
261 >285 >
262 Cancel286 Cancel
...@@ -264,10 +288,10 @@ export default function Sidebar() {...@@ -264,10 +288,10 @@ export default function Sidebar() {
264 </div>288 </div>
265 </div>289 </div>
266 ) : (290 ) : (
267 <div className="border-t border-border p-3">291 <div className="border-t border-white/8 p-3">
268 <button292 <button
269 type="button"293 type="button"
270 className="flex w-full items-center justify-center gap-1 rounded-md border border-dashed border-border py-2 text-xs text-muted-foreground/70 transition-colors duration-150 hover:border-ring hover:text-muted-foreground"294 className="flex w-full items-center justify-center gap-1 rounded-md border border-dashed border-white/12 py-2 text-xs text-[#a0a0a0]/50 transition-colors duration-150 hover:border-white/20 hover:text-[#a0a0a0]/70"
271 onClick={() => setAddingProject(true)}295 onClick={() => setAddingProject(true)}
272 >296 >
273 + Add project297 + Add project
apps/renderer/src/components/ui/accordion.tsx deleted-67
...@@ -1,67 +0,0 @@
1import { Accordion as AccordionPrimitive } from "@base-ui/react/accordion";
2import { ChevronDownIcon } from "lucide-react";
3
4import { cn } from "#/lib/utils";
5
6function Accordion(props: AccordionPrimitive.Root.Props) {
7 return <AccordionPrimitive.Root data-slot="accordion" {...props} />;
8}
9
10function AccordionItem({ className, ...props }: AccordionPrimitive.Item.Props) {
11 return (
12 <AccordionPrimitive.Item
13 className={cn("border-b last:border-b-0", className)}
14 data-slot="accordion-item"
15 {...props}
16 />
17 );
18}
19
20function AccordionTrigger({
21 className,
22 children,
23 ...props
24}: AccordionPrimitive.Trigger.Props) {
25 return (
26 <AccordionPrimitive.Header className="flex">
27 <AccordionPrimitive.Trigger
28 className={cn(
29 "flex flex-1 cursor-pointer items-start justify-between gap-4 rounded-md py-4 text-left font-medium text-sm outline-none transition-all focus-visible:ring-[3px] focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-64 data-panel-open:*:data-[slot=accordion-indicator]:rotate-180",
30 className,
31 )}
32 data-slot="accordion-trigger"
33 {...props}
34 >
35 {children}
36 <ChevronDownIcon
37 className="pointer-events-none size-4 shrink-0 translate-y-0.5 opacity-80 transition-transform duration-200 ease-in-out"
38 data-slot="accordion-indicator"
39 />
40 </AccordionPrimitive.Trigger>
41 </AccordionPrimitive.Header>
42 );
43}
44
45function AccordionPanel({
46 className,
47 children,
48 ...props
49}: AccordionPrimitive.Panel.Props) {
50 return (
51 <AccordionPrimitive.Panel
52 className="h-(--accordion-panel-height) overflow-hidden text-muted-foreground text-sm transition-[height] duration-200 ease-in-out data-ending-style:h-0 data-starting-style:h-0"
53 data-slot="accordion-panel"
54 {...props}
55 >
56 <div className={cn("pt-0 pb-4", className)}>{children}</div>
57 </AccordionPrimitive.Panel>
58 );
59}
60
61export {
62 Accordion,
63 AccordionItem,
64 AccordionTrigger,
65 AccordionPanel,
66 AccordionPanel as AccordionContent,
67};
apps/renderer/src/components/ui/alert-dialog.tsx deleted-167
...@@ -1,167 +0,0 @@
1import { AlertDialog as AlertDialogPrimitive } from "@base-ui/react/alert-dialog";
2
3import { cn } from "#/lib/utils";
4
5const AlertDialogCreateHandle = AlertDialogPrimitive.createHandle;
6
7const AlertDialog = AlertDialogPrimitive.Root;
8
9const AlertDialogPortal = AlertDialogPrimitive.Portal;
10
11function AlertDialogTrigger(props: AlertDialogPrimitive.Trigger.Props) {
12 return (
13 <AlertDialogPrimitive.Trigger data-slot="alert-dialog-trigger" {...props} />
14 );
15}
16
17function AlertDialogBackdrop({
18 className,
19 ...props
20}: AlertDialogPrimitive.Backdrop.Props) {
21 return (
22 <AlertDialogPrimitive.Backdrop
23 className={cn(
24 "fixed inset-0 z-50 bg-black/32 backdrop-blur-sm transition-all duration-200 ease-out data-ending-style:opacity-0 data-starting-style:opacity-0",
25 className,
26 )}
27 data-slot="alert-dialog-backdrop"
28 {...props}
29 />
30 );
31}
32
33function AlertDialogViewport({
34 className,
35 ...props
36}: AlertDialogPrimitive.Viewport.Props) {
37 return (
38 <AlertDialogPrimitive.Viewport
39 className={cn(
40 "fixed inset-0 z-50 grid grid-rows-[1fr_auto_3fr] justify-items-center p-4",
41 className,
42 )}
43 data-slot="alert-dialog-viewport"
44 {...props}
45 />
46 );
47}
48
49function AlertDialogPopup({
50 className,
51 bottomStickOnMobile = true,
52 ...props
53}: AlertDialogPrimitive.Popup.Props & {
54 bottomStickOnMobile?: boolean;
55}) {
56 return (
57 <AlertDialogPortal>
58 <AlertDialogBackdrop />
59 <AlertDialogViewport
60 className={cn(
61 bottomStickOnMobile &&
62 "max-sm:grid-rows-[1fr_auto] max-sm:p-0 max-sm:pt-12",
63 )}
64 >
65 <AlertDialogPrimitive.Popup
66 className={cn(
67 "-translate-y-[calc(1.25rem*var(--nested-dialogs))] relative row-start-2 flex max-h-full min-h-0 w-full min-w-0 max-w-lg scale-[calc(1-0.1*var(--nested-dialogs))] flex-col rounded-2xl border bg-popover not-dark:bg-clip-padding text-popover-foreground opacity-[calc(1-0.1*var(--nested-dialogs))] shadow-lg/5 transition-[scale,opacity,translate] duration-200 ease-in-out will-change-transform before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--radius-2xl)-1px)] before:shadow-[0_1px_--theme(--color-black/4%)] data-nested:data-ending-style:translate-y-8 data-nested:data-starting-style:translate-y-8 data-nested-dialog-open:origin-top data-ending-style:scale-98 data-starting-style:scale-98 data-ending-style:opacity-0 data-starting-style:opacity-0 dark:before:shadow-[0_-1px_--theme(--color-white/6%)]",
68 bottomStickOnMobile &&
69 "max-sm:max-w-none max-sm:rounded-none max-sm:border-x-0 max-sm:border-t max-sm:border-b-0 max-sm:opacity-[calc(1-min(var(--nested-dialogs),1))] max-sm:data-ending-style:translate-y-4 max-sm:data-starting-style:translate-y-4 max-sm:before:hidden max-sm:before:rounded-none",
70 className,
71 )}
72 data-slot="alert-dialog-popup"
73 {...props}
74 />
75 </AlertDialogViewport>
76 </AlertDialogPortal>
77 );
78}
79
80function AlertDialogHeader({
81 className,
82 ...props
83}: React.ComponentProps<"div">) {
84 return (
85 <div
86 className={cn(
87 "flex flex-col gap-2 p-6 text-center max-sm:pb-4 sm:text-left",
88 className,
89 )}
90 data-slot="alert-dialog-header"
91 {...props}
92 />
93 );
94}
95
96function AlertDialogFooter({
97 className,
98 variant = "default",
99 ...props
100}: React.ComponentProps<"div"> & {
101 variant?: "default" | "bare";
102}) {
103 return (
104 <div
105 className={cn(
106 "flex flex-col-reverse gap-2 px-6 sm:flex-row sm:justify-end sm:rounded-b-[calc(var(--radius-2xl)-1px)]",
107 variant === "default" && "border-t bg-muted/72 py-4",
108 variant === "bare" && "pb-6",
109 className,
110 )}
111 data-slot="alert-dialog-footer"
112 {...props}
113 />
114 );
115}
116
117function AlertDialogTitle({
118 className,
119 ...props
120}: AlertDialogPrimitive.Title.Props) {
121 return (
122 <AlertDialogPrimitive.Title
123 className={cn(
124 "font-heading font-semibold text-xl leading-none",
125 className,
126 )}
127 data-slot="alert-dialog-title"
128 {...props}
129 />
130 );
131}
132
133function AlertDialogDescription({
134 className,
135 ...props
136}: AlertDialogPrimitive.Description.Props) {
137 return (
138 <AlertDialogPrimitive.Description
139 className={cn("text-muted-foreground text-sm", className)}
140 data-slot="alert-dialog-description"
141 {...props}
142 />
143 );
144}
145
146function AlertDialogClose(props: AlertDialogPrimitive.Close.Props) {
147 return (
148 <AlertDialogPrimitive.Close data-slot="alert-dialog-close" {...props} />
149 );
150}
151
152export {
153 AlertDialogCreateHandle,
154 AlertDialog,
155 AlertDialogPortal,
156 AlertDialogBackdrop,
157 AlertDialogBackdrop as AlertDialogOverlay,
158 AlertDialogTrigger,
159 AlertDialogPopup,
160 AlertDialogPopup as AlertDialogContent,
161 AlertDialogHeader,
162 AlertDialogFooter,
163 AlertDialogTitle,
164 AlertDialogDescription,
165 AlertDialogClose,
166 AlertDialogViewport,
167};
apps/renderer/src/components/ui/alert.tsx deleted-80
...@@ -1,80 +0,0 @@
1import { type VariantProps, cva } from "class-variance-authority";
2import type * as React from "react";
3
4import { cn } from "#/lib/utils";
5
6const alertVariants = cva(
7 "relative grid w-full items-start gap-x-2 gap-y-0.5 rounded-xl border px-3.5 py-3 text-card-foreground text-sm has-[>svg]:has-data-[slot=alert-action]:grid-cols-[calc(var(--spacing)*4)_1fr_auto] has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] has-data-[slot=alert-action]:grid-cols-[1fr_auto] has-[>svg]:gap-x-2 [&>svg]:h-lh [&>svg]:w-4",
8 {
9 defaultVariants: {
10 variant: "default",
11 },
12 variants: {
13 variant: {
14 default:
15 "bg-transparent dark:bg-input/32 [&>svg]:text-muted-foreground",
16 error:
17 "border-destructive/32 bg-destructive/4 [&>svg]:text-destructive",
18 info: "border-info/32 bg-info/4 [&>svg]:text-info",
19 success: "border-success/32 bg-success/4 [&>svg]:text-success",
20 warning: "border-warning/32 bg-warning/4 [&>svg]:text-warning",
21 },
22 },
23 },
24);
25
26function Alert({
27 className,
28 variant,
29 ...props
30}: React.ComponentProps<"div"> & VariantProps<typeof alertVariants>) {
31 return (
32 <div
33 className={cn(alertVariants({ variant }), className)}
34 data-slot="alert"
35 role="alert"
36 {...props}
37 />
38 );
39}
40
41function AlertTitle({ className, ...props }: React.ComponentProps<"div">) {
42 return (
43 <div
44 className={cn("font-medium [svg~&]:col-start-2", className)}
45 data-slot="alert-title"
46 {...props}
47 />
48 );
49}
50
51function AlertDescription({
52 className,
53 ...props
54}: React.ComponentProps<"div">) {
55 return (
56 <div
57 className={cn(
58 "flex flex-col gap-2.5 text-muted-foreground [svg~&]:col-start-2",
59 className,
60 )}
61 data-slot="alert-description"
62 {...props}
63 />
64 );
65}
66
67function AlertAction({ className, ...props }: React.ComponentProps<"div">) {
68 return (
69 <div
70 className={cn(
71 "flex gap-1 max-sm:col-start-2 max-sm:mt-2 sm:row-start-1 sm:row-end-3 sm:self-center sm:[[data-slot=alert-description]~&]:col-start-2 sm:[[data-slot=alert-title]~&]:col-start-2 sm:[svg~&]:col-start-2 sm:[svg~[data-slot=alert-description]~&]:col-start-3 sm:[svg~[data-slot=alert-title]~&]:col-start-3",
72 className,
73 )}
74 data-slot="alert-action"
75 {...props}
76 />
77 );
78}
79
80export { Alert, AlertTitle, AlertDescription, AlertAction };
apps/renderer/src/components/ui/autocomplete.tsx deleted-308
...@@ -1,308 +0,0 @@
1import { Autocomplete as AutocompletePrimitive } from "@base-ui/react/autocomplete";
2import { ChevronsUpDownIcon, XIcon } from "lucide-react";
3
4import { Input } from "#/components/ui/input";
5import { ScrollArea } from "#/components/ui/scroll-area";
6import { cn } from "#/lib/utils";
7
8const Autocomplete = AutocompletePrimitive.Root;
9
10function AutocompleteInput({
11 className,
12 showTrigger = false,
13 showClear = false,
14 startAddon,
15 size,
16 ...props
17}: Omit<AutocompletePrimitive.Input.Props, "size"> & {
18 showTrigger?: boolean;
19 showClear?: boolean;
20 startAddon?: React.ReactNode;
21 size?: "sm" | "default" | "lg" | number;
22 ref?: React.Ref<HTMLInputElement>;
23}) {
24 const sizeValue = (size ?? "default") as "sm" | "default" | "lg" | number;
25
26 return (
27 <div className="relative not-has-[>*.w-full]:w-fit w-full text-foreground has-disabled:opacity-64">
28 {startAddon && (
29 <div
30 aria-hidden="true"
31 className="[&_svg]:-mx-0.5 pointer-events-none absolute inset-y-0 start-px z-10 flex items-center ps-[calc(--spacing(3)-1px)] opacity-80 has-[+[data-size=sm]]:ps-[calc(--spacing(2.5)-1px)] [&_svg:not([class*='size-'])]:size-4.5 sm:[&_svg:not([class*='size-'])]:size-4"
32 data-slot="autocomplete-start-addon"
33 >
34 {startAddon}
35 </div>
36 )}
37 <AutocompletePrimitive.Input
38 className={cn(
39 startAddon &&
40 "data-[size=sm]:*:data-[slot=autocomplete-input]:ps-[calc(--spacing(7.5)-1px)] *:data-[slot=autocomplete-input]:ps-[calc(--spacing(8.5)-1px)] sm:data-[size=sm]:*:data-[slot=autocomplete-input]:ps-[calc(--spacing(7)-1px)] sm:*:data-[slot=autocomplete-input]:ps-[calc(--spacing(8)-1px)]",
41 sizeValue === "sm"
42 ? "has-[+[data-slot=autocomplete-trigger],+[data-slot=autocomplete-clear]]:*:data-[slot=autocomplete-input]:pe-6.5"
43 : "has-[+[data-slot=autocomplete-trigger],+[data-slot=autocomplete-clear]]:*:data-[slot=autocomplete-input]:pe-7",
44 className,
45 )}
46 data-slot="autocomplete-input"
47 render={<Input nativeInput size={sizeValue} />}
48 {...props}
49 />
50 {showTrigger && (
51 <AutocompleteTrigger
52 className={cn(
53 "-translate-y-1/2 absolute top-1/2 inline-flex size-8 shrink-0 cursor-pointer items-center justify-center rounded-md border border-transparent opacity-80 outline-none transition-colors pointer-coarse:after:absolute pointer-coarse:after:min-h-11 pointer-coarse:after:min-w-11 hover:opacity-100 has-[+[data-slot=autocomplete-clear]]:hidden sm:size-7 [&_svg:not([class*='size-'])]:size-4.5 sm:[&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0",
54 sizeValue === "sm" ? "end-0" : "end-0.5",
55 )}
56 >
57 <ChevronsUpDownIcon />
58 </AutocompleteTrigger>
59 )}
60 {showClear && (
61 <AutocompleteClear
62 className={cn(
63 "-translate-y-1/2 absolute top-1/2 inline-flex size-8 shrink-0 cursor-pointer items-center justify-center rounded-md border border-transparent opacity-80 outline-none transition-colors pointer-coarse:after:absolute pointer-coarse:after:min-h-11 pointer-coarse:after:min-w-11 hover:opacity-100 has-[+[data-slot=autocomplete-clear]]:hidden sm:size-7 [&_svg:not([class*='size-'])]:size-4.5 sm:[&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0",
64 sizeValue === "sm" ? "end-0" : "end-0.5",
65 )}
66 >
67 <XIcon />
68 </AutocompleteClear>
69 )}
70 </div>
71 );
72}
73
74function AutocompletePopup({
75 className,
76 children,
77 side = "bottom",
78 sideOffset = 4,
79 alignOffset,
80 align = "start",
81 ...props
82}: AutocompletePrimitive.Popup.Props & {
83 align?: AutocompletePrimitive.Positioner.Props["align"];
84 sideOffset?: AutocompletePrimitive.Positioner.Props["sideOffset"];
85 alignOffset?: AutocompletePrimitive.Positioner.Props["alignOffset"];
86 side?: AutocompletePrimitive.Positioner.Props["side"];
87}) {
88 return (
89 <AutocompletePrimitive.Portal>
90 <AutocompletePrimitive.Positioner
91 align={align}
92 alignOffset={alignOffset}
93 className="z-50 select-none"
94 data-slot="autocomplete-positioner"
95 side={side}
96 sideOffset={sideOffset}
97 >
98 <span
99 className={cn(
100 "relative flex max-h-full min-w-(--anchor-width) max-w-(--available-width) origin-(--transform-origin) rounded-lg border bg-popover not-dark:bg-clip-padding shadow-lg/5 transition-[scale,opacity] before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--radius-lg)-1px)] before:shadow-[0_1px_--theme(--color-black/4%)] dark:before:shadow-[0_-1px_--theme(--color-white/6%)]",
101 className,
102 )}
103 >
104 <AutocompletePrimitive.Popup
105 className="flex max-h-[min(var(--available-height),23rem)] flex-1 flex-col text-foreground"
106 data-slot="autocomplete-popup"
107 {...props}
108 >
109 {children}
110 </AutocompletePrimitive.Popup>
111 </span>
112 </AutocompletePrimitive.Positioner>
113 </AutocompletePrimitive.Portal>
114 );
115}
116
117function AutocompleteItem({
118 className,
119 children,
120 ...props
121}: AutocompletePrimitive.Item.Props) {
122 return (
123 <AutocompletePrimitive.Item
124 className={cn(
125 "flex min-h-8 cursor-default select-none items-center rounded-sm px-2 py-1 text-base outline-none data-disabled:pointer-events-none data-highlighted:bg-accent data-highlighted:text-accent-foreground data-disabled:opacity-64 sm:min-h-7 sm:text-sm",
126 className,
127 )}
128 data-slot="autocomplete-item"
129 {...props}
130 >
131 {children}
132 </AutocompletePrimitive.Item>
133 );
134}
135
136function AutocompleteSeparator({
137 className,
138 ...props
139}: AutocompletePrimitive.Separator.Props) {
140 return (
141 <AutocompletePrimitive.Separator
142 className={cn("mx-2 my-1 h-px bg-border last:hidden", className)}
143 data-slot="autocomplete-separator"
144 {...props}
145 />
146 );
147}
148
149function AutocompleteGroup({
150 className,
151 ...props
152}: AutocompletePrimitive.Group.Props) {
153 return (
154 <AutocompletePrimitive.Group
155 className={cn("[[role=group]+&]:mt-1.5", className)}
156 data-slot="autocomplete-group"
157 {...props}
158 />
159 );
160}
161
162function AutocompleteGroupLabel({
163 className,
164 ...props
165}: AutocompletePrimitive.GroupLabel.Props) {
166 return (
167 <AutocompletePrimitive.GroupLabel
168 className={cn(
169 "px-2 py-1.5 font-medium text-muted-foreground text-xs",
170 className,
171 )}
172 data-slot="autocomplete-group-label"
173 {...props}
174 />
175 );
176}
177
178function AutocompleteEmpty({
179 className,
180 ...props
181}: AutocompletePrimitive.Empty.Props) {
182 return (
183 <AutocompletePrimitive.Empty
184 className={cn(
185 "not-empty:p-2 text-center text-base text-muted-foreground sm:text-sm",
186 className,
187 )}
188 data-slot="autocomplete-empty"
189 {...props}
190 />
191 );
192}
193
194function AutocompleteRow({
195 className,
196 ...props
197}: AutocompletePrimitive.Row.Props) {
198 return (
199 <AutocompletePrimitive.Row
200 className={className}
201 data-slot="autocomplete-row"
202 {...props}
203 />
204 );
205}
206
207function AutocompleteValue({ ...props }: AutocompletePrimitive.Value.Props) {
208 return (
209 <AutocompletePrimitive.Value data-slot="autocomplete-value" {...props} />
210 );
211}
212
213function AutocompleteList({
214 className,
215 ...props
216}: AutocompletePrimitive.List.Props) {
217 return (
218 <ScrollArea scrollbarGutter scrollFade>
219 <AutocompletePrimitive.List
220 className={cn(
221 "not-empty:scroll-py-1 not-empty:p-1 in-data-has-overflow-y:pe-3",
222 className,
223 )}
224 data-slot="autocomplete-list"
225 {...props}
226 />
227 </ScrollArea>
228 );
229}
230
231function AutocompleteClear({
232 className,
233 ...props
234}: AutocompletePrimitive.Clear.Props) {
235 return (
236 <AutocompletePrimitive.Clear
237 className={cn(
238 "-translate-y-1/2 absolute end-0.5 top-1/2 inline-flex size-8 shrink-0 cursor-pointer items-center justify-center rounded-md border border-transparent opacity-80 outline-none transition-[color,background-color,box-shadow,opacity] pointer-coarse:after:absolute pointer-coarse:after:min-h-11 pointer-coarse:after:min-w-11 hover:opacity-100 sm:size-7 [&_svg:not([class*='size-'])]:size-4.5 sm:[&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0",
239 className,
240 )}
241 data-slot="autocomplete-clear"
242 {...props}
243 >
244 <XIcon />
245 </AutocompletePrimitive.Clear>
246 );
247}
248
249function AutocompleteStatus({
250 className,
251 ...props
252}: AutocompletePrimitive.Status.Props) {
253 return (
254 <AutocompletePrimitive.Status
255 className={cn(
256 "px-3 py-2 font-medium text-muted-foreground text-xs empty:m-0 empty:p-0",
257 className,
258 )}
259 data-slot="autocomplete-status"
260 {...props}
261 />
262 );
263}
264
265function AutocompleteCollection({
266 ...props
267}: AutocompletePrimitive.Collection.Props) {
268 return (
269 <AutocompletePrimitive.Collection
270 data-slot="autocomplete-collection"
271 {...props}
272 />
273 );
274}
275
276function AutocompleteTrigger({
277 className,
278 ...props
279}: AutocompletePrimitive.Trigger.Props) {
280 return (
281 <AutocompletePrimitive.Trigger
282 className={className}
283 data-slot="autocomplete-trigger"
284 {...props}
285 />
286 );
287}
288
289const useAutocompleteFilter = AutocompletePrimitive.useFilter;
290
291export {
292 Autocomplete,
293 AutocompleteInput,
294 AutocompleteTrigger,
295 AutocompletePopup,
296 AutocompleteItem,
297 AutocompleteSeparator,
298 AutocompleteGroup,
299 AutocompleteGroupLabel,
300 AutocompleteEmpty,
301 AutocompleteValue,
302 AutocompleteList,
303 AutocompleteClear,
304 AutocompleteStatus,
305 AutocompleteRow,
306 AutocompleteCollection,
307 useAutocompleteFilter,
308};
apps/renderer/src/components/ui/avatar.tsx deleted-44
...@@ -1,44 +0,0 @@
1import { Avatar as AvatarPrimitive } from "@base-ui/react/avatar";
2
3import { cn } from "#/lib/utils";
4
5function Avatar({ className, ...props }: AvatarPrimitive.Root.Props) {
6 return (
7 <AvatarPrimitive.Root
8 className={cn(
9 "inline-flex size-8 shrink-0 select-none items-center justify-center overflow-hidden rounded-full bg-background align-middle font-medium text-xs",
10 className,
11 )}
12 data-slot="avatar"
13 {...props}
14 />
15 );
16}
17
18function AvatarImage({ className, ...props }: AvatarPrimitive.Image.Props) {
19 return (
20 <AvatarPrimitive.Image
21 className={cn("size-full object-cover", className)}
22 data-slot="avatar-image"
23 {...props}
24 />
25 );
26}
27
28function AvatarFallback({
29 className,
30 ...props
31}: AvatarPrimitive.Fallback.Props) {
32 return (
33 <AvatarPrimitive.Fallback
34 className={cn(
35 "flex size-full items-center justify-center rounded-full bg-muted",
36 className,
37 )}
38 data-slot="avatar-fallback"
39 {...props}
40 />
41 );
42}
43
44export { Avatar, AvatarImage, AvatarFallback };
apps/renderer/src/components/ui/badge.tsx deleted-58
...@@ -1,58 +0,0 @@
1import { mergeProps } from "@base-ui/react/merge-props";
2import { useRender } from "@base-ui/react/use-render";
3import { type VariantProps, cva } from "class-variance-authority";
4
5import { cn } from "#/lib/utils";
6
7const badgeVariants = cva(
8 "relative inline-flex shrink-0 items-center justify-center gap-1 whitespace-nowrap rounded-sm border border-transparent font-medium outline-none transition-shadow focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background disabled:pointer-events-none disabled:opacity-64 [&_svg:not([class*='opacity-'])]:opacity-80 [&_svg:not([class*='size-'])]:size-3.5 sm:[&_svg:not([class*='size-'])]:size-3 [&_svg]:pointer-events-none [&_svg]:shrink-0 [button,a&]:cursor-pointer [button,a&]:pointer-coarse:after:absolute [button,a&]:pointer-coarse:after:size-full [button,a&]:pointer-coarse:after:min-h-11 [button,a&]:pointer-coarse:after:min-w-11",
9 {
10 defaultVariants: {
11 size: "default",
12 variant: "default",
13 },
14 variants: {
15 size: {
16 default:
17 "h-5.5 min-w-5.5 px-[calc(--spacing(1)-1px)] text-sm sm:h-4.5 sm:min-w-4.5 sm:text-xs",
18 lg: "h-6.5 min-w-6.5 px-[calc(--spacing(1.5)-1px)] text-base sm:h-5.5 sm:min-w-5.5 sm:text-sm",
19 sm: "h-5 min-w-5 rounded-[calc(var(--radius-sm)-2px)] px-[calc(--spacing(1)-1px)] text-xs sm:h-4 sm:min-w-4 sm:text-[.625rem]",
20 },
21 variant: {
22 default:
23 "bg-primary text-primary-foreground [button,a&]:hover:bg-primary/90",
24 destructive:
25 "bg-destructive text-white [button,a&]:hover:bg-destructive/90",
26 error:
27 "bg-destructive/8 text-destructive-foreground dark:bg-destructive/16",
28 info: "bg-info/8 text-info-foreground dark:bg-info/16",
29 outline:
30 "border-input bg-background text-foreground dark:bg-input/32 [button,a&]:hover:bg-accent/50 dark:[button,a&]:hover:bg-input/48",
31 secondary:
32 "bg-secondary text-secondary-foreground [button,a&]:hover:bg-secondary/90",
33 success: "bg-success/8 text-success-foreground dark:bg-success/16",
34 warning: "bg-warning/8 text-warning-foreground dark:bg-warning/16",
35 },
36 },
37 },
38);
39
40interface BadgeProps extends useRender.ComponentProps<"span"> {
41 variant?: VariantProps<typeof badgeVariants>["variant"];
42 size?: VariantProps<typeof badgeVariants>["size"];
43}
44
45function Badge({ className, variant, size, render, ...props }: BadgeProps) {
46 const defaultProps = {
47 className: cn(badgeVariants({ className, size, variant })),
48 "data-slot": "badge",
49 };
50
51 return useRender({
52 defaultTagName: "span",
53 props: mergeProps<"span">(defaultProps, props),
54 render,
55 });
56}
57
58export { Badge, badgeVariants };
apps/renderer/src/components/ui/button.tsx deleted-71
...@@ -1,71 +0,0 @@
1import { mergeProps } from "@base-ui/react/merge-props";
2import { useRender } from "@base-ui/react/use-render";
3import { type VariantProps, cva } from "class-variance-authority";
4import type * as React from "react";
5
6import { cn } from "#/lib/utils";
7
8const buttonVariants = cva(
9 "[&_svg]:-mx-0.5 relative inline-flex shrink-0 cursor-pointer items-center justify-center gap-2 whitespace-nowrap rounded-lg border font-medium text-base outline-none transition-shadow before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--radius-lg)-1px)] pointer-coarse:after:absolute pointer-coarse:after:size-full pointer-coarse:after:min-h-11 pointer-coarse:after:min-w-11 focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background disabled:pointer-events-none disabled:opacity-64 sm:text-sm [&_svg:not([class*='opacity-'])]:opacity-80 [&_svg:not([class*='size-'])]:size-4.5 sm:[&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0",
10 {
11 defaultVariants: {
12 size: "default",
13 variant: "default",
14 },
15 variants: {
16 size: {
17 default: "h-9 px-[calc(--spacing(3)-1px)] sm:h-8",
18 icon: "size-9 sm:size-8",
19 "icon-lg": "size-10 sm:size-9",
20 "icon-sm": "size-8 sm:size-7",
21 "icon-xl":
22 "size-11 sm:size-10 [&_svg:not([class*='size-'])]:size-5 sm:[&_svg:not([class*='size-'])]:size-4.5",
23 "icon-xs":
24 "size-7 rounded-md before:rounded-[calc(var(--radius-md)-1px)] sm:size-6 not-in-data-[slot=input-group]:[&_svg:not([class*='size-'])]:size-4 sm:not-in-data-[slot=input-group]:[&_svg:not([class*='size-'])]:size-3.5",
25 lg: "h-10 px-[calc(--spacing(3.5)-1px)] sm:h-9",
26 sm: "h-8 gap-1.5 px-[calc(--spacing(2.5)-1px)] sm:h-7",
27 xl: "h-11 px-[calc(--spacing(4)-1px)] text-lg sm:h-10 sm:text-base [&_svg:not([class*='size-'])]:size-5 sm:[&_svg:not([class*='size-'])]:size-4.5",
28 xs: "h-7 gap-1 rounded-md px-[calc(--spacing(2)-1px)] text-sm before:rounded-[calc(var(--radius-md)-1px)] sm:h-6 sm:text-xs [&_svg:not([class*='size-'])]:size-4 sm:[&_svg:not([class*='size-'])]:size-3.5",
29 },
30 variant: {
31 default:
32 "not-disabled:inset-shadow-[0_1px_--theme(--color-white/16%)] border-primary bg-primary text-primary-foreground shadow-primary/24 shadow-xs [:active,[data-pressed]]:inset-shadow-[0_1px_--theme(--color-black/8%)] [:disabled,:active,[data-pressed]]:shadow-none [:hover,[data-pressed]]:bg-primary/90",
33 destructive:
34 "not-disabled:inset-shadow-[0_1px_--theme(--color-white/16%)] border-destructive bg-destructive text-white shadow-destructive/24 shadow-xs [:active,[data-pressed]]:inset-shadow-[0_1px_--theme(--color-black/8%)] [:disabled,:active,[data-pressed]]:shadow-none [:hover,[data-pressed]]:bg-destructive/90",
35 "destructive-outline":
36 "border-input bg-popover not-dark:bg-clip-padding text-destructive-foreground shadow-xs/5 not-disabled:not-active:not-data-pressed:before:shadow-[0_1px_--theme(--color-black/4%)] dark:bg-input/32 dark:not-disabled:before:shadow-[0_-1px_--theme(--color-white/2%)] dark:not-disabled:not-active:not-data-pressed:before:shadow-[0_-1px_--theme(--color-white/6%)] [:disabled,:active,[data-pressed]]:shadow-none [:hover,[data-pressed]]:border-destructive/32 [:hover,[data-pressed]]:bg-destructive/4",
37 ghost:
38 "border-transparent text-foreground data-pressed:bg-accent [:hover,[data-pressed]]:bg-accent",
39 link: "border-transparent underline-offset-4 [:hover,[data-pressed]]:underline",
40 outline:
41 "border-input bg-popover not-dark:bg-clip-padding text-foreground shadow-xs/5 not-disabled:not-active:not-data-pressed:before:shadow-[0_1px_--theme(--color-black/4%)] dark:bg-input/32 dark:not-disabled:before:shadow-[0_-1px_--theme(--color-white/2%)] dark:not-disabled:not-active:not-data-pressed:before:shadow-[0_-1px_--theme(--color-white/6%)] [:disabled,:active,[data-pressed]]:shadow-none [:hover,[data-pressed]]:bg-accent/50 dark:[:hover,[data-pressed]]:bg-input/64",
42 secondary:
43 "border-transparent bg-secondary text-secondary-foreground [:active,[data-pressed]]:bg-secondary/80 [:hover,[data-pressed]]:bg-secondary/90",
44 },
45 },
46 },
47);
48
49interface ButtonProps extends useRender.ComponentProps<"button"> {
50 variant?: VariantProps<typeof buttonVariants>["variant"];
51 size?: VariantProps<typeof buttonVariants>["size"];
52}
53
54function Button({ className, variant, size, render, ...props }: ButtonProps) {
55 const typeValue: React.ButtonHTMLAttributes<HTMLButtonElement>["type"] =
56 render ? undefined : "button";
57
58 const defaultProps = {
59 className: cn(buttonVariants({ className, size, variant })),
60 "data-slot": "button",
61 type: typeValue,
62 };
63
64 return useRender({
65 defaultTagName: "button",
66 props: mergeProps<"button">(defaultProps, props),
67 render,
68 });
69}
70
71export { Button, buttonVariants };
apps/renderer/src/components/ui/card.tsx deleted-242
...@@ -1,242 +0,0 @@
1import { mergeProps } from "@base-ui/react/merge-props";
2import { useRender } from "@base-ui/react/use-render";
3
4import { cn } from "#/lib/utils";
5
6function Card({
7 className,
8 render,
9 ...props
10}: useRender.ComponentProps<"div">) {
11 const defaultProps = {
12 className: cn(
13 "relative flex flex-col rounded-2xl border bg-card not-dark:bg-clip-padding text-card-foreground shadow-xs/5 before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--radius-2xl)-1px)] before:shadow-[0_1px_--theme(--color-black/4%)] dark:before:shadow-[0_-1px_--theme(--color-white/6%)]",
14 className,
15 ),
16 "data-slot": "card",
17 };
18
19 return useRender({
20 defaultTagName: "div",
21 props: mergeProps<"div">(defaultProps, props),
22 render,
23 });
24}
25
26function CardFrame({
27 className,
28 render,
29 ...props
30}: useRender.ComponentProps<"div">) {
31 const defaultProps = {
32 className: cn(
33 "flex flex-col relative rounded-2xl border bg-card before:bg-muted/72 not-dark:bg-clip-padding text-card-foreground shadow-xs/5 before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--radius-2xl)-1px)] before:shadow-[0_1px_--theme(--color-black/4%)] dark:before:shadow-[0_-1px_--theme(--color-white/6%)] *:data-[slot=card]:-m-px *:not-last:data-[slot=card]:rounded-b-xl *:not-last:data-[slot=card]:before:rounded-b-[calc(var(--radius-xl)-1px)] *:not-first:data-[slot=card]:rounded-t-xl *:not-first:data-[slot=card]:before:rounded-t-[calc(var(--radius-xl)-1px)] *:data-[slot=card]:[clip-path:inset(-1rem_1px)] *:data-[slot=card]:first:[clip-path:inset(1px_1px_-1rem_1px_round_calc(var(--radius-2xl)-1px))] *:data-[slot=card]:last:[clip-path:inset(-1rem_1px_1px_1px_round_calc(var(--radius-2xl)-1px))] *:data-[slot=card]:shadow-none *:data-[slot=card]:before:hidden *:data-[slot=card]:bg-clip-padding",
34 className,
35 ),
36 "data-slot": "card-frame",
37 };
38
39 return useRender({
40 defaultTagName: "div",
41 props: mergeProps<"div">(defaultProps, props),
42 render,
43 });
44}
45
46function CardFrameHeader({
47 className,
48 render,
49 ...props
50}: useRender.ComponentProps<"div">) {
51 const defaultProps = {
52 className: cn("relative flex flex-col px-6 py-4", className),
53 "data-slot": "card-frame-header",
54 };
55
56 return useRender({
57 defaultTagName: "div",
58 props: mergeProps<"div">(defaultProps, props),
59 render,
60 });
61}
62
63function CardFrameTitle({
64 className,
65 render,
66 ...props
67}: useRender.ComponentProps<"div">) {
68 const defaultProps = {
69 className: cn("font-semibold text-sm", className),
70 "data-slot": "card-frame-title",
71 };
72
73 return useRender({
74 defaultTagName: "div",
75 props: mergeProps<"div">(defaultProps, props),
76 render,
77 });
78}
79
80function CardFrameDescription({
81 className,
82 render,
83 ...props
84}: useRender.ComponentProps<"div">) {
85 const defaultProps = {
86 className: cn("text-muted-foreground text-sm", className),
87 "data-slot": "card-frame-description",
88 };
89
90 return useRender({
91 defaultTagName: "div",
92 props: mergeProps<"div">(defaultProps, props),
93 render,
94 });
95}
96
97function CardFrameFooter({
98 className,
99 render,
100 ...props
101}: useRender.ComponentProps<"div">) {
102 const defaultProps = {
103 className: cn("px-6 py-4", className),
104 "data-slot": "card-frame-footer",
105 };
106
107 return useRender({
108 defaultTagName: "div",
109 props: mergeProps<"div">(defaultProps, props),
110 render,
111 });
112}
113
114function CardHeader({
115 className,
116 render,
117 ...props
118}: useRender.ComponentProps<"div">) {
119 const defaultProps = {
120 className: cn(
121 "grid auto-rows-min grid-rows-[auto_auto] items-start gap-1.5 p-6 in-[[data-slot=card]:has(>[data-slot=card-panel])]:pb-4 has-data-[slot=card-action]:grid-cols-[1fr_auto]",
122 className,
123 ),
124 "data-slot": "card-header",
125 };
126
127 return useRender({
128 defaultTagName: "div",
129 props: mergeProps<"div">(defaultProps, props),
130 render,
131 });
132}
133
134function CardTitle({
135 className,
136 render,
137 ...props
138}: useRender.ComponentProps<"div">) {
139 const defaultProps = {
140 className: cn("font-semibold text-lg leading-none", className),
141 "data-slot": "card-title",
142 };
143
144 return useRender({
145 defaultTagName: "div",
146 props: mergeProps<"div">(defaultProps, props),
147 render,
148 });
149}
150
151function CardDescription({
152 className,
153 render,
154 ...props
155}: useRender.ComponentProps<"div">) {
156 const defaultProps = {
157 className: cn("text-muted-foreground text-sm", className),
158 "data-slot": "card-description",
159 };
160
161 return useRender({
162 defaultTagName: "div",
163 props: mergeProps<"div">(defaultProps, props),
164 render,
165 });
166}
167
168function CardAction({
169 className,
170 render,
171 ...props
172}: useRender.ComponentProps<"div">) {
173 const defaultProps = {
174 className: cn(
175 "col-start-2 row-span-2 row-start-1 self-start justify-self-end inline-flex",
176 className,
177 ),
178 "data-slot": "card-action",
179 };
180
181 return useRender({
182 defaultTagName: "div",
183 props: mergeProps<"div">(defaultProps, props),
184 render,
185 });
186}
187
188function CardPanel({
189 className,
190 render,
191 ...props
192}: useRender.ComponentProps<"div">) {
193 const defaultProps = {
194 className: cn(
195 "flex-1 p-6 in-[[data-slot=card]:has(>[data-slot=card-header]:not(.border-b))]:pt-0 in-[[data-slot=card]:has(>[data-slot=card-footer]:not(.border-t))]:pb-0",
196 className,
197 ),
198 "data-slot": "card-panel",
199 };
200
201 return useRender({
202 defaultTagName: "div",
203 props: mergeProps<"div">(defaultProps, props),
204 render,
205 });
206}
207
208function CardFooter({
209 className,
210 render,
211 ...props
212}: useRender.ComponentProps<"div">) {
213 const defaultProps = {
214 className: cn(
215 "flex items-center p-6 in-[[data-slot=card]:has(>[data-slot=card-panel])]:pt-4",
216 className,
217 ),
218 "data-slot": "card-footer",
219 };
220
221 return useRender({
222 defaultTagName: "div",
223 props: mergeProps<"div">(defaultProps, props),
224 render,
225 });
226}
227
228export {
229 Card,
230 CardFrame,
231 CardFrameHeader,
232 CardFrameTitle,
233 CardFrameDescription,
234 CardFrameFooter,
235 CardAction,
236 CardDescription,
237 CardFooter,
238 CardHeader,
239 CardPanel,
240 CardPanel as CardContent,
241 CardTitle,
242};
apps/renderer/src/components/ui/checkbox-group.tsx deleted-14
...@@ -1,14 +0,0 @@
1import { CheckboxGroup as CheckboxGroupPrimitive } from "@base-ui/react/checkbox-group";
2
3import { cn } from "#/lib/utils";
4
5function CheckboxGroup({ className, ...props }: CheckboxGroupPrimitive.Props) {
6 return (
7 <CheckboxGroupPrimitive
8 className={cn("flex flex-col items-start gap-3", className)}
9 {...props}
10 />
11 );
12}
13
14export { CheckboxGroup };
apps/renderer/src/components/ui/checkbox.tsx deleted-58
...@@ -1,58 +0,0 @@
1import { Checkbox as CheckboxPrimitive } from "@base-ui/react/checkbox";
2
3import { cn } from "#/lib/utils";
4
5function Checkbox({ className, ...props }: CheckboxPrimitive.Root.Props) {
6 return (
7 <CheckboxPrimitive.Root
8 className={cn(
9 "relative inline-flex size-4.5 shrink-0 items-center justify-center rounded-[4px] border border-input bg-background not-dark:bg-clip-padding shadow-xs/5 outline-none ring-ring transition-shadow before:pointer-events-none before:absolute before:inset-0 before:rounded-[3px] not-data-disabled:not-data-checked:not-aria-invalid:before:shadow-[0_1px_--theme(--color-black/4%)] focus-visible:ring-2 focus-visible:ring-offset-1 focus-visible:ring-offset-background aria-invalid:border-destructive/36 focus-visible:aria-invalid:border-destructive/64 focus-visible:aria-invalid:ring-destructive/48 data-disabled:opacity-64 sm:size-4 dark:not-data-checked:bg-input/32 dark:aria-invalid:ring-destructive/24 dark:not-data-disabled:not-data-checked:not-aria-invalid:before:shadow-[0_-1px_--theme(--color-white/6%)] [[data-disabled],[data-checked],[aria-invalid]]:shadow-none",
10 className,
11 )}
12 data-slot="checkbox"
13 {...props}
14 >
15 <CheckboxPrimitive.Indicator
16 className="-inset-px absolute flex items-center justify-center rounded-[4px] text-primary-foreground data-unchecked:hidden data-checked:bg-primary data-indeterminate:text-foreground"
17 data-slot="checkbox-indicator"
18 render={(props, state) => (
19 <span {...props}>
20 {state.indeterminate ? (
21 <svg
22 className="size-3.5 sm:size-3"
23 fill="none"
24 height="24"
25 stroke="currentColor"
26 strokeLinecap="round"
27 strokeLinejoin="round"
28 strokeWidth="3"
29 viewBox="0 0 24 24"
30 width="24"
31 xmlns="http://www.w3.org/2000/svg"
32 >
33 <path d="M5.252 12h13.496" />
34 </svg>
35 ) : (
36 <svg
37 className="size-3.5 sm:size-3"
38 fill="none"
39 height="24"
40 stroke="currentColor"
41 strokeLinecap="round"
42 strokeLinejoin="round"
43 strokeWidth="3"
44 viewBox="0 0 24 24"
45 width="24"
46 xmlns="http://www.w3.org/2000/svg"
47 >
48 <path d="M5.252 12.7 10.2 18.63 18.748 5.37" />
49 </svg>
50 )}
51 </span>
52 )}
53 />
54 </CheckboxPrimitive.Root>
55 );
56}
57
58export { Checkbox };
apps/renderer/src/components/ui/collapsible.tsx deleted-43
...@@ -1,43 +0,0 @@
1import { Collapsible as CollapsiblePrimitive } from "@base-ui/react/collapsible";
2
3import { cn } from "#/lib/utils";
4
5function Collapsible({ ...props }: CollapsiblePrimitive.Root.Props) {
6 return <CollapsiblePrimitive.Root data-slot="collapsible" {...props} />;
7}
8
9function CollapsibleTrigger({
10 className,
11 ...props
12}: CollapsiblePrimitive.Trigger.Props) {
13 return (
14 <CollapsiblePrimitive.Trigger
15 className={cn("cursor-pointer", className)}
16 data-slot="collapsible-trigger"
17 {...props}
18 />
19 );
20}
21
22function CollapsiblePanel({
23 className,
24 ...props
25}: CollapsiblePrimitive.Panel.Props) {
26 return (
27 <CollapsiblePrimitive.Panel
28 className={cn(
29 "h-(--collapsible-panel-height) overflow-hidden transition-[height] duration-200 data-ending-style:h-0 data-starting-style:h-0",
30 className,
31 )}
32 data-slot="collapsible-panel"
33 {...props}
34 />
35 );
36}
37
38export {
39 Collapsible,
40 CollapsibleTrigger,
41 CollapsiblePanel,
42 CollapsiblePanel as CollapsibleContent,
43};
apps/renderer/src/components/ui/combobox.tsx deleted-422
...@@ -1,422 +0,0 @@
1import { Combobox as ComboboxPrimitive } from "@base-ui/react/combobox";
2import { ChevronsUpDownIcon, XIcon } from "lucide-react";
3import * as React from "react";
4
5import { Input } from "#/components/ui/input";
6import { ScrollArea } from "#/components/ui/scroll-area";
7import { cn } from "#/lib/utils";
8
9const ComboboxContext = React.createContext<{
10 chipsRef: React.RefObject<Element | null> | null;
11 multiple: boolean;
12}>({
13 chipsRef: null,
14 multiple: false,
15});
16
17function Combobox<Value, Multiple extends boolean | undefined = false>(
18 props: ComboboxPrimitive.Root.Props<Value, Multiple>,
19): React.JSX.Element {
20 const chipsRef = React.useRef<Element | null>(null);
21 return (
22 <ComboboxContext.Provider value={{ chipsRef, multiple: !!props.multiple }}>
23 <ComboboxPrimitive.Root {...props} />
24 </ComboboxContext.Provider>
25 );
26}
27
28function ComboboxChipsInput({
29 className,
30 size,
31 ...props
32}: Omit<ComboboxPrimitive.Input.Props, "size"> & {
33 size?: "sm" | "default" | "lg" | number;
34 ref?: React.Ref<HTMLInputElement>;
35}) {
36 const sizeValue = (size ?? "default") as "sm" | "default" | "lg" | number;
37
38 return (
39 <ComboboxPrimitive.Input
40 className={cn(
41 "min-w-12 flex-1 text-base outline-none sm:text-sm [[data-slot=combobox-chip]+&]:ps-0.5",
42 sizeValue === "sm" ? "ps-1.5" : "ps-2",
43 className,
44 )}
45 data-size={typeof sizeValue === "string" ? sizeValue : undefined}
46 data-slot="combobox-chips-input"
47 size={typeof sizeValue === "number" ? sizeValue : undefined}
48 {...props}
49 />
50 );
51}
52
53function ComboboxInput({
54 className,
55 showTrigger = true,
56 showClear = false,
57 startAddon,
58 size,
59 ...props
60}: Omit<ComboboxPrimitive.Input.Props, "size"> & {
61 showTrigger?: boolean;
62 showClear?: boolean;
63 startAddon?: React.ReactNode;
64 size?: "sm" | "default" | "lg" | number;
65 ref?: React.Ref<HTMLInputElement>;
66}) {
67 const sizeValue = (size ?? "default") as "sm" | "default" | "lg" | number;
68
69 return (
70 <div className="relative not-has-[>*.w-full]:w-fit w-full text-foreground has-disabled:opacity-64">
71 {startAddon && (
72 <div
73 aria-hidden="true"
74 className="[&_svg]:-mx-0.5 pointer-events-none absolute inset-y-0 start-px z-10 flex items-center ps-[calc(--spacing(3)-1px)] opacity-80 has-[+[data-size=sm]]:ps-[calc(--spacing(2.5)-1px)] [&_svg:not([class*='size-'])]:size-4.5 sm:[&_svg:not([class*='size-'])]:size-4"
75 data-slot="combobox-start-addon"
76 >
77 {startAddon}
78 </div>
79 )}
80 <ComboboxPrimitive.Input
81 className={cn(
82 startAddon &&
83 "data-[size=sm]:*:data-[slot=combobox-input]:ps-[calc(--spacing(7.5)-1px)] *:data-[slot=combobox-input]:ps-[calc(--spacing(8.5)-1px)] sm:data-[size=sm]:*:data-[slot=combobox-input]:ps-[calc(--spacing(7)-1px)] sm:*:data-[slot=combobox-input]:ps-[calc(--spacing(8)-1px)]",
84 sizeValue === "sm"
85 ? "has-[+[data-slot=combobox-trigger],+[data-slot=combobox-clear]]:*:data-[slot=combobox-input]:pe-6.5"
86 : "has-[+[data-slot=combobox-trigger],+[data-slot=combobox-clear]]:*:data-[slot=combobox-input]:pe-7",
87 className,
88 )}
89 data-slot="combobox-input"
90 render={
91 <Input
92 className="has-disabled:opacity-100"
93 nativeInput
94 size={sizeValue}
95 />
96 }
97 {...props}
98 />
99 {showTrigger && (
100 <ComboboxTrigger
101 className={cn(
102 "-translate-y-1/2 absolute top-1/2 inline-flex size-8 shrink-0 cursor-pointer items-center justify-center rounded-md border border-transparent opacity-80 outline-none transition-opacity pointer-coarse:after:absolute pointer-coarse:after:min-h-11 pointer-coarse:after:min-w-11 hover:opacity-100 has-[+[data-slot=combobox-clear]]:hidden sm:size-7 [&_svg:not([class*='size-'])]:size-4.5 sm:[&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0",
103 sizeValue === "sm" ? "end-0" : "end-0.5",
104 )}
105 >
106 <ChevronsUpDownIcon />
107 </ComboboxTrigger>
108 )}
109 {showClear && (
110 <ComboboxClear
111 className={cn(
112 "-translate-y-1/2 absolute top-1/2 inline-flex size-8 shrink-0 cursor-pointer items-center justify-center rounded-md border border-transparent opacity-80 outline-none transition-opacity pointer-coarse:after:absolute pointer-coarse:after:min-h-11 pointer-coarse:after:min-w-11 hover:opacity-100 has-[+[data-slot=combobox-clear]]:hidden sm:size-7 [&_svg:not([class*='size-'])]:size-4.5 sm:[&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0",
113 sizeValue === "sm" ? "end-0" : "end-0.5",
114 )}
115 >
116 <XIcon />
117 </ComboboxClear>
118 )}
119 </div>
120 );
121}
122
123function ComboboxTrigger({
124 className,
125 ...props
126}: ComboboxPrimitive.Trigger.Props) {
127 return (
128 <ComboboxPrimitive.Trigger
129 className={className}
130 data-slot="combobox-trigger"
131 {...props}
132 />
133 );
134}
135
136function ComboboxPopup({
137 className,
138 children,
139 side = "bottom",
140 sideOffset = 4,
141 alignOffset,
142 align = "start",
143 ...props
144}: ComboboxPrimitive.Popup.Props & {
145 align?: ComboboxPrimitive.Positioner.Props["align"];
146 sideOffset?: ComboboxPrimitive.Positioner.Props["sideOffset"];
147 alignOffset?: ComboboxPrimitive.Positioner.Props["alignOffset"];
148 side?: ComboboxPrimitive.Positioner.Props["side"];
149}) {
150 const { chipsRef } = React.useContext(ComboboxContext);
151
152 return (
153 <ComboboxPrimitive.Portal>
154 <ComboboxPrimitive.Positioner
155 align={align}
156 alignOffset={alignOffset}
157 anchor={chipsRef}
158 className="z-50 select-none"
159 data-slot="combobox-positioner"
160 side={side}
161 sideOffset={sideOffset}
162 >
163 <span
164 className={cn(
165 "relative flex max-h-full min-w-(--anchor-width) max-w-(--available-width) origin-(--transform-origin) rounded-lg border bg-popover not-dark:bg-clip-padding shadow-lg/5 transition-[scale,opacity] before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--radius-lg)-1px)] before:shadow-[0_1px_--theme(--color-black/4%)] dark:before:shadow-[0_-1px_--theme(--color-white/6%)]",
166 className,
167 )}
168 >
169 <ComboboxPrimitive.Popup
170 className="flex max-h-[min(var(--available-height),23rem)] flex-1 flex-col text-foreground"
171 data-slot="combobox-popup"
172 {...props}
173 >
174 {children}
175 </ComboboxPrimitive.Popup>
176 </span>
177 </ComboboxPrimitive.Positioner>
178 </ComboboxPrimitive.Portal>
179 );
180}
181
182function ComboboxItem({
183 className,
184 children,
185 ...props
186}: ComboboxPrimitive.Item.Props) {
187 return (
188 <ComboboxPrimitive.Item
189 className={cn(
190 "grid min-h-8 in-data-[side=none]:min-w-[calc(var(--anchor-width)+1.25rem)] cursor-default grid-cols-[1rem_1fr] items-center gap-2 rounded-sm py-1 ps-2 pe-4 text-base outline-none data-disabled:pointer-events-none data-highlighted:bg-accent data-highlighted:text-accent-foreground data-disabled:opacity-64 sm:min-h-7 sm:text-sm [&_svg:not([class*='size-'])]:size-4.5 sm:[&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0",
191 className,
192 )}
193 data-slot="combobox-item"
194 {...props}
195 >
196 <ComboboxPrimitive.ItemIndicator className="col-start-1">
197 <svg
198 fill="none"
199 height="24"
200 stroke="currentColor"
201 strokeLinecap="round"
202 strokeLinejoin="round"
203 strokeWidth="2"
204 viewBox="0 0 24 24"
205 width="24"
206 xmlns="http://www.w3.org/1500/svg"
207 >
208 <path d="M5.252 12.7 10.2 18.63 18.748 5.37" />
209 </svg>
210 </ComboboxPrimitive.ItemIndicator>
211 <div className="col-start-2">{children}</div>
212 </ComboboxPrimitive.Item>
213 );
214}
215
216function ComboboxSeparator({
217 className,
218 ...props
219}: ComboboxPrimitive.Separator.Props) {
220 return (
221 <ComboboxPrimitive.Separator
222 className={cn("mx-2 my-1 h-px bg-border last:hidden", className)}
223 data-slot="combobox-separator"
224 {...props}
225 />
226 );
227}
228
229function ComboboxGroup({ className, ...props }: ComboboxPrimitive.Group.Props) {
230 return (
231 <ComboboxPrimitive.Group
232 className={cn("[[role=group]+&]:mt-1.5", className)}
233 data-slot="combobox-group"
234 {...props}
235 />
236 );
237}
238
239function ComboboxGroupLabel({
240 className,
241 ...props
242}: ComboboxPrimitive.GroupLabel.Props) {
243 return (
244 <ComboboxPrimitive.GroupLabel
245 className={cn(
246 "px-2 py-1.5 font-medium text-muted-foreground text-xs",
247 className,
248 )}
249 data-slot="combobox-group-label"
250 {...props}
251 />
252 );
253}
254
255function ComboboxEmpty({ className, ...props }: ComboboxPrimitive.Empty.Props) {
256 return (
257 <ComboboxPrimitive.Empty
258 className={cn(
259 "not-empty:p-2 text-center text-base text-muted-foreground sm:text-sm",
260 className,
261 )}
262 data-slot="combobox-empty"
263 {...props}
264 />
265 );
266}
267
268function ComboboxRow({ className, ...props }: ComboboxPrimitive.Row.Props) {
269 return (
270 <ComboboxPrimitive.Row
271 className={className}
272 data-slot="combobox-row"
273 {...props}
274 />
275 );
276}
277
278function ComboboxValue({ ...props }: ComboboxPrimitive.Value.Props) {
279 return <ComboboxPrimitive.Value data-slot="combobox-value" {...props} />;
280}
281
282function ComboboxList({ className, ...props }: ComboboxPrimitive.List.Props) {
283 return (
284 <ScrollArea scrollbarGutter scrollFade>
285 <ComboboxPrimitive.List
286 className={cn(
287 "not-empty:scroll-py-1 not-empty:px-1 not-empty:py-1 in-data-has-overflow-y:pe-3",
288 className,
289 )}
290 data-slot="combobox-list"
291 {...props}
292 />
293 </ScrollArea>
294 );
295}
296
297function ComboboxClear({ className, ...props }: ComboboxPrimitive.Clear.Props) {
298 return (
299 <ComboboxPrimitive.Clear
300 className={className}
301 data-slot="combobox-clear"
302 {...props}
303 />
304 );
305}
306
307function ComboboxStatus({
308 className,
309 ...props
310}: ComboboxPrimitive.Status.Props) {
311 return (
312 <ComboboxPrimitive.Status
313 className={cn(
314 "px-3 py-2 font-medium text-muted-foreground text-xs empty:m-0 empty:p-0",
315 className,
316 )}
317 data-slot="combobox-status"
318 {...props}
319 />
320 );
321}
322
323function ComboboxCollection(props: ComboboxPrimitive.Collection.Props) {
324 return (
325 <ComboboxPrimitive.Collection data-slot="combobox-collection" {...props} />
326 );
327}
328
329function ComboboxChips({
330 className,
331 children,
332 startAddon,
333 ...props
334}: ComboboxPrimitive.Chips.Props & {
335 startAddon?: React.ReactNode;
336}) {
337 const { chipsRef } = React.useContext(ComboboxContext);
338
339 return (
340 <ComboboxPrimitive.Chips
341 className={cn(
342 "relative inline-flex min-h-9 w-full flex-wrap gap-1 rounded-lg border border-input bg-background not-dark:bg-clip-padding p-[calc(--spacing(1)-1px)] text-base shadow-xs/5 outline-none ring-ring/24 transition-shadow *:min-h-7 before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--radius-lg)-1px)] not-has-disabled:not-focus-within:not-aria-invalid:before:shadow-[0_1px_--theme(--color-black/4%)] focus-within:border-ring focus-within:ring-[3px] has-disabled:pointer-events-none has-data-[size=lg]:min-h-10 has-data-[size=sm]:min-h-8 has-aria-invalid:border-destructive/36 has-autofill:bg-foreground/4 has-disabled:opacity-64 has-[:disabled,:focus-within,[aria-invalid]]:shadow-none focus-within:has-aria-invalid:border-destructive/64 focus-within:has-aria-invalid:ring-destructive/16 has-data-[size=lg]:*:min-h-8 has-data-[size=sm]:*:min-h-6 sm:min-h-8 sm:text-sm sm:has-data-[size=lg]:min-h-9 sm:has-data-[size=sm]:min-h-7 sm:*:min-h-6 sm:has-data-[size=lg]:*:min-h-7 sm:has-data-[size=sm]:*:min-h-5 dark:not-has-disabled:bg-input/32 dark:has-autofill:bg-foreground/8 dark:has-aria-invalid:ring-destructive/24 dark:not-has-disabled:not-focus-within:not-aria-invalid:before:shadow-[0_-1px_--theme(--color-white/6%)]",
343 className,
344 )}
345 data-slot="combobox-chips"
346 onMouseDown={(e) => {
347 const target = e.target as HTMLElement;
348 const isChip = target.closest('[data-slot="combobox-chip"]');
349 if (isChip || !chipsRef?.current) return;
350 e.preventDefault();
351 const input: HTMLInputElement | null =
352 chipsRef.current.querySelector("input");
353 if (input && !chipsRef.current.querySelector("input:focus")) {
354 input.focus();
355 }
356 }}
357 ref={chipsRef as React.Ref<HTMLDivElement> | null}
358 {...props}
359 >
360 {startAddon && (
361 <div
362 aria-hidden="true"
363 className="[&_svg]:-ms-0.5 [&_svg]:-me-1.5 flex shrink-0 items-center ps-2 opacity-80 has-[~[data-size=sm]]:has-[+[data-slot=combobox-chip]]:pe-1.5 has-[~[data-size=sm]]:ps-1.5 has-[+[data-slot=combobox-chip]]:pe-2 [&_svg:not([class*='size-'])]:size-4.5 sm:[&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none"
364 data-slot="combobox-start-addon"
365 >
366 {startAddon}
367 </div>
368 )}
369 {children}
370 </ComboboxPrimitive.Chips>
371 );
372}
373
374function ComboboxChip({ children, ...props }: ComboboxPrimitive.Chip.Props) {
375 return (
376 <ComboboxPrimitive.Chip
377 className="flex items-center rounded-[calc(var(--radius-md)-1px)] bg-accent ps-2 font-medium text-accent-foreground text-sm outline-none sm:text-xs/(--text-xs--line-height) [&_svg:not([class*='size-'])]:size-4 sm:[&_svg:not([class*='size-'])]:size-3.5"
378 data-slot="combobox-chip"
379 {...props}
380 >
381 {children}
382 <ComboboxChipRemove />
383 </ComboboxPrimitive.Chip>
384 );
385}
386
387function ComboboxChipRemove(props: ComboboxPrimitive.ChipRemove.Props) {
388 return (
389 <ComboboxPrimitive.ChipRemove
390 aria-label="Remove"
391 className="h-full shrink-0 cursor-pointer px-1.5 opacity-80 hover:opacity-100 [&_svg:not([class*='size-'])]:size-4 sm:[&_svg:not([class*='size-'])]:size-3.5"
392 data-slot="combobox-chip-remove"
393 {...props}
394 >
395 <XIcon />
396 </ComboboxPrimitive.ChipRemove>
397 );
398}
399
400const useComboboxFilter = ComboboxPrimitive.useFilter;
401
402export {
403 Combobox,
404 ComboboxChipsInput,
405 ComboboxInput,
406 ComboboxTrigger,
407 ComboboxPopup,
408 ComboboxItem,
409 ComboboxSeparator,
410 ComboboxGroup,
411 ComboboxGroupLabel,
412 ComboboxEmpty,
413 ComboboxValue,
414 ComboboxList,
415 ComboboxClear,
416 ComboboxStatus,
417 ComboboxRow,
418 ComboboxCollection,
419 ComboboxChips,
420 ComboboxChip,
421 useComboboxFilter,
422};
apps/renderer/src/components/ui/command.tsx deleted-262
...@@ -1,262 +0,0 @@
1import { Dialog as CommandDialogPrimitive } from "@base-ui/react/dialog";
2import { SearchIcon } from "lucide-react";
3import type * as React from "react";
4import {
5 Autocomplete,
6 AutocompleteCollection,
7 AutocompleteEmpty,
8 AutocompleteGroup,
9 AutocompleteGroupLabel,
10 AutocompleteInput,
11 AutocompleteItem,
12 AutocompleteList,
13 AutocompleteSeparator,
14} from "#/components/ui/autocomplete";
15import { cn } from "#/lib/utils";
16
17const CommandDialog = CommandDialogPrimitive.Root;
18
19const CommandDialogPortal = CommandDialogPrimitive.Portal;
20
21const CommandCreateHandle = CommandDialogPrimitive.createHandle;
22
23function CommandDialogTrigger(props: CommandDialogPrimitive.Trigger.Props) {
24 return (
25 <CommandDialogPrimitive.Trigger
26 data-slot="command-dialog-trigger"
27 {...props}
28 />
29 );
30}
31
32function CommandDialogBackdrop({
33 className,
34 ...props
35}: CommandDialogPrimitive.Backdrop.Props) {
36 return (
37 <CommandDialogPrimitive.Backdrop
38 className={cn(
39 "fixed inset-0 z-50 bg-black/32 backdrop-blur-sm transition-all duration-200 data-ending-style:opacity-0 data-starting-style:opacity-0",
40 className,
41 )}
42 data-slot="command-dialog-backdrop"
43 {...props}
44 />
45 );
46}
47
48function CommandDialogViewport({
49 className,
50 ...props
51}: CommandDialogPrimitive.Viewport.Props) {
52 return (
53 <CommandDialogPrimitive.Viewport
54 className={cn(
55 "fixed inset-0 z-50 flex flex-col items-center px-4 py-[max(--spacing(4),4vh)] sm:py-[10vh]",
56 className,
57 )}
58 data-slot="command-dialog-viewport"
59 {...props}
60 />
61 );
62}
63
64function CommandDialogPopup({
65 className,
66 children,
67 ...props
68}: CommandDialogPrimitive.Popup.Props) {
69 return (
70 <CommandDialogPortal>
71 <CommandDialogBackdrop />
72 <CommandDialogViewport>
73 <CommandDialogPrimitive.Popup
74 className={cn(
75 "-translate-y-[calc(1.25rem*var(--nested-dialogs))] relative row-start-2 flex max-h-105 min-h-0 w-full min-w-0 max-w-xl scale-[calc(1-0.1*var(--nested-dialogs))] flex-col rounded-2xl border bg-popover not-dark:bg-clip-padding text-popover-foreground opacity-[calc(1-0.1*var(--nested-dialogs))] shadow-lg/5 outline-none transition-[scale,opacity,translate] duration-200 ease-in-out will-change-transform before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--radius-2xl)-1px)] before:bg-muted/72 before:shadow-[0_1px_--theme(--color-black/4%)] data-nested:data-ending-style:translate-y-8 data-nested:data-starting-style:translate-y-8 data-nested-dialog-open:origin-top data-ending-style:scale-98 data-starting-style:scale-98 data-ending-style:opacity-0 data-starting-style:opacity-0 **:data-[slot=scroll-area-viewport]:data-has-overflow-y:pe-1 dark:before:shadow-[0_-1px_--theme(--color-white/6%)]",
76 className,
77 )}
78 data-slot="command-dialog-popup"
79 {...props}
80 >
81 {children}
82 </CommandDialogPrimitive.Popup>
83 </CommandDialogViewport>
84 </CommandDialogPortal>
85 );
86}
87
88function Command({
89 autoHighlight = "always",
90 keepHighlight = true,
91 ...props
92}: React.ComponentProps<typeof Autocomplete>) {
93 return (
94 <Autocomplete
95 autoHighlight={autoHighlight}
96 inline
97 keepHighlight={keepHighlight}
98 open
99 {...props}
100 />
101 );
102}
103
104function CommandInput({
105 className,
106 placeholder = undefined,
107 ...props
108}: React.ComponentProps<typeof AutocompleteInput>) {
109 return (
110 <div className="px-2.5 py-1.5">
111 <AutocompleteInput
112 autoFocus
113 className={cn(
114 "border-transparent! bg-transparent! shadow-none before:hidden has-focus-visible:ring-0",
115 className,
116 )}
117 placeholder={placeholder}
118 size="lg"
119 startAddon={<SearchIcon />}
120 {...props}
121 />
122 </div>
123 );
124}
125
126function CommandList({
127 className,
128 ...props
129}: React.ComponentProps<typeof AutocompleteList>) {
130 return (
131 <AutocompleteList
132 className={cn("not-empty:scroll-py-2 not-empty:p-2", className)}
133 data-slot="command-list"
134 {...props}
135 />
136 );
137}
138
139function CommandEmpty({
140 className,
141 ...props
142}: React.ComponentProps<typeof AutocompleteEmpty>) {
143 return (
144 <AutocompleteEmpty
145 className={cn("not-empty:py-6", className)}
146 data-slot="command-empty"
147 {...props}
148 />
149 );
150}
151
152function CommandPanel({ className, ...props }: React.ComponentProps<"div">) {
153 return (
154 <div
155 className="-mx-px not-has-[+[data-slot=command-footer]]:-mb-px relative min-h-0 rounded-t-xl not-has-[+[data-slot=command-footer]]:rounded-b-2xl border border-b-0 bg-popover bg-clip-padding shadow-xs/5 [clip-path:inset(0_1px)] not-has-[+[data-slot=command-footer]]:[clip-path:inset(0_1px_1px_1px_round_0_0_calc(var(--radius-2xl)-1px)_calc(var(--radius-2xl)-1px))] before:pointer-events-none before:absolute before:inset-0 before:rounded-t-[calc(var(--radius-xl)-1px)] **:data-[slot=scroll-area-scrollbar]:mt-2"
156 {...props}
157 />
158 );
159}
160
161function CommandGroup({
162 className,
163 ...props
164}: React.ComponentProps<typeof AutocompleteGroup>) {
165 return (
166 <AutocompleteGroup
167 className={className}
168 data-slot="command-group"
169 {...props}
170 />
171 );
172}
173
174function CommandGroupLabel({
175 className,
176 ...props
177}: React.ComponentProps<typeof AutocompleteGroupLabel>) {
178 return (
179 <AutocompleteGroupLabel
180 className={className}
181 data-slot="command-group-label"
182 {...props}
183 />
184 );
185}
186
187function CommandCollection({
188 ...props
189}: React.ComponentProps<typeof AutocompleteCollection>) {
190 return <AutocompleteCollection data-slot="command-collection" {...props} />;
191}
192
193function CommandItem({
194 className,
195 ...props
196}: React.ComponentProps<typeof AutocompleteItem>) {
197 return (
198 <AutocompleteItem
199 className={cn("py-1.5", className)}
200 data-slot="command-item"
201 {...props}
202 />
203 );
204}
205
206function CommandSeparator({
207 className,
208 ...props
209}: React.ComponentProps<typeof AutocompleteSeparator>) {
210 return (
211 <AutocompleteSeparator
212 className={cn("my-2", className)}
213 data-slot="command-separator"
214 {...props}
215 />
216 );
217}
218
219function CommandShortcut({ className, ...props }: React.ComponentProps<"kbd">) {
220 return (
221 <kbd
222 className={cn(
223 "ms-auto font-medium font-sans text-muted-foreground/72 text-xs tracking-widest",
224 className,
225 )}
226 data-slot="command-shortcut"
227 {...props}
228 />
229 );
230}
231
232function CommandFooter({ className, ...props }: React.ComponentProps<"div">) {
233 return (
234 <div
235 className={cn(
236 "flex items-center justify-between gap-2 rounded-b-[calc(var(--radius-2xl)-1px)] border-t px-5 py-3 text-muted-foreground text-xs",
237 className,
238 )}
239 data-slot="command-footer"
240 {...props}
241 />
242 );
243}
244
245export {
246 CommandCreateHandle,
247 Command,
248 CommandCollection,
249 CommandDialog,
250 CommandDialogPopup,
251 CommandDialogTrigger,
252 CommandEmpty,
253 CommandFooter,
254 CommandGroup,
255 CommandGroupLabel,
256 CommandInput,
257 CommandItem,
258 CommandList,
259 CommandPanel,
260 CommandSeparator,
261 CommandShortcut,
262};
apps/renderer/src/components/ui/dialog.tsx deleted-194
...@@ -1,194 +0,0 @@
1import { Dialog as DialogPrimitive } from "@base-ui/react/dialog";
2import { XIcon } from "lucide-react";
3import { Button } from "#/components/ui/button";
4import { ScrollArea } from "#/components/ui/scroll-area";
5import { cn } from "#/lib/utils";
6
7const DialogCreateHandle = DialogPrimitive.createHandle;
8
9const Dialog = DialogPrimitive.Root;
10
11const DialogPortal = DialogPrimitive.Portal;
12
13function DialogTrigger(props: DialogPrimitive.Trigger.Props) {
14 return <DialogPrimitive.Trigger data-slot="dialog-trigger" {...props} />;
15}
16
17function DialogClose(props: DialogPrimitive.Close.Props) {
18 return <DialogPrimitive.Close data-slot="dialog-close" {...props} />;
19}
20
21function DialogBackdrop({
22 className,
23 ...props
24}: DialogPrimitive.Backdrop.Props) {
25 return (
26 <DialogPrimitive.Backdrop
27 className={cn(
28 "fixed inset-0 z-50 bg-black/32 backdrop-blur-sm transition-all duration-200 data-ending-style:opacity-0 data-starting-style:opacity-0",
29 className,
30 )}
31 data-slot="dialog-backdrop"
32 {...props}
33 />
34 );
35}
36
37function DialogViewport({
38 className,
39 ...props
40}: DialogPrimitive.Viewport.Props) {
41 return (
42 <DialogPrimitive.Viewport
43 className={cn(
44 "fixed inset-0 z-50 grid grid-rows-[1fr_auto_3fr] justify-items-center p-4",
45 className,
46 )}
47 data-slot="dialog-viewport"
48 {...props}
49 />
50 );
51}
52
53function DialogPopup({
54 className,
55 children,
56 showCloseButton = true,
57 bottomStickOnMobile = true,
58 ...props
59}: DialogPrimitive.Popup.Props & {
60 showCloseButton?: boolean;
61 bottomStickOnMobile?: boolean;
62}) {
63 return (
64 <DialogPortal>
65 <DialogBackdrop />
66 <DialogViewport
67 className={cn(
68 bottomStickOnMobile &&
69 "max-sm:grid-rows-[1fr_auto] max-sm:p-0 max-sm:pt-12",
70 )}
71 >
72 <DialogPrimitive.Popup
73 className={cn(
74 "-translate-y-[calc(1.25rem*var(--nested-dialogs))] relative row-start-2 flex max-h-full min-h-0 w-full min-w-0 max-w-lg scale-[calc(1-0.1*var(--nested-dialogs))] flex-col rounded-2xl border bg-popover not-dark:bg-clip-padding text-popover-foreground opacity-[calc(1-0.1*var(--nested-dialogs))] shadow-lg/5 transition-[scale,opacity,translate] duration-200 ease-in-out will-change-transform before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--radius-2xl)-1px)] before:shadow-[0_1px_--theme(--color-black/4%)] data-nested:data-ending-style:translate-y-8 data-nested:data-starting-style:translate-y-8 data-nested-dialog-open:origin-top data-ending-style:scale-98 data-starting-style:scale-98 data-ending-style:opacity-0 data-starting-style:opacity-0 dark:before:shadow-[0_-1px_--theme(--color-white/6%)]",
75 bottomStickOnMobile &&
76 "max-sm:max-w-none max-sm:rounded-none max-sm:border-x-0 max-sm:border-t max-sm:border-b-0 max-sm:opacity-[calc(1-min(var(--nested-dialogs),1))] max-sm:data-ending-style:translate-y-4 max-sm:data-starting-style:translate-y-4 max-sm:before:hidden max-sm:before:rounded-none",
77 className,
78 )}
79 data-slot="dialog-popup"
80 {...props}
81 >
82 {children}
83 {showCloseButton && (
84 <DialogPrimitive.Close
85 aria-label="Close"
86 className="absolute end-2 top-2"
87 render={<Button size="icon" variant="ghost" />}
88 >
89 <XIcon />
90 </DialogPrimitive.Close>
91 )}
92 </DialogPrimitive.Popup>
93 </DialogViewport>
94 </DialogPortal>
95 );
96}
97
98function DialogHeader({ className, ...props }: React.ComponentProps<"div">) {
99 return (
100 <div
101 className={cn(
102 "flex flex-col gap-2 p-6 in-[[data-slot=dialog-popup]:has([data-slot=dialog-panel])]:pb-3 max-sm:pb-4",
103 className,
104 )}
105 data-slot="dialog-header"
106 {...props}
107 />
108 );
109}
110
111function DialogFooter({
112 className,
113 variant = "default",
114 ...props
115}: React.ComponentProps<"div"> & {
116 variant?: "default" | "bare";
117}) {
118 return (
119 <div
120 className={cn(
121 "flex flex-col-reverse gap-2 px-6 sm:flex-row sm:justify-end sm:rounded-b-[calc(var(--radius-2xl)-1px)]",
122 variant === "default" && "border-t bg-muted/72 py-4",
123 variant === "bare" &&
124 "in-[[data-slot=dialog-popup]:has([data-slot=dialog-panel])]:pt-3 pt-4 pb-6",
125 className,
126 )}
127 data-slot="dialog-footer"
128 {...props}
129 />
130 );
131}
132
133function DialogTitle({ className, ...props }: DialogPrimitive.Title.Props) {
134 return (
135 <DialogPrimitive.Title
136 className={cn(
137 "font-heading font-semibold text-xl leading-none",
138 className,
139 )}
140 data-slot="dialog-title"
141 {...props}
142 />
143 );
144}
145
146function DialogDescription({
147 className,
148 ...props
149}: DialogPrimitive.Description.Props) {
150 return (
151 <DialogPrimitive.Description
152 className={cn("text-muted-foreground text-sm", className)}
153 data-slot="dialog-description"
154 {...props}
155 />
156 );
157}
158
159function DialogPanel({
160 className,
161 scrollFade = true,
162 ...props
163}: React.ComponentProps<"div"> & { scrollFade?: boolean }) {
164 return (
165 <ScrollArea scrollFade={scrollFade}>
166 <div
167 className={cn(
168 "p-6 in-[[data-slot=dialog-popup]:has([data-slot=dialog-header])]:pt-1 in-[[data-slot=dialog-popup]:has([data-slot=dialog-footer]:not(.border-t))]:pb-1",
169 className,
170 )}
171 data-slot="dialog-panel"
172 {...props}
173 />
174 </ScrollArea>
175 );
176}
177
178export {
179 DialogCreateHandle,
180 Dialog,
181 DialogTrigger,
182 DialogPortal,
183 DialogClose,
184 DialogBackdrop,
185 DialogBackdrop as DialogOverlay,
186 DialogPopup,
187 DialogPopup as DialogContent,
188 DialogHeader,
189 DialogFooter,
190 DialogTitle,
191 DialogDescription,
192 DialogPanel,
193 DialogViewport,
194};
apps/renderer/src/components/ui/empty.tsx deleted-127
...@@ -1,127 +0,0 @@
1import { type VariantProps, cva } from "class-variance-authority";
2
3import { cn } from "#/lib/utils";
4
5function Empty({ className, ...props }: React.ComponentProps<"div">) {
6 return (
7 <div
8 className={cn(
9 "flex min-w-0 flex-1 flex-col items-center justify-center gap-6 text-balance p-6 text-center md:p-12",
10 className,
11 )}
12 data-slot="empty"
13 {...props}
14 />
15 );
16}
17
18function EmptyHeader({ className, ...props }: React.ComponentProps<"div">) {
19 return (
20 <div
21 className={cn(
22 "flex max-w-sm flex-col items-center text-center",
23 className,
24 )}
25 data-slot="empty-header"
26 {...props}
27 />
28 );
29}
30
31const emptyMediaVariants = cva(
32 "flex shrink-0 items-center justify-center [&_svg]:pointer-events-none [&_svg]:shrink-0",
33 {
34 defaultVariants: {
35 variant: "default",
36 },
37 variants: {
38 variant: {
39 default: "bg-transparent",
40 icon: "relative flex size-9 shrink-0 items-center justify-center rounded-md border bg-card not-dark:bg-clip-padding text-foreground shadow-sm/5 before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--radius-md)-1px)] before:shadow-[0_1px_--theme(--color-black/4%)] dark:before:shadow-[0_-1px_--theme(--color-white/6%)] [&_svg:not([class*='size-'])]:size-4.5",
41 },
42 },
43 },
44);
45
46function EmptyMedia({
47 className,
48 variant = "default",
49 ...props
50}: React.ComponentProps<"div"> & VariantProps<typeof emptyMediaVariants>) {
51 return (
52 <div
53 className={cn("relative mb-6", className)}
54 data-slot="empty-media"
55 data-variant={variant}
56 {...props}
57 >
58 {variant === "icon" && (
59 <>
60 <div
61 aria-hidden="true"
62 className={cn(
63 emptyMediaVariants({ className, variant }),
64 "-translate-x-0.5 -rotate-10 pointer-events-none absolute bottom-px origin-bottom-left scale-84 shadow-none",
65 )}
66 />
67 <div
68 aria-hidden="true"
69 className={cn(
70 emptyMediaVariants({ className, variant }),
71 "pointer-events-none absolute bottom-px origin-bottom-right translate-x-0.5 rotate-10 scale-84 shadow-none",
72 )}
73 />
74 </>
75 )}
76 <div
77 className={cn(emptyMediaVariants({ className, variant }))}
78 {...props}
79 />
80 </div>
81 );
82}
83
84function EmptyTitle({ className, ...props }: React.ComponentProps<"div">) {
85 return (
86 <div
87 className={cn("font-heading font-semibold text-xl", className)}
88 data-slot="empty-title"
89 {...props}
90 />
91 );
92}
93
94function EmptyDescription({ className, ...props }: React.ComponentProps<"p">) {
95 return (
96 <div
97 className={cn(
98 "text-muted-foreground text-sm [&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4 [[data-slot=empty-title]+&]:mt-1",
99 className,
100 )}
101 data-slot="empty-description"
102 {...props}
103 />
104 );
105}
106
107function EmptyContent({ className, ...props }: React.ComponentProps<"div">) {
108 return (
109 <div
110 className={cn(
111 "flex w-full min-w-0 max-w-sm flex-col items-center gap-4 text-balance text-sm",
112 className,
113 )}
114 data-slot="empty-content"
115 {...props}
116 />
117 );
118}
119
120export {
121 Empty,
122 EmptyHeader,
123 EmptyTitle,
124 EmptyDescription,
125 EmptyContent,
126 EmptyMedia,
127};
apps/renderer/src/components/ui/field.tsx deleted-72
...@@ -1,72 +0,0 @@
1import { Field as FieldPrimitive } from "@base-ui/react/field";
2
3import { cn } from "#/lib/utils";
4
5function Field({ className, ...props }: FieldPrimitive.Root.Props) {
6 return (
7 <FieldPrimitive.Root
8 className={cn("flex flex-col items-start gap-2", className)}
9 data-slot="field"
10 {...props}
11 />
12 );
13}
14
15function FieldLabel({ className, ...props }: FieldPrimitive.Label.Props) {
16 return (
17 <FieldPrimitive.Label
18 className={cn(
19 "inline-flex items-center gap-2 font-medium text-base/4.5 text-foreground sm:text-sm/4",
20 className,
21 )}
22 data-slot="field-label"
23 {...props}
24 />
25 );
26}
27
28function FieldItem({ className, ...props }: FieldPrimitive.Item.Props) {
29 return (
30 <FieldPrimitive.Item
31 className={cn("flex", className)}
32 data-slot="field-item"
33 {...props}
34 />
35 );
36}
37
38function FieldDescription({
39 className,
40 ...props
41}: FieldPrimitive.Description.Props) {
42 return (
43 <FieldPrimitive.Description
44 className={cn("text-muted-foreground text-xs", className)}
45 data-slot="field-description"
46 {...props}
47 />
48 );
49}
50
51function FieldError({ className, ...props }: FieldPrimitive.Error.Props) {
52 return (
53 <FieldPrimitive.Error
54 className={cn("text-destructive-foreground text-xs", className)}
55 data-slot="field-error"
56 {...props}
57 />
58 );
59}
60
61const FieldControl = FieldPrimitive.Control;
62const FieldValidity = FieldPrimitive.Validity;
63
64export {
65 Field,
66 FieldLabel,
67 FieldDescription,
68 FieldError,
69 FieldControl,
70 FieldItem,
71 FieldValidity,
72};
apps/renderer/src/components/ui/fieldset.tsx deleted-27
...@@ -1,27 +0,0 @@
1import { Fieldset as FieldsetPrimitive } from "@base-ui/react/fieldset";
2
3import { cn } from "#/lib/utils";
4
5function Fieldset({ className, ...props }: FieldsetPrimitive.Root.Props) {
6 return (
7 <FieldsetPrimitive.Root
8 className={cn("flex w-full max-w-64 flex-col gap-6", className)}
9 data-slot="fieldset"
10 {...props}
11 />
12 );
13}
14function FieldsetLegend({
15 className,
16 ...props
17}: FieldsetPrimitive.Legend.Props) {
18 return (
19 <FieldsetPrimitive.Legend
20 className={cn("font-semibold text-foreground", className)}
21 data-slot="fieldset-legend"
22 {...props}
23 />
24 );
25}
26
27export { Fieldset, FieldsetLegend };
apps/renderer/src/components/ui/form.tsx deleted-15
...@@ -1,15 +0,0 @@
1import { Form as FormPrimitive } from "@base-ui/react/form";
2
3import { cn } from "#/lib/utils";
4
5function Form({ className, ...props }: FormPrimitive.Props) {
6 return (
7 <FormPrimitive
8 className={cn("flex w-full flex-col gap-4", className)}
9 data-slot="form"
10 {...props}
11 />
12 );
13}
14
15export { Form };
apps/renderer/src/components/ui/input-group.tsx deleted-99
...@@ -1,99 +0,0 @@
1import { type VariantProps, cva } from "class-variance-authority";
2import type * as React from "react";
3
4import { Input, type InputProps } from "#/components/ui/input";
5import { Textarea, type TextareaProps } from "#/components/ui/textarea";
6import { cn } from "#/lib/utils";
7
8function InputGroup({ className, ...props }: React.ComponentProps<"div">) {
9 return (
10 <div
11 className={cn(
12 "relative inline-flex w-full min-w-0 items-center rounded-lg border border-input bg-background not-dark:bg-clip-padding text-base text-foreground shadow-xs/5 ring-ring/24 transition-shadow before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--radius-lg)-1px)] not-has-[input:disabled,textarea:disabled]:not-has-[input:focus-visible,textarea:focus-visible]:not-has-[input[aria-invalid],textarea[aria-invalid]]:before:shadow-[0_1px_--theme(--color-black/4%)] has-[input:focus-visible,textarea:focus-visible]:has-[input[aria-invalid],textarea[aria-invalid]]:border-destructive/64 has-[input:focus-visible,textarea:focus-visible]:has-[input[aria-invalid],textarea[aria-invalid]]:ring-destructive/16 has-[textarea]:h-auto has-data-[align=block-end]:h-auto has-data-[align=block-start]:h-auto has-data-[align=block-end]:flex-col has-data-[align=block-start]:flex-col has-[input:focus-visible,textarea:focus-visible]:border-ring has-[input[aria-invalid],textarea[aria-invalid]]:border-destructive/36 has-autofill:bg-foreground/4 has-[input:disabled,textarea:disabled]:opacity-64 has-[input:disabled,textarea:disabled,input:focus-visible,textarea:focus-visible,input[aria-invalid],textarea[aria-invalid]]:shadow-none has-[input:focus-visible,textarea:focus-visible]:ring-[3px] sm:text-sm dark:bg-input/32 dark:has-autofill:bg-foreground/8 dark:has-[input[aria-invalid],textarea[aria-invalid]]:ring-destructive/24 dark:not-has-[input:disabled,textarea:disabled]:not-has-[input:focus-visible,textarea:focus-visible]:not-has-[input[aria-invalid],textarea[aria-invalid]]:before:shadow-[0_-1px_--theme(--color-white/6%)] has-data-[align=inline-start]:**:[[data-size=sm]_input]:ps-1.5 has-data-[align=inline-end]:**:[[data-size=sm]_input]:pe-1.5 *:[[data-slot=input-control],[data-slot=textarea-control]]:contents *:[[data-slot=input-control],[data-slot=textarea-control]]:before:hidden has-[[data-align=block-start],[data-align=block-end]]:**:[input]:h-auto has-data-[align=inline-start]:**:[input]:ps-2 has-data-[align=inline-end]:**:[input]:pe-2 has-data-[align=block-end]:**:[input]:pt-1.5 has-data-[align=block-start]:**:[input]:pb-1.5 **:[textarea]:min-h-20.5 **:[textarea]:resize-none **:[textarea]:py-[calc(--spacing(3)-1px)] **:[textarea]:max-sm:min-h-23.5 **:[textarea_button]:rounded-[calc(var(--radius-md)-1px)]",
13 className,
14 )}
15 data-slot="input-group"
16 role="group"
17 {...props}
18 />
19 );
20}
21
22const inputGroupAddonVariants = cva(
23 "[&_svg]:-mx-0.5 flex h-auto cursor-text select-none items-center justify-center gap-2 leading-none [&>kbd]:rounded-[calc(var(--radius)-5px)] in-[[data-slot=input-group]:has([data-slot=input-control],[data-slot=textarea-control])]:[&_svg:not([class*='size-'])]:size-4.5 sm:in-[[data-slot=input-group]:has([data-slot=input-control],[data-slot=textarea-control])]:[&_svg:not([class*='size-'])]:size-4 not-has-[button]:**:[svg:not([class*='opacity-'])]:opacity-80",
24 {
25 defaultVariants: {
26 align: "inline-start",
27 },
28 variants: {
29 align: {
30 "block-end":
31 "order-last w-full justify-start px-[calc(--spacing(3)-1px)] pb-[calc(--spacing(3)-1px)] [.border-t]:pt-[calc(--spacing(3)-1px)] [[data-size=sm]+&]:px-[calc(--spacing(2.5)-1px)]",
32 "block-start":
33 "order-first w-full justify-start px-[calc(--spacing(3)-1px)] pt-[calc(--spacing(3)-1px)] [.border-b]:pb-[calc(--spacing(3)-1px)] [[data-size=sm]+&]:px-[calc(--spacing(2.5)-1px)]",
34 "inline-end":
35 "has-[>:last-child[data-slot=badge]]:-me-1.5 has-[>button]:-me-2 order-last pe-[calc(--spacing(3)-1px)] has-[>kbd:last-child]:me-[-0.35rem] [[data-size=sm]+&]:pe-[calc(--spacing(2.5)-1px)]",
36 "inline-start":
37 "has-[>:last-child[data-slot=badge]]:-ms-1.5 has-[>button]:-ms-2 order-first ps-[calc(--spacing(3)-1px)] has-[>kbd:last-child]:ms-[-0.35rem] [[data-size=sm]+&]:ps-[calc(--spacing(2.5)-1px)]",
38 },
39 },
40 },
41);
42
43function InputGroupAddon({
44 className,
45 align = "inline-start",
46 ...props
47}: React.ComponentProps<"div"> & VariantProps<typeof inputGroupAddonVariants>) {
48 return (
49 <div
50 className={cn(inputGroupAddonVariants({ align }), className)}
51 data-align={align}
52 data-slot="input-group-addon"
53 onMouseDown={(e) => {
54 const target = e.target as HTMLElement;
55 const isInteractive = target.closest(
56 "button, a, input, select, textarea, [role='button'], [role='combobox'], [role='listbox'], [data-slot='select-trigger']",
57 );
58 if (isInteractive) return;
59 e.preventDefault();
60 const parent = e.currentTarget.parentElement;
61 const input = parent?.querySelector<
62 HTMLInputElement | HTMLTextAreaElement
63 >("input, textarea");
64 if (input && !parent?.querySelector("input:focus, textarea:focus")) {
65 input.focus();
66 }
67 }}
68 {...props}
69 />
70 );
71}
72
73function InputGroupText({ className, ...props }: React.ComponentProps<"span">) {
74 return (
75 <span
76 className={cn(
77 "[&_svg]:-mx-0.5 line-clamp-1 flex items-center gap-2 text-muted-foreground leading-none in-[[data-slot=input-group]:has([data-slot=input-control],[data-slot=textarea-control])]:[&_svg:not([class*='size-'])]:size-4.5 sm:in-[[data-slot=input-group]:has([data-slot=input-control],[data-slot=textarea-control])]:[&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none",
78 className,
79 )}
80 {...props}
81 />
82 );
83}
84
85function InputGroupInput({ className, ...props }: InputProps) {
86 return <Input className={className} unstyled {...props} />;
87}
88
89function InputGroupTextarea({ className, ...props }: TextareaProps) {
90 return <Textarea className={className} unstyled {...props} />;
91}
92
93export {
94 InputGroup,
95 InputGroupAddon,
96 InputGroupText,
97 InputGroupInput,
98 InputGroupTextarea,
99};
apps/renderer/src/components/ui/input.tsx deleted-64
...@@ -1,64 +0,0 @@
1import { Input as InputPrimitive } from "@base-ui/react/input";
2import type * as React from "react";
3
4import { cn } from "#/lib/utils";
5
6type InputProps = Omit<
7 InputPrimitive.Props & React.RefAttributes<HTMLInputElement>,
8 "size"
9> & {
10 size?: "sm" | "default" | "lg" | number;
11 unstyled?: boolean;
12 nativeInput?: boolean;
13};
14
15function Input({
16 className,
17 size = "default",
18 unstyled = false,
19 nativeInput = false,
20 ...props
21}: InputProps) {
22 const inputClassName = cn(
23 "h-8.5 w-full min-w-0 rounded-[inherit] px-[calc(--spacing(3)-1px)] leading-8.5 outline-none placeholder:text-muted-foreground/72 sm:h-7.5 sm:leading-7.5 [transition:background-color_5000000s_ease-in-out_0s]",
24 size === "sm" &&
25 "h-7.5 px-[calc(--spacing(2.5)-1px)] leading-7.5 sm:h-6.5 sm:leading-6.5",
26 size === "lg" && "h-9.5 leading-9.5 sm:h-8.5 sm:leading-8.5",
27 props.type === "search" &&
28 "[&::-webkit-search-cancel-button]:appearance-none [&::-webkit-search-decoration]:appearance-none [&::-webkit-search-results-button]:appearance-none [&::-webkit-search-results-decoration]:appearance-none",
29 props.type === "file" &&
30 "text-muted-foreground file:me-3 file:bg-transparent file:font-medium file:text-foreground file:text-sm",
31 );
32
33 return (
34 <span
35 className={
36 cn(
37 !unstyled &&
38 "relative inline-flex w-full rounded-lg border border-input bg-background not-dark:bg-clip-padding text-base text-foreground shadow-xs/5 ring-ring/24 transition-shadow before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--radius-lg)-1px)] not-has-disabled:not-has-focus-visible:not-has-aria-invalid:before:shadow-[0_1px_--theme(--color-black/4%)] has-focus-visible:has-aria-invalid:border-destructive/64 has-focus-visible:has-aria-invalid:ring-destructive/16 has-aria-invalid:border-destructive/36 has-focus-visible:border-ring has-autofill:bg-foreground/4 has-disabled:opacity-64 has-[:disabled,:focus-visible,[aria-invalid]]:shadow-none has-focus-visible:ring-[3px] sm:text-sm dark:bg-input/32 dark:has-autofill:bg-foreground/8 dark:has-aria-invalid:ring-destructive/24 dark:not-has-disabled:not-has-focus-visible:not-has-aria-invalid:before:shadow-[0_-1px_--theme(--color-white/6%)]",
39 className,
40 ) || undefined
41 }
42 data-size={size}
43 data-slot="input-control"
44 >
45 {nativeInput ? (
46 <input
47 className={inputClassName}
48 data-slot="input"
49 size={typeof size === "number" ? size : undefined}
50 {...props}
51 />
52 ) : (
53 <InputPrimitive
54 className={inputClassName}
55 data-slot="input"
56 size={typeof size === "number" ? size : undefined}
57 {...props}
58 />
59 )}
60 </span>
61 );
62}
63
64export { Input, type InputProps };
apps/renderer/src/components/ui/kbd.tsx deleted-28
...@@ -1,28 +0,0 @@
1import type * as React from "react";
2
3import { cn } from "#/lib/utils";
4
5function Kbd({ className, ...props }: React.ComponentProps<"kbd">) {
6 return (
7 <kbd
8 className={cn(
9 "pointer-events-none inline-flex h-5 min-w-5 select-none items-center justify-center gap-1 rounded bg-muted px-1 font-medium font-sans text-muted-foreground text-xs [&_svg:not([class*='size-'])]:size-3",
10 className,
11 )}
12 data-slot="kbd"
13 {...props}
14 />
15 );
16}
17
18function KbdGroup({ className, ...props }: React.ComponentProps<"kbd">) {
19 return (
20 <kbd
21 className={cn("inline-flex items-center gap-1", className)}
22 data-slot="kbd-group"
23 {...props}
24 />
25 );
26}
27
28export { Kbd, KbdGroup };
apps/renderer/src/components/ui/label.tsx deleted-26
...@@ -1,26 +0,0 @@
1import { mergeProps } from "@base-ui/react/merge-props";
2import { useRender } from "@base-ui/react/use-render";
3
4import { cn } from "#/lib/utils";
5
6function Label({
7 className,
8 render,
9 ...props
10}: useRender.ComponentProps<"label">) {
11 const defaultProps = {
12 className: cn(
13 "inline-flex items-center gap-2 text-base/4.5 sm:text-sm/4 font-medium text-foreground",
14 className,
15 ),
16 "data-slot": "label",
17 };
18
19 return useRender({
20 defaultTagName: "label",
21 props: mergeProps<"label">(defaultProps, props),
22 render,
23 });
24}
25
26export { Label };
apps/renderer/src/components/ui/menu.tsx deleted-308
...@@ -1,308 +0,0 @@
1import { Menu as MenuPrimitive } from "@base-ui/react/menu";
2import { ChevronRightIcon } from "lucide-react";
3import type * as React from "react";
4import { cn } from "#/lib/utils";
5
6const MenuCreateHandle = MenuPrimitive.createHandle;
7
8const Menu = MenuPrimitive.Root;
9
10const MenuPortal = MenuPrimitive.Portal;
11
12function MenuTrigger(props: MenuPrimitive.Trigger.Props) {
13 return <MenuPrimitive.Trigger data-slot="menu-trigger" {...props} />;
14}
15
16function MenuPopup({
17 children,
18 className,
19 sideOffset = 4,
20 align = "center",
21 alignOffset,
22 side = "bottom",
23 ...props
24}: MenuPrimitive.Popup.Props & {
25 align?: MenuPrimitive.Positioner.Props["align"];
26 sideOffset?: MenuPrimitive.Positioner.Props["sideOffset"];
27 alignOffset?: MenuPrimitive.Positioner.Props["alignOffset"];
28 side?: MenuPrimitive.Positioner.Props["side"];
29}) {
30 return (
31 <MenuPrimitive.Portal>
32 <MenuPrimitive.Positioner
33 align={align}
34 alignOffset={alignOffset}
35 className="z-50"
36 data-slot="menu-positioner"
37 side={side}
38 sideOffset={sideOffset}
39 >
40 <MenuPrimitive.Popup
41 className={cn(
42 "relative flex not-[class*='w-']:min-w-32 origin-(--transform-origin) rounded-lg border bg-popover not-dark:bg-clip-padding shadow-lg/5 outline-none before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--radius-lg)-1px)] before:shadow-[0_1px_--theme(--color-black/4%)] focus:outline-none dark:before:shadow-[0_-1px_--theme(--color-white/6%)]",
43 className,
44 )}
45 data-slot="menu-popup"
46 {...props}
47 >
48 <div className="max-h-(--available-height) w-full overflow-y-auto p-1">
49 {children}
50 </div>
51 </MenuPrimitive.Popup>
52 </MenuPrimitive.Positioner>
53 </MenuPrimitive.Portal>
54 );
55}
56
57function MenuGroup(props: MenuPrimitive.Group.Props) {
58 return <MenuPrimitive.Group data-slot="menu-group" {...props} />;
59}
60
61function MenuItem({
62 className,
63 inset,
64 variant = "default",
65 ...props
66}: MenuPrimitive.Item.Props & {
67 inset?: boolean;
68 variant?: "default" | "destructive";
69}) {
70 return (
71 <MenuPrimitive.Item
72 className={cn(
73 "[&>svg]:-mx-0.5 flex min-h-8 cursor-default select-none items-center gap-2 rounded-sm px-2 py-1 text-base text-foreground outline-none data-disabled:pointer-events-none data-highlighted:bg-accent data-inset:ps-8 data-[variant=destructive]:text-destructive-foreground data-highlighted:text-accent-foreground data-disabled:opacity-64 sm:min-h-7 sm:text-sm [&>svg:not([class*='opacity-'])]:opacity-80 [&>svg:not([class*='size-'])]:size-4.5 sm:[&>svg:not([class*='size-'])]:size-4 [&>svg]:pointer-events-none [&>svg]:shrink-0",
74 className,
75 )}
76 data-inset={inset}
77 data-slot="menu-item"
78 data-variant={variant}
79 {...props}
80 />
81 );
82}
83
84function MenuCheckboxItem({
85 className,
86 children,
87 checked,
88 variant = "default",
89 ...props
90}: MenuPrimitive.CheckboxItem.Props & {
91 variant?: "default" | "switch";
92}) {
93 return (
94 <MenuPrimitive.CheckboxItem
95 checked={checked}
96 className={cn(
97 "grid min-h-8 in-data-[side=none]:min-w-[calc(var(--anchor-width)+1.25rem)] cursor-default items-center gap-2 rounded-sm py-1 ps-2 text-base text-foreground outline-none data-disabled:pointer-events-none data-highlighted:bg-accent data-highlighted:text-accent-foreground data-disabled:opacity-64 sm:min-h-7 sm:text-sm [&_svg:not([class*='size-'])]:size-4.5 sm:[&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0",
98 variant === "switch"
99 ? "grid-cols-[1fr_auto] gap-4 pe-1.5"
100 : "grid-cols-[1rem_1fr] pe-4",
101 className,
102 )}
103 data-slot="menu-checkbox-item"
104 {...props}
105 >
106 {variant === "switch" ? (
107 <>
108 <span className="col-start-1">{children}</span>
109 <MenuPrimitive.CheckboxItemIndicator
110 className="inset-shadow-[0_1px_--theme(--color-black/4%)] inline-flex h-[calc(var(--thumb-size)+2px)] w-[calc(var(--thumb-size)*2-2px)] shrink-0 items-center rounded-full p-px outline-none transition-[background-color,box-shadow] duration-200 [--thumb-size:--spacing(4)] focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background data-checked:bg-primary data-unchecked:bg-input data-disabled:opacity-64 sm:[--thumb-size:--spacing(3)]"
111 keepMounted
112 >
113 <span className="pointer-events-none block aspect-square h-full in-[[data-slot=menu-checkbox-item][data-checked]]:origin-[var(--thumb-size)_50%] origin-left in-[[data-slot=menu-checkbox-item][data-checked]]:translate-x-[calc(var(--thumb-size)-4px)] in-[[data-slot=menu-checkbox-item]:active]:not-data-disabled:scale-x-110 in-[[data-slot=menu-checkbox-item]:active]:rounded-[var(--thumb-size)/calc(var(--thumb-size)*1.10)] rounded-(--thumb-size) bg-background shadow-sm/5 will-change-transform [transition:translate_.15s,border-radius_.15s,scale_.1s_.1s,transform-origin_.15s]" />
114 </MenuPrimitive.CheckboxItemIndicator>
115 </>
116 ) : (
117 <>
118 <MenuPrimitive.CheckboxItemIndicator className="col-start-1">
119 <svg
120 fill="none"
121 height="24"
122 stroke="currentColor"
123 strokeLinecap="round"
124 strokeLinejoin="round"
125 strokeWidth="2"
126 viewBox="0 0 24 24"
127 width="24"
128 xmlns="http://www.w3.org/2000/svg"
129 >
130 <path d="M5.252 12.7 10.2 18.63 18.748 5.37" />
131 </svg>
132 </MenuPrimitive.CheckboxItemIndicator>
133 <span className="col-start-2">{children}</span>
134 </>
135 )}
136 </MenuPrimitive.CheckboxItem>
137 );
138}
139
140function MenuRadioGroup(props: MenuPrimitive.RadioGroup.Props) {
141 return <MenuPrimitive.RadioGroup data-slot="menu-radio-group" {...props} />;
142}
143
144function MenuRadioItem({
145 className,
146 children,
147 ...props
148}: MenuPrimitive.RadioItem.Props) {
149 return (
150 <MenuPrimitive.RadioItem
151 className={cn(
152 "grid min-h-8 in-data-[side=none]:min-w-[calc(var(--anchor-width)+1.25rem)] cursor-default grid-cols-[1rem_1fr] items-center gap-2 rounded-sm py-1 ps-2 pe-4 text-base text-foreground outline-none data-disabled:pointer-events-none data-highlighted:bg-accent data-highlighted:text-accent-foreground data-disabled:opacity-64 sm:min-h-7 sm:text-sm [&_svg:not([class*='size-'])]:size-4.5 sm:[&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0",
153 className,
154 )}
155 data-slot="menu-radio-item"
156 {...props}
157 >
158 <MenuPrimitive.RadioItemIndicator className="col-start-1">
159 <svg
160 fill="none"
161 height="24"
162 stroke="currentColor"
163 strokeLinecap="round"
164 strokeLinejoin="round"
165 strokeWidth="2"
166 viewBox="0 0 24 24"
167 width="24"
168 xmlns="http://www.w3.org/2000/svg"
169 >
170 <path d="M5.252 12.7 10.2 18.63 18.748 5.37" />
171 </svg>
172 </MenuPrimitive.RadioItemIndicator>
173 <span className="col-start-2">{children}</span>
174 </MenuPrimitive.RadioItem>
175 );
176}
177
178function MenuGroupLabel({
179 className,
180 inset,
181 ...props
182}: MenuPrimitive.GroupLabel.Props & {
183 inset?: boolean;
184}) {
185 return (
186 <MenuPrimitive.GroupLabel
187 className={cn(
188 "px-2 py-1.5 font-medium text-muted-foreground text-xs data-inset:ps-9 sm:data-inset:ps-8",
189 className,
190 )}
191 data-inset={inset}
192 data-slot="menu-label"
193 {...props}
194 />
195 );
196}
197
198function MenuSeparator({ className, ...props }: MenuPrimitive.Separator.Props) {
199 return (
200 <MenuPrimitive.Separator
201 className={cn("mx-2 my-1 h-px bg-border", className)}
202 data-slot="menu-separator"
203 {...props}
204 />
205 );
206}
207
208function MenuShortcut({ className, ...props }: React.ComponentProps<"kbd">) {
209 return (
210 <kbd
211 className={cn(
212 "ms-auto font-medium font-sans text-muted-foreground/72 text-xs tracking-widest",
213 className,
214 )}
215 data-slot="menu-shortcut"
216 {...props}
217 />
218 );
219}
220
221function MenuSub(props: MenuPrimitive.SubmenuRoot.Props) {
222 return <MenuPrimitive.SubmenuRoot data-slot="menu-sub" {...props} />;
223}
224
225function MenuSubTrigger({
226 className,
227 inset,
228 children,
229 ...props
230}: MenuPrimitive.SubmenuTrigger.Props & {
231 inset?: boolean;
232}) {
233 return (
234 <MenuPrimitive.SubmenuTrigger
235 className={cn(
236 "flex min-h-8 items-center gap-2 rounded-sm px-2 py-1 text-base text-foreground outline-none data-disabled:pointer-events-none data-highlighted:bg-accent data-popup-open:bg-accent data-inset:ps-8 data-highlighted:text-accent-foreground data-popup-open:text-accent-foreground data-disabled:opacity-64 sm:min-h-7 sm:text-sm [&_svg:not([class*='size-'])]:size-4.5 sm:[&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none",
237 className,
238 )}
239 data-inset={inset}
240 data-slot="menu-sub-trigger"
241 {...props}
242 >
243 {children}
244 <ChevronRightIcon className="-me-0.5 ms-auto opacity-80" />
245 </MenuPrimitive.SubmenuTrigger>
246 );
247}
248
249function MenuSubPopup({
250 className,
251 sideOffset = 0,
252 alignOffset,
253 align = "start",
254 ...props
255}: MenuPrimitive.Popup.Props & {
256 align?: MenuPrimitive.Positioner.Props["align"];
257 sideOffset?: MenuPrimitive.Positioner.Props["sideOffset"];
258 alignOffset?: MenuPrimitive.Positioner.Props["alignOffset"];
259}) {
260 const defaultAlignOffset = align !== "center" ? -5 : undefined;
261
262 return (
263 <MenuPopup
264 align={align}
265 alignOffset={alignOffset ?? defaultAlignOffset}
266 className={className}
267 data-slot="menu-sub-content"
268 side="inline-end"
269 sideOffset={sideOffset}
270 {...props}
271 />
272 );
273}
274
275export {
276 MenuCreateHandle,
277 MenuCreateHandle as DropdownMenuCreateHandle,
278 Menu,
279 Menu as DropdownMenu,
280 MenuPortal,
281 MenuPortal as DropdownMenuPortal,
282 MenuTrigger,
283 MenuTrigger as DropdownMenuTrigger,
284 MenuPopup,
285 MenuPopup as DropdownMenuContent,
286 MenuGroup,
287 MenuGroup as DropdownMenuGroup,
288 MenuItem,
289 MenuItem as DropdownMenuItem,
290 MenuCheckboxItem,
291 MenuCheckboxItem as DropdownMenuCheckboxItem,
292 MenuRadioGroup,
293 MenuRadioGroup as DropdownMenuRadioGroup,
294 MenuRadioItem,
295 MenuRadioItem as DropdownMenuRadioItem,
296 MenuGroupLabel,
297 MenuGroupLabel as DropdownMenuLabel,
298 MenuSeparator,
299 MenuSeparator as DropdownMenuSeparator,
300 MenuShortcut,
301 MenuShortcut as DropdownMenuShortcut,
302 MenuSub,
303 MenuSub as DropdownMenuSub,
304 MenuSubTrigger,
305 MenuSubTrigger as DropdownMenuSubTrigger,
306 MenuSubPopup,
307 MenuSubPopup as DropdownMenuSubContent,
308};
apps/renderer/src/components/ui/popover.tsx deleted-102
...@@ -1,102 +0,0 @@
1import { Popover as PopoverPrimitive } from "@base-ui/react/popover";
2
3import { cn } from "#/lib/utils";
4
5const PopoverCreateHandle = PopoverPrimitive.createHandle;
6
7const Popover = PopoverPrimitive.Root;
8
9function PopoverTrigger(props: PopoverPrimitive.Trigger.Props) {
10 return <PopoverPrimitive.Trigger data-slot="popover-trigger" {...props} />;
11}
12
13function PopoverPopup({
14 children,
15 className,
16 side = "bottom",
17 align = "center",
18 sideOffset = 4,
19 alignOffset = 0,
20 tooltipStyle = false,
21 ...props
22}: PopoverPrimitive.Popup.Props & {
23 side?: PopoverPrimitive.Positioner.Props["side"];
24 align?: PopoverPrimitive.Positioner.Props["align"];
25 sideOffset?: PopoverPrimitive.Positioner.Props["sideOffset"];
26 alignOffset?: PopoverPrimitive.Positioner.Props["alignOffset"];
27 tooltipStyle?: boolean;
28}) {
29 return (
30 <PopoverPrimitive.Portal>
31 <PopoverPrimitive.Positioner
32 align={align}
33 alignOffset={alignOffset}
34 className="z-50 h-(--positioner-height) w-(--positioner-width) max-w-(--available-width) transition-[top,left,right,bottom,transform] data-instant:transition-none"
35 data-slot="popover-positioner"
36 side={side}
37 sideOffset={sideOffset}
38 >
39 <PopoverPrimitive.Popup
40 className={cn(
41 "relative flex h-(--popup-height,auto) w-(--popup-width,auto) origin-(--transform-origin) rounded-lg border bg-popover not-dark:bg-clip-padding text-popover-foreground shadow-lg/5 transition-[width,height,scale,opacity] before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--radius-lg)-1px)] before:shadow-[0_1px_--theme(--color-black/4%)] data-starting-style:scale-98 data-starting-style:opacity-0 dark:before:shadow-[0_-1px_--theme(--color-white/6%)]",
42 tooltipStyle &&
43 "w-fit text-balance rounded-md text-xs shadow-md/5 before:rounded-[calc(var(--radius-md)-1px)]",
44 className,
45 )}
46 data-slot="popover-popup"
47 {...props}
48 >
49 <PopoverPrimitive.Viewport
50 className={cn(
51 "relative size-full max-h-(--available-height) overflow-clip px-(--viewport-inline-padding) py-4 outline-none [--viewport-inline-padding:--spacing(4)] data-instant:transition-none **:data-current:data-ending-style:opacity-0 **:data-current:data-starting-style:opacity-0 **:data-previous:data-ending-style:opacity-0 **:data-previous:data-starting-style:opacity-0 **:data-current:w-[calc(var(--popup-width)-2*var(--viewport-inline-padding)-2px)] **:data-previous:w-[calc(var(--popup-width)-2*var(--viewport-inline-padding)-2px)] **:data-current:opacity-100 **:data-previous:opacity-100 **:data-current:transition-opacity **:data-previous:transition-opacity",
52 tooltipStyle
53 ? "py-1 [--viewport-inline-padding:--spacing(2)]"
54 : "not-data-transitioning:overflow-y-auto",
55 )}
56 data-slot="popover-viewport"
57 >
58 {children}
59 </PopoverPrimitive.Viewport>
60 </PopoverPrimitive.Popup>
61 </PopoverPrimitive.Positioner>
62 </PopoverPrimitive.Portal>
63 );
64}
65
66function PopoverClose({ ...props }: PopoverPrimitive.Close.Props) {
67 return <PopoverPrimitive.Close data-slot="popover-close" {...props} />;
68}
69
70function PopoverTitle({ className, ...props }: PopoverPrimitive.Title.Props) {
71 return (
72 <PopoverPrimitive.Title
73 className={cn("font-semibold text-lg leading-none", className)}
74 data-slot="popover-title"
75 {...props}
76 />
77 );
78}
79
80function PopoverDescription({
81 className,
82 ...props
83}: PopoverPrimitive.Description.Props) {
84 return (
85 <PopoverPrimitive.Description
86 className={cn("text-muted-foreground text-sm", className)}
87 data-slot="popover-description"
88 {...props}
89 />
90 );
91}
92
93export {
94 PopoverCreateHandle,
95 Popover,
96 PopoverTrigger,
97 PopoverPopup,
98 PopoverPopup as PopoverContent,
99 PopoverTitle,
100 PopoverDescription,
101 PopoverClose,
102};
apps/renderer/src/components/ui/progress.tsx deleted-79
...@@ -1,79 +0,0 @@
1import { Progress as ProgressPrimitive } from "@base-ui/react/progress";
2
3import { cn } from "#/lib/utils";
4
5function Progress({
6 className,
7 children,
8 ...props
9}: ProgressPrimitive.Root.Props) {
10 return (
11 <ProgressPrimitive.Root
12 className={cn("flex w-full flex-col gap-2", className)}
13 data-slot="progress"
14 {...props}
15 >
16 {children ? (
17 children
18 ) : (
19 <ProgressTrack>
20 <ProgressIndicator />
21 </ProgressTrack>
22 )}
23 </ProgressPrimitive.Root>
24 );
25}
26
27function ProgressLabel({ className, ...props }: ProgressPrimitive.Label.Props) {
28 return (
29 <ProgressPrimitive.Label
30 className={cn("font-medium text-sm", className)}
31 data-slot="progress-label"
32 {...props}
33 />
34 );
35}
36
37function ProgressTrack({ className, ...props }: ProgressPrimitive.Track.Props) {
38 return (
39 <ProgressPrimitive.Track
40 className={cn(
41 "block h-1.5 w-full overflow-hidden rounded-full bg-input",
42 className,
43 )}
44 data-slot="progress-track"
45 {...props}
46 />
47 );
48}
49
50function ProgressIndicator({
51 className,
52 ...props
53}: ProgressPrimitive.Indicator.Props) {
54 return (
55 <ProgressPrimitive.Indicator
56 className={cn("bg-primary transition-all duration-500", className)}
57 data-slot="progress-indicator"
58 {...props}
59 />
60 );
61}
62
63function ProgressValue({ className, ...props }: ProgressPrimitive.Value.Props) {
64 return (
65 <ProgressPrimitive.Value
66 className={cn("text-sm tabular-nums", className)}
67 data-slot="progress-value"
68 {...props}
69 />
70 );
71}
72
73export {
74 Progress,
75 ProgressLabel,
76 ProgressTrack,
77 ProgressIndicator,
78 ProgressValue,
79};
apps/renderer/src/components/ui/radio-group.tsx deleted-34
...@@ -1,34 +0,0 @@
1import { Radio as RadioPrimitive } from "@base-ui/react/radio";
2import { RadioGroup as RadioGroupPrimitive } from "@base-ui/react/radio-group";
3
4import { cn } from "#/lib/utils";
5
6function RadioGroup({ className, ...props }: RadioGroupPrimitive.Props) {
7 return (
8 <RadioGroupPrimitive
9 className={cn("flex flex-col gap-3", className)}
10 data-slot="radio-group"
11 {...props}
12 />
13 );
14}
15
16function Radio({ className, ...props }: RadioPrimitive.Root.Props) {
17 return (
18 <RadioPrimitive.Root
19 className={cn(
20 "relative inline-flex size-4.5 shrink-0 items-center justify-center rounded-full border border-input bg-background not-dark:bg-clip-padding shadow-xs/5 outline-none transition-shadow before:pointer-events-none before:absolute before:inset-0 before:rounded-full not-data-disabled:not-data-checked:not-aria-invalid:before:shadow-[0_1px_--theme(--color-black/4%)] focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background aria-invalid:border-destructive/36 focus-visible:aria-invalid:border-destructive/64 focus-visible:aria-invalid:ring-destructive/48 data-disabled:opacity-64 sm:size-4 dark:not-data-checked:bg-input/32 dark:aria-invalid:ring-destructive/24 dark:not-data-disabled:not-data-checked:not-aria-invalid:before:shadow-[0_-1px_--theme(--color-white/6%)] [[data-disabled],[data-checked],[aria-invalid]]:shadow-none",
21 className,
22 )}
23 data-slot="radio"
24 {...props}
25 >
26 <RadioPrimitive.Indicator
27 className="-inset-px absolute flex size-4.5 items-center justify-center rounded-full before:size-2 before:rounded-full before:bg-primary-foreground data-unchecked:hidden data-checked:bg-primary sm:size-4 sm:before:size-1.5"
28 data-slot="radio-indicator"
29 />
30 </RadioPrimitive.Root>
31 );
32}
33
34export { RadioGroup, Radio, Radio as RadioGroupItem };
apps/renderer/src/components/ui/scroll-area.tsx deleted-62
...@@ -1,62 +0,0 @@
1import { ScrollArea as ScrollAreaPrimitive } from "@base-ui/react/scroll-area";
2
3import { cn } from "#/lib/utils";
4
5function ScrollArea({
6 className,
7 children,
8 scrollFade = false,
9 scrollbarGutter = false,
10 ...props
11}: ScrollAreaPrimitive.Root.Props & {
12 scrollFade?: boolean;
13 scrollbarGutter?: boolean;
14}) {
15 return (
16 <ScrollAreaPrimitive.Root
17 className={cn("size-full min-h-0", className)}
18 {...props}
19 >
20 <ScrollAreaPrimitive.Viewport
21 className={cn(
22 "h-full overscroll-contain rounded-[inherit] outline-none transition-shadows focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background data-has-overflow-x:overscroll-x-contain",
23 scrollFade &&
24 "mask-t-from-[calc(100%-min(var(--fade-size),var(--scroll-area-overflow-y-start)))] mask-b-from-[calc(100%-min(var(--fade-size),var(--scroll-area-overflow-y-end)))] mask-l-from-[calc(100%-min(var(--fade-size),var(--scroll-area-overflow-x-start)))] mask-r-from-[calc(100%-min(var(--fade-size),var(--scroll-area-overflow-x-end)))] [--fade-size:1.5rem]",
25 scrollbarGutter &&
26 "data-has-overflow-y:pe-2.5 data-has-overflow-x:pb-2.5",
27 )}
28 data-slot="scroll-area-viewport"
29 >
30 {children}
31 </ScrollAreaPrimitive.Viewport>
32 <ScrollBar orientation="vertical" />
33 <ScrollBar orientation="horizontal" />
34 <ScrollAreaPrimitive.Corner data-slot="scroll-area-corner" />
35 </ScrollAreaPrimitive.Root>
36 );
37}
38
39function ScrollBar({
40 className,
41 orientation = "vertical",
42 ...props
43}: ScrollAreaPrimitive.Scrollbar.Props) {
44 return (
45 <ScrollAreaPrimitive.Scrollbar
46 className={cn(
47 "m-1 flex opacity-0 transition-opacity delay-300 data-[orientation=horizontal]:h-1.5 data-[orientation=vertical]:w-1.5 data-[orientation=horizontal]:flex-col data-hovering:opacity-100 data-scrolling:opacity-100 data-hovering:delay-0 data-scrolling:delay-0 data-hovering:duration-100 data-scrolling:duration-100",
48 className,
49 )}
50 data-slot="scroll-area-scrollbar"
51 orientation={orientation}
52 {...props}
53 >
54 <ScrollAreaPrimitive.Thumb
55 className="relative flex-1 rounded-full bg-foreground/20"
56 data-slot="scroll-area-thumb"
57 />
58 </ScrollAreaPrimitive.Scrollbar>
59 );
60}
61
62export { ScrollArea, ScrollBar };
apps/renderer/src/components/ui/select.tsx deleted-187
...@@ -1,187 +0,0 @@
1import { Select as SelectPrimitive } from "@base-ui/react/select";
2import {
3 ChevronDownIcon,
4 ChevronUpIcon,
5 ChevronsUpDownIcon,
6} from "lucide-react";
7
8import { cn } from "#/lib/utils";
9
10const Select = SelectPrimitive.Root;
11
12function SelectTrigger({
13 className,
14 size = "default",
15 children,
16 ...props
17}: SelectPrimitive.Trigger.Props & {
18 size?: "sm" | "default" | "lg";
19}) {
20 return (
21 <SelectPrimitive.Trigger
22 className={cn(
23 "relative inline-flex min-h-9 w-full min-w-36 select-none items-center justify-center gap-2 rounded-lg border border-input bg-background not-dark:bg-clip-padding px-[calc(--spacing(3)-1px)] text-left text-base text-foreground shadow-xs/5 outline-none ring-ring/24 transition-shadow before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--radius-lg)-1px)] not-data-disabled:not-focus-visible:not-aria-invalid:not-data-pressed:before:shadow-[0_1px_--theme(--color-black/4%)] pointer-coarse:after:absolute pointer-coarse:after:size-full pointer-coarse:after:min-h-11 focus-visible:border-ring focus-visible:ring-[3px] aria-invalid:border-destructive/36 focus-visible:aria-invalid:border-destructive/64 focus-visible:aria-invalid:ring-destructive/16 data-disabled:pointer-events-none data-disabled:opacity-64 sm:min-h-8 sm:text-sm dark:bg-input/32 dark:aria-invalid:ring-destructive/24 dark:not-data-disabled:not-focus-visible:not-aria-invalid:not-data-pressed:before:shadow-[0_-1px_--theme(--color-white/6%)] [&_svg:not([class*='opacity-'])]:opacity-80 [&_svg:not([class*='size-'])]:size-4.5 sm:[&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0 [[data-disabled],:focus-visible,[aria-invalid],[data-pressed]]:shadow-none",
24 size === "sm" &&
25 "min-h-8 gap-1.5 px-[calc(--spacing(2.5)-1px)] sm:min-h-7",
26 size === "lg" && "min-h-10 sm:min-h-9",
27 className,
28 )}
29 data-slot="select-trigger"
30 {...props}
31 >
32 {children}
33 <SelectPrimitive.Icon data-slot="select-icon">
34 <ChevronsUpDownIcon className="-me-1 size-4.5 opacity-80 sm:size-4" />
35 </SelectPrimitive.Icon>
36 </SelectPrimitive.Trigger>
37 );
38}
39
40function SelectValue({ className, ...props }: SelectPrimitive.Value.Props) {
41 return (
42 <SelectPrimitive.Value
43 className={cn(
44 "flex-1 truncate data-placeholder:text-muted-foreground",
45 className,
46 )}
47 data-slot="select-value"
48 {...props}
49 />
50 );
51}
52
53function SelectPopup({
54 className,
55 children,
56 side = "bottom",
57 sideOffset = 4,
58 align = "start",
59 alignOffset = 0,
60 alignItemWithTrigger = true,
61 ...props
62}: SelectPrimitive.Popup.Props & {
63 side?: SelectPrimitive.Positioner.Props["side"];
64 sideOffset?: SelectPrimitive.Positioner.Props["sideOffset"];
65 align?: SelectPrimitive.Positioner.Props["align"];
66 alignOffset?: SelectPrimitive.Positioner.Props["alignOffset"];
67 alignItemWithTrigger?: SelectPrimitive.Positioner.Props["alignItemWithTrigger"];
68}) {
69 return (
70 <SelectPrimitive.Portal>
71 <SelectPrimitive.Positioner
72 align={align}
73 alignItemWithTrigger={alignItemWithTrigger}
74 alignOffset={alignOffset}
75 className="z-50 select-none"
76 data-slot="select-positioner"
77 side={side}
78 sideOffset={sideOffset}
79 >
80 <SelectPrimitive.Popup
81 className="origin-(--transform-origin) text-foreground"
82 data-slot="select-popup"
83 {...props}
84 >
85 <SelectPrimitive.ScrollUpArrow
86 className="top-0 z-50 flex h-6 w-full cursor-default items-center justify-center before:pointer-events-none before:absolute before:inset-x-px before:top-px before:h-[200%] before:rounded-t-[calc(var(--radius-lg)-1px)] before:bg-linear-to-b before:from-50% before:from-popover"
87 data-slot="select-scroll-up-arrow"
88 >
89 <ChevronUpIcon className="relative size-4.5 sm:size-4" />
90 </SelectPrimitive.ScrollUpArrow>
91 <div className="relative h-full min-w-(--anchor-width) rounded-lg border bg-popover not-dark:bg-clip-padding shadow-lg/5 before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--radius-lg)-1px)] before:shadow-[0_1px_--theme(--color-black/4%)] dark:before:shadow-[0_-1px_--theme(--color-white/6%)]">
92 <SelectPrimitive.List
93 className={cn(
94 "max-h-(--available-height) overflow-y-auto p-1",
95 className,
96 )}
97 data-slot="select-list"
98 >
99 {children}
100 </SelectPrimitive.List>
101 </div>
102 <SelectPrimitive.ScrollDownArrow
103 className="bottom-0 z-50 flex h-6 w-full cursor-default items-center justify-center before:pointer-events-none before:absolute before:inset-x-px before:bottom-px before:h-[200%] before:rounded-b-[calc(var(--radius-lg)-1px)] before:bg-linear-to-t before:from-50% before:from-popover"
104 data-slot="select-scroll-down-arrow"
105 >
106 <ChevronDownIcon className="relative size-4.5 sm:size-4" />
107 </SelectPrimitive.ScrollDownArrow>
108 </SelectPrimitive.Popup>
109 </SelectPrimitive.Positioner>
110 </SelectPrimitive.Portal>
111 );
112}
113
114function SelectItem({
115 className,
116 children,
117 ...props
118}: SelectPrimitive.Item.Props) {
119 return (
120 <SelectPrimitive.Item
121 className={cn(
122 "grid min-h-8 in-data-[side=none]:min-w-[calc(var(--anchor-width)+1.25rem)] cursor-default grid-cols-[1rem_1fr] items-center gap-2 rounded-sm py-1 ps-2 pe-4 text-base outline-none data-disabled:pointer-events-none data-highlighted:bg-accent data-highlighted:text-accent-foreground data-disabled:opacity-64 sm:min-h-7 sm:text-sm [&_svg:not([class*='size-'])]:size-4.5 sm:[&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0",
123 className,
124 )}
125 data-slot="select-item"
126 {...props}
127 >
128 <SelectPrimitive.ItemIndicator className="col-start-1">
129 <svg
130 fill="none"
131 height="24"
132 stroke="currentColor"
133 strokeLinecap="round"
134 strokeLinejoin="round"
135 strokeWidth="2"
136 viewBox="0 0 24 24"
137 width="24"
138 xmlns="http://www.w3.org/1500/svg"
139 >
140 <path d="M5.252 12.7 10.2 18.63 18.748 5.37" />
141 </svg>
142 </SelectPrimitive.ItemIndicator>
143 <SelectPrimitive.ItemText className="col-start-2 min-w-0">
144 {children}
145 </SelectPrimitive.ItemText>
146 </SelectPrimitive.Item>
147 );
148}
149
150function SelectSeparator({
151 className,
152 ...props
153}: SelectPrimitive.Separator.Props) {
154 return (
155 <SelectPrimitive.Separator
156 className={cn("mx-2 my-1 h-px bg-border", className)}
157 data-slot="select-separator"
158 {...props}
159 />
160 );
161}
162
163function SelectGroup(props: SelectPrimitive.Group.Props) {
164 return <SelectPrimitive.Group data-slot="select-group" {...props} />;
165}
166
167function SelectGroupLabel(props: SelectPrimitive.GroupLabel.Props) {
168 return (
169 <SelectPrimitive.GroupLabel
170 className="px-2 py-1.5 font-medium text-muted-foreground text-xs"
171 data-slot="select-group-label"
172 {...props}
173 />
174 );
175}
176
177export {
178 Select,
179 SelectTrigger,
180 SelectValue,
181 SelectPopup,
182 SelectPopup as SelectContent,
183 SelectItem,
184 SelectSeparator,
185 SelectGroup,
186 SelectGroupLabel,
187};
apps/renderer/src/components/ui/separator.tsx deleted-23
...@@ -1,23 +0,0 @@
1import { Separator as SeparatorPrimitive } from "@base-ui/react/separator";
2
3import { cn } from "#/lib/utils";
4
5function Separator({
6 className,
7 orientation = "horizontal",
8 ...props
9}: SeparatorPrimitive.Props) {
10 return (
11 <SeparatorPrimitive
12 className={cn(
13 "shrink-0 bg-border data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:w-px data-[orientation=vertical]:not-[[class^='h-']]:not-[[class*='_h-']]:self-stretch",
14 className,
15 )}
16 data-slot="separator"
17 orientation={orientation}
18 {...props}
19 />
20 );
21}
22
23export { Separator };
apps/renderer/src/components/ui/sheet.tsx deleted-201
...@@ -1,201 +0,0 @@
1import { Dialog as SheetPrimitive } from "@base-ui/react/dialog";
2import { XIcon } from "lucide-react";
3import { Button } from "#/components/ui/button";
4import { ScrollArea } from "#/components/ui/scroll-area";
5import { cn } from "#/lib/utils";
6
7const Sheet = SheetPrimitive.Root;
8
9const SheetPortal = SheetPrimitive.Portal;
10
11function SheetTrigger(props: SheetPrimitive.Trigger.Props) {
12 return <SheetPrimitive.Trigger data-slot="sheet-trigger" {...props} />;
13}
14
15function SheetClose(props: SheetPrimitive.Close.Props) {
16 return <SheetPrimitive.Close data-slot="sheet-close" {...props} />;
17}
18
19function SheetBackdrop({ className, ...props }: SheetPrimitive.Backdrop.Props) {
20 return (
21 <SheetPrimitive.Backdrop
22 className={cn(
23 "fixed inset-0 z-50 bg-black/32 backdrop-blur-sm transition-all duration-200 data-ending-style:opacity-0 data-starting-style:opacity-0",
24 className,
25 )}
26 data-slot="sheet-backdrop"
27 {...props}
28 />
29 );
30}
31
32function SheetViewport({
33 className,
34 side,
35 variant = "default",
36 ...props
37}: SheetPrimitive.Viewport.Props & {
38 side?: "right" | "left" | "top" | "bottom";
39 variant?: "default" | "inset";
40}) {
41 return (
42 <SheetPrimitive.Viewport
43 className={cn(
44 "fixed inset-0 z-50 grid",
45 side === "bottom" && "grid grid-rows-[1fr_auto] pt-12",
46 side === "top" && "grid grid-rows-[auto_1fr] pb-12",
47 side === "left" && "flex justify-start",
48 side === "right" && "flex justify-end",
49 variant === "inset" && "sm:p-4",
50 )}
51 data-slot="sheet-viewport"
52 {...props}
53 />
54 );
55}
56
57function SheetPopup({
58 className,
59 children,
60 showCloseButton = true,
61 side = "right",
62 variant = "default",
63 ...props
64}: SheetPrimitive.Popup.Props & {
65 showCloseButton?: boolean;
66 side?: "right" | "left" | "top" | "bottom";
67 variant?: "default" | "inset";
68}) {
69 return (
70 <SheetPortal>
71 <SheetBackdrop />
72 <SheetViewport side={side} variant={variant}>
73 <SheetPrimitive.Popup
74 className={cn(
75 "relative flex max-h-full min-h-0 w-full min-w-0 flex-col bg-popover not-dark:bg-clip-padding text-popover-foreground shadow-lg/5 transition-[opacity,translate] duration-200 ease-in-out will-change-transform before:pointer-events-none before:absolute before:inset-0 before:shadow-[0_1px_--theme(--color-black/4%)] data-ending-style:opacity-0 data-starting-style:opacity-0 max-sm:before:hidden dark:before:shadow-[0_-1px_--theme(--color-white/6%)]",
76 side === "bottom" &&
77 "row-start-2 border-t data-ending-style:translate-y-8 data-starting-style:translate-y-8",
78 side === "top" &&
79 "data-ending-style:-translate-y-8 data-starting-style:-translate-y-8 border-b",
80 side === "left" &&
81 "data-ending-style:-translate-x-8 data-starting-style:-translate-x-8 w-[calc(100%-(--spacing(12)))] max-w-md border-e",
82 side === "right" &&
83 "col-start-2 w-[calc(100%-(--spacing(12)))] max-w-md border-s data-ending-style:translate-x-8 data-starting-style:translate-x-8",
84 variant === "inset" &&
85 "before:hidden sm:rounded-2xl sm:border sm:before:rounded-[calc(var(--radius-2xl)-1px)] sm:**:data-[slot=sheet-footer]:rounded-b-[calc(var(--radius-2xl)-1px)]",
86 className,
87 )}
88 data-slot="sheet-popup"
89 {...props}
90 >
91 {children}
92 {showCloseButton && (
93 <SheetPrimitive.Close
94 aria-label="Close"
95 className="absolute end-2 top-2"
96 render={<Button size="icon" variant="ghost" />}
97 >
98 <XIcon />
99 </SheetPrimitive.Close>
100 )}
101 </SheetPrimitive.Popup>
102 </SheetViewport>
103 </SheetPortal>
104 );
105}
106
107function SheetHeader({ className, ...props }: React.ComponentProps<"div">) {
108 return (
109 <div
110 className={cn(
111 "flex flex-col gap-2 p-6 in-[[data-slot=sheet-popup]:has([data-slot=sheet-panel])]:pb-3 max-sm:pb-4",
112 className,
113 )}
114 data-slot="sheet-header"
115 {...props}
116 />
117 );
118}
119
120function SheetFooter({
121 className,
122 variant = "default",
123 ...props
124}: React.ComponentProps<"div"> & {
125 variant?: "default" | "bare";
126}) {
127 return (
128 <div
129 className={cn(
130 "flex flex-col-reverse gap-2 px-6 sm:flex-row sm:justify-end",
131 variant === "default" && "border-t bg-muted/72 py-4",
132 variant === "bare" &&
133 "in-[[data-slot=sheet-popup]:has([data-slot=sheet-panel])]:pt-3 pt-4 pb-6",
134 className,
135 )}
136 data-slot="sheet-footer"
137 {...props}
138 />
139 );
140}
141
142function SheetTitle({ className, ...props }: SheetPrimitive.Title.Props) {
143 return (
144 <SheetPrimitive.Title
145 className={cn(
146 "font-heading font-semibold text-xl leading-none",
147 className,
148 )}
149 data-slot="sheet-title"
150 {...props}
151 />
152 );
153}
154
155function SheetDescription({
156 className,
157 ...props
158}: SheetPrimitive.Description.Props) {
159 return (
160 <SheetPrimitive.Description
161 className={cn("text-muted-foreground text-sm", className)}
162 data-slot="sheet-description"
163 {...props}
164 />
165 );
166}
167
168function SheetPanel({
169 className,
170 scrollFade = true,
171 ...props
172}: React.ComponentProps<"div"> & { scrollFade?: boolean }) {
173 return (
174 <ScrollArea scrollFade={scrollFade}>
175 <div
176 className={cn(
177 "p-6 in-[[data-slot=sheet-popup]:has([data-slot=sheet-header])]:pt-1 in-[[data-slot=sheet-popup]:has([data-slot=sheet-footer]:not(.border-t))]:pb-1",
178 className,
179 )}
180 data-slot="sheet-panel"
181 {...props}
182 />
183 </ScrollArea>
184 );
185}
186
187export {
188 Sheet,
189 SheetTrigger,
190 SheetPortal,
191 SheetClose,
192 SheetBackdrop,
193 SheetBackdrop as SheetOverlay,
194 SheetPopup,
195 SheetPopup as SheetContent,
196 SheetHeader,
197 SheetFooter,
198 SheetTitle,
199 SheetDescription,
200 SheetPanel,
201};
apps/renderer/src/components/ui/skeleton.tsx deleted-16
...@@ -1,16 +0,0 @@
1import { cn } from "#/lib/utils";
2
3function Skeleton({ className, ...props }: React.ComponentProps<"div">) {
4 return (
5 <div
6 className={cn(
7 "animate-skeleton rounded-sm [--skeleton-highlight:--alpha(var(--color-white)/64%)] [background:linear-gradient(120deg,transparent_40%,var(--skeleton-highlight),transparent_60%)_var(--color-muted)_0_0/200%_100%_fixed] dark:[--skeleton-highlight:--alpha(var(--color-white)/4%)]",
8 className,
9 )}
10 data-slot="skeleton"
11 {...props}
12 />
13 );
14}
15
16export { Skeleton };
apps/renderer/src/components/ui/spinner.tsx deleted-18
...@@ -1,18 +0,0 @@
1import { Loader2Icon } from "lucide-react";
2import { cn } from "#/lib/utils";
3
4function Spinner({
5 className,
6 ...props
7}: React.ComponentProps<typeof Loader2Icon>) {
8 return (
9 <Loader2Icon
10 aria-label="Loading"
11 className={cn("animate-spin", className)}
12 role="status"
13 {...props}
14 />
15 );
16}
17
18export { Spinner };
apps/renderer/src/components/ui/switch.tsx deleted-25
...@@ -1,25 +0,0 @@
1import { Switch as SwitchPrimitive } from "@base-ui/react/switch";
2
3import { cn } from "#/lib/utils";
4
5function Switch({ className, ...props }: SwitchPrimitive.Root.Props) {
6 return (
7 <SwitchPrimitive.Root
8 className={cn(
9 "inline-flex h-[calc(var(--thumb-size)+2px)] w-[calc(var(--thumb-size)*2-2px)] shrink-0 items-center rounded-full p-px outline-none transition-[background-color,box-shadow] duration-200 [--thumb-size:--spacing(5)] focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background data-checked:bg-primary data-unchecked:bg-input data-disabled:opacity-64 sm:[--thumb-size:--spacing(4)]",
10 className,
11 )}
12 data-slot="switch"
13 {...props}
14 >
15 <SwitchPrimitive.Thumb
16 className={cn(
17 "pointer-events-none block aspect-square h-full origin-left in-[[role=switch]:active,[data-slot=label]:active]:not-data-disabled:scale-x-110 in-[[role=switch]:active,[data-slot=label]:active]:rounded-[var(--thumb-size)/calc(var(--thumb-size)*1.1)] rounded-(--thumb-size) bg-background shadow-sm/5 will-change-transform [transition:translate_.15s,border-radius_.15s,scale_.1s_.1s,transform-origin_.15s] data-checked:origin-[var(--thumb-size)_50%] data-checked:translate-x-[calc(var(--thumb-size)-4px)]",
18 )}
19 data-slot="switch-thumb"
20 />
21 </SwitchPrimitive.Root>
22 );
23}
24
25export { Switch };
apps/renderer/src/components/ui/table.tsx deleted-126
...@@ -1,126 +0,0 @@
1import type * as React from "react";
2
3import { cn } from "#/lib/utils";
4
5function Table({ className, ...props }: React.ComponentProps<"table">) {
6 return (
7 <div
8 className="relative w-full overflow-x-auto"
9 data-slot="table-container"
10 >
11 <table
12 className={cn(
13 "w-full caption-bottom in-data-[slot=frame]:border-separate in-data-[slot=frame]:border-spacing-0 text-sm",
14 className,
15 )}
16 data-slot="table"
17 {...props}
18 />
19 </div>
20 );
21}
22
23function TableHeader({ className, ...props }: React.ComponentProps<"thead">) {
24 return (
25 <thead
26 className={cn(
27 "[&_tr]:border-b in-data-[slot=frame]:**:[th]:h-9 in-data-[slot=frame]:*:[tr]:border-none in-data-[slot=frame]:*:[tr]:hover:bg-transparent",
28 className,
29 )}
30 data-slot="table-header"
31 {...props}
32 />
33 );
34}
35
36function TableBody({ className, ...props }: React.ComponentProps<"tbody">) {
37 return (
38 <tbody
39 className={cn(
40 "relative in-data-[slot=frame]:rounded-xl in-data-[slot=frame]:shadow-xs/5 before:pointer-events-none before:absolute before:inset-px not-in-data-[slot=frame]:before:hidden before:rounded-[calc(var(--radius-xl)-1px)] before:shadow-[0_1px_--theme(--color-black/4%)] dark:before:shadow-[0_-1px_--theme(--color-white/8%)] [&_tr:last-child]:border-0 in-data-[slot=frame]:*:[tr]:border-0 in-data-[slot=frame]:*:[tr]:*:[td]:border-b in-data-[slot=frame]:*:[tr]:*:[td]:bg-background in-data-[slot=frame]:*:[tr]:*:[td]:bg-clip-padding in-data-[slot=frame]:*:[tr]:first:*:[td]:first:rounded-ss-xl in-data-[slot=frame]:*:[tr]:*:[td]:first:border-s in-data-[slot=frame]:*:[tr]:first:*:[td]:border-t in-data-[slot=frame]:*:[tr]:last:*:[td]:last:rounded-ee-xl in-data-[slot=frame]:*:[tr]:*:[td]:last:border-e in-data-[slot=frame]:*:[tr]:first:*:[td]:last:rounded-se-xl in-data-[slot=frame]:*:[tr]:last:*:[td]:first:rounded-es-xl in-data-[slot=frame]:*:[tr]:hover:*:[td]:bg-transparent in-data-[slot=frame]:*:[tr]:data-[state=selected]:*:[td]:bg-muted/72",
41 className,
42 )}
43 data-slot="table-body"
44 {...props}
45 />
46 );
47}
48
49function TableFooter({ className, ...props }: React.ComponentProps<"tfoot">) {
50 return (
51 <tfoot
52 className={cn(
53 "border-t in-data-[slot=frame]:border-none bg-muted/72 in-data-[slot=frame]:bg-transparent font-medium [&>tr]:last:border-b-0 in-data-[slot=frame]:*:[tr]:hover:bg-transparent",
54 className,
55 )}
56 data-slot="table-footer"
57 {...props}
58 />
59 );
60}
61
62function TableRow({ className, ...props }: React.ComponentProps<"tr">) {
63 return (
64 <tr
65 className={cn(
66 "border-b transition-colors hover:bg-muted/72 in-data-[slot=frame]:hover:bg-transparent data-[state=selected]:bg-muted/72 in-data-[slot=frame]:data-[state=selected]:bg-transparent",
67 className,
68 )}
69 data-slot="table-row"
70 {...props}
71 />
72 );
73}
74
75function TableHead({ className, ...props }: React.ComponentProps<"th">) {
76 return (
77 <th
78 className={cn(
79 "h-10 whitespace-nowrap px-2.5 text-left align-middle font-medium text-muted-foreground leading-none has-[[role=checkbox]]:w-px has-[[role=checkbox]]:pe-0",
80 className,
81 )}
82 data-slot="table-head"
83 {...props}
84 />
85 );
86}
87
88function TableCell({ className, ...props }: React.ComponentProps<"td">) {
89 return (
90 <td
91 className={cn(
92 "whitespace-nowrap p-2.5 align-middle leading-none in-data-[slot=frame]:first:p-[calc(--spacing(2.5)-1px)] in-data-[slot=frame]:last:p-[calc(--spacing(2.5)-1px)] has-[[role=checkbox]]:pe-0",
93 className,
94 )}
95 data-slot="table-cell"
96 {...props}
97 />
98 );
99}
100
101function TableCaption({
102 className,
103 ...props
104}: React.ComponentProps<"caption">) {
105 return (
106 <caption
107 className={cn(
108 "in-data-[slot=frame]:my-4 mt-4 text-muted-foreground text-sm",
109 className,
110 )}
111 data-slot="table-caption"
112 {...props}
113 />
114 );
115}
116
117export {
118 Table,
119 TableHeader,
120 TableBody,
121 TableFooter,
122 TableHead,
123 TableRow,
124 TableCell,
125 TableCaption,
126};
apps/renderer/src/components/ui/tabs.tsx deleted-85
...@@ -1,85 +0,0 @@
1import { Tabs as TabsPrimitive } from "@base-ui/react/tabs";
2
3import { cn } from "#/lib/utils";
4
5type TabsVariant = "default" | "underline";
6
7function Tabs({ className, ...props }: TabsPrimitive.Root.Props) {
8 return (
9 <TabsPrimitive.Root
10 className={cn(
11 "flex flex-col gap-2 data-[orientation=vertical]:flex-row",
12 className,
13 )}
14 data-slot="tabs"
15 {...props}
16 />
17 );
18}
19
20function TabsList({
21 variant = "default",
22 className,
23 children,
24 ...props
25}: TabsPrimitive.List.Props & {
26 variant?: TabsVariant;
27}) {
28 return (
29 <TabsPrimitive.List
30 className={cn(
31 "relative z-0 flex w-fit items-center justify-center gap-x-0.5 text-muted-foreground",
32 "data-[orientation=vertical]:flex-col",
33 variant === "default"
34 ? "rounded-lg bg-muted p-0.5 text-muted-foreground/72"
35 : "data-[orientation=vertical]:px-1 data-[orientation=horizontal]:py-1 *:data-[slot=tabs-tab]:hover:bg-accent",
36 className,
37 )}
38 data-slot="tabs-list"
39 {...props}
40 >
41 {children}
42 <TabsPrimitive.Indicator
43 className={cn(
44 "-translate-y-(--active-tab-bottom) absolute bottom-0 left-0 h-(--active-tab-height) w-(--active-tab-width) translate-x-(--active-tab-left) transition-[width,translate] duration-200 ease-in-out",
45 variant === "underline"
46 ? "data-[orientation=vertical]:-translate-x-px z-10 bg-primary data-[orientation=horizontal]:h-0.5 data-[orientation=vertical]:w-0.5 data-[orientation=horizontal]:translate-y-px"
47 : "-z-1 rounded-md bg-background shadow-sm/5 dark:bg-input",
48 )}
49 data-slot="tab-indicator"
50 />
51 </TabsPrimitive.List>
52 );
53}
54
55function TabsTab({ className, ...props }: TabsPrimitive.Tab.Props) {
56 return (
57 <TabsPrimitive.Tab
58 className={cn(
59 "[&_svg]:-mx-0.5 relative flex h-9 shrink-0 grow cursor-pointer items-center justify-center gap-1.5 whitespace-nowrap rounded-md border border-transparent px-[calc(--spacing(2.5)-1px)] font-medium text-base outline-none transition-[color,background-color,box-shadow] hover:text-muted-foreground focus-visible:ring-2 focus-visible:ring-ring data-disabled:pointer-events-none data-[orientation=vertical]:w-full data-[orientation=vertical]:justify-start data-active:text-foreground data-disabled:opacity-64 sm:h-8 sm:text-sm [&_svg:not([class*='size-'])]:size-4.5 sm:[&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0",
60 className,
61 )}
62 data-slot="tabs-tab"
63 {...props}
64 />
65 );
66}
67
68function TabsPanel({ className, ...props }: TabsPrimitive.Panel.Props) {
69 return (
70 <TabsPrimitive.Panel
71 className={cn("flex-1 outline-none", className)}
72 data-slot="tabs-content"
73 {...props}
74 />
75 );
76}
77
78export {
79 Tabs,
80 TabsList,
81 TabsTab,
82 TabsTab as TabsTrigger,
83 TabsPanel,
84 TabsPanel as TabsContent,
85};
apps/renderer/src/components/ui/textarea.tsx deleted-49
...@@ -1,49 +0,0 @@
1import { Field as FieldPrimitive } from "@base-ui/react/field";
2import { mergeProps } from "@base-ui/react/merge-props";
3import type * as React from "react";
4
5import { cn } from "#/lib/utils";
6
7type TextareaProps = React.ComponentProps<"textarea"> & {
8 size?: "sm" | "default" | "lg" | number;
9 unstyled?: boolean;
10};
11
12function Textarea({
13 className,
14 size = "default",
15 unstyled = false,
16 ...props
17}: TextareaProps) {
18 return (
19 <span
20 className={
21 cn(
22 !unstyled &&
23 "relative inline-flex w-full rounded-lg border border-input bg-background not-dark:bg-clip-padding text-base text-foreground shadow-xs/5 ring-ring/24 transition-shadow before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--radius-lg)-1px)] has-focus-visible:has-aria-invalid:border-destructive/64 has-focus-visible:has-aria-invalid:ring-destructive/16 has-aria-invalid:border-destructive/36 has-focus-visible:border-ring has-disabled:opacity-64 has-[:disabled,:focus-visible,[aria-invalid]]:shadow-none has-focus-visible:ring-[3px] not-has-disabled:has-not-focus-visible:not-has-aria-invalid:before:shadow-[0_1px_--theme(--color-black/4%)] sm:text-sm dark:bg-input/32 dark:has-aria-invalid:ring-destructive/24 dark:not-has-disabled:has-not-focus-visible:not-has-aria-invalid:before:shadow-[0_-1px_--theme(--color-white/6%)]",
24 className,
25 ) || undefined
26 }
27 data-size={size}
28 data-slot="textarea-control"
29 >
30 <FieldPrimitive.Control
31 render={(defaultProps) => (
32 <textarea
33 className={cn(
34 "field-sizing-content min-h-17.5 w-full rounded-[inherit] px-[calc(--spacing(3)-1px)] py-[calc(--spacing(1.5)-1px)] outline-none max-sm:min-h-20.5",
35 size === "sm" &&
36 "min-h-16.5 px-[calc(--spacing(2.5)-1px)] py-[calc(--spacing(1)-1px)] max-sm:min-h-19.5",
37 size === "lg" &&
38 "min-h-18.5 py-[calc(--spacing(2)-1px)] max-sm:min-h-21.5",
39 )}
40 data-slot="textarea"
41 {...mergeProps(defaultProps, props)}
42 />
43 )}
44 />
45 </span>
46 );
47}
48
49export { Textarea, type TextareaProps };
apps/renderer/src/components/ui/toast.tsx deleted-267
...@@ -1,267 +0,0 @@
1import { Toast } from "@base-ui/react/toast";
2import {
3 CircleAlertIcon,
4 CircleCheckIcon,
5 InfoIcon,
6 LoaderCircleIcon,
7 TriangleAlertIcon,
8} from "lucide-react";
9
10import { buttonVariants } from "#/components/ui/button";
11import { cn } from "#/lib/utils";
12
13const toastManager = Toast.createToastManager();
14const anchoredToastManager = Toast.createToastManager();
15
16const TOAST_ICONS = {
17 error: CircleAlertIcon,
18 info: InfoIcon,
19 loading: LoaderCircleIcon,
20 success: CircleCheckIcon,
21 warning: TriangleAlertIcon,
22} as const;
23
24type ToastPosition =
25 | "top-left"
26 | "top-center"
27 | "top-right"
28 | "bottom-left"
29 | "bottom-center"
30 | "bottom-right";
31
32interface ToastProviderProps extends Toast.Provider.Props {
33 position?: ToastPosition;
34}
35
36function ToastProvider({
37 children,
38 position = "bottom-right",
39 ...props
40}: ToastProviderProps) {
41 return (
42 <Toast.Provider toastManager={toastManager} {...props}>
43 {children}
44 <Toasts position={position} />
45 </Toast.Provider>
46 );
47}
48
49function Toasts({ position = "bottom-right" }: { position: ToastPosition }) {
50 const { toasts } = Toast.useToastManager();
51 const isTop = position.startsWith("top");
52
53 return (
54 <Toast.Portal data-slot="toast-portal">
55 <Toast.Viewport
56 className={cn(
57 "fixed z-50 mx-auto flex w-[calc(100%-var(--toast-inset)*2)] max-w-90 [--toast-inset:--spacing(4)] sm:[--toast-inset:--spacing(8)]",
58 // Vertical positioning
59 "data-[position*=top]:top-(--toast-inset)",
60 "data-[position*=bottom]:bottom-(--toast-inset)",
61 // Horizontal positioning
62 "data-[position*=left]:left-(--toast-inset)",
63 "data-[position*=right]:right-(--toast-inset)",
64 "data-[position*=center]:-translate-x-1/2 data-[position*=center]:left-1/2",
65 )}
66 data-position={position}
67 data-slot="toast-viewport"
68 >
69 {toasts.map((toast) => {
70 const Icon = toast.type
71 ? TOAST_ICONS[toast.type as keyof typeof TOAST_ICONS]
72 : null;
73
74 return (
75 <Toast.Root
76 className={cn(
77 "absolute z-[calc(9999-var(--toast-index))] h-(--toast-calc-height) w-full select-none rounded-lg border bg-popover not-dark:bg-clip-padding text-popover-foreground shadow-lg/5 [transition:transform_.5s_cubic-bezier(.22,1,.36,1),opacity_.5s,height_.15s] before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--radius-lg)-1px)] before:shadow-[0_1px_--theme(--color-black/4%)] dark:before:shadow-[0_-1px_--theme(--color-white/6%)]",
78 // Base positioning using data-position
79 "data-[position*=right]:right-0 data-[position*=right]:left-auto",
80 "data-[position*=left]:right-auto data-[position*=left]:left-0",
81 "data-[position*=center]:right-0 data-[position*=center]:left-0",
82 "data-[position*=top]:top-0 data-[position*=top]:bottom-auto data-[position*=top]:origin-top",
83 "data-[position*=bottom]:top-auto data-[position*=bottom]:bottom-0 data-[position*=bottom]:origin-bottom",
84 // Gap fill for hover
85 "after:absolute after:left-0 after:h-[calc(var(--toast-gap)+1px)] after:w-full",
86 "data-[position*=top]:after:top-full",
87 "data-[position*=bottom]:after:bottom-full",
88 // Define some variables
89 "[--toast-calc-height:var(--toast-frontmost-height,var(--toast-height))] [--toast-gap:--spacing(3)] [--toast-peek:--spacing(3)] [--toast-scale:calc(max(0,1-(var(--toast-index)*.1)))] [--toast-shrink:calc(1-var(--toast-scale))]",
90 // Define offset-y variable
91 "data-[position*=top]:[--toast-calc-offset-y:calc(var(--toast-offset-y)+var(--toast-index)*var(--toast-gap)+var(--toast-swipe-movement-y))]",
92 "data-[position*=bottom]:[--toast-calc-offset-y:calc(var(--toast-offset-y)*-1+var(--toast-index)*var(--toast-gap)*-1+var(--toast-swipe-movement-y))]",
93 // Default state transform
94 "data-[position*=top]:transform-[translateX(var(--toast-swipe-movement-x))_translateY(calc(var(--toast-swipe-movement-y)+(var(--toast-index)*var(--toast-peek))+(var(--toast-shrink)*var(--toast-calc-height))))_scale(var(--toast-scale))]",
95 "data-[position*=bottom]:transform-[translateX(var(--toast-swipe-movement-x))_translateY(calc(var(--toast-swipe-movement-y)-(var(--toast-index)*var(--toast-peek))-(var(--toast-shrink)*var(--toast-calc-height))))_scale(var(--toast-scale))]",
96 // Limited state
97 "data-limited:opacity-0",
98 // Expanded state
99 "data-expanded:h-(--toast-height)",
100 "data-position:data-expanded:transform-[translateX(var(--toast-swipe-movement-x))_translateY(var(--toast-calc-offset-y))]",
101 // Starting and ending animations
102 "data-[position*=top]:data-starting-style:transform-[translateY(calc(-100%-var(--toast-inset)))]",
103 "data-[position*=bottom]:data-starting-style:transform-[translateY(calc(100%+var(--toast-inset)))]",
104 "data-ending-style:opacity-0",
105 // Ending animations (direction-aware)
106 "data-ending-style:not-data-limited:not-data-swipe-direction:transform-[translateY(calc(100%+var(--toast-inset)))]",
107 "data-ending-style:data-[swipe-direction=left]:transform-[translateX(calc(var(--toast-swipe-movement-x)-100%-var(--toast-inset)))_translateY(var(--toast-calc-offset-y))]",
108 "data-ending-style:data-[swipe-direction=right]:transform-[translateX(calc(var(--toast-swipe-movement-x)+100%+var(--toast-inset)))_translateY(var(--toast-calc-offset-y))]",
109 "data-ending-style:data-[swipe-direction=up]:transform-[translateY(calc(var(--toast-swipe-movement-y)-100%-var(--toast-inset)))]",
110 "data-ending-style:data-[swipe-direction=down]:transform-[translateY(calc(var(--toast-swipe-movement-y)+100%+var(--toast-inset)))]",
111 // Ending animations (expanded)
112 "data-expanded:data-ending-style:data-[swipe-direction=left]:transform-[translateX(calc(var(--toast-swipe-movement-x)-100%-var(--toast-inset)))_translateY(var(--toast-calc-offset-y))]",
113 "data-expanded:data-ending-style:data-[swipe-direction=right]:transform-[translateX(calc(var(--toast-swipe-movement-x)+100%+var(--toast-inset)))_translateY(var(--toast-calc-offset-y))]",
114 "data-expanded:data-ending-style:data-[swipe-direction=up]:transform-[translateY(calc(var(--toast-swipe-movement-y)-100%-var(--toast-inset)))]",
115 "data-expanded:data-ending-style:data-[swipe-direction=down]:transform-[translateY(calc(var(--toast-swipe-movement-y)+100%+var(--toast-inset)))]",
116 )}
117 data-position={position}
118 key={toast.id}
119 swipeDirection={
120 position.includes("center")
121 ? [isTop ? "up" : "down"]
122 : position.includes("left")
123 ? ["left", isTop ? "up" : "down"]
124 : ["right", isTop ? "up" : "down"]
125 }
126 toast={toast}
127 >
128 <Toast.Content className="pointer-events-auto flex items-center justify-between gap-1.5 overflow-hidden px-3.5 py-3 text-sm transition-opacity duration-250 data-behind:not-data-expanded:pointer-events-none data-behind:opacity-0 data-expanded:opacity-100">
129 <div className="flex gap-2">
130 {Icon && (
131 <div
132 className="[&>svg]:h-lh [&>svg]:w-4 [&_svg]:pointer-events-none [&_svg]:shrink-0"
133 data-slot="toast-icon"
134 >
135 <Icon className="in-data-[type=loading]:animate-spin in-data-[type=error]:text-destructive in-data-[type=info]:text-info in-data-[type=success]:text-success in-data-[type=warning]:text-warning in-data-[type=loading]:opacity-80" />
136 </div>
137 )}
138
139 <div className="flex flex-col gap-0.5">
140 <Toast.Title
141 className="font-medium"
142 data-slot="toast-title"
143 />
144 <Toast.Description
145 className="text-muted-foreground"
146 data-slot="toast-description"
147 />
148 </div>
149 </div>
150 {toast.actionProps && (
151 <Toast.Action
152 className={buttonVariants({ size: "xs" })}
153 data-slot="toast-action"
154 >
155 {toast.actionProps.children}
156 </Toast.Action>
157 )}
158 </Toast.Content>
159 </Toast.Root>
160 );
161 })}
162 </Toast.Viewport>
163 </Toast.Portal>
164 );
165}
166
167function AnchoredToastProvider({ children, ...props }: Toast.Provider.Props) {
168 return (
169 <Toast.Provider toastManager={anchoredToastManager} {...props}>
170 {children}
171 <AnchoredToasts />
172 </Toast.Provider>
173 );
174}
175
176function AnchoredToasts() {
177 const { toasts } = Toast.useToastManager();
178
179 return (
180 <Toast.Portal data-slot="toast-portal-anchored">
181 <Toast.Viewport
182 className="outline-none"
183 data-slot="toast-viewport-anchored"
184 >
185 {toasts.map((toast) => {
186 const Icon = toast.type
187 ? TOAST_ICONS[toast.type as keyof typeof TOAST_ICONS]
188 : null;
189 const tooltipStyle =
190 (toast.data as { tooltipStyle?: boolean })?.tooltipStyle ?? false;
191 const positionerProps = toast.positionerProps;
192
193 if (!positionerProps?.anchor) {
194 return null;
195 }
196
197 return (
198 <Toast.Positioner
199 className="z-50 max-w-[min(--spacing(64),var(--available-width))]"
200 data-slot="toast-positioner"
201 key={toast.id}
202 sideOffset={positionerProps.sideOffset ?? 4}
203 toast={toast}
204 >
205 <Toast.Root
206 className={cn(
207 "relative text-balance border bg-popover not-dark:bg-clip-padding text-popover-foreground text-xs transition-[scale,opacity] before:pointer-events-none before:absolute before:inset-0 before:shadow-[0_1px_--theme(--color-black/4%)] data-ending-style:scale-98 data-starting-style:scale-98 data-ending-style:opacity-0 data-starting-style:opacity-0 dark:before:shadow-[0_-1px_--theme(--color-white/6%)]",
208 tooltipStyle
209 ? "rounded-md shadow-md/5 before:rounded-[calc(var(--radius-md)-1px)]"
210 : "rounded-lg shadow-lg/5 before:rounded-[calc(var(--radius-lg)-1px)]",
211 )}
212 data-slot="toast-popup"
213 toast={toast}
214 >
215 {tooltipStyle ? (
216 <Toast.Content className="pointer-events-auto px-2 py-1">
217 <Toast.Title data-slot="toast-title" />
218 </Toast.Content>
219 ) : (
220 <Toast.Content className="pointer-events-auto flex items-center justify-between gap-1.5 overflow-hidden px-3.5 py-3 text-sm">
221 <div className="flex gap-2">
222 {Icon && (
223 <div
224 className="[&>svg]:h-lh [&>svg]:w-4 [&_svg]:pointer-events-none [&_svg]:shrink-0"
225 data-slot="toast-icon"
226 >
227 <Icon className="in-data-[type=loading]:animate-spin in-data-[type=error]:text-destructive in-data-[type=info]:text-info in-data-[type=success]:text-success in-data-[type=warning]:text-warning in-data-[type=loading]:opacity-80" />
228 </div>
229 )}
230
231 <div className="flex flex-col gap-0.5">
232 <Toast.Title
233 className="font-medium"
234 data-slot="toast-title"
235 />
236 <Toast.Description
237 className="text-muted-foreground"
238 data-slot="toast-description"
239 />
240 </div>
241 </div>
242 {toast.actionProps && (
243 <Toast.Action
244 className={buttonVariants({ size: "xs" })}
245 data-slot="toast-action"
246 >
247 {toast.actionProps.children}
248 </Toast.Action>
249 )}
250 </Toast.Content>
251 )}
252 </Toast.Root>
253 </Toast.Positioner>
254 );
255 })}
256 </Toast.Viewport>
257 </Toast.Portal>
258 );
259}
260
261export {
262 ToastProvider,
263 type ToastPosition,
264 toastManager,
265 AnchoredToastProvider,
266 anchoredToastManager,
267};
apps/renderer/src/hooks/useTheme.ts deleted-90
...@@ -1,90 +0,0 @@
1import { useCallback, useEffect, useSyncExternalStore } from "react";
2
3type Theme = "light" | "dark" | "system";
4
5const STORAGE_KEY = "codething:theme";
6const MEDIA_QUERY = "(prefers-color-scheme: dark)";
7
8let listeners: Array<() => void> = [];
9function emitChange() {
10 for (const listener of listeners) listener();
11}
12
13function getSystemDark(): boolean {
14 return window.matchMedia(MEDIA_QUERY).matches;
15}
16
17function getStored(): Theme {
18 const raw = localStorage.getItem(STORAGE_KEY);
19 if (raw === "light" || raw === "dark" || raw === "system") return raw;
20 return "system";
21}
22
23function applyTheme(theme: Theme, suppressTransitions = false) {
24 if (suppressTransitions) {
25 document.documentElement.classList.add("no-transitions");
26 }
27 const isDark = theme === "dark" || (theme === "system" && getSystemDark());
28 document.documentElement.classList.toggle("dark", isDark);
29 if (suppressTransitions) {
30 // Force a reflow so the no-transitions class takes effect before removal
31 document.documentElement.offsetHeight;
32 requestAnimationFrame(() => {
33 document.documentElement.classList.remove("no-transitions");
34 });
35 }
36}
37
38// Apply immediately on module load to prevent flash
39applyTheme(getStored());
40
41function getSnapshot(): Theme {
42 return getStored();
43}
44
45function subscribe(listener: () => void): () => void {
46 listeners.push(listener);
47
48 // Listen for system preference changes
49 const mq = window.matchMedia(MEDIA_QUERY);
50 const handleChange = () => {
51 if (getStored() === "system") applyTheme("system", true);
52 emitChange();
53 };
54 mq.addEventListener("change", handleChange);
55
56 // Listen for storage changes from other tabs
57 const handleStorage = (e: StorageEvent) => {
58 if (e.key === STORAGE_KEY) {
59 applyTheme(getStored(), true);
60 emitChange();
61 }
62 };
63 window.addEventListener("storage", handleStorage);
64
65 return () => {
66 listeners = listeners.filter((l) => l !== listener);
67 mq.removeEventListener("change", handleChange);
68 window.removeEventListener("storage", handleStorage);
69 };
70}
71
72export function useTheme() {
73 const theme = useSyncExternalStore(subscribe, getSnapshot);
74
75 const resolvedTheme: "light" | "dark" =
76 theme === "system" ? (getSystemDark() ? "dark" : "light") : theme;
77
78 const setTheme = useCallback((next: Theme) => {
79 localStorage.setItem(STORAGE_KEY, next);
80 applyTheme(next, true);
81 emitChange();
82 }, []);
83
84 // Keep DOM in sync on mount/change
85 useEffect(() => {
86 applyTheme(theme);
87 }, [theme]);
88
89 return { theme, setTheme, resolvedTheme } as const;
90}
apps/renderer/src/index.css+26-194
...@@ -1,178 +1,7 @@...@@ -1,178 +1,7 @@
1@import "tailwindcss";1@import "tailwindcss";
22
3@custom-variant dark (&:is(.dark, .dark *));
4
5@theme inline {
6 --animate-skeleton: skeleton 2s -1s infinite linear;
7 --color-warning-foreground: var(--warning-foreground);
8 --color-warning: var(--warning);
9 --color-success-foreground: var(--success-foreground);
10 --color-success: var(--success);
11 --color-info-foreground: var(--info-foreground);
12 --color-info: var(--info);
13 --color-destructive-foreground: var(--destructive-foreground);
14 --color-sidebar-ring: var(--sidebar-ring);
15 --color-sidebar-border: var(--sidebar-border);
16 --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
17 --color-sidebar-accent: var(--sidebar-accent);
18 --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
19 --color-sidebar-primary: var(--sidebar-primary);
20 --color-sidebar-foreground: var(--sidebar-foreground);
21 --color-sidebar: var(--sidebar);
22 --color-chart-5: var(--chart-5);
23 --color-chart-4: var(--chart-4);
24 --color-chart-3: var(--chart-3);
25 --color-chart-2: var(--chart-2);
26 --color-chart-1: var(--chart-1);
27 --color-ring: var(--ring);
28 --color-input: var(--input);
29 --color-border: var(--border);
30 --color-destructive: var(--destructive);
31 --color-accent-foreground: var(--accent-foreground);
32 --color-accent: var(--accent);
33 --color-muted-foreground: var(--muted-foreground);
34 --color-muted: var(--muted);
35 --color-secondary-foreground: var(--secondary-foreground);
36 --color-secondary: var(--secondary);
37 --color-primary-foreground: var(--primary-foreground);
38 --color-primary: var(--primary);
39 --color-popover-foreground: var(--popover-foreground);
40 --color-popover: var(--popover);
41 --color-card-foreground: var(--card-foreground);
42 --color-card: var(--card);
43 --color-foreground: var(--foreground);
44 --color-background: var(--background);
45 --radius-sm: calc(var(--radius) - 4px);
46 --radius-md: calc(var(--radius) - 2px);
47 --radius-lg: var(--radius);
48 --radius-xl: calc(var(--radius) + 4px);
49 --radius-2xl: calc(var(--radius) + 8px);
50 --radius-3xl: calc(var(--radius) + 12px);
51 --radius-4xl: calc(var(--radius) + 16px);
52 @keyframes skeleton {
53 to {
54 background-position: -200% 0;
55 }
56 }
57}
58
59@layer base {
60 * {
61 @apply border-border outline-ring/50;
62 }
63 body {
64 @apply bg-background text-foreground;
65 }
66}
67
68/* Suppress all transitions during theme changes */
69.no-transitions,
70.no-transitions *,
71.no-transitions *::before,
72.no-transitions *::after {
73 transition-duration: 0s !important;
74 animation-duration: 0s !important;
75}
76
77:root {3:root {
78 color-scheme: light;4 color-scheme: dark;
79 --radius: 0.625rem;
80 --background: var(--color-white);
81 --foreground: var(--color-neutral-800);
82 --card: var(--color-white);
83 --card-foreground: var(--color-neutral-800);
84 --popover: var(--color-white);
85 --popover-foreground: var(--color-neutral-800);
86 --primary: var(--color-neutral-800);
87 --primary-foreground: var(--color-neutral-50);
88 --secondary: --alpha(var(--color-black) / 4%);
89 --secondary-foreground: var(--color-neutral-800);
90 --muted: --alpha(var(--color-black) / 4%);
91 --muted-foreground: color-mix(
92 in srgb,
93 var(--color-neutral-500) 90%,
94 var(--color-black)
95 );
96 --accent: --alpha(var(--color-black) / 4%);
97 --accent-foreground: var(--color-neutral-800);
98 --destructive: var(--color-red-500);
99 --border: --alpha(var(--color-black) / 8%);
100 --input: --alpha(var(--color-black) / 10%);
101 --ring: var(--color-neutral-400);
102 --chart-1: oklch(0.646 0.222 41.116);
103 --chart-2: oklch(0.6 0.118 184.704);
104 --chart-3: oklch(0.398 0.07 227.392);
105 --chart-4: oklch(0.828 0.189 84.429);
106 --chart-5: oklch(0.769 0.188 70.08);
107 --sidebar: oklch(0.985 0 0);
108 --sidebar-foreground: oklch(0.141 0.005 285.823);
109 --sidebar-primary: oklch(0.21 0.006 285.885);
110 --sidebar-primary-foreground: oklch(0.985 0 0);
111 --sidebar-accent: oklch(0.967 0.001 286.375);
112 --sidebar-accent-foreground: oklch(0.21 0.006 285.885);
113 --sidebar-border: oklch(0.92 0.004 286.32);
114 --sidebar-ring: oklch(0.705 0.015 286.067);
115 --destructive-foreground: var(--color-red-700);
116 --info: var(--color-blue-500);
117 --info-foreground: var(--color-blue-700);
118 --success: var(--color-emerald-500);
119 --success-foreground: var(--color-emerald-700);
120 --warning: var(--color-amber-500);
121 --warning-foreground: var(--color-amber-700);
122
123 @variant dark {
124 color-scheme: dark;
125 --background: color-mix(
126 in srgb,
127 var(--color-neutral-950) 95%,
128 var(--color-white)
129 );
130 --foreground: var(--color-neutral-100);
131 --card: color-mix(in srgb, var(--background) 98%, var(--color-white));
132 --card-foreground: var(--color-neutral-100);
133 --popover: color-mix(in srgb, var(--background) 98%, var(--color-white));
134 --popover-foreground: var(--color-neutral-100);
135 --primary: var(--color-neutral-100);
136 --primary-foreground: var(--color-neutral-800);
137 --secondary: --alpha(var(--color-white) / 4%);
138 --secondary-foreground: var(--color-neutral-100);
139 --muted: --alpha(var(--color-white) / 4%);
140 --muted-foreground: color-mix(
141 in srgb,
142 var(--color-neutral-500) 90%,
143 var(--color-white)
144 );
145 --accent: --alpha(var(--color-white) / 4%);
146 --accent-foreground: var(--color-neutral-100);
147 --destructive: color-mix(
148 in srgb,
149 var(--color-red-500) 90%,
150 var(--color-white)
151 );
152 --border: --alpha(var(--color-white) / 6%);
153 --input: --alpha(var(--color-white) / 8%);
154 --ring: var(--color-neutral-500);
155 --chart-1: oklch(0.488 0.243 264.376);
156 --chart-2: oklch(0.696 0.17 162.48);
157 --chart-3: oklch(0.769 0.188 70.08);
158 --chart-4: oklch(0.627 0.265 303.9);
159 --chart-5: oklch(0.645 0.246 16.439);
160 --sidebar: oklch(0.21 0.006 285.885);
161 --sidebar-foreground: oklch(0.985 0 0);
162 --sidebar-primary: oklch(0.488 0.243 264.376);
163 --sidebar-primary-foreground: oklch(0.985 0 0);
164 --sidebar-accent: oklch(0.274 0.006 286.033);
165 --sidebar-accent-foreground: oklch(0.985 0 0);
166 --sidebar-border: oklch(1 0 0 / 10%);
167 --sidebar-ring: oklch(0.552 0.016 285.938);
168 --destructive-foreground: var(--color-red-400);
169 --info: var(--color-blue-500);
170 --info-foreground: var(--color-blue-400);
171 --success: var(--color-emerald-500);
172 --success-foreground: var(--color-emerald-400);
173 --warning: var(--color-amber-500);
174 --warning-foreground: var(--color-amber-400);
175 }
176}5}
1776
178body {7body {
...@@ -181,6 +10,8 @@ body {...@@ -181,6 +10,8 @@ body {
181 margin: 0;10 margin: 0;
182 padding: 0;11 padding: 0;
183 min-height: 100vh;12 min-height: 100vh;
13 color: #e0e0e0;
14 background: #0c0c0c;
184}15}
18516
186body::after {17body::after {
...@@ -224,20 +55,25 @@ input {...@@ -224,20 +55,25 @@ input {
224}55}
22556
226::-webkit-scrollbar-thumb {57::-webkit-scrollbar-thumb {
227 background: rgba(0, 0, 0, 0.15);58 background: rgba(255, 255, 255, 0.1);
228 border-radius: 3px;59 border-radius: 3px;
229}60}
23061
231::-webkit-scrollbar-thumb:hover {62::-webkit-scrollbar-thumb:hover {
232 background: rgba(0, 0, 0, 0.25);63 background: rgba(255, 255, 255, 0.18);
233}64}
23465
235.dark ::-webkit-scrollbar-thumb {66/* Reasoning select — clickable label surface */
236 background: rgba(255, 255, 255, 0.1);67label:has(> select#reasoning-effort) {
68 position: relative;
237}69}
23870label:has(> select#reasoning-effort) select {
239.dark ::-webkit-scrollbar-thumb:hover {71 position: absolute;
240 background: rgba(255, 255, 255, 0.18);72 inset: 0;
73 opacity: 0;
74 cursor: pointer;
75 width: 100%;
76 height: 100%;
241}77}
24278
243/* Chat markdown rendering */79/* Chat markdown rendering */
...@@ -272,38 +108,34 @@ input {...@@ -272,38 +108,34 @@ input {
272}108}
273109
274.chat-markdown a {110.chat-markdown a {
275 color: var(--info-foreground);111 color: #93c5fd;
276 text-decoration: underline;112 text-decoration: underline;
277 text-decoration-color: color-mix(113 text-decoration-color: rgba(147, 197, 253, 0.4);
278 in srgb,
279 var(--info-foreground) 40%,
280 transparent
281 );
282}114}
283115
284.chat-markdown a:hover {116.chat-markdown a:hover {
285 opacity: 0.8;117 color: #bfdbfe;
286}118}
287119
288.chat-markdown blockquote {120.chat-markdown blockquote {
289 border-left: 2px solid var(--border);121 border-left: 2px solid rgba(255, 255, 255, 0.2);
290 padding-left: 0.8rem;122 padding-left: 0.8rem;
291 color: var(--muted-foreground);123 color: rgba(224, 224, 224, 0.75);
292}124}
293125
294.chat-markdown :not(pre) > code {126.chat-markdown :not(pre) > code {
295 border: 1px solid var(--border);127 border: 1px solid rgba(255, 255, 255, 0.12);
296 border-radius: 0.375rem;128 border-radius: 0.375rem;
297 background: var(--muted);129 background: rgba(255, 255, 255, 0.07);
298 padding: 0.1rem 0.35rem;130 padding: 0.1rem 0.35rem;
299 color: var(--foreground);131 color: #f4f4f5;
300}132}
301133
302.chat-markdown pre {134.chat-markdown pre {
303 overflow-x: auto;135 overflow-x: auto;
304 border: 1px solid var(--border);136 border: 1px solid rgba(255, 255, 255, 0.12);
305 border-radius: 0.75rem;137 border-radius: 0.75rem;
306 background: var(--muted);138 background: rgba(0, 0, 0, 0.3);
307 padding: 0.8rem 0.9rem;139 padding: 0.8rem 0.9rem;
308}140}
309141
...@@ -321,7 +153,7 @@ input {...@@ -321,7 +153,7 @@ input {
321153
322.chat-markdown th,154.chat-markdown th,
323.chat-markdown td {155.chat-markdown td {
324 border: 1px solid var(--border);156 border: 1px solid rgba(255, 255, 255, 0.12);
325 padding: 0.35rem 0.45rem;157 padding: 0.35rem 0.45rem;
326 text-align: left;158 text-align: left;
327}159}
apps/renderer/src/lib/utils.ts deleted-6
...@@ -1,6 +0,0 @@
1import { type ClassValue, clsx } from "clsx";
2import { twMerge } from "tailwind-merge";
3
4export function cn(...inputs: ClassValue[]) {
5 return twMerge(clsx(inputs));
6}
apps/renderer/tsconfig.json+1-5
...@@ -4,11 +4,7 @@...@@ -4,11 +4,7 @@
4 "composite": true,4 "composite": true,
5 "jsx": "react-jsx",5 "jsx": "react-jsx",
6 "lib": ["ES2023", "DOM", "DOM.Iterable"],6 "lib": ["ES2023", "DOM", "DOM.Iterable"],
7 "exactOptionalPropertyTypes": false,7 "types": ["vite/client"]
8 "types": ["vite/client"],
9 "paths": {
10 "#/*": ["./src/*"]
11 }
12 },8 },
13 "include": ["src", "vite.config.ts"]9 "include": ["src", "vite.config.ts"]
14}10}
apps/renderer/vite.config.ts-6
...@@ -1,4 +1,3 @@...@@ -1,4 +1,3 @@
1import path from "node:path";
2import tailwindcss from "@tailwindcss/vite";1import tailwindcss from "@tailwindcss/vite";
3import react from "@vitejs/plugin-react";2import react from "@vitejs/plugin-react";
4import { defineConfig } from "vite";3import { defineConfig } from "vite";
...@@ -12,11 +11,6 @@ export default defineConfig({...@@ -12,11 +11,6 @@ export default defineConfig({
12 }),11 }),
13 tailwindcss(),12 tailwindcss(),
14 ],13 ],
15 resolve: {
16 alias: {
17 "#": path.resolve(__dirname, "src"),
18 },
19 },
20 server: {14 server: {
21 port: Number(process.env.ELECTRON_RENDERER_PORT ?? 5173),15 port: Number(process.env.ELECTRON_RENDERER_PORT ?? 5173),
22 strictPort: true,16 strictPort: true,
bun.lock+1-96
...@@ -34,17 +34,12 @@...@@ -34,17 +34,12 @@
34 "version": "0.0.0",34 "version": "0.0.0",
35 "dependencies": {35 "dependencies": {
36 "@acme/contracts": "workspace:*",36 "@acme/contracts": "workspace:*",
37 "@base-ui/react": "^1.1.0",
38 "class-variance-authority": "^0.7.1",
39 "clsx": "^2.1.1",
40 "highlight.js": "^11.11.1",37 "highlight.js": "^11.11.1",
41 "lucide-react": "^0.563.0",
42 "react": "^19.0.0",38 "react": "^19.0.0",
43 "react-dom": "^19.0.0",39 "react-dom": "^19.0.0",
44 "react-markdown": "^10.1.0",40 "react-markdown": "^10.1.0",
45 "rehype-highlight": "^7.0.2",41 "rehype-highlight": "^7.0.2",
46 "remark-gfm": "^4.0.1",42 "remark-gfm": "^4.0.1",
47 "tailwind-merge": "^3.4.0",
48 "zod": "^3.24.1",43 "zod": "^3.24.1",
49 },44 },
50 "devDependencies": {45 "devDependencies": {
...@@ -109,18 +104,12 @@...@@ -109,18 +104,12 @@
109104
110 "@babel/plugin-transform-react-jsx-source": ["@babel/plugin-transform-react-jsx-source@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw=="],105 "@babel/plugin-transform-react-jsx-source": ["@babel/plugin-transform-react-jsx-source@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw=="],
111106
112 "@babel/runtime": ["@babel/runtime@7.28.6", "", {}, "sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA=="],
113
114 "@babel/template": ["@babel/template@7.28.6", "", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/parser": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ=="],107 "@babel/template": ["@babel/template@7.28.6", "", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/parser": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ=="],
115108
116 "@babel/traverse": ["@babel/traverse@7.29.0", "", { "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.29.0", "@babel/template": "^7.28.6", "@babel/types": "^7.29.0", "debug": "^4.3.1" } }, "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA=="],109 "@babel/traverse": ["@babel/traverse@7.29.0", "", { "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.29.0", "@babel/template": "^7.28.6", "@babel/types": "^7.29.0", "debug": "^4.3.1" } }, "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA=="],
117110
118 "@babel/types": ["@babel/types@7.29.0", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A=="],111 "@babel/types": ["@babel/types@7.29.0", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A=="],
119112
120 "@base-ui/react": ["@base-ui/react@1.1.0", "", { "dependencies": { "@babel/runtime": "^7.28.4", "@base-ui/utils": "0.2.4", "@floating-ui/react-dom": "^2.1.6", "@floating-ui/utils": "^0.2.10", "reselect": "^5.1.1", "tabbable": "^6.4.0", "use-sync-external-store": "^1.6.0" }, "peerDependencies": { "@types/react": "^17 || ^18 || ^19", "react": "^17 || ^18 || ^19", "react-dom": "^17 || ^18 || ^19" }, "optionalPeers": ["@types/react"] }, "sha512-ikcJRNj1mOiF2HZ5jQHrXoVoHcNHdBU5ejJljcBl+VTLoYXR6FidjTN86GjO6hyshi6TZFuNvv0dEOgaOFv6Lw=="],
121
122 "@base-ui/utils": ["@base-ui/utils@0.2.4", "", { "dependencies": { "@babel/runtime": "^7.28.4", "@floating-ui/utils": "^0.2.10", "reselect": "^5.1.1", "use-sync-external-store": "^1.6.0" }, "peerDependencies": { "@types/react": "^17 || ^18 || ^19", "react": "^17 || ^18 || ^19", "react-dom": "^17 || ^18 || ^19" }, "optionalPeers": ["@types/react"] }, "sha512-smZwpMhjO29v+jrZusBSc5T+IJ3vBb9cjIiBjtKcvWmRj9Z4DWGVR3efr1eHR56/bqY5a4qyY9ElkOY5ljo3ng=="],
123
124 "@electron/get": ["@electron/get@2.0.3", "", { "dependencies": { "debug": "^4.1.1", "env-paths": "^2.2.0", "fs-extra": "^8.1.0", "got": "^11.8.5", "progress": "^2.0.3", "semver": "^6.2.0", "sumchecker": "^3.0.1" }, "optionalDependencies": { "global-agent": "^3.0.0" } }, "sha512-Qkzpg2s9GnVV2I2BjRksUi43U5e6+zaQMcjoJy0C+C5oxaKl+fmckGDQFtRpZpZV0NQekuZZ+tGz7EA9TVnQtQ=="],113 "@electron/get": ["@electron/get@2.0.3", "", { "dependencies": { "debug": "^4.1.1", "env-paths": "^2.2.0", "fs-extra": "^8.1.0", "got": "^11.8.5", "progress": "^2.0.3", "semver": "^6.2.0", "sumchecker": "^3.0.1" }, "optionalDependencies": { "global-agent": "^3.0.0" } }, "sha512-Qkzpg2s9GnVV2I2BjRksUi43U5e6+zaQMcjoJy0C+C5oxaKl+fmckGDQFtRpZpZV0NQekuZZ+tGz7EA9TVnQtQ=="],
125114
126 "@electron/node-gyp": ["@electron/node-gyp@github:electron/node-gyp#06b29aa", { "dependencies": { "env-paths": "^2.2.0", "exponential-backoff": "^3.1.1", "glob": "^8.1.0", "graceful-fs": "^4.2.6", "make-fetch-happen": "^10.2.1", "nopt": "^6.0.0", "proc-log": "^2.0.1", "semver": "^7.3.5", "tar": "^6.2.1", "which": "^2.0.2" }, "bin": "./bin/node-gyp.js" }, "electron-node-gyp-06b29aa"],115 "@electron/node-gyp": ["@electron/node-gyp@github:electron/node-gyp#06b29aa", { "dependencies": { "env-paths": "^2.2.0", "exponential-backoff": "^3.1.1", "glob": "^8.1.0", "graceful-fs": "^4.2.6", "make-fetch-happen": "^10.2.1", "nopt": "^6.0.0", "proc-log": "^2.0.1", "semver": "^7.3.5", "tar": "^6.2.1", "which": "^2.0.2" }, "bin": "./bin/node-gyp.js" }, "electron-node-gyp-06b29aa"],
...@@ -179,14 +168,6 @@...@@ -179,14 +168,6 @@
179168
180 "@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.3", "", { "os": "win32", "cpu": "x64" }, "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA=="],169 "@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.3", "", { "os": "win32", "cpu": "x64" }, "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA=="],
181170
182 "@floating-ui/core": ["@floating-ui/core@1.7.4", "", { "dependencies": { "@floating-ui/utils": "^0.2.10" } }, "sha512-C3HlIdsBxszvm5McXlB8PeOEWfBhcGBTZGkGlWc2U0KFY5IwG5OQEuQ8rq52DZmcHDlPLd+YFBK+cZcytwIFWg=="],
183
184 "@floating-ui/dom": ["@floating-ui/dom@1.7.5", "", { "dependencies": { "@floating-ui/core": "^1.7.4", "@floating-ui/utils": "^0.2.10" } }, "sha512-N0bD2kIPInNHUHehXhMke1rBGs1dwqvC9O9KYMyyjK7iXt7GAhnro7UlcuYcGdS/yYOlq0MAVgrow8IbWJwyqg=="],
185
186 "@floating-ui/react-dom": ["@floating-ui/react-dom@2.1.7", "", { "dependencies": { "@floating-ui/dom": "^1.7.5" }, "peerDependencies": { "react": ">=16.8.0", "react-dom": ">=16.8.0" } }, "sha512-0tLRojf/1Go2JgEVm+3Frg9A3IW8bJgKgdO0BN5RkF//ufuz2joZM63Npau2ff3J6lUVYgDSNzNkR+aH3IVfjg=="],
187
188 "@floating-ui/utils": ["@floating-ui/utils@0.2.10", "", {}, "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ=="],
189
190 "@gar/promisify": ["@gar/promisify@1.1.3", "", {}, "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw=="],171 "@gar/promisify": ["@gar/promisify@1.1.3", "", {}, "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw=="],
191172
192 "@hapi/address": ["@hapi/address@5.1.1", "", { "dependencies": { "@hapi/hoek": "^11.0.2" } }, "sha512-A+po2d/dVoY7cYajycYI43ZbYMXukuopIsqCjh5QzsBCipDtdofHntljDlpccMjIfTy6UOkg+5KPriwYch2bXA=="],173 "@hapi/address": ["@hapi/address@5.1.1", "", { "dependencies": { "@hapi/hoek": "^11.0.2" } }, "sha512-A+po2d/dVoY7cYajycYI43ZbYMXukuopIsqCjh5QzsBCipDtdofHntljDlpccMjIfTy6UOkg+5KPriwYch2bXA=="],
...@@ -201,16 +182,6 @@...@@ -201,16 +182,6 @@
201182
202 "@hapi/topo": ["@hapi/topo@6.0.2", "", { "dependencies": { "@hapi/hoek": "^11.0.2" } }, "sha512-KR3rD5inZbGMrHmgPxsJ9dbi6zEK+C3ZwUwTa+eMwWLz7oijWUTWD2pMSNNYJAU6Qq+65NkxXjqHr/7LM2Xkqg=="],183 "@hapi/topo": ["@hapi/topo@6.0.2", "", { "dependencies": { "@hapi/hoek": "^11.0.2" } }, "sha512-KR3rD5inZbGMrHmgPxsJ9dbi6zEK+C3ZwUwTa+eMwWLz7oijWUTWD2pMSNNYJAU6Qq+65NkxXjqHr/7LM2Xkqg=="],
203184
204 "@inquirer/ansi": ["@inquirer/ansi@1.0.2", "", {}, "sha512-S8qNSZiYzFd0wAcyG5AXCvUHC5Sr7xpZ9wZ2py9XR88jUz8wooStVx5M6dRzczbBWjic9NP7+rY0Xi7qqK/aMQ=="],
205
206 "@inquirer/confirm": ["@inquirer/confirm@5.1.21", "", { "dependencies": { "@inquirer/core": "^10.3.2", "@inquirer/type": "^3.0.10" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-KR8edRkIsUayMXV+o3Gv+q4jlhENF9nMYUZs9PA2HzrXeHI8M5uDag70U7RJn9yyiMZSbtF5/UexBtAVtZGSbQ=="],
207
208 "@inquirer/core": ["@inquirer/core@10.3.2", "", { "dependencies": { "@inquirer/ansi": "^1.0.2", "@inquirer/figures": "^1.0.15", "@inquirer/type": "^3.0.10", "cli-width": "^4.1.0", "mute-stream": "^2.0.0", "signal-exit": "^4.1.0", "wrap-ansi": "^6.2.0", "yoctocolors-cjs": "^2.1.3" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-43RTuEbfP8MbKzedNqBrlhhNKVwoK//vUFNW3Q3vZ88BLcrs4kYpGg+B2mm5p2K/HfygoCxuKwJJiv8PbGmE0A=="],
209
210 "@inquirer/figures": ["@inquirer/figures@1.0.15", "", {}, "sha512-t2IEY+unGHOzAaVM5Xx6DEWKeXlDDcNPeDyUpsRc6CUhBfU3VQOEl+Vssh7VNp1dR8MdUJBWhuObjXCsVpjN5g=="],
211
212 "@inquirer/type": ["@inquirer/type@3.0.10", "", { "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-BvziSRxfz5Ov8ch0z/n3oijRSEcEsHnhggm4xFZe93DHcUCTlutlq9Ox4SVENAfcRD22UQq7T/atg9Wr3k09eA=="],
213
214 "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="],185 "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="],
215186
216 "@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="],187 "@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="],
...@@ -223,18 +194,10 @@...@@ -223,18 +194,10 @@
223194
224 "@malept/cross-spawn-promise": ["@malept/cross-spawn-promise@2.0.0", "", { "dependencies": { "cross-spawn": "^7.0.1" } }, "sha512-1DpKU0Z5ThltBwjNySMC14g0CkbyhCaz9FkhxqNsZI6uAPJXFS8cMXlBKo26FJ8ZuW6S9GCMcR9IO5k2X5/9Fg=="],195 "@malept/cross-spawn-promise": ["@malept/cross-spawn-promise@2.0.0", "", { "dependencies": { "cross-spawn": "^7.0.1" } }, "sha512-1DpKU0Z5ThltBwjNySMC14g0CkbyhCaz9FkhxqNsZI6uAPJXFS8cMXlBKo26FJ8ZuW6S9GCMcR9IO5k2X5/9Fg=="],
225196
226 "@mswjs/interceptors": ["@mswjs/interceptors@0.41.2", "", { "dependencies": { "@open-draft/deferred-promise": "^2.2.0", "@open-draft/logger": "^0.3.0", "@open-draft/until": "^2.0.0", "is-node-process": "^1.2.0", "outvariant": "^1.4.3", "strict-event-emitter": "^0.5.1" } }, "sha512-7G0Uf0yK3f2bjElBLGHIQzgRgMESczOMyYVasq1XK8P5HaXtlW4eQhz9MBL+TQILZLaruq+ClGId+hH0w4jvWw=="],
227
228 "@npmcli/fs": ["@npmcli/fs@2.1.2", "", { "dependencies": { "@gar/promisify": "^1.1.3", "semver": "^7.3.5" } }, "sha512-yOJKRvohFOaLqipNtwYB9WugyZKhC/DZC4VYPmpaCzDBrA8YpK3qHZ8/HGscMnE4GqbkLNuVcCnxkeQEdGt6LQ=="],197 "@npmcli/fs": ["@npmcli/fs@2.1.2", "", { "dependencies": { "@gar/promisify": "^1.1.3", "semver": "^7.3.5" } }, "sha512-yOJKRvohFOaLqipNtwYB9WugyZKhC/DZC4VYPmpaCzDBrA8YpK3qHZ8/HGscMnE4GqbkLNuVcCnxkeQEdGt6LQ=="],
229198
230 "@npmcli/move-file": ["@npmcli/move-file@2.0.1", "", { "dependencies": { "mkdirp": "^1.0.4", "rimraf": "^3.0.2" } }, "sha512-mJd2Z5TjYWq/ttPLLGqArdtnC74J6bOzg4rMDnN+p1xTacZ2yPRCk2y0oSWQtygLR9YVQXgOcONrwtnk3JupxQ=="],199 "@npmcli/move-file": ["@npmcli/move-file@2.0.1", "", { "dependencies": { "mkdirp": "^1.0.4", "rimraf": "^3.0.2" } }, "sha512-mJd2Z5TjYWq/ttPLLGqArdtnC74J6bOzg4rMDnN+p1xTacZ2yPRCk2y0oSWQtygLR9YVQXgOcONrwtnk3JupxQ=="],
231200
232 "@open-draft/deferred-promise": ["@open-draft/deferred-promise@2.2.0", "", {}, "sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA=="],
233
234 "@open-draft/logger": ["@open-draft/logger@0.3.0", "", { "dependencies": { "is-node-process": "^1.2.0", "outvariant": "^1.4.0" } }, "sha512-X2g45fzhxH238HKO4xbSr7+wBS8Fvw6ixhTDuvLd5mqh6bJJCFAPwU9mPDxbcrRtfxv4u5IHCEH77BmxvXmmxQ=="],
235
236 "@open-draft/until": ["@open-draft/until@2.1.0", "", {}, "sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg=="],
237
238 "@oxfmt/darwin-arm64": ["@oxfmt/darwin-arm64@0.28.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-jmUfF7cNJPw57bEK7sMIqrYRgn4LH428tSgtgLTCtjuGuu1ShREyrkeB7y8HtkXRfhBs4lVY+HMLhqElJvZ6ww=="],201 "@oxfmt/darwin-arm64": ["@oxfmt/darwin-arm64@0.28.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-jmUfF7cNJPw57bEK7sMIqrYRgn4LH428tSgtgLTCtjuGuu1ShREyrkeB7y8HtkXRfhBs4lVY+HMLhqElJvZ6ww=="],
239202
240 "@oxfmt/darwin-x64": ["@oxfmt/darwin-x64@0.28.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-S6vlV8S7jbjzJOSjfVg2CimUC0r7/aHDLdUm/3+/B/SU/s1jV7ivqWkMv1/8EB43d1BBwT9JQ60ZMTkBqeXSFA=="],203 "@oxfmt/darwin-x64": ["@oxfmt/darwin-x64@0.28.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-S6vlV8S7jbjzJOSjfVg2CimUC0r7/aHDLdUm/3+/B/SU/s1jV7ivqWkMv1/8EB43d1BBwT9JQ60ZMTkBqeXSFA=="],
...@@ -395,8 +358,6 @@...@@ -395,8 +358,6 @@
395358
396 "@types/responselike": ["@types/responselike@1.0.3", "", { "dependencies": { "@types/node": "*" } }, "sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw=="],359 "@types/responselike": ["@types/responselike@1.0.3", "", { "dependencies": { "@types/node": "*" } }, "sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw=="],
397360
398 "@types/statuses": ["@types/statuses@2.0.6", "", {}, "sha512-xMAgYwceFhRA2zY+XbEA7mxYbA093wdiW8Vu6gZPGWy9cmOyU9XesH1tNcEWsKFd5Vzrqx5T3D38PWx1FIIXkA=="],
399
400 "@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="],361 "@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="],
401362
402 "@types/yauzl": ["@types/yauzl@2.10.3", "", { "dependencies": { "@types/node": "*" } }, "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q=="],363 "@types/yauzl": ["@types/yauzl@2.10.3", "", { "dependencies": { "@types/node": "*" } }, "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q=="],
...@@ -499,24 +460,18 @@...@@ -499,24 +460,18 @@
499460
500 "chownr": ["chownr@2.0.0", "", {}, "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ=="],461 "chownr": ["chownr@2.0.0", "", {}, "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ=="],
501462
502 "class-variance-authority": ["class-variance-authority@0.7.1", "", { "dependencies": { "clsx": "^2.1.1" } }, "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg=="],
503
504 "clean-stack": ["clean-stack@2.2.0", "", {}, "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A=="],463 "clean-stack": ["clean-stack@2.2.0", "", {}, "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A=="],
505464
506 "cli-cursor": ["cli-cursor@3.1.0", "", { "dependencies": { "restore-cursor": "^3.1.0" } }, "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw=="],465 "cli-cursor": ["cli-cursor@3.1.0", "", { "dependencies": { "restore-cursor": "^3.1.0" } }, "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw=="],
507466
508 "cli-spinners": ["cli-spinners@2.9.2", "", {}, "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg=="],467 "cli-spinners": ["cli-spinners@2.9.2", "", {}, "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg=="],
509468
510 "cli-width": ["cli-width@4.1.0", "", {}, "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ=="],
511
512 "cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="],469 "cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="],
513470
514 "clone": ["clone@1.0.4", "", {}, "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg=="],471 "clone": ["clone@1.0.4", "", {}, "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg=="],
515472
516 "clone-response": ["clone-response@1.0.3", "", { "dependencies": { "mimic-response": "^1.0.0" } }, "sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA=="],473 "clone-response": ["clone-response@1.0.3", "", { "dependencies": { "mimic-response": "^1.0.0" } }, "sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA=="],
517474
518 "clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="],
519
520 "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="],475 "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="],
521476
522 "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="],477 "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="],
...@@ -535,8 +490,6 @@...@@ -535,8 +490,6 @@
535490
536 "convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="],491 "convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="],
537492
538 "cookie": ["cookie@1.1.1", "", {}, "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ=="],
539
540 "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="],493 "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="],
541494
542 "csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="],495 "csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="],
...@@ -661,8 +614,6 @@...@@ -661,8 +614,6 @@
661614
662 "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="],615 "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="],
663616
664 "graphql": ["graphql@16.12.0", "", {}, "sha512-DKKrynuQRne0PNpEbzuEdHlYOMksHSUI8Zc9Unei5gTsMNA2/vMpoMz/yKba50pejK56qj98qM0SjYxAKi13gQ=="],
665
666 "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="],617 "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="],
667618
668 "has-property-descriptors": ["has-property-descriptors@1.0.2", "", { "dependencies": { "es-define-property": "^1.0.0" } }, "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg=="],619 "has-property-descriptors": ["has-property-descriptors@1.0.2", "", { "dependencies": { "es-define-property": "^1.0.0" } }, "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg=="],
...@@ -681,8 +632,6 @@...@@ -681,8 +632,6 @@
681632
682 "hast-util-whitespace": ["hast-util-whitespace@3.0.0", "", { "dependencies": { "@types/hast": "^3.0.0" } }, "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw=="],633 "hast-util-whitespace": ["hast-util-whitespace@3.0.0", "", { "dependencies": { "@types/hast": "^3.0.0" } }, "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw=="],
683634
684 "headers-polyfill": ["headers-polyfill@4.0.3", "", {}, "sha512-IScLbePpkvO846sIwOtOTDjutRMWdXdJmXdMvk6gCBHxFO8d+QKOQedyZSxFTTFYRSmlgSTDtXqqq4pcenBXLQ=="],
685
686 "highlight.js": ["highlight.js@11.11.1", "", {}, "sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w=="],635 "highlight.js": ["highlight.js@11.11.1", "", {}, "sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w=="],
687636
688 "html-url-attributes": ["html-url-attributes@3.0.1", "", {}, "sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ=="],637 "html-url-attributes": ["html-url-attributes@3.0.1", "", {}, "sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ=="],
...@@ -731,8 +680,6 @@...@@ -731,8 +680,6 @@
731680
732 "is-lambda": ["is-lambda@1.0.1", "", {}, "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ=="],681 "is-lambda": ["is-lambda@1.0.1", "", {}, "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ=="],
733682
734 "is-node-process": ["is-node-process@1.2.0", "", {}, "sha512-Vg4o6/fqPxIjtxgUH5QLJhwZ7gW5diGCVlXpuUfELC62CuxM1iHcRe51f2W1FDy04Ai4KJkagKjx3XaqyfRKXw=="],
735
736 "is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="],683 "is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="],
737684
738 "is-plain-obj": ["is-plain-obj@4.1.0", "", {}, "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg=="],685 "is-plain-obj": ["is-plain-obj@4.1.0", "", {}, "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg=="],
...@@ -807,8 +754,6 @@...@@ -807,8 +754,6 @@
807754
808 "lru-cache": ["lru-cache@7.18.3", "", {}, "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA=="],755 "lru-cache": ["lru-cache@7.18.3", "", {}, "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA=="],
809756
810 "lucide-react": ["lucide-react@0.563.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-8dXPB2GI4dI8jV4MgUDGBeLdGk8ekfqVZ0BdLcrRzocGgG75ltNEmWS+gE7uokKF/0oSUuczNDT+g9hFJ23FkA=="],
811
812 "magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="],757 "magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="],
813758
814 "make-fetch-happen": ["make-fetch-happen@10.2.1", "", { "dependencies": { "agentkeepalive": "^4.2.1", "cacache": "^16.1.0", "http-cache-semantics": "^4.1.0", "http-proxy-agent": "^5.0.0", "https-proxy-agent": "^5.0.0", "is-lambda": "^1.0.1", "lru-cache": "^7.7.1", "minipass": "^3.1.6", "minipass-collect": "^1.0.2", "minipass-fetch": "^2.0.3", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", "negotiator": "^0.6.3", "promise-retry": "^2.0.1", "socks-proxy-agent": "^7.0.0", "ssri": "^9.0.0" } }, "sha512-NgOPbRiaQM10DYXvN3/hhGVI2M5MtITFryzBGxHM5p4wnFxsVCbxkrBrDsk+EZ5OB4jEOT7AjDxtdF+KVEFT7w=="],759 "make-fetch-happen": ["make-fetch-happen@10.2.1", "", { "dependencies": { "agentkeepalive": "^4.2.1", "cacache": "^16.1.0", "http-cache-semantics": "^4.1.0", "http-proxy-agent": "^5.0.0", "https-proxy-agent": "^5.0.0", "is-lambda": "^1.0.1", "lru-cache": "^7.7.1", "minipass": "^3.1.6", "minipass-collect": "^1.0.2", "minipass-fetch": "^2.0.3", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", "negotiator": "^0.6.3", "promise-retry": "^2.0.1", "socks-proxy-agent": "^7.0.0", "ssri": "^9.0.0" } }, "sha512-NgOPbRiaQM10DYXvN3/hhGVI2M5MtITFryzBGxHM5p4wnFxsVCbxkrBrDsk+EZ5OB4jEOT7AjDxtdF+KVEFT7w=="],
...@@ -939,10 +884,6 @@...@@ -939,10 +884,6 @@
939884
940 "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],885 "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
941886
942 "msw": ["msw@2.12.9", "", { "dependencies": { "@inquirer/confirm": "^5.0.0", "@mswjs/interceptors": "^0.41.2", "@open-draft/deferred-promise": "^2.2.0", "@types/statuses": "^2.0.6", "cookie": "^1.0.2", "graphql": "^16.12.0", "headers-polyfill": "^4.0.2", "is-node-process": "^1.2.0", "outvariant": "^1.4.3", "path-to-regexp": "^6.3.0", "picocolors": "^1.1.1", "rettime": "^0.10.1", "statuses": "^2.0.2", "strict-event-emitter": "^0.5.1", "tough-cookie": "^6.0.0", "type-fest": "^5.2.0", "until-async": "^3.0.2", "yargs": "^17.7.2" }, "peerDependencies": { "typescript": ">= 4.8.x" }, "optionalPeers": ["typescript"], "bin": { "msw": "cli/index.js" } }, "sha512-NYbi51C6M3dujGmcmuGemu68jy12KqQPoVWGeroKToLGsBgrwG5ErM8WctoIIg49/EV49SEvYM9WSqO4G7kNeQ=="],
943
944 "mute-stream": ["mute-stream@2.0.0", "", {}, "sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA=="],
945
946 "mz": ["mz@2.7.0", "", { "dependencies": { "any-promise": "^1.0.0", "object-assign": "^4.0.1", "thenify-all": "^1.0.0" } }, "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q=="],887 "mz": ["mz@2.7.0", "", { "dependencies": { "any-promise": "^1.0.0", "object-assign": "^4.0.1", "thenify-all": "^1.0.0" } }, "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q=="],
947888
948 "nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],889 "nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
...@@ -975,8 +916,6 @@...@@ -975,8 +916,6 @@
975916
976 "ora": ["ora@5.4.1", "", { "dependencies": { "bl": "^4.1.0", "chalk": "^4.1.0", "cli-cursor": "^3.1.0", "cli-spinners": "^2.5.0", "is-interactive": "^1.0.0", "is-unicode-supported": "^0.1.0", "log-symbols": "^4.1.0", "strip-ansi": "^6.0.0", "wcwidth": "^1.0.1" } }, "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ=="],917 "ora": ["ora@5.4.1", "", { "dependencies": { "bl": "^4.1.0", "chalk": "^4.1.0", "cli-cursor": "^3.1.0", "cli-spinners": "^2.5.0", "is-interactive": "^1.0.0", "is-unicode-supported": "^0.1.0", "log-symbols": "^4.1.0", "strip-ansi": "^6.0.0", "wcwidth": "^1.0.1" } }, "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ=="],
977918
978 "outvariant": ["outvariant@1.4.3", "", {}, "sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA=="],
979
980 "oxfmt": ["oxfmt@0.28.0", "", { "dependencies": { "tinypool": "2.1.0" }, "optionalDependencies": { "@oxfmt/darwin-arm64": "0.28.0", "@oxfmt/darwin-x64": "0.28.0", "@oxfmt/linux-arm64-gnu": "0.28.0", "@oxfmt/linux-arm64-musl": "0.28.0", "@oxfmt/linux-x64-gnu": "0.28.0", "@oxfmt/linux-x64-musl": "0.28.0", "@oxfmt/win32-arm64": "0.28.0", "@oxfmt/win32-x64": "0.28.0" }, "bin": { "oxfmt": "bin/oxfmt" } }, "sha512-3+hhBqPE6Kp22KfJmnstrZbl+KdOVSEu1V0ABaFIg1rYLtrMgrupx9znnHgHLqKxAVHebjTdiCJDk30CXOt6cw=="],919 "oxfmt": ["oxfmt@0.28.0", "", { "dependencies": { "tinypool": "2.1.0" }, "optionalDependencies": { "@oxfmt/darwin-arm64": "0.28.0", "@oxfmt/darwin-x64": "0.28.0", "@oxfmt/linux-arm64-gnu": "0.28.0", "@oxfmt/linux-arm64-musl": "0.28.0", "@oxfmt/linux-x64-gnu": "0.28.0", "@oxfmt/linux-x64-musl": "0.28.0", "@oxfmt/win32-arm64": "0.28.0", "@oxfmt/win32-x64": "0.28.0" }, "bin": { "oxfmt": "bin/oxfmt" } }, "sha512-3+hhBqPE6Kp22KfJmnstrZbl+KdOVSEu1V0ABaFIg1rYLtrMgrupx9znnHgHLqKxAVHebjTdiCJDk30CXOt6cw=="],
981920
982 "oxlint": ["oxlint@1.43.0", "", { "optionalDependencies": { "@oxlint/darwin-arm64": "1.43.0", "@oxlint/darwin-x64": "1.43.0", "@oxlint/linux-arm64-gnu": "1.43.0", "@oxlint/linux-arm64-musl": "1.43.0", "@oxlint/linux-x64-gnu": "1.43.0", "@oxlint/linux-x64-musl": "1.43.0", "@oxlint/win32-arm64": "1.43.0", "@oxlint/win32-x64": "1.43.0" }, "peerDependencies": { "oxlint-tsgolint": ">=0.11.2" }, "optionalPeers": ["oxlint-tsgolint"], "bin": { "oxlint": "bin/oxlint" } }, "sha512-xiqTCsKZch+R61DPCjyqUVP2MhkQlRRYxLRBeBDi+dtQJ90MOgdcjIktvDCgXz0bgtx94EQzHEndsizZjMX2OA=="],921 "oxlint": ["oxlint@1.43.0", "", { "optionalDependencies": { "@oxlint/darwin-arm64": "1.43.0", "@oxlint/darwin-x64": "1.43.0", "@oxlint/linux-arm64-gnu": "1.43.0", "@oxlint/linux-arm64-musl": "1.43.0", "@oxlint/linux-x64-gnu": "1.43.0", "@oxlint/linux-x64-musl": "1.43.0", "@oxlint/win32-arm64": "1.43.0", "@oxlint/win32-x64": "1.43.0" }, "peerDependencies": { "oxlint-tsgolint": ">=0.11.2" }, "optionalPeers": ["oxlint-tsgolint"], "bin": { "oxlint": "bin/oxlint" } }, "sha512-xiqTCsKZch+R61DPCjyqUVP2MhkQlRRYxLRBeBDi+dtQJ90MOgdcjIktvDCgXz0bgtx94EQzHEndsizZjMX2OA=="],
...@@ -991,8 +930,6 @@...@@ -991,8 +930,6 @@
991930
992 "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="],931 "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="],
993932
994 "path-to-regexp": ["path-to-regexp@6.3.0", "", {}, "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ=="],
995
996 "pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="],933 "pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="],
997934
998 "pathval": ["pathval@2.0.1", "", {}, "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ=="],935 "pathval": ["pathval@2.0.1", "", {}, "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ=="],
...@@ -1057,8 +994,6 @@...@@ -1057,8 +994,6 @@
1057994
1058 "require-directory": ["require-directory@2.1.1", "", {}, "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="],995 "require-directory": ["require-directory@2.1.1", "", {}, "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="],
1059996
1060 "reselect": ["reselect@5.1.1", "", {}, "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w=="],
1061
1062 "resolve-alpn": ["resolve-alpn@1.2.1", "", {}, "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g=="],997 "resolve-alpn": ["resolve-alpn@1.2.1", "", {}, "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g=="],
1063998
1064 "resolve-from": ["resolve-from@5.0.0", "", {}, "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw=="],999 "resolve-from": ["resolve-from@5.0.0", "", {}, "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw=="],
...@@ -1069,8 +1004,6 @@...@@ -1069,8 +1004,6 @@
10691004
1070 "retry": ["retry@0.12.0", "", {}, "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow=="],1005 "retry": ["retry@0.12.0", "", {}, "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow=="],
10711006
1072 "rettime": ["rettime@0.10.1", "", {}, "sha512-uyDrIlUEH37cinabq0AX4QbgV4HbFZ/gqoiunWQ1UqBtRvTTytwhNYjE++pO/MjPTZL5KQCf2bEoJ/BJNVQ5Kw=="],
1073
1074 "rimraf": ["rimraf@3.0.2", "", { "dependencies": { "glob": "^7.1.3" }, "bin": { "rimraf": "bin.js" } }, "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA=="],1007 "rimraf": ["rimraf@3.0.2", "", { "dependencies": { "glob": "^7.1.3" }, "bin": { "rimraf": "bin.js" } }, "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA=="],
10751008
1076 "roarr": ["roarr@2.15.4", "", { "dependencies": { "boolean": "^3.0.1", "detect-node": "^2.0.4", "globalthis": "^1.0.1", "json-stringify-safe": "^5.0.1", "semver-compare": "^1.0.0", "sprintf-js": "^1.1.2" } }, "sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A=="],1009 "roarr": ["roarr@2.15.4", "", { "dependencies": { "boolean": "^3.0.1", "detect-node": "^2.0.4", "globalthis": "^1.0.1", "json-stringify-safe": "^5.0.1", "semver-compare": "^1.0.0", "sprintf-js": "^1.1.2" } }, "sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A=="],
...@@ -1119,12 +1052,8 @@...@@ -1119,12 +1052,8 @@
11191052
1120 "stackback": ["stackback@0.0.2", "", {}, "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw=="],1053 "stackback": ["stackback@0.0.2", "", {}, "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw=="],
11211054
1122 "statuses": ["statuses@2.0.2", "", {}, "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw=="],
1123
1124 "std-env": ["std-env@3.10.0", "", {}, "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg=="],1055 "std-env": ["std-env@3.10.0", "", {}, "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg=="],
11251056
1126 "strict-event-emitter": ["strict-event-emitter@0.5.1", "", {}, "sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ=="],
1127
1128 "string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],1057 "string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
11291058
1130 "string_decoder": ["string_decoder@1.3.0", "", { "dependencies": { "safe-buffer": "~5.2.0" } }, "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA=="],1059 "string_decoder": ["string_decoder@1.3.0", "", { "dependencies": { "safe-buffer": "~5.2.0" } }, "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA=="],
...@@ -1145,12 +1074,6 @@...@@ -1145,12 +1074,6 @@
11451074
1146 "supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],1075 "supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
11471076
1148 "tabbable": ["tabbable@6.4.0", "", {}, "sha512-05PUHKSNE8ou2dwIxTngl4EzcnsCDZGJ/iCLtDflR/SHB/ny14rXc+qU5P4mG9JkusiV7EivzY9Mhm55AzAvCg=="],
1149
1150 "tagged-tag": ["tagged-tag@1.0.0", "", {}, "sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng=="],
1151
1152 "tailwind-merge": ["tailwind-merge@3.4.0", "", {}, "sha512-uSaO4gnW+b3Y2aWoWfFpX62vn2sR3skfhbjsEnaBI81WD1wBLlHZe5sWf0AqjksNdYTbGBEd0UasQMT3SNV15g=="],
1153
1154 "tailwindcss": ["tailwindcss@4.1.18", "", {}, "sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw=="],1077 "tailwindcss": ["tailwindcss@4.1.18", "", {}, "sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw=="],
11551078
1156 "tapable": ["tapable@2.3.0", "", {}, "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg=="],1079 "tapable": ["tapable@2.3.0", "", {}, "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg=="],
...@@ -1173,14 +1096,8 @@...@@ -1173,14 +1096,8 @@
11731096
1174 "tinyspy": ["tinyspy@4.0.4", "", {}, "sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q=="],1097 "tinyspy": ["tinyspy@4.0.4", "", {}, "sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q=="],
11751098
1176 "tldts": ["tldts@7.0.23", "", { "dependencies": { "tldts-core": "^7.0.23" }, "bin": { "tldts": "bin/cli.js" } }, "sha512-ASdhgQIBSay0R/eXggAkQ53G4nTJqTXqC2kbaBbdDwM7SkjyZyO0OaaN1/FH7U/yCeqOHDwFO5j8+Os/IS1dXw=="],
1177
1178 "tldts-core": ["tldts-core@7.0.23", "", {}, "sha512-0g9vrtDQLrNIiCj22HSe9d4mLVG3g5ph5DZ8zCKBr4OtrspmNB6ss7hVyzArAeE88ceZocIEGkyW1Ime7fxPtQ=="],
1179
1180 "to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="],1099 "to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="],
11811100
1182 "tough-cookie": ["tough-cookie@6.0.0", "", { "dependencies": { "tldts": "^7.0.5" } }, "sha512-kXuRi1mtaKMrsLUxz3sQYvVl37B0Ns6MzfrtV5DvJceE9bPyspOqk9xxv7XbZWcfLWbFmm997vl83qUWVJA64w=="],
1183
1184 "tree-kill": ["tree-kill@1.2.2", "", { "bin": { "tree-kill": "cli.js" } }, "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A=="],1101 "tree-kill": ["tree-kill@1.2.2", "", { "bin": { "tree-kill": "cli.js" } }, "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A=="],
11851102
1186 "trim-lines": ["trim-lines@3.0.1", "", {}, "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg=="],1103 "trim-lines": ["trim-lines@3.0.1", "", {}, "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg=="],
...@@ -1207,7 +1124,7 @@...@@ -1207,7 +1124,7 @@
12071124
1208 "turbo-windows-arm64": ["turbo-windows-arm64@2.8.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-afTUGKBRmOJU1smQSBnFGcbq0iabAPwh1uXu2BVk7BREg30/1gMnJh9DFEQTah+UD3n3ru8V55J83RQNFfqoyw=="],1125 "turbo-windows-arm64": ["turbo-windows-arm64@2.8.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-afTUGKBRmOJU1smQSBnFGcbq0iabAPwh1uXu2BVk7BREg30/1gMnJh9DFEQTah+UD3n3ru8V55J83RQNFfqoyw=="],
12091126
1210 "type-fest": ["type-fest@5.4.4", "", { "dependencies": { "tagged-tag": "^1.0.0" } }, "sha512-JnTrzGu+zPV3aXIUhnyWJj4z/wigMsdYajGLIYakqyOW1nPllzXEJee0QQbHj+CTIQtXGlAjuK0UY+2xTyjVAw=="],1127 "type-fest": ["type-fest@0.13.1", "", {}, "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg=="],
12111128
1212 "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],1129 "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
12131130
...@@ -1237,12 +1154,8 @@...@@ -1237,12 +1154,8 @@
12371154
1238 "unixify": ["unixify@1.0.0", "", { "dependencies": { "normalize-path": "^2.1.1" } }, "sha512-6bc58dPYhCMHHuwxldQxO3RRNZ4eCogZ/st++0+fcC1nr0jiGUtAdBJ2qzmLQWSxbtz42pWt4QQMiZ9HvZf5cg=="],1155 "unixify": ["unixify@1.0.0", "", { "dependencies": { "normalize-path": "^2.1.1" } }, "sha512-6bc58dPYhCMHHuwxldQxO3RRNZ4eCogZ/st++0+fcC1nr0jiGUtAdBJ2qzmLQWSxbtz42pWt4QQMiZ9HvZf5cg=="],
12391156
1240 "until-async": ["until-async@3.0.2", "", {}, "sha512-IiSk4HlzAMqTUseHHe3VhIGyuFmN90zMTpD3Z3y8jeQbzLIq500MVM7Jq2vUAnTKAFPJrqwkzr6PoTcPhGcOiw=="],
1241
1242 "update-browserslist-db": ["update-browserslist-db@1.2.3", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w=="],1157 "update-browserslist-db": ["update-browserslist-db@1.2.3", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w=="],
12431158
1244 "use-sync-external-store": ["use-sync-external-store@1.6.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w=="],
1245
1246 "util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="],1159 "util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="],
12471160
1248 "vfile": ["vfile@6.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "vfile-message": "^4.0.0" } }, "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q=="],1161 "vfile": ["vfile@6.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "vfile-message": "^4.0.0" } }, "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q=="],
...@@ -1279,8 +1192,6 @@...@@ -1279,8 +1192,6 @@
12791192
1280 "yauzl": ["yauzl@2.10.0", "", { "dependencies": { "buffer-crc32": "~0.2.3", "fd-slicer": "~1.1.0" } }, "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g=="],1193 "yauzl": ["yauzl@2.10.0", "", { "dependencies": { "buffer-crc32": "~0.2.3", "fd-slicer": "~1.1.0" } }, "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g=="],
12811194
1282 "yoctocolors-cjs": ["yoctocolors-cjs@2.1.3", "", {}, "sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw=="],
1283
1284 "zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],1195 "zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
12851196
1286 "zwitch": ["zwitch@2.0.4", "", {}, "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A=="],1197 "zwitch": ["zwitch@2.0.4", "", {}, "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A=="],
...@@ -1297,10 +1208,6 @@...@@ -1297,10 +1208,6 @@
12971208
1298 "@electron/get/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],1209 "@electron/get/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
12991210
1300 "@inquirer/core/signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="],
1301
1302 "@inquirer/core/wrap-ansi": ["wrap-ansi@6.2.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA=="],
1303
1304 "@tailwindcss/node/jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="],1211 "@tailwindcss/node/jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="],
13051212
1306 "@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.8.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" }, "bundled": true }, "sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg=="],1213 "@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.8.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" }, "bundled": true }, "sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg=="],
...@@ -1347,8 +1254,6 @@...@@ -1347,8 +1254,6 @@
13471254
1348 "rimraf/glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="],1255 "rimraf/glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="],
13491256
1350 "serialize-error/type-fest": ["type-fest@0.13.1", "", {}, "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg=="],
1351
1352 "ssri/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="],1257 "ssri/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="],
13531258
1354 "vite/esbuild": ["esbuild@0.25.12", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.12", "@esbuild/android-arm": "0.25.12", "@esbuild/android-arm64": "0.25.12", "@esbuild/android-x64": "0.25.12", "@esbuild/darwin-arm64": "0.25.12", "@esbuild/darwin-x64": "0.25.12", "@esbuild/freebsd-arm64": "0.25.12", "@esbuild/freebsd-x64": "0.25.12", "@esbuild/linux-arm": "0.25.12", "@esbuild/linux-arm64": "0.25.12", "@esbuild/linux-ia32": "0.25.12", "@esbuild/linux-loong64": "0.25.12", "@esbuild/linux-mips64el": "0.25.12", "@esbuild/linux-ppc64": "0.25.12", "@esbuild/linux-riscv64": "0.25.12", "@esbuild/linux-s390x": "0.25.12", "@esbuild/linux-x64": "0.25.12", "@esbuild/netbsd-arm64": "0.25.12", "@esbuild/netbsd-x64": "0.25.12", "@esbuild/openbsd-arm64": "0.25.12", "@esbuild/openbsd-x64": "0.25.12", "@esbuild/openharmony-arm64": "0.25.12", "@esbuild/sunos-x64": "0.25.12", "@esbuild/win32-arm64": "0.25.12", "@esbuild/win32-ia32": "0.25.12", "@esbuild/win32-x64": "0.25.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg=="],1259 "vite/esbuild": ["esbuild@0.25.12", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.12", "@esbuild/android-arm": "0.25.12", "@esbuild/android-arm64": "0.25.12", "@esbuild/android-x64": "0.25.12", "@esbuild/darwin-arm64": "0.25.12", "@esbuild/darwin-x64": "0.25.12", "@esbuild/freebsd-arm64": "0.25.12", "@esbuild/freebsd-x64": "0.25.12", "@esbuild/linux-arm": "0.25.12", "@esbuild/linux-arm64": "0.25.12", "@esbuild/linux-ia32": "0.25.12", "@esbuild/linux-loong64": "0.25.12", "@esbuild/linux-mips64el": "0.25.12", "@esbuild/linux-ppc64": "0.25.12", "@esbuild/linux-riscv64": "0.25.12", "@esbuild/linux-s390x": "0.25.12", "@esbuild/linux-x64": "0.25.12", "@esbuild/netbsd-arm64": "0.25.12", "@esbuild/netbsd-x64": "0.25.12", "@esbuild/openbsd-arm64": "0.25.12", "@esbuild/openbsd-x64": "0.25.12", "@esbuild/openharmony-arm64": "0.25.12", "@esbuild/sunos-x64": "0.25.12", "@esbuild/win32-arm64": "0.25.12", "@esbuild/win32-ia32": "0.25.12", "@esbuild/win32-x64": "0.25.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg=="],