| author | |
| committer | |
| log | 1ab32562049fdb51de7ffc3a7cc8af19f721a306 |
| tree | a2d0e4dd5a28405cec241ec11b5b2ebaf63c719f |
| parent | 377d21211a6b2873437e488590da13d81fa9e926 |
| signature | Commit is signed but in an unrecognized format. |
10 files changed, 394 insertions(+), 97 deletions(-)
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 class="bg-slate-100"> | 8 | <body> |
| 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+1| ... | @@ -13,6 +13,7 @@ | ... | @@ -13,6 +13,7 @@ |
| 13 | "dependencies": { | 13 | "dependencies": { |
| 14 | "@acme/contracts": "workspace:*", | 14 | "@acme/contracts": "workspace:*", |
| 15 | "highlight.js": "^11.11.1", | 15 | "highlight.js": "^11.11.1", |
| 16 | "lucide-react": "^0.563.0", | ||
| 16 | "react": "^19.0.0", | 17 | "react": "^19.0.0", |
| 17 | "react-dom": "^19.0.0", | 18 | "react-dom": "^19.0.0", |
| 18 | "react-markdown": "^10.1.0", | 19 | "react-markdown": "^10.1.0", |
apps/renderer/src/App.tsx+3-3| ... | @@ -31,10 +31,10 @@ function Layout() { | ... | @@ -31,10 +31,10 @@ function Layout() { |
| 31 | 31 | ||
| 32 | if (!api) { | 32 | if (!api) { |
| 33 | return ( | 33 | return ( |
| 34 | <div className="flex h-screen flex-col bg-[#0c0c0c] text-[#e0e0e0]"> | 34 | <div className="flex h-screen flex-col bg-background text-foreground"> |
| 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-[#a0a0a0]/60"> | 37 | <p className="text-sm text-muted-foreground"> |
| 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 | } |
| 44 | 44 | ||
| 45 | return ( | 45 | return ( |
| 46 | <div className="flex h-screen overflow-hidden bg-[#0c0c0c] text-[#e0e0e0]"> | 46 | <div className="flex h-screen overflow-hidden bg-background text-foreground"> |
| 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 = { |
| 15 | 15 | ||
| 16 | export default function ChatMarkdown({ text }: ChatMarkdownProps) { | 16 | export default function ChatMarkdown({ text }: ChatMarkdownProps) { |
| 17 | return ( | 17 | return ( |
| 18 | <div className="chat-markdown text-sm leading-relaxed text-[#d0d0d0]"> | 18 | <div className="chat-markdown text-sm leading-relaxed text-foreground/80"> |
| 19 | <ReactMarkdown | 19 | <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+43-43| ... | @@ -47,10 +47,10 @@ function editorLabel(editor: (typeof EDITORS)[number]): string { | ... | @@ -47,10 +47,10 @@ function editorLabel(editor: (typeof EDITORS)[number]): string { |
| 47 | const LAST_EDITOR_KEY = "codething:last-editor"; | 47 | const LAST_EDITOR_KEY = "codething:last-editor"; |
| 48 | 48 | ||
| 49 | function workToneClass(tone: "thinking" | "tool" | "info" | "error"): string { | 49 | function workToneClass(tone: "thinking" | "tool" | "info" | "error"): string { |
| 50 | if (tone === "error") return "text-rose-300/50"; | 50 | if (tone === "error") return "text-rose-300/50 dark:text-rose-300/50"; |
| 51 | if (tone === "tool") return "text-[#8a8a8a]"; | 51 | if (tone === "tool") return "text-muted-foreground/70"; |
| 52 | if (tone === "thinking") return "text-[#707070]"; | 52 | if (tone === "thinking") return "text-muted-foreground/50"; |
| 53 | return "text-[#606060]"; | 53 | return "text-muted-foreground/40"; |
| 54 | } | 54 | } |
| 55 | 55 | ||
| 56 | interface PendingApprovalCard { | 56 | interface PendingApprovalCard { |
| ... | @@ -488,7 +488,7 @@ export default function ChatView() { | ... | @@ -488,7 +488,7 @@ export default function ChatView() { |
| 488 | // Empty state: no active thread | 488 | // Empty state: no active thread |
| 489 | if (!activeThread) { | 489 | if (!activeThread) { |
| 490 | return ( | 490 | return ( |
| 491 | <div className="flex flex-1 flex-col bg-[#0c0c0c] text-[#a0a0a0]/40"> | 491 | <div className="flex flex-1 flex-col bg-background text-muted-foreground/40"> |
| 492 | <div className="drag-region h-[52px] shrink-0" /> | 492 | <div className="drag-region h-[52px] shrink-0" /> |
| 493 | <div className="flex flex-1 items-center justify-center"> | 493 | <div className="flex flex-1 items-center justify-center"> |
| 494 | <div className="text-center"> | 494 | <div className="text-center"> |
| ... | @@ -500,11 +500,11 @@ export default function ChatView() { | ... | @@ -500,11 +500,11 @@ export default function ChatView() { |
| 500 | } | 500 | } |
| 501 | 501 | ||
| 502 | return ( | 502 | return ( |
| 503 | <div className="flex flex-1 flex-col bg-[#0c0c0c]"> | 503 | <div className="flex flex-1 flex-col bg-background"> |
| 504 | {/* Top bar */} | 504 | {/* Top bar */} |
| 505 | <header className="drag-region flex items-center justify-between border-b border-white/8 px-5 pt-[28px] pb-3"> | 505 | <header className="drag-region flex items-center justify-between border-b border-border px-5 pt-[28px] pb-3"> |
| 506 | <div className="flex items-center gap-3"> | 506 | <div className="flex items-center gap-3"> |
| 507 | <h2 className="text-sm font-medium text-[#e0e0e0]"> | 507 | <h2 className="text-sm font-medium text-foreground"> |
| 508 | {activeThread.title} | 508 | {activeThread.title} |
| 509 | </h2> | 509 | </h2> |
| 510 | </div> | 510 | </div> |
| ... | @@ -514,23 +514,23 @@ export default function ChatView() { | ... | @@ -514,23 +514,23 @@ export default function ChatView() { |
| 514 | <div className="relative" ref={editorMenuRef}> | 514 | <div className="relative" ref={editorMenuRef}> |
| 515 | <button | 515 | <button |
| 516 | type="button" | 516 | type="button" |
| 517 | className="rounded-md px-2 py-1 text-[10px] text-[#a0a0a0]/40 transition-colors duration-150 hover:text-[#a0a0a0]/60" | 517 | className="rounded-md px-2 py-1 text-[10px] text-muted-foreground/40 transition-colors duration-150 hover:text-muted-foreground/60" |
| 518 | onClick={() => setIsEditorMenuOpen((v) => !v)} | 518 | onClick={() => setIsEditorMenuOpen((v) => !v)} |
| 519 | > | 519 | > |
| 520 | Open in&hellip; | 520 | Open in&hellip; |
| 521 | </button> | 521 | </button> |
| 522 | {isEditorMenuOpen && ( | 522 | {isEditorMenuOpen && ( |
| 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"> | 523 | <div className="absolute right-0 top-full z-50 mt-1 min-w-[120px] rounded-md border border-border bg-popover py-1 shadow-xl"> |
| 524 | {EDITORS.map((editor) => ( | 524 | {EDITORS.map((editor) => ( |
| 525 | <button | 525 | <button |
| 526 | key={editor.id} | 526 | key={editor.id} |
| 527 | type="button" | 527 | type="button" |
| 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]" | 528 | className="flex w-full items-center gap-2 px-3 py-1.5 text-left text-[11px] text-foreground hover:bg-accent" |
| 529 | onClick={() => openInEditor(editor.id)} | 529 | onClick={() => openInEditor(editor.id)} |
| 530 | > | 530 | > |
| 531 | {editorLabel(editor)} | 531 | {editorLabel(editor)} |
| 532 | {editor.id === lastEditor && ( | 532 | {editor.id === lastEditor && ( |
| 533 | <kbd className="ml-auto text-[9px] text-[#a0a0a0]/40"> | 533 | <kbd className="ml-auto text-[9px] text-muted-foreground/40"> |
| 534 | {navigator.platform.includes("Mac") | 534 | {navigator.platform.includes("Mac") |
| 535 | ? "\u2318O" | 535 | ? "\u2318O" |
| 536 | : "Ctrl+O"} | 536 | : "Ctrl+O"} |
| ... | @@ -547,8 +547,8 @@ export default function ChatView() { | ... | @@ -547,8 +547,8 @@ export default function ChatView() { |
| 547 | type="button" | 547 | type="button" |
| 548 | 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 ${ |
| 549 | state.diffOpen | 549 | state.diffOpen |
| 550 | ? "bg-white/10 text-white" | 550 | ? "bg-accent text-foreground" |
| 551 | : "text-[#a0a0a0]/40 hover:text-[#a0a0a0]/60" | 551 | : "text-muted-foreground/40 hover:text-muted-foreground/60" |
| 552 | }`} | 552 | }`} |
| 553 | onClick={() => dispatch({ type: "TOGGLE_DIFF" })} | 553 | onClick={() => dispatch({ type: "TOGGLE_DIFF" })} |
| 554 | > | 554 | > |
| ... | @@ -591,7 +591,7 @@ export default function ChatView() { | ... | @@ -591,7 +591,7 @@ export default function ChatView() { |
| 591 | <div className="mt-2 flex flex-wrap gap-1.5"> | 591 | <div className="mt-2 flex flex-wrap gap-1.5"> |
| 592 | <button | 592 | <button |
| 593 | type="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" | 594 | className="rounded-md border border-border bg-accent px-2 py-1 text-[11px] text-foreground transition-colors duration-150 hover:bg-accent/80 disabled:cursor-not-allowed disabled:opacity-50" |
| 595 | disabled={isResponding} | 595 | disabled={isResponding} |
| 596 | onClick={() => | 596 | onClick={() => |
| 597 | void onRespondToApproval(approval.requestId, "accept") | 597 | void onRespondToApproval(approval.requestId, "accept") |
| ... | @@ -614,7 +614,7 @@ export default function ChatView() { | ... | @@ -614,7 +614,7 @@ export default function ChatView() { |
| 614 | </button> | 614 | </button> |
| 615 | <button | 615 | <button |
| 616 | type="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" | 617 | className="rounded-md border border-border px-2 py-1 text-[11px] text-foreground/90 transition-colors duration-150 hover:bg-accent disabled:cursor-not-allowed disabled:opacity-50" |
| 618 | disabled={isResponding} | 618 | disabled={isResponding} |
| 619 | onClick={() => | 619 | onClick={() => |
| 620 | void onRespondToApproval(approval.requestId, "decline") | 620 | void onRespondToApproval(approval.requestId, "decline") |
| ... | @@ -643,7 +643,7 @@ export default function ChatView() { | ... | @@ -643,7 +643,7 @@ export default function ChatView() { |
| 643 | <div className="flex-1 overflow-y-auto px-5 py-4"> | 643 | <div className="flex-1 overflow-y-auto px-5 py-4"> |
| 644 | {activeThread.messages.length === 0 && !isWorking ? ( | 644 | {activeThread.messages.length === 0 && !isWorking ? ( |
| 645 | <div className="flex h-full items-center justify-center"> | 645 | <div className="flex h-full items-center justify-center"> |
| 646 | <p className="text-sm text-[#a0a0a0]/30">Send a message to start the conversation.</p> | 646 | <p className="text-sm text-muted-foreground/30">Send a message to start the conversation.</p> |
| 647 | </div> | 647 | </div> |
| 648 | ) : ( | 648 | ) : ( |
| 649 | <div className="mx-auto max-w-3xl space-y-4"> | 649 | <div className="mx-auto max-w-3xl space-y-4"> |
| ... | @@ -654,18 +654,18 @@ export default function ChatView() { | ... | @@ -654,18 +654,18 @@ export default function ChatView() { |
| 654 | (completionDividerBeforeEntryId === timelineEntry.id || | 654 | (completionDividerBeforeEntryId === timelineEntry.id || |
| 655 | timelineEntries[index - 1]?.kind === "work") && ( | 655 | timelineEntries[index - 1]?.kind === "work") && ( |
| 656 | <div className="my-3 flex items-center gap-3"> | 656 | <div className="my-3 flex items-center gap-3"> |
| 657 | <span className="h-px flex-1 bg-white/[0.1]" /> | 657 | <span className="h-px flex-1 bg-border" /> |
| 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"> | 658 | <span className="rounded-full border border-border bg-background px-2.5 py-1 text-[10px] uppercase tracking-[0.14em] text-muted-foreground/80"> |
| 659 | {completionSummary | 659 | {completionSummary |
| 660 | ? `Response • ${completionSummary}` | 660 | ? `Response • ${completionSummary}` |
| 661 | : "Response"} | 661 | : "Response"} |
| 662 | </span> | 662 | </span> |
| 663 | <span className="h-px flex-1 bg-white/[0.1]" /> | 663 | <span className="h-px flex-1 bg-border" /> |
| 664 | </div> | 664 | </div> |
| 665 | )} | 665 | )} |
| 666 | {timelineEntry.kind === "work" ? ( | 666 | {timelineEntry.kind === "work" ? ( |
| 667 | <div className="flex items-start gap-2 py-0.5 pl-1.5"> | 667 | <div className="flex items-start gap-2 py-0.5 pl-1.5"> |
| 668 | <span className="mt-[7px] h-1.5 w-1.5 shrink-0 rounded-full bg-white/[0.18]" /> | 668 | <span className="mt-[7px] h-1.5 w-1.5 shrink-0 rounded-full bg-muted-foreground/30" /> |
| 669 | <p | 669 | <p |
| 670 | className={`py-[2px] text-[11px] leading-relaxed ${workToneClass(timelineEntry.entry.tone)}`} | 670 | className={`py-[2px] text-[11px] leading-relaxed ${workToneClass(timelineEntry.entry.tone)}`} |
| 671 | > | 671 | > |
| ... | @@ -686,11 +686,11 @@ export default function ChatView() { | ... | @@ -686,11 +686,11 @@ export default function ChatView() { |
| 686 | </div> | 686 | </div> |
| 687 | ) : timelineEntry.message.role === "user" ? ( | 687 | ) : timelineEntry.message.role === "user" ? ( |
| 688 | <div className="flex justify-end"> | 688 | <div className="flex justify-end"> |
| 689 | <div className="max-w-[80%] rounded-2xl rounded-br-sm border border-white/[0.08] bg-white/[0.05] px-4 py-3"> | 689 | <div className="max-w-[80%] rounded-2xl rounded-br-sm border border-border bg-secondary px-4 py-3"> |
| 690 | <pre className="whitespace-pre-wrap break-words font-mono text-sm leading-relaxed text-[#e0e0e0]"> | 690 | <pre className="whitespace-pre-wrap break-words font-mono text-sm leading-relaxed text-foreground"> |
| 691 | {timelineEntry.message.text} | 691 | {timelineEntry.message.text} |
| 692 | </pre> | 692 | </pre> |
| 693 | <p className="mt-1.5 text-right text-[10px] text-[#a0a0a0]/30"> | 693 | <p className="mt-1.5 text-right text-[10px] text-muted-foreground/30"> |
| 694 | {formatTimestamp(timelineEntry.message.createdAt)} | 694 | {formatTimestamp(timelineEntry.message.createdAt)} |
| 695 | </p> | 695 | </p> |
| 696 | </div> | 696 | </div> |
| ... | @@ -717,7 +717,7 @@ export default function ChatView() { | ... | @@ -717,7 +717,7 @@ export default function ChatView() { |
| 717 | </span> | 717 | </span> |
| 718 | </div> | 718 | </div> |
| 719 | )} | 719 | )} |
| 720 | <p className="mt-1.5 text-[10px] text-[#a0a0a0]/30"> | 720 | <p className="mt-1.5 text-[10px] text-muted-foreground/30"> |
| 721 | {formatMessageMeta( | 721 | {formatMessageMeta( |
| 722 | timelineEntry.message.createdAt, | 722 | timelineEntry.message.createdAt, |
| 723 | timelineEntry.message.streaming | 723 | timelineEntry.message.streaming |
| ... | @@ -739,12 +739,12 @@ export default function ChatView() { | ... | @@ -739,12 +739,12 @@ export default function ChatView() { |
| 739 | ))} | 739 | ))} |
| 740 | {isWorking && ( | 740 | {isWorking && ( |
| 741 | <div className="flex items-center gap-2 py-0.5 pl-1.5"> | 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]" /> | 742 | <span className="h-1.5 w-1.5 shrink-0 rounded-full bg-muted-foreground/30" /> |
| 743 | <div className="flex items-center pt-1"> | 743 | <div className="flex items-center pt-1"> |
| 744 | <span className="inline-flex items-center gap-[3px]"> | 744 | <span className="inline-flex items-center gap-[3px]"> |
| 745 | <span className="h-1 w-1 rounded-full bg-white/20 animate-pulse" /> | 745 | <span className="h-1 w-1 rounded-full bg-muted-foreground/30 animate-pulse" /> |
| 746 | <span className="h-1 w-1 rounded-full bg-white/20 animate-pulse [animation-delay:200ms]" /> | 746 | <span className="h-1 w-1 rounded-full bg-muted-foreground/30 animate-pulse [animation-delay:200ms]" /> |
| 747 | <span className="h-1 w-1 rounded-full bg-white/20 animate-pulse [animation-delay:400ms]" /> | 747 | <span className="h-1 w-1 rounded-full bg-muted-foreground/30 animate-pulse [animation-delay:400ms]" /> |
| 748 | </span> | 748 | </span> |
| 749 | </div> | 749 | </div> |
| 750 | </div> | 750 | </div> |
| ... | @@ -757,12 +757,12 @@ export default function ChatView() { | ... | @@ -757,12 +757,12 @@ export default function ChatView() { |
| 757 | {/* Input bar */} | 757 | {/* Input bar */} |
| 758 | <div className="px-5 pb-4 pt-2"> | 758 | <div className="px-5 pb-4 pt-2"> |
| 759 | <form onSubmit={onSend} className="mx-auto max-w-3xl"> | 759 | <form onSubmit={onSend} className="mx-auto max-w-3xl"> |
| 760 | <div className="group rounded-[20px] border border-white/8 bg-[#141416] transition-colors duration-200 focus-within:border-white/16"> | 760 | <div className="group rounded-[20px] border border-border bg-card transition-colors duration-200 focus-within:border-ring"> |
| 761 | {/* Textarea area */} | 761 | {/* Textarea area */} |
| 762 | <div className="px-4 pt-4 pb-2"> | 762 | <div className="px-4 pt-4 pb-2"> |
| 763 | <textarea | 763 | <textarea |
| 764 | ref={textareaRef} | 764 | ref={textareaRef} |
| 765 | className="w-full resize-none bg-transparent text-[14px] leading-relaxed text-[#e0e0e0] placeholder:text-[#a0a0a0]/35 focus:outline-none" | 765 | className="w-full resize-none bg-transparent text-[14px] leading-relaxed text-foreground placeholder:text-muted-foreground/35 focus:outline-none" |
| 766 | rows={2} | 766 | rows={2} |
| 767 | value={prompt} | 767 | value={prompt} |
| 768 | onChange={(e) => setPrompt(e.target.value)} | 768 | onChange={(e) => setPrompt(e.target.value)} |
| ... | @@ -781,7 +781,7 @@ export default function ChatView() { | ... | @@ -781,7 +781,7 @@ export default function ChatView() { |
| 781 | <div className="relative" ref={modelMenuRef}> | 781 | <div className="relative" ref={modelMenuRef}> |
| 782 | <button | 782 | <button |
| 783 | type="button" | 783 | type="button" |
| 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]" | 784 | className="inline-flex items-center gap-1.5 rounded-lg px-2.5 py-1.5 text-[13px] text-muted-foreground/70 transition-colors duration-150 hover:bg-accent hover:text-foreground/80" |
| 785 | onClick={() => setIsModelMenuOpen((open) => !open)} | 785 | onClick={() => setIsModelMenuOpen((open) => !open)} |
| 786 | > | 786 | > |
| 787 | <span className="max-w-[180px] truncate">{selectedModel}</span> | 787 | <span className="max-w-[180px] truncate">{selectedModel}</span> |
| ... | @@ -803,8 +803,8 @@ export default function ChatView() { | ... | @@ -803,8 +803,8 @@ export default function ChatView() { |
| 803 | </svg> | 803 | </svg> |
| 804 | </button> | 804 | </button> |
| 805 | {isModelMenuOpen && ( | 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"> | 806 | <div className="absolute bottom-full left-0 z-20 mb-2 w-[320px] rounded-2xl border border-border bg-popover/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> | 807 | <p className="px-2 py-1 text-[11px] text-muted-foreground/70">Select model</p> |
| 808 | <div className="max-h-72 overflow-y-auto"> | 808 | <div className="max-h-72 overflow-y-auto"> |
| 809 | {modelOptions.map((model) => { | 809 | {modelOptions.map((model) => { |
| 810 | const isSelected = model === selectedModel; | 810 | const isSelected = model === selectedModel; |
| ... | @@ -814,15 +814,15 @@ export default function ChatView() { | ... | @@ -814,15 +814,15 @@ export default function ChatView() { |
| 814 | type="button" | 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 ${ | 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 | 816 | isSelected |
| 817 | ? "bg-white/8 text-white" | 817 | ? "bg-accent text-foreground" |
| 818 | : "text-[#d4d4d4] hover:bg-white/5" | 818 | : "text-foreground/90 hover:bg-accent" |
| 819 | }`} | 819 | }`} |
| 820 | onClick={() => onModelSelect(model)} | 820 | onClick={() => onModelSelect(model)} |
| 821 | > | 821 | > |
| 822 | <span className="truncate">{model}</span> | 822 | <span className="truncate">{model}</span> |
| 823 | <span | 823 | <span |
| 824 | className={`pt-0.5 text-sm ${ | 824 | className={`pt-0.5 text-sm ${ |
| 825 | isSelected ? "text-white" : "text-transparent" | 825 | isSelected ? "text-foreground" : "text-transparent" |
| 826 | }`} | 826 | }`} |
| 827 | > | 827 | > |
| 828 | ✓ | 828 | ✓ |
| ... | @@ -836,11 +836,11 @@ export default function ChatView() { | ... | @@ -836,11 +836,11 @@ export default function ChatView() { |
| 836 | </div> | 836 | </div> |
| 837 | 837 | ||
| 838 | {/* Divider */} | 838 | {/* Divider */} |
| 839 | <div className="mx-0.5 h-4 w-px bg-white/8" /> | 839 | <div className="mx-0.5 h-4 w-px bg-border" /> |
| 840 | 840 | ||
| 841 | {/* Reasoning effort */} | 841 | {/* Reasoning effort */} |
| 842 | <label | 842 | <label |
| 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]" | 843 | className="inline-flex cursor-pointer items-center gap-1.5 rounded-lg px-2.5 py-1.5 text-[13px] text-muted-foreground/70 transition-colors duration-150 hover:bg-accent hover:text-foreground/80" |
| 844 | htmlFor="reasoning-effort" | 844 | htmlFor="reasoning-effort" |
| 845 | > | 845 | > |
| 846 | <span>{selectedEffort.charAt(0).toUpperCase() + selectedEffort.slice(1)}</span> | 846 | <span>{selectedEffort.charAt(0).toUpperCase() + selectedEffort.slice(1)}</span> |
| ... | @@ -851,7 +851,7 @@ export default function ChatView() { | ... | @@ -851,7 +851,7 @@ export default function ChatView() { |
| 851 | onChange={(event) => setSelectedEffort(event.target.value)} | 851 | onChange={(event) => setSelectedEffort(event.target.value)} |
| 852 | > | 852 | > |
| 853 | {REASONING_OPTIONS.map((effort) => ( | 853 | {REASONING_OPTIONS.map((effort) => ( |
| 854 | <option key={effort} value={effort} className="bg-[#1b1b1d]"> | 854 | <option key={effort} value={effort} className="bg-popover"> |
| 855 | {effort} | 855 | {effort} |
| 856 | {effort === DEFAULT_REASONING ? " (default)" : ""} | 856 | {effort === DEFAULT_REASONING ? " (default)" : ""} |
| 857 | </option> | 857 | </option> |
| ... | @@ -876,12 +876,12 @@ export default function ChatView() { | ... | @@ -876,12 +876,12 @@ export default function ChatView() { |
| 876 | </label> | 876 | </label> |
| 877 | 877 | ||
| 878 | {/* Divider */} | 878 | {/* Divider */} |
| 879 | <div className="mx-0.5 h-4 w-px bg-white/[0.08]" /> | 879 | <div className="mx-0.5 h-4 w-px bg-border" /> |
| 880 | 880 | ||
| 881 | {/* Runtime mode toggle */} | 881 | {/* Runtime mode toggle */} |
| 882 | <button | 882 | <button |
| 883 | type="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]" | 884 | className="inline-flex items-center gap-1.5 rounded-lg px-2.5 py-1.5 text-[13px] text-muted-foreground/70 transition-colors duration-150 hover:bg-accent hover:text-foreground/80" |
| 885 | disabled={isSwitchingRuntimeMode} | 885 | disabled={isSwitchingRuntimeMode} |
| 886 | onClick={() => | 886 | onClick={() => |
| 887 | void handleRuntimeModeChange( | 887 | void handleRuntimeModeChange( |
| ... | @@ -975,7 +975,7 @@ export default function ChatView() { | ... | @@ -975,7 +975,7 @@ export default function ChatView() { |
| 975 | ) : ( | 975 | ) : ( |
| 976 | <button | 976 | <button |
| 977 | type="submit" | 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" | 978 | className="flex h-8 w-8 items-center justify-center rounded-full bg-primary/90 text-primary-foreground transition-all duration-150 hover:bg-primary hover:scale-105 disabled:opacity-30 disabled:hover:scale-100" |
| 979 | disabled={isSending || isConnecting || !prompt.trim()} | 979 | disabled={isSending || isConnecting || !prompt.trim()} |
| 980 | aria-label={ | 980 | aria-label={ |
| 981 | isConnecting ? "Connecting" : isSending ? "Sending" : "Send message" | 981 | isConnecting ? "Connecting" : isSending ? "Sending" : "Send message" |
apps/renderer/src/components/DiffPanel.tsx+9-9| ... | @@ -4,13 +4,13 @@ export default function DiffPanel() { | ... | @@ -4,13 +4,13 @@ export default function DiffPanel() { |
| 4 | const { dispatch } = useStore(); | 4 | const { dispatch } = useStore(); |
| 5 | 5 | ||
| 6 | return ( | 6 | return ( |
| 7 | <aside className="flex h-full w-[400px] shrink-0 flex-col border-l border-white/[0.08] bg-[#141414]"> | 7 | <aside className="flex h-full w-[400px] shrink-0 flex-col border-l border-border bg-card"> |
| 8 | {/* Header */} | 8 | {/* Header */} |
| 9 | <div className="drag-region flex items-center justify-between border-b border-white/[0.08] px-4 pt-[28px] pb-3"> | 9 | <div className="drag-region flex items-center justify-between border-b border-border px-4 pt-[28px] pb-3"> |
| 10 | <h3 className="text-xs font-medium text-[#e0e0e0]">Uncommitted changes</h3> | 10 | <h3 className="text-xs font-medium text-foreground">Uncommitted changes</h3> |
| 11 | <button | 11 | <button |
| 12 | type="button" | 12 | type="button" |
| 13 | className="text-[#a0a0a0]/40 transition-colors duration-150 hover:text-[#a0a0a0]/70" | 13 | className="text-muted-foreground/60 transition-colors duration-150 hover:text-muted-foreground" |
| 14 | onClick={() => dispatch({ type: "TOGGLE_DIFF" })} | 14 | onClick={() => dispatch({ type: "TOGGLE_DIFF" })} |
| 15 | > | 15 | > |
| 16 | <span className="text-sm">&times;</span> | 16 | <span className="text-sm">&times;</span> |
| ... | @@ -20,12 +20,12 @@ export default function DiffPanel() { | ... | @@ -20,12 +20,12 @@ export default function DiffPanel() { |
| 20 | {/* Placeholder content */} | 20 | {/* Placeholder content */} |
| 21 | <div className="flex-1 overflow-y-auto p-4"> | 21 | <div className="flex-1 overflow-y-auto p-4"> |
| 22 | <div className="space-y-3"> | 22 | <div className="space-y-3"> |
| 23 | <div className="rounded-lg border border-white/[0.06] bg-white/[0.02] p-3"> | 23 | <div className="rounded-lg border border-border/80 bg-muted p-3"> |
| 24 | <div className="mb-2 flex items-center gap-2"> | 24 | <div className="mb-2 flex items-center gap-2"> |
| 25 | <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"> |
| 26 | M | 26 | M |
| 27 | </span> | 27 | </span> |
| 28 | <span className="font-mono text-xs text-[#a0a0a0]/60">src/App.tsx</span> | 28 | <span className="font-mono text-xs text-muted-foreground/80">src/App.tsx</span> |
| 29 | </div> | 29 | </div> |
| 30 | <div className="space-y-0.5 font-mono text-[11px]"> | 30 | <div className="space-y-0.5 font-mono text-[11px]"> |
| 31 | <div className="text-red-400/60">- const oldValue = "hello";</div> | 31 | <div className="text-red-400/60">- const oldValue = "hello";</div> |
| ... | @@ -33,17 +33,17 @@ export default function DiffPanel() { | ... | @@ -33,17 +33,17 @@ export default function DiffPanel() { |
| 33 | </div> | 33 | </div> |
| 34 | </div> | 34 | </div> |
| 35 | 35 | ||
| 36 | <div className="rounded-lg border border-white/[0.06] bg-white/[0.02] p-3"> | 36 | <div className="rounded-lg border border-border/80 bg-muted p-3"> |
| 37 | <div className="flex items-center gap-2"> | 37 | <div className="flex items-center gap-2"> |
| 38 | <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"> |
| 39 | A | 39 | A |
| 40 | </span> | 40 | </span> |
| 41 | <span className="font-mono text-xs text-[#a0a0a0]/60">src/utils.ts</span> | 41 | <span className="font-mono text-xs text-muted-foreground/80">src/utils.ts</span> |
| 42 | </div> | 42 | </div> |
| 43 | </div> | 43 | </div> |
| 44 | </div> | 44 | </div> |
| 45 | 45 | ||
| 46 | <p className="mt-6 text-center text-[10px] text-[#a0a0a0]/25"> | 46 | <p className="mt-6 text-center text-[10px] text-muted-foreground/30"> |
| 47 | Diff integration coming soon | 47 | Diff integration coming soon |
| 48 | </p> | 48 | </p> |
| 49 | </div> | 49 | </div> |
apps/renderer/src/components/Sidebar.tsx+46-24| ... | @@ -1,9 +1,13 @@ | ... | @@ -1,9 +1,13 @@ |
| 1 | import { MonitorIcon, MoonIcon, SunIcon } from "lucide-react"; | ||
| 1 | import { useCallback, useEffect, useMemo, useState } from "react"; | 2 | import { useCallback, useEffect, useMemo, useState } from "react"; |
| 3 | import { useTheme } from "../hooks/useTheme"; | ||
| 2 | import { DEFAULT_MODEL } from "../model-logic"; | 4 | import { DEFAULT_MODEL } from "../model-logic"; |
| 3 | import { readNativeApi } from "../session-logic"; | 5 | import { readNativeApi } from "../session-logic"; |
| 4 | import { useStore } from "../store"; | 6 | import { useStore } from "../store"; |
| 5 | import type { Project } from "../types"; | 7 | import type { Project } from "../types"; |
| 6 | 8 | ||
| 9 | const THEME_CYCLE = { system: "light", light: "dark", dark: "system" } as const; | ||
| 10 | |||
| 7 | 11 | ||
| 8 | function formatRelativeTime(iso: string): string { | 12 | function formatRelativeTime(iso: string): string { |
| 9 | const diff = Date.now() - new Date(iso).getTime(); | 13 | const diff = Date.now() - new Date(iso).getTime(); |
| ... | @@ -26,6 +30,7 @@ function threadStatusLabel( | ... | @@ -26,6 +30,7 @@ function threadStatusLabel( |
| 26 | export default function Sidebar() { | 30 | export default function Sidebar() { |
| 27 | const { state, dispatch } = useStore(); | 31 | const { state, dispatch } = useStore(); |
| 28 | const api = useMemo(() => readNativeApi(), []); | 32 | const api = useMemo(() => readNativeApi(), []); |
| 33 | const { theme, setTheme } = useTheme(); | ||
| 29 | const [addingProject, setAddingProject] = useState(false); | 34 | const [addingProject, setAddingProject] = useState(false); |
| 30 | const [newCwd, setNewCwd] = useState(""); | 35 | const [newCwd, setNewCwd] = useState(""); |
| 31 | const [isPickingFolder, setIsPickingFolder] = useState(false); | 36 | const [isPickingFolder, setIsPickingFolder] = useState(false); |
| ... | @@ -120,22 +125,39 @@ export default function Sidebar() { | ... | @@ -120,22 +125,39 @@ export default function Sidebar() { |
| 120 | }, [handleNewThread, state.activeThreadId, state.projects, state.threads]); | 125 | }, [handleNewThread, state.activeThreadId, state.projects, state.threads]); |
| 121 | 126 | ||
| 122 | return ( | 127 | return ( |
| 123 | <aside className="sidebar flex h-full w-[260px] shrink-0 flex-col border-r border-white/8 bg-[#141414]"> | 128 | <aside className="sidebar flex h-full w-[260px] shrink-0 flex-col border-r border-border bg-card"> |
| 124 | {/* Drag region / traffic light space */} | 129 | {/* Drag region / traffic light space */} |
| 125 | <div className="drag-region h-[52px] shrink-0" /> | 130 | <div className="drag-region h-[52px] shrink-0" /> |
| 126 | {/* Branding */} | 131 | {/* Branding */} |
| 127 | <div className="flex items-center gap-2 px-4 pb-4"> | 132 | <div className="flex items-center gap-2 px-4 pb-4"> |
| 128 | <div className="flex h-7 w-7 items-center justify-center rounded-lg bg-white text-xs font-bold text-[#0c0c0c]"> | 133 | <div className="flex h-7 w-7 items-center justify-center rounded-lg bg-primary text-xs font-bold text-primary-foreground"> |
| 129 | CT | 134 | CT |
| 130 | </div> | 135 | </div> |
| 131 | <span className="text-sm font-semibold tracking-tight text-[#e0e0e0]">CodeThing</span> | 136 | <span className="flex-1 text-sm font-semibold tracking-tight text-foreground"> |
| 137 | CodeThing | ||
| 138 | </span> | ||
| 139 | <button | ||
| 140 | type="button" | ||
| 141 | className="rounded-md p-1.5 text-muted-foreground/80 transition-colors duration-150 hover:bg-accent hover:text-muted-foreground" | ||
| 142 | onClick={() => setTheme(THEME_CYCLE[theme])} | ||
| 143 | aria-label={`Theme: ${theme}`} | ||
| 144 | title={`Theme: ${theme}`} | ||
| 145 | > | ||
| 146 | {theme === "system" ? ( | ||
| 147 | <MonitorIcon className="size-3.5" /> | ||
| 148 | ) : theme === "light" ? ( | ||
| 149 | <SunIcon className="size-3.5" /> | ||
| 150 | ) : ( | ||
| 151 | <MoonIcon className="size-3.5" /> | ||
| 152 | )} | ||
| 153 | </button> | ||
| 132 | </div> | 154 | </div> |
| 133 | 155 | ||
| 134 | {/* New thread (global) */} | 156 | {/* New thread (global) */} |
| 135 | <div className="px-3 pb-3"> | 157 | <div className="px-3 pb-3"> |
| 136 | <button | 158 | <button |
| 137 | type="button" | 159 | type="button" |
| 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]" | 160 | 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" |
| 139 | onClick={() => { | 161 | onClick={() => { |
| 140 | if (state.projects.length === 0) { | 162 | if (state.projects.length === 0) { |
| 141 | setAddingProject(true); | 163 | setAddingProject(true); |
| ... | @@ -145,7 +167,7 @@ export default function Sidebar() { | ... | @@ -145,7 +167,7 @@ export default function Sidebar() { |
| 145 | if (firstProject) handleNewThread(firstProject.id); | 167 | if (firstProject) handleNewThread(firstProject.id); |
| 146 | }} | 168 | }} |
| 147 | > | 169 | > |
| 148 | <span className="text-white">+</span> | 170 | <span className="text-foreground">+</span> |
| 149 | New thread | 171 | New thread |
| 150 | </button> | 172 | </button> |
| 151 | </div> | 173 | </div> |
| ... | @@ -167,21 +189,21 @@ export default function Sidebar() { | ... | @@ -167,21 +189,21 @@ export default function Sidebar() { |
| 167 | {/* Project header */} | 189 | {/* Project header */} |
| 168 | <button | 190 | <button |
| 169 | type="button" | 191 | type="button" |
| 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" | 192 | className="flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-left transition-colors duration-150 hover:bg-accent" |
| 171 | onClick={() => dispatch({ type: "TOGGLE_PROJECT", projectId: project.id })} | 193 | onClick={() => dispatch({ type: "TOGGLE_PROJECT", projectId: project.id })} |
| 172 | > | 194 | > |
| 173 | <span className="text-[10px] text-[#a0a0a0]/50"> | 195 | <span className="text-[10px] text-muted-foreground/70"> |
| 174 | {project.expanded ? "â–¼" : "â–¶"} | 196 | {project.expanded ? "â–¼" : "â–¶"} |
| 175 | </span> | 197 | </span> |
| 176 | <span className="flex-1 truncate text-xs font-medium text-[#e0e0e0]/90"> | 198 | <span className="flex-1 truncate text-xs font-medium text-foreground/90"> |
| 177 | {project.name} | 199 | {project.name} |
| 178 | </span> | 200 | </span> |
| 179 | <span className="text-[10px] text-[#a0a0a0]/40">{threads.length}</span> | 201 | <span className="text-[10px] text-muted-foreground/60">{threads.length}</span> |
| 180 | </button> | 202 | </button> |
| 181 | 203 | ||
| 182 | {/* Threads */} | 204 | {/* Threads */} |
| 183 | {project.expanded && ( | 205 | {project.expanded && ( |
| 184 | <div className="ml-2 border-l border-white/6 pl-2"> | 206 | <div className="ml-2 border-l border-border/80 pl-2"> |
| 185 | {threads.map((thread) => { | 207 | {threads.map((thread) => { |
| 186 | const isActive = state.activeThreadId === thread.id; | 208 | const isActive = state.activeThreadId === thread.id; |
| 187 | const threadStatus = threadStatusLabel( | 209 | const threadStatus = threadStatusLabel( |
| ... | @@ -192,7 +214,7 @@ export default function Sidebar() { | ... | @@ -192,7 +214,7 @@ export default function Sidebar() { |
| 192 | key={thread.id} | 214 | key={thread.id} |
| 193 | type="button" | 215 | type="button" |
| 194 | className={`flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-left transition-colors duration-150 ${ | 216 | className={`flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-left transition-colors duration-150 ${ |
| 195 | isActive ? "bg-white/10 text-white" : "text-[#a0a0a0]/70 hover:bg-white/4" | 217 | isActive ? "bg-accent text-foreground" : "text-muted-foreground hover:bg-secondary" |
| 196 | }`} | 218 | }`} |
| 197 | onClick={() => | 219 | onClick={() => |
| 198 | dispatch({ | 220 | dispatch({ |
| ... | @@ -203,8 +225,8 @@ export default function Sidebar() { | ... | @@ -203,8 +225,8 @@ export default function Sidebar() { |
| 203 | > | 225 | > |
| 204 | <div className="flex min-w-0 flex-1 items-center gap-1.5"> | 226 | <div className="flex min-w-0 flex-1 items-center gap-1.5"> |
| 205 | {threadStatus && ( | 227 | {threadStatus && ( |
| 206 | <span className="inline-flex items-center gap-1 text-[10px] text-[#7ed8ff]/80"> | 228 | <span className="inline-flex items-center gap-1 text-[10px] text-sky-600 dark:text-sky-300/80"> |
| 207 | <span className="h-1.5 w-1.5 animate-pulse rounded-full bg-sky-300/80" /> | 229 | <span className="h-1.5 w-1.5 animate-pulse rounded-full bg-sky-500 dark:bg-sky-300/80" /> |
| 208 | <span className="hidden xl:inline"> | 230 | <span className="hidden xl:inline"> |
| 209 | {threadStatus} | 231 | {threadStatus} |
| 210 | </span> | 232 | </span> |
| ... | @@ -214,7 +236,7 @@ export default function Sidebar() { | ... | @@ -214,7 +236,7 @@ export default function Sidebar() { |
| 214 | {thread.title} | 236 | {thread.title} |
| 215 | </span> | 237 | </span> |
| 216 | </div> | 238 | </div> |
| 217 | <span className="shrink-0 text-[10px] text-[#a0a0a0]/30"> | 239 | <span className="shrink-0 text-[10px] text-muted-foreground/40"> |
| 218 | {formatRelativeTime(thread.createdAt)} | 240 | {formatRelativeTime(thread.createdAt)} |
| 219 | </span> | 241 | </span> |
| 220 | </button> | 242 | </button> |
| ... | @@ -224,7 +246,7 @@ export default function Sidebar() { | ... | @@ -224,7 +246,7 @@ export default function Sidebar() { |
| 224 | {/* New thread within project */} | 246 | {/* New thread within project */} |
| 225 | <button | 247 | <button |
| 226 | type="button" | 248 | type="button" |
| 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" | 249 | 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" |
| 228 | onClick={() => handleNewThread(project.id)} | 250 | onClick={() => handleNewThread(project.id)} |
| 229 | > | 251 | > |
| 230 | <span>+</span> New thread | 252 | <span>+</span> New thread |
| ... | @@ -236,7 +258,7 @@ export default function Sidebar() { | ... | @@ -236,7 +258,7 @@ export default function Sidebar() { |
| 236 | })} | 258 | })} |
| 237 | 259 | ||
| 238 | {state.projects.length === 0 && !addingProject && ( | 260 | {state.projects.length === 0 && !addingProject && ( |
| 239 | <div className="px-2 pt-4 text-center text-xs text-[#a0a0a0]/40"> | 261 | <div className="px-2 pt-4 text-center text-xs text-muted-foreground/60"> |
| 240 | No projects yet. | 262 | No projects yet. |
| 241 | <br /> | 263 | <br /> |
| 242 | Add one to get started. | 264 | Add one to get started. |
| ... | @@ -246,12 +268,12 @@ export default function Sidebar() { | ... | @@ -246,12 +268,12 @@ export default function Sidebar() { |
| 246 | 268 | ||
| 247 | {/* Add project form */} | 269 | {/* Add project form */} |
| 248 | {addingProject ? ( | 270 | {addingProject ? ( |
| 249 | <div className="border-t border-white/8 p-3"> | 271 | <div className="border-t border-border p-3"> |
| 250 | <p className="mb-2 text-[10px] font-medium uppercase tracking-wider text-[#a0a0a0]/50"> | 272 | <p className="mb-2 text-[10px] font-medium uppercase tracking-wider text-muted-foreground/70"> |
| 251 | Add project | 273 | Add project |
| 252 | </p> | 274 | </p> |
| 253 | <input | 275 | <input |
| 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" | 276 | 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" |
| 255 | placeholder="/path/to/project" | 277 | placeholder="/path/to/project" |
| 256 | value={newCwd} | 278 | value={newCwd} |
| 257 | onChange={(e) => setNewCwd(e.target.value)} | 279 | onChange={(e) => setNewCwd(e.target.value)} |
| ... | @@ -263,7 +285,7 @@ export default function Sidebar() { | ... | @@ -263,7 +285,7 @@ export default function Sidebar() { |
| 263 | {api && ( | 285 | {api && ( |
| 264 | <button | 286 | <button |
| 265 | type="button" | 287 | type="button" |
| 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" | 288 | 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" |
| 267 | onClick={() => void handlePickFolder()} | 289 | onClick={() => void handlePickFolder()} |
| 268 | disabled={isPickingFolder} | 290 | disabled={isPickingFolder} |
| 269 | > | 291 | > |
| ... | @@ -273,14 +295,14 @@ export default function Sidebar() { | ... | @@ -273,14 +295,14 @@ export default function Sidebar() { |
| 273 | <div className="flex gap-2"> | 295 | <div className="flex gap-2"> |
| 274 | <button | 296 | <button |
| 275 | type="button" | 297 | type="button" |
| 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" | 298 | 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" |
| 277 | onClick={handleAddProject} | 299 | onClick={handleAddProject} |
| 278 | > | 300 | > |
| 279 | Add | 301 | Add |
| 280 | </button> | 302 | </button> |
| 281 | <button | 303 | <button |
| 282 | type="button" | 304 | type="button" |
| 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" | 305 | 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" |
| 284 | onClick={() => setAddingProject(false)} | 306 | onClick={() => setAddingProject(false)} |
| 285 | > | 307 | > |
| 286 | Cancel | 308 | Cancel |
| ... | @@ -288,10 +310,10 @@ export default function Sidebar() { | ... | @@ -288,10 +310,10 @@ export default function Sidebar() { |
| 288 | </div> | 310 | </div> |
| 289 | </div> | 311 | </div> |
| 290 | ) : ( | 312 | ) : ( |
| 291 | <div className="border-t border-white/8 p-3"> | 313 | <div className="border-t border-border p-3"> |
| 292 | <button | 314 | <button |
| 293 | type="button" | 315 | type="button" |
| 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" | 316 | 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" |
| 295 | onClick={() => setAddingProject(true)} | 317 | onClick={() => setAddingProject(true)} |
| 296 | > | 318 | > |
| 297 | + Add project | 319 | + Add project |
apps/renderer/src/hooks/useTheme.ts created+90| ... | @@ -0,0 +1,90 @@ | ||
| 1 | import { useCallback, useEffect, useSyncExternalStore } from "react"; | ||
| 2 | |||
| 3 | type Theme = "light" | "dark" | "system"; | ||
| 4 | |||
| 5 | const STORAGE_KEY = "codething:theme"; | ||
| 6 | const MEDIA_QUERY = "(prefers-color-scheme: dark)"; | ||
| 7 | |||
| 8 | let listeners: Array<() => void> = []; | ||
| 9 | function emitChange() { | ||
| 10 | for (const listener of listeners) listener(); | ||
| 11 | } | ||
| 12 | |||
| 13 | function getSystemDark(): boolean { | ||
| 14 | return window.matchMedia(MEDIA_QUERY).matches; | ||
| 15 | } | ||
| 16 | |||
| 17 | function getStored(): Theme { | ||
| 18 | const raw = localStorage.getItem(STORAGE_KEY); | ||
| 19 | if (raw === "light" || raw === "dark" || raw === "system") return raw; | ||
| 20 | return "system"; | ||
| 21 | } | ||
| 22 | |||
| 23 | function 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 | ||
| 39 | applyTheme(getStored()); | ||
| 40 | |||
| 41 | function getSnapshot(): Theme { | ||
| 42 | return getStored(); | ||
| 43 | } | ||
| 44 | |||
| 45 | function 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 | |||
| 72 | export 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+197-16| ... | @@ -1,7 +1,178 @@ | ... | @@ -1,7 +1,178 @@ |
| 1 | @import "tailwindcss"; | 1 | @import "tailwindcss"; |
| 2 | 2 | ||
| 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 | |||
| 3 | :root { | 77 | :root { |
| 4 | color-scheme: dark; | 78 | color-scheme: light; |
| 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 | } | ||
| 5 | } | 176 | } |
| 6 | 177 | ||
| 7 | body { | 178 | body { |
| ... | @@ -10,8 +181,6 @@ body { | ... | @@ -10,8 +181,6 @@ body { |
| 10 | margin: 0; | 181 | margin: 0; |
| 11 | padding: 0; | 182 | padding: 0; |
| 12 | min-height: 100vh; | 183 | min-height: 100vh; |
| 13 | color: #e0e0e0; | ||
| 14 | background: #0c0c0c; | ||
| 15 | } | 184 | } |
| 16 | 185 | ||
| 17 | body::after { | 186 | body::after { |
| ... | @@ -55,15 +224,23 @@ input { | ... | @@ -55,15 +224,23 @@ input { |
| 55 | } | 224 | } |
| 56 | 225 | ||
| 57 | ::-webkit-scrollbar-thumb { | 226 | ::-webkit-scrollbar-thumb { |
| 58 | background: rgba(255, 255, 255, 0.1); | 227 | background: rgba(0, 0, 0, 0.15); |
| 59 | border-radius: 3px; | 228 | border-radius: 3px; |
| 60 | } | 229 | } |
| 61 | 230 | ||
| 62 | ::-webkit-scrollbar-thumb:hover { | 231 | ::-webkit-scrollbar-thumb:hover { |
| 232 | background: rgba(0, 0, 0, 0.25); | ||
| 233 | } | ||
| 234 | |||
| 235 | .dark ::-webkit-scrollbar-thumb { | ||
| 236 | background: rgba(255, 255, 255, 0.1); | ||
| 237 | } | ||
| 238 | |||
| 239 | .dark ::-webkit-scrollbar-thumb:hover { | ||
| 63 | background: rgba(255, 255, 255, 0.18); | 240 | background: rgba(255, 255, 255, 0.18); |
| 64 | } | 241 | } |
| 65 | 242 | ||
| 66 | /* Reasoning select — clickable label surface */ | 243 | /* Reasoning select -- clickable label surface */ |
| 67 | label:has(> select#reasoning-effort) { | 244 | label:has(> select#reasoning-effort) { |
| 68 | position: relative; | 245 | position: relative; |
| 69 | } | 246 | } |
| ... | @@ -108,34 +285,38 @@ label:has(> select#reasoning-effort) select { | ... | @@ -108,34 +285,38 @@ label:has(> select#reasoning-effort) select { |
| 108 | } | 285 | } |
| 109 | 286 | ||
| 110 | .chat-markdown a { | 287 | .chat-markdown a { |
| 111 | color: #93c5fd; | 288 | color: var(--info-foreground); |
| 112 | text-decoration: underline; | 289 | text-decoration: underline; |
| 113 | text-decoration-color: rgba(147, 197, 253, 0.4); | 290 | text-decoration-color: color-mix( |
| 291 | in srgb, | ||
| 292 | var(--info-foreground) 40%, | ||
| 293 | transparent | ||
| 294 | ); | ||
| 114 | } | 295 | } |
| 115 | 296 | ||
| 116 | .chat-markdown a:hover { | 297 | .chat-markdown a:hover { |
| 117 | color: #bfdbfe; | 298 | opacity: 0.8; |
| 118 | } | 299 | } |
| 119 | 300 | ||
| 120 | .chat-markdown blockquote { | 301 | .chat-markdown blockquote { |
| 121 | border-left: 2px solid rgba(255, 255, 255, 0.2); | 302 | border-left: 2px solid var(--border); |
| 122 | padding-left: 0.8rem; | 303 | padding-left: 0.8rem; |
| 123 | color: rgba(224, 224, 224, 0.75); | 304 | color: var(--muted-foreground); |
| 124 | } | 305 | } |
| 125 | 306 | ||
| 126 | .chat-markdown :not(pre) > code { | 307 | .chat-markdown :not(pre) > code { |
| 127 | border: 1px solid rgba(255, 255, 255, 0.12); | 308 | border: 1px solid var(--border); |
| 128 | border-radius: 0.375rem; | 309 | border-radius: 0.375rem; |
| 129 | background: rgba(255, 255, 255, 0.07); | 310 | background: var(--muted); |
| 130 | padding: 0.1rem 0.35rem; | 311 | padding: 0.1rem 0.35rem; |
| 131 | color: #f4f4f5; | 312 | color: var(--foreground); |
| 132 | } | 313 | } |
| 133 | 314 | ||
| 134 | .chat-markdown pre { | 315 | .chat-markdown pre { |
| 135 | overflow-x: auto; | 316 | overflow-x: auto; |
| 136 | border: 1px solid rgba(255, 255, 255, 0.12); | 317 | border: 1px solid var(--border); |
| 137 | border-radius: 0.75rem; | 318 | border-radius: 0.75rem; |
| 138 | background: rgba(0, 0, 0, 0.3); | 319 | background: var(--muted); |
| 139 | padding: 0.8rem 0.9rem; | 320 | padding: 0.8rem 0.9rem; |
| 140 | } | 321 | } |
| 141 | 322 | ||
| ... | @@ -153,7 +334,7 @@ label:has(> select#reasoning-effort) select { | ... | @@ -153,7 +334,7 @@ label:has(> select#reasoning-effort) select { |
| 153 | 334 | ||
| 154 | .chat-markdown th, | 335 | .chat-markdown th, |
| 155 | .chat-markdown td { | 336 | .chat-markdown td { |
| 156 | border: 1px solid rgba(255, 255, 255, 0.12); | 337 | border: 1px solid var(--border); |
| 157 | padding: 0.35rem 0.45rem; | 338 | padding: 0.35rem 0.45rem; |
| 158 | text-align: left; | 339 | text-align: left; |
| 159 | } | 340 | } |
bun.lock+3| ... | @@ -35,6 +35,7 @@ | ... | @@ -35,6 +35,7 @@ |
| 35 | "dependencies": { | 35 | "dependencies": { |
| 36 | "@acme/contracts": "workspace:*", | 36 | "@acme/contracts": "workspace:*", |
| 37 | "highlight.js": "^11.11.1", | 37 | "highlight.js": "^11.11.1", |
| 38 | "lucide-react": "^0.563.0", | ||
| 38 | "react": "^19.0.0", | 39 | "react": "^19.0.0", |
| 39 | "react-dom": "^19.0.0", | 40 | "react-dom": "^19.0.0", |
| 40 | "react-markdown": "^10.1.0", | 41 | "react-markdown": "^10.1.0", |
| ... | @@ -754,6 +755,8 @@ | ... | @@ -754,6 +755,8 @@ |
| 754 | 755 | ||
| 755 | "lru-cache": ["lru-cache@7.18.3", "", {}, "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA=="], | 756 | "lru-cache": ["lru-cache@7.18.3", "", {}, "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA=="], |
| 756 | 757 | ||
| 758 | "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=="], | ||
| 759 | |||
| 757 | "magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], | 760 | "magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], |
| 758 | 761 | ||
| 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=="], | 762 | "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=="], |