diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 205cf79..6f9f0ae 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -2896,7 +2896,7 @@ Claude Code invokes this command on each update, piping a JSON payload to stdin. | **ws** | Fastest, most lightweight WebSocket library for Node. No Socket.IO overhead needed since we only push JSON messages | | **React 18** | Stable, widely known, strong TypeScript support. No need for Server Components or RSC given this is a client-rendered SPA | | **Vite** | Fast builds, native ESM, excellent dev experience. Proxy config handles the dev server split cleanly | -| **Tailwind CSS** | Utility-first approach keeps styles colocated with markup. No CSS module boilerplate. Custom theme config for the dark UI | +| **Tailwind CSS** | Utility-first approach keeps styles colocated with markup. No CSS module boilerplate. Colors are CSS-variable-backed tokens (`darkMode: "class"`) so the Dark/Light toggle re-themes the app with one class flip rather than per-component `dark:` variants | | **React Router 6** | Standard routing for React SPAs. Layout routes with `` give clean shell composition | | **Lucide React** | Tree-shakeable icon library. Only imports what's used (~20 icons) | | **TypeScript Strict** | Catches null/undefined bugs at compile time. `noUncheckedIndexedAccess` prevents array bounds issues | diff --git a/client/README.md b/client/README.md index 6747623..1734df0 100644 --- a/client/README.md +++ b/client/README.md @@ -54,7 +54,7 @@ The client is a single-page application (SPA) built with modern web technologies - **React 18.3** - Component-based UI with hooks and concurrent features - **TypeScript 5.7** - Full type safety across components, utilities, and API contracts - **Vite 6.1** - Lightning-fast HMR during development, optimized production builds -- **Tailwind CSS 3.4** - Utility-first CSS framework for rapid UI development +- **Tailwind CSS 3.4** - Utility-first CSS framework; colors are CSS-variable-backed tokens (`darkMode: "class"`) so a Dark/Light toggle (next to the language switcher in the sidebar, `useTheme` hook) re-themes the whole app by flipping one class - **React Router 6.28** - Client-side routing with nested layouts - **WebSocket** - Real-time event streaming from server - **Lucide Icons** - Modern, consistent icon set @@ -203,17 +203,18 @@ client/ │ │ │ ├── hooks/ │ │ ├── useWebSocket.ts # Auto-reconnecting WebSocket hook -│ │ └── useNotifications.ts # Browser push notification triggers +│ │ ├── useNotifications.ts # Browser push notification triggers +│ │ └── useTheme.ts # Dark/light mode: toggles the `dark` class, persists to localStorage │ │ │ ├── i18n/ # Internationalization (en / zh / vi / ko) │ ├── App.tsx # Root component + router setup │ ├── main.tsx # Entry point -│ └── index.css # Tailwind + custom utilities +│ └── index.css # Tailwind + CSS-variable color tokens (dark/light) │ ├── public/ # Static assets (sw.js service worker) ├── index.html # HTML template ├── vite.config.ts # Vite + proxy config -├── tailwind.config.js # Custom dark theme +├── tailwind.config.js # Dark/light color tokens (`darkMode: "class"`, CSS-variable-backed) ├── tsconfig.json # Strict TypeScript config └── package.json ``` diff --git a/client/package.json b/client/package.json index 0ecb3ab..62adb1d 100644 --- a/client/package.json +++ b/client/package.json @@ -7,8 +7,8 @@ "dev": "vite", "build": "tsc -b && vite build", "preview": "vite preview", - "test": "vitest run", - "test:watch": "vitest" + "test": "NODE_OPTIONS=--no-experimental-webstorage vitest run", + "test:watch": "NODE_OPTIONS=--no-experimental-webstorage vitest" }, "dependencies": { "@fontsource/inter": "^5.2.8", diff --git a/client/src/components/AgentCard.tsx b/client/src/components/AgentCard.tsx index 19f9378..4c176ef 100644 --- a/client/src/components/AgentCard.tsx +++ b/client/src/components/AgentCard.tsx @@ -194,14 +194,14 @@ export function AgentCard({ agent, session, label, onClick }: AgentCardProps) { {isMain ? : }
-

+

{/* Auto-generated main-agent titles (e.g. "Main Agent - Session 229d93fd" or "Main Agent - work - e3f8e613") swap the placeholder for the real session name when one exists; custom (sub)agent names are left untouched. */} {isMain ? mainAgentDisplayName(agent.name, realSessionName) : agent.name}

