Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@
@tailwind utilities;

body {
@apply bg-zinc-950 text-zinc-100;
@apply bg-zinc-100 text-zinc-900 dark:bg-zinc-950 dark:text-zinc-100;
}
7 changes: 5 additions & 2 deletions src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { useState } from "react";
import { SettingsProvider } from "@/context/settings-context";
import { ThemeProvider } from "@/context/theme-context";
import "./globals.css";

export default function RootLayout({
Expand All @@ -20,10 +21,12 @@ export default function RootLayout({
);

return (
<html lang="en">
<html lang="en" className="dark">
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai bot Mar 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Hard-coding className="dark" on <html> forces an incorrect initial theme for users who previously selected light mode, causing a dark-to-light flash after hydration.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/app/layout.tsx, line 24:

<comment>Hard-coding `className="dark"` on `<html>` forces an incorrect initial theme for users who previously selected light mode, causing a dark-to-light flash after hydration.</comment>

<file context>
@@ -20,10 +21,12 @@ export default function RootLayout({
 
   return (
-    <html lang="en">
+    <html lang="en" className="dark">
       <body className="min-h-screen">
         <QueryClientProvider client={queryClient}>
</file context>
Fix with Cubic

<body className="min-h-screen">
<QueryClientProvider client={queryClient}>
<SettingsProvider>{children}</SettingsProvider>
<ThemeProvider>
<SettingsProvider>{children}</SettingsProvider>
</ThemeProvider>
</QueryClientProvider>
</body>
</html>
Expand Down
14 changes: 7 additions & 7 deletions src/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,10 @@ export default function HomePage() {
<div className="flex flex-col items-center justify-center min-h-screen px-4">
<div className="w-full max-w-2xl mx-auto flex flex-col items-center gap-8">
<div className="text-center space-y-2">
<h1 className="text-3xl font-semibold text-zinc-100">
<h1 className="text-3xl font-semibold text-zinc-900 dark:text-zinc-100">
What should the agent do?
</h1>
<p className="text-sm text-zinc-500">
<p className="text-sm text-zinc-500 dark:text-zinc-500">
It can browse the web, interact with pages, and report back.
</p>
</div>
Expand All @@ -59,16 +59,16 @@ export default function HomePage() {
<ChatInput
onSend={handleSend}
disabled={isCreating}
placeholder="Describe a task"
placeholder="Describe a task\u2026"
footer={<SettingsBar />}
/>
{isCreating && (
<p className="text-sm text-zinc-500 text-center mt-2">
Starting session
<p className="text-sm text-zinc-500 dark:text-zinc-500 text-center mt-2">
Starting session\u2026
</p>
)}
{error && (
<p className="text-sm text-red-400 text-center mt-2 max-w-lg mx-auto break-all">
<p className="text-sm text-red-500 dark:text-red-400 text-center mt-2 max-w-lg mx-auto break-all">
{error}
</p>
)}
Expand All @@ -80,7 +80,7 @@ export default function HomePage() {
key={s}
onClick={() => handleSend(s)}
disabled={isCreating}
className="px-3 py-1.5 text-[13px] text-zinc-400 bg-zinc-900 border border-zinc-800 rounded-full hover:bg-zinc-800 hover:text-zinc-300 transition-colors disabled:opacity-50"
className="px-3 py-1.5 text-[13px] text-zinc-500 dark:text-zinc-400 bg-zinc-200 dark:bg-zinc-900 border border-zinc-300 dark:border-zinc-800 rounded-full hover:bg-zinc-300 dark:hover:bg-zinc-800 hover:text-zinc-700 dark:hover:text-zinc-300 transition-colors disabled:opacity-50"
>
{s}
</button>
Expand Down
10 changes: 8 additions & 2 deletions src/app/session/[id]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { SessionProvider, useSession } from "@/context/session-context";
import { ChatInput } from "@/components/chat-input";
import { ChatMessages } from "@/components/chat-messages";
import { BrowserPanel } from "@/components/browser-panel";
import { ThemeToggle } from "@/components/theme-toggle";

function SessionPage() {
const { session, turns, isBusy, isTerminal, isSending, sendMessage, stopTask } =
Expand All @@ -28,6 +29,11 @@ function SessionPage() {
<div className="flex h-screen w-full overflow-hidden">
{/* Chat column */}
<div className="flex-1 flex flex-col min-w-0">
{/* Header with theme toggle */}
<div className="flex items-center justify-end px-4 pt-3 pb-1">
<ThemeToggle />
</div>

{/* Messages */}
<div
ref={chatRef}
Expand All @@ -47,12 +53,12 @@ function SessionPage() {
placeholder={
isTerminal
? "Session has ended"
: "Send a follow-up"
: "Send a follow-up\u2026"
}
/>
</div>

{/* Browser panel always visible on desktop */}
{/* Browser panel \u2014 always visible on desktop */}
<div className="hidden lg:block w-[55%] shrink-0">
<BrowserPanel
liveUrl={liveUrl}
Expand Down
16 changes: 8 additions & 8 deletions src/components/browser-panel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,31 +29,31 @@ export function BrowserPanel({ liveUrl, turns, isSessionEnded }: BrowserPanelPro
const currentUrl = useMemo(() => extractCurrentUrl(turns), [turns]);

return (
<div className="flex flex-col h-full bg-zinc-950 border-l border-zinc-800">
<div className="flex flex-col h-full bg-zinc-50 dark:bg-zinc-950 border-l border-zinc-300 dark:border-zinc-800">
{/* Header */}
<div className="h-11 border-b border-zinc-800 flex items-center px-4">
<span className="text-[13px] font-medium text-zinc-300">
<div className="h-11 border-b border-zinc-300 dark:border-zinc-800 flex items-center px-4">
<span className="text-[13px] font-medium text-zinc-700 dark:text-zinc-300">
Agent&apos;s Browser
</span>
</div>

{/* URL bar */}
<div className="h-8 border-b border-zinc-800 flex items-center px-4 gap-2">
<span className="text-[11px] text-zinc-500">
{isSessionEnded ? "Session ended" : currentUrl || "Waiting"}
<div className="h-8 border-b border-zinc-300 dark:border-zinc-800 flex items-center px-4 gap-2">
<span className="text-[11px] text-zinc-500 dark:text-zinc-500">
{isSessionEnded ? "Session ended" : currentUrl || "Waiting\u2026"}
</span>
</div>

{/* Browser view */}
<div className="flex-1 bg-zinc-900 relative overflow-hidden">
<div className="flex-1 bg-white dark:bg-zinc-900 relative overflow-hidden">
{liveUrl ? (
<iframe
src={liveUrl}
className="w-full h-full border-0"
allow="clipboard-read; clipboard-write"
/>
) : (
<div className="flex items-center justify-center h-full text-zinc-600 text-sm">
<div className="flex items-center justify-center h-full text-zinc-400 dark:text-zinc-600 text-sm">
{isSessionEnded
? "Session has ended"
: "Browser will appear here when the agent starts browsing"}
Expand Down
8 changes: 4 additions & 4 deletions src/components/chat-input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export function ChatInput({
return (
<div className="w-full px-4 pb-4 pt-2">
<form onSubmit={handleSubmit}>
<div className="flex flex-col rounded-2xl bg-zinc-900 border border-zinc-800 focus-within:border-zinc-600 transition-colors">
<div className="flex flex-col rounded-2xl bg-white dark:bg-zinc-900 border border-zinc-300 dark:border-zinc-800 focus-within:border-zinc-400 dark:focus-within:border-zinc-600 transition-colors shadow-sm dark:shadow-none">
<div className="flex items-end gap-2 p-3">
<textarea
ref={textareaRef}
Expand All @@ -63,7 +63,7 @@ export function ChatInput({
placeholder={placeholder}
disabled={disabled}
rows={1}
className="flex-1 bg-transparent text-zinc-100 placeholder-zinc-500 resize-none outline-none text-[15px] leading-relaxed min-h-[24px] max-h-[200px]"
className="flex-1 bg-transparent text-zinc-900 dark:text-zinc-100 placeholder-zinc-400 dark:placeholder-zinc-500 resize-none outline-none text-[15px] leading-relaxed min-h-[24px] max-h-[200px]"
/>
<div className="flex items-center gap-2 shrink-0">
{isProcessing ? (
Expand All @@ -81,10 +81,10 @@ export function ChatInput({
<button
type="submit"
disabled={!value.trim() || disabled}
className="w-8 h-8 rounded-full bg-white hover:bg-zinc-200 disabled:opacity-30 disabled:hover:bg-white flex items-center justify-center transition-colors"
className="w-8 h-8 rounded-full bg-zinc-900 dark:bg-white hover:bg-zinc-700 dark:hover:bg-zinc-200 disabled:opacity-30 disabled:hover:bg-zinc-900 dark:disabled:hover:bg-white flex items-center justify-center transition-colors"
title="Send"
>
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="black" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="white" className="dark:stroke-black" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
<line x1="12" y1="19" x2="12" y2="5" />
<polyline points="5 12 12 5 19 12" />
</svg>
Expand Down
8 changes: 4 additions & 4 deletions src/components/chat-messages.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export function ChatMessages({ turns, isBusy }: ChatMessagesProps) {
<div key={turn.id} className="space-y-4">
{/* User message */}
<div className="flex justify-end">
<div className="bg-zinc-800 rounded-2xl px-4 py-2.5 max-w-[480px] text-[15px] text-zinc-100 whitespace-pre-wrap">
<div className="bg-zinc-200 dark:bg-zinc-800 rounded-2xl px-4 py-2.5 max-w-[480px] text-[15px] text-zinc-900 dark:text-zinc-100 whitespace-pre-wrap">
{turn.userMessage.content}
</div>
</div>
Expand All @@ -29,14 +29,14 @@ export function ChatMessages({ turns, isBusy }: ChatMessagesProps) {

{/* Final answer */}
{turn.finalContent && (
<div className="text-[15px] text-zinc-200 leading-relaxed">
<div className="text-[15px] text-zinc-800 dark:text-zinc-200 leading-relaxed">
<Markdown>{turn.finalContent}</Markdown>
</div>
)}

{/* Thinking indicator for incomplete turn */}
{!turn.isComplete && isBusy && turn.steps.length === 0 && (
<ThinkingIndicator label="Starting" />
<ThinkingIndicator label="Starting\u2026" />
)}
</div>
))}
Expand All @@ -45,7 +45,7 @@ export function ChatMessages({ turns, isBusy }: ChatMessagesProps) {
{isBusy &&
(turns.length === 0 ||
turns[turns.length - 1]?.isComplete) && (
<ThinkingIndicator label="Thinking" />
<ThinkingIndicator label="Thinking\u2026" />
)}
</div>
);
Expand Down
18 changes: 9 additions & 9 deletions src/components/markdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,47 +25,47 @@ const components: Components = {
href={href}
target="_blank"
rel="noopener noreferrer"
className="text-blue-400 font-medium underline underline-offset-4 hover:text-blue-300"
className="text-blue-600 dark:text-blue-400 font-medium underline underline-offset-4 hover:text-blue-500 dark:hover:text-blue-300"
>
{children}
</a>
),
blockquote: ({ children }) => (
<blockquote className="border-l-2 border-zinc-600 pl-4 italic text-zinc-400">{children}</blockquote>
<blockquote className="border-l-2 border-zinc-300 dark:border-zinc-600 pl-4 italic text-zinc-500 dark:text-zinc-400">{children}</blockquote>
),
ul: ({ children }) => (
<ul className="my-4 ml-6 list-disc [&>li]:mt-1.5">{children}</ul>
),
ol: ({ children }) => (
<ol className="my-4 ml-6 list-decimal [&>li]:mt-1.5">{children}</ol>
),
hr: () => <hr className="my-5 border-zinc-700" />,
hr: () => <hr className="my-5 border-zinc-300 dark:border-zinc-700" />,
table: ({ children }) => (
<table className="my-4 w-full border-separate border-spacing-0 overflow-y-auto">{children}</table>
),
th: ({ children }) => (
<th className="bg-zinc-800 px-3 py-2 text-left text-xs font-bold first:rounded-tl-lg last:rounded-tr-lg">
<th className="bg-zinc-200 dark:bg-zinc-800 px-3 py-2 text-left text-xs font-bold first:rounded-tl-lg last:rounded-tr-lg">
{children}
</th>
),
td: ({ children }) => (
<td className="border-b border-l border-zinc-800 px-3 py-2 text-left last:border-r">{children}</td>
<td className="border-b border-l border-zinc-300 dark:border-zinc-800 px-3 py-2 text-left last:border-r">{children}</td>
),
tr: ({ children }) => (
<tr className="m-0 border-b border-zinc-800 p-0 first:border-t [&:last-child>td:first-child]:rounded-bl-lg [&:last-child>td:last-child]:rounded-br-lg">
<tr className="m-0 border-b border-zinc-300 dark:border-zinc-800 p-0 first:border-t [&:last-child>td:first-child]:rounded-bl-lg [&:last-child>td:last-child]:rounded-br-lg">
{children}
</tr>
),
pre: ({ children }) => (
<pre className="my-3 overflow-x-auto rounded-lg bg-zinc-800/50 p-4 text-sm">{children}</pre>
<pre className="my-3 overflow-x-auto rounded-lg bg-zinc-100 dark:bg-zinc-800/50 p-4 text-sm">{children}</pre>
),
code: ({ className, children }) => {
const isBlock = className?.includes("language-");
if (isBlock) {
return <code className={`font-mono text-sm text-zinc-200 ${className}`}>{children}</code>;
return <code className={`font-mono text-sm text-zinc-800 dark:text-zinc-200 ${className}`}>{children}</code>;
}
return (
<code className="bg-zinc-800 rounded border border-zinc-700 px-1 py-0.5 font-semibold text-sm">
<code className="bg-zinc-200 dark:bg-zinc-800 rounded border border-zinc-300 dark:border-zinc-700 px-1 py-0.5 font-semibold text-sm">
{children}
</code>
);
Expand Down
28 changes: 16 additions & 12 deletions src/components/model-selector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { useState, useRef, useEffect, useCallback, type ReactNode } from "react"
import { Cpu, User, HardDrive, Globe, Check } from "lucide-react";
import { useSettings } from "@/context/settings-context";
import { COUNTRIES, POPULAR_CODES, COUNTRY_BY_CODE } from "@/lib/countries";
import { ThemeToggle } from "@/components/theme-toggle";

const MODELS = [
{ value: "bu-mini", label: "BU Mini" },
Expand Down Expand Up @@ -70,13 +71,13 @@ function IconDropdown({
className={`h-8 w-8 rounded-lg border flex items-center justify-center transition-colors ${
isActive
? `${colors.bg} ${colors.text} border-transparent`
: "bg-transparent hover:bg-zinc-800 text-zinc-500 border-zinc-700"
: "bg-transparent hover:bg-zinc-200 dark:hover:bg-zinc-800 text-zinc-500 border-zinc-300 dark:border-zinc-700"
}`}
>
{icon}
</button>
{open && (
<div className="absolute bottom-full mb-1 right-0 w-[200px] bg-zinc-900 border border-zinc-700 rounded-lg shadow-xl z-50 flex flex-col max-h-[320px] overflow-y-auto">
<div className="absolute bottom-full mb-1 right-0 w-[200px] bg-white dark:bg-zinc-900 border border-zinc-300 dark:border-zinc-700 rounded-lg shadow-xl z-50 flex flex-col max-h-[320px] overflow-y-auto">
{children}
</div>
)}
Expand All @@ -99,13 +100,13 @@ function DropdownRow({
<button
type="button"
onClick={onSelect}
className={`w-full text-left px-3 py-1.5 text-xs hover:bg-zinc-800 flex items-center gap-2 ${
selected ? "text-white font-medium" : "text-zinc-400"
className={`w-full text-left px-3 py-1.5 text-xs hover:bg-zinc-100 dark:hover:bg-zinc-800 flex items-center gap-2 ${
selected ? "text-zinc-900 dark:text-white font-medium" : "text-zinc-500 dark:text-zinc-400"
}`}
>
{icon && <span className="w-4 flex-shrink-0">{icon}</span>}
<span className="flex-1 truncate">{label}</span>
{selected && <Check size={12} className="text-zinc-400 flex-shrink-0" />}
{selected && <Check size={12} className="text-zinc-500 dark:text-zinc-400 flex-shrink-0" />}
</button>
);
}
Expand Down Expand Up @@ -165,13 +166,13 @@ function ProxyDropdown({
onToggle={onToggle}
onClose={onClose}
>
<div className="p-2 border-b border-zinc-800">
<div className="p-2 border-b border-zinc-200 dark:border-zinc-800">
<input
ref={inputRef}
value={search}
onChange={(e) => setSearch(e.target.value)}
placeholder="Search countries"
className="w-full bg-zinc-800 text-zinc-200 text-xs rounded px-2 py-1.5 outline-none placeholder-zinc-500"
placeholder="Search countries\u2026"
className="w-full bg-zinc-100 dark:bg-zinc-800 text-zinc-800 dark:text-zinc-200 text-xs rounded px-2 py-1.5 outline-none placeholder-zinc-400 dark:placeholder-zinc-500"
/>
</div>
<div className="overflow-y-auto flex-1">
Expand All @@ -193,7 +194,7 @@ function ProxyDropdown({
key={c.code}
label={c.name}
icon={
<span className="text-[10px] text-zinc-600 uppercase">
<span className="text-[10px] text-zinc-400 dark:text-zinc-600 uppercase">
{c.code}
</span>
}
Expand All @@ -212,15 +213,15 @@ function ProxyDropdown({
key={c.code}
label={c.name}
icon={
<span className="text-[10px] text-zinc-600 uppercase">
<span className="text-[10px] text-zinc-400 dark:text-zinc-600 uppercase">
{c.code}
</span>
}
selected={proxyCountryCode === c.code}
onSelect={() => selectCountry(c.code)}
/>
))}
<div className="mx-2 my-1 h-px bg-zinc-800" />
<div className="mx-2 my-1 h-px bg-zinc-200 dark:bg-zinc-800" />
<div className="px-3 pt-1 pb-1 text-[10px] text-zinc-500 uppercase tracking-wider">
All countries
</div>
Expand All @@ -229,7 +230,7 @@ function ProxyDropdown({
key={c.code}
label={c.name}
icon={
<span className="text-[10px] text-zinc-600 uppercase">
<span className="text-[10px] text-zinc-400 dark:text-zinc-600 uppercase">
{c.code}
</span>
}
Expand Down Expand Up @@ -272,6 +273,9 @@ export function SettingsBar() {

return (
<div className="flex items-center gap-1">
{/* Theme toggle */}
<ThemeToggle />

{/* Model */}
<IconDropdown
icon={<Cpu size={16} />}
Expand Down
6 changes: 3 additions & 3 deletions src/components/step-section.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,16 +32,16 @@ export function StepSection({ step }: { step: TaskStep }) {
<X className="w-3 h-3 text-red-500" strokeWidth={2.5} />
</div>
) : (
<div className="w-[18px] h-[18px] rounded-full border border-zinc-700" />
<div className="w-[18px] h-[18px] rounded-full border border-zinc-300 dark:border-zinc-700" />
)}
</div>
<span className="text-[14px] text-zinc-200 font-medium">
<span className="text-[14px] text-zinc-800 dark:text-zinc-200 font-medium">
{isError ? "Stopped" : step.title}
</span>
</>
)}
<ChevronUp
className={`w-4 h-4 ml-auto text-zinc-600 group-hover:text-zinc-400 transition-transform ${
className={`w-4 h-4 ml-auto text-zinc-400 dark:text-zinc-600 group-hover:text-zinc-600 dark:group-hover:text-zinc-400 transition-transform ${
expanded ? "rotate-0" : "rotate-180"
}`}
/>
Expand Down
Loading