authorgravatar for me@t3.ggTheo Browne <me@t3.gg> 2026-02-08 00:28:21-08:00
committergravatar for me@t3.ggTheo Browne <me@t3.gg> 2026-02-08 00:28:21-08:00
logf0ac59306842c0392b2e11ac35ebf24763131de4
treef135ce1aa67c5945edd122e596e64c43d3eef563
parentfdb654de45d8bcac6bfee3d79199fb9bdcde844d
signature Commit is signed but in an unrecognized format.

Better input box and react compiler


5 files changed, 102 insertions(+), 52 deletions(-)

TODO.md+1
......@@ -3,6 +3,7 @@
33- [ ] Remove model selection from project creation
44- [ ] First message vanishes when thread is created?? Awful ux
55- [ ] Show more clearly when thinking is happening
6- [ ] Enable react compiler
67
78## Bigger overhauls
89
apps/renderer/package.json+1
......@@ -21,6 +21,7 @@
2121 "@types/react": "^19.0.0",
2222 "@types/react-dom": "^19.0.0",
2323 "@vitejs/plugin-react": "^4.3.4",
24 "babel-plugin-react-compiler": "^19.0.0-beta-e552027-20250112",
2425 "tailwindcss": "^4.0.0",
2526 "typescript": "^5.7.3",
2627 "vite": "^6.0.5"
apps/renderer/src/components/ChatView.tsx+79-51
......@@ -439,38 +439,46 @@ export default function ChatView() {
439439 </div>
440440
441441 {/* Input bar */}
442 <div className="border-t border-white/[0.08] px-5 py-3">
442 <div className="px-5 pb-4 pt-2">
443443 <form
444444 onSubmit={onSend}
445 className="mx-auto flex max-w-3xl items-end gap-2"
445 className="mx-auto max-w-3xl"
446446 >
447 <div className="flex-1 rounded-2xl border border-white/[0.1] bg-[#121214] px-3 py-3">
448 <textarea
449 ref={textareaRef}
450 className="w-full resize-none bg-transparent px-1 pb-1 font-mono text-sm text-[#e0e0e0] placeholder:text-[#a0a0a0]/30 focus:outline-none"
451 rows={1}
452 value={prompt}
453 onChange={(e) => setPrompt(e.target.value)}
454 onKeyDown={onKeyDown}
455 placeholder={
456 phase === "disconnected"
457 ? "Type a message (session auto-connects)..."
458 : "Type a message..."
459 }
460 disabled={isSending || isConnecting}
461 />
462 <div className="mt-2 flex items-center justify-between gap-2">
463 <div className="flex items-center gap-2">
447 <div className="group rounded-[20px] border border-white/[0.08] bg-[#141416] transition-colors duration-200 focus-within:border-white/[0.16]">
448 {/* Textarea area */}
449 <div className="px-4 pt-4 pb-2">
450 <textarea
451 ref={textareaRef}
452 className="w-full resize-none bg-transparent text-[14px] leading-relaxed text-[#e0e0e0] placeholder:text-[#a0a0a0]/35 focus:outline-none"
453 rows={2}
454 value={prompt}
455 onChange={(e) => setPrompt(e.target.value)}
456 onKeyDown={onKeyDown}
457 placeholder={
458 phase === "disconnected"
459 ? "Ask for follow-up changes"
460 : "Ask anything..."
461 }
462 disabled={isSending || isConnecting}
463 />
464 </div>
465
466 {/* Bottom toolbar */}
467 <div className="flex items-center justify-between px-3 pb-3">
468 <div className="flex items-center gap-1">
469 {/* Model picker */}
464470 <div className="relative" ref={modelMenuRef}>
465471 <button
466472 type="button"
467 className="inline-flex items-center gap-2 rounded-full border border-white/[0.1] bg-white/[0.04] px-3 py-1.5 text-xs text-[#d8d8d8] transition-colors duration-150 hover:bg-white/[0.08]"
473 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]"
468474 onClick={() => setIsModelMenuOpen((open) => !open)}
469475 >
470 <span className="max-w-[180px] truncate font-mono">
476 <span className="max-w-[180px] truncate">
471477 {selectedModel}
472478 </span>
473 <span className="text-[10px] text-[#a0a0a0]/70">▼</span>
479 <svg width="10" height="10" viewBox="0 0 10 10" fill="none" className="opacity-50">
480 <path d="M2.5 4L5 6.5L7.5 4" stroke="currentColor" strokeWidth="1.2" strokeLinecap="round" strokeLinejoin="round" />
481 </svg>
474482 </button>
475483 {isModelMenuOpen && (
476484 <div className="absolute bottom-full left-0 z-20 mb-2 w-[320px] rounded-2xl border border-white/[0.1] bg-[#1b1b1d]/95 p-2 shadow-[0_16px_40px_rgba(0,0,0,0.55)] backdrop-blur">
......@@ -506,14 +514,19 @@ export default function ChatView() {
506514 </div>
507515 )}
508516 </div>
517
518 {/* Divider */}
519 <div className="mx-0.5 h-4 w-px bg-white/[0.08]" />
520
521 {/* Reasoning effort */}
509522 <label
510 className="inline-flex items-center gap-2 rounded-full border border-white/[0.1] bg-white/[0.04] px-3 py-1.5 text-xs text-[#d8d8d8]"
523 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/[0.06] hover:text-[#d0d0d0]"
511524 htmlFor="reasoning-effort"
512525 >
513 <span>Reasoning</span>
526 <span>{selectedEffort.charAt(0).toUpperCase() + selectedEffort.slice(1)}</span>
514527 <select
515528 id="reasoning-effort"
516 className="bg-transparent font-mono text-xs text-[#d8d8d8] outline-none"
529 className="absolute opacity-0 w-0 h-0"
517530 value={selectedEffort}
518531 onChange={(event) => setSelectedEffort(event.target.value)}
519532 >
......@@ -524,36 +537,51 @@ export default function ChatView() {
524537 </option>
525538 ))}
526539 </select>
540 <svg width="10" height="10" viewBox="0 0 10 10" fill="none" className="opacity-50">
541 <path d="M2.5 4L5 6.5L7.5 4" stroke="currentColor" strokeWidth="1.2" strokeLinecap="round" strokeLinejoin="round" />
542 </svg>
527543 </label>
528544 </div>
529 {activeProject && (
530 <span className="text-[10px] text-[#a0a0a0]/35">
531 {activeProject.name}
532 </span>
533 )}
545
546 {/* Right side: send / stop button */}
547 <div className="flex items-center gap-2">
548 {activeProject && (
549 <span className="text-[11px] text-[#a0a0a0]/25">
550 {activeProject.name}
551 </span>
552 )}
553 {phase === "running" ? (
554 <button
555 type="button"
556 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"
557 onClick={() => void onInterrupt()}
558 aria-label="Stop generation"
559 >
560 <svg width="12" height="12" viewBox="0 0 12 12" fill="currentColor">
561 <rect x="2" y="2" width="8" height="8" rx="1.5" />
562 </svg>
563 </button>
564 ) : (
565 <button
566 type="submit"
567 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"
568 disabled={isSending || isConnecting || !prompt.trim()}
569 aria-label={isConnecting ? "Connecting" : isSending ? "Sending" : "Send message"}
570 >
571 {isConnecting || isSending ? (
572 <svg width="14" height="14" viewBox="0 0 14 14" fill="none" className="animate-spin">
573 <circle cx="7" cy="7" r="5.5" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeDasharray="20 12" />
574 </svg>
575 ) : (
576 <svg width="14" height="14" viewBox="0 0 14 14" fill="none">
577 <path d="M7 11.5V2.5M7 2.5L3 6.5M7 2.5L11 6.5" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round" />
578 </svg>
579 )}
580 </button>
581 )}
582 </div>
534583 </div>
535584 </div>
536 {phase === "running" ? (
537 <button
538 type="button"
539 className="shrink-0 rounded-xl bg-rose-600/80 px-4 py-3 text-xs font-medium text-white transition-colors duration-150 hover:bg-rose-600"
540 onClick={() => void onInterrupt()}
541 >
542 Stop
543 </button>
544 ) : (
545 <button
546 type="submit"
547 className="shrink-0 rounded-xl bg-white px-4 py-3 text-xs font-medium text-[#0c0c0c] transition-colors duration-150 hover:bg-white/90 disabled:opacity-40"
548 disabled={isSending || isConnecting || !prompt.trim()}
549 >
550 {isConnecting
551 ? "Connecting..."
552 : isSending
553 ? "Sending..."
554 : "Send"}
555 </button>
556 )}
557585 </form>
558586 </div>
559587 </div>
apps/renderer/src/index.css+13
......@@ -63,3 +63,16 @@ input {
6363::-webkit-scrollbar-thumb:hover {
6464 background: rgba(255, 255, 255, 0.18);
6565}
66
67/* Reasoning select — clickable label surface */
68label:has(> select#reasoning-effort) {
69 position: relative;
70}
71label:has(> select#reasoning-effort) select {
72 position: absolute;
73 inset: 0;
74 opacity: 0;
75 cursor: pointer;
76 width: 100%;
77 height: 100%;
78}
apps/renderer/vite.config.ts+8-1
......@@ -3,7 +3,14 @@ import react from "@vitejs/plugin-react";
33import { defineConfig } from "vite";
44
55export default defineConfig({
6 plugins: [react(), tailwindcss()],
6 plugins: [
7 react({
8 babel: {
9 plugins: [["babel-plugin-react-compiler", { target: "19" }]],
10 },
11 }),
12 tailwindcss(),
13 ],
714 server: {
815 port: Number(process.env.ELECTRON_RENDERER_PORT ?? 5173),
916 strictPort: true,