- {subtitle &&

{subtitle}

} + {subtitle &&

{subtitle}

}
{/* compact: cards are narrow — inline reason chip would squeeze the @@ -210,10 +210,10 @@ export function AgentCard({ agent, session, label, onClick }: AgentCardProps) { {agent.task && ( -

{agent.task}

+

{agent.task}

)} -
+
{agent.current_tool && ( @@ -243,7 +243,7 @@ export function AgentCard({ agent, session, label, onClick }: AgentCardProps) { {t("ran")} {formatDuration(agent.started_at, agent.ended_at)} - {timeAgo(agent.ended_at)} + {timeAgo(agent.ended_at)} ) : ( diff --git a/client/src/components/AlertsNotifications.tsx b/client/src/components/AlertsNotifications.tsx index f5b04a5..719b474 100644 --- a/client/src/components/AlertsNotifications.tsx +++ b/client/src/components/AlertsNotifications.tsx @@ -403,8 +403,8 @@ export function AlertsNotifications() { onClick={() => setTab(tb.key)} className={`inline-flex items-center gap-2 text-xs font-medium px-3.5 py-2 rounded-lg transition-colors ${ active - ? "bg-surface-4 text-gray-100 shadow-sm" - : "text-gray-500 hover:text-gray-300 hover:bg-surface-3" + ? "bg-surface-4 text-fg-primary shadow-sm" + : "text-fg-muted hover:text-fg-secondary hover:bg-surface-3" }`} > @@ -413,10 +413,10 @@ export function AlertsNotifications() { {tb.badge} @@ -432,8 +432,8 @@ export function AlertsNotifications() {
-

{t("rules.title")}

-

{ts("alertsHub.rulesHint")}

+

{t("rules.title")}

+

{ts("alertsHub.rulesHint")}

- {formError &&

{formError}

} + {formError &&

{formError}

}
)} @@ -680,7 +680,7 @@ export function AlertsNotifications() {
{rule.name} @@ -688,7 +688,7 @@ export function AlertsNotifications() { {t(`ruleTypes.${rule.rule_type}`)}
-

+

{describeRule(rule, t)} ·{" "} {t("rules.cooldown", { seconds: rule.cooldown_seconds })}

@@ -698,8 +698,8 @@ export function AlertsNotifications() { onClick={() => onToggleRule(rule)} className={`text-xs px-2.5 py-1.5 rounded-md border transition-colors ${ rule.enabled - ? "border-emerald-500/30 text-emerald-400 hover:bg-emerald-500/10" - : "border-border text-gray-500 hover:text-gray-300 hover:bg-surface-3" + ? "border-status-success/30 text-status-success hover:bg-status-success/10" + : "border-border text-fg-muted hover:text-fg-secondary hover:bg-surface-3" }`} title={rule.enabled ? t("rules.disable") : t("rules.enable")} > @@ -707,7 +707,7 @@ export function AlertsNotifications() { @@ -222,11 +222,11 @@ export function DateTimePicker({ onClick={() => setViewDate(new Date(viewDate.getFullYear(), viewDate.getMonth() - 1, 1)) } - className="p-1 hover:bg-surface-2 rounded text-gray-400 hover:text-white" + className="p-1 hover:bg-surface-2 rounded text-fg-secondary hover:text-white" > - + {viewDate.toLocaleString(undefined, { month: "long", year: "numeric" })} @@ -244,7 +244,7 @@ export function DateTimePicker({
{["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"].map((day) => ( -
+
{day}
))} @@ -254,7 +254,7 @@ export function DateTimePicker({ {/* Time Picker */}
-
+
Time
@@ -262,7 +262,7 @@ export function DateTimePicker({ type="time" value={timeValue} onChange={handleTimeChange} - className="bg-surface-2 border border-border rounded px-2 py-1 text-xs text-gray-200 focus:outline-none focus:border-accent w-[85px] time-input-custom" + className="bg-surface-2 border border-border rounded px-2 py-1 text-xs text-fg-secondary focus:outline-none focus:border-accent w-[85px] time-input-custom" />
diff --git a/client/src/components/EmptyState.tsx b/client/src/components/EmptyState.tsx index 5b5aac6..063c3c2 100644 --- a/client/src/components/EmptyState.tsx +++ b/client/src/components/EmptyState.tsx @@ -91,10 +91,10 @@ export function EmptyState({ icon: Icon, title, description, action }: EmptyStat return (
- +
-

{title}

-

{description}

+

{title}

+

{description}

{action}
); diff --git a/client/src/components/EventDetail.tsx b/client/src/components/EventDetail.tsx index 4239ad5..a2ec15a 100644 --- a/client/src/components/EventDetail.tsx +++ b/client/src/components/EventDetail.tsx @@ -284,7 +284,7 @@ function SummaryBlock({ return (
- + {t("eventDetail.summary")}
@@ -293,19 +293,19 @@ function SummaryBlock({ - + {summary.headline}
{summary.bullets.length > 0 && ( -