From be2e3d71b230573e7c61a28972f658bf38e485c6 Mon Sep 17 00:00:00 2001 From: oratis Date: Thu, 28 May 2026 21:41:57 +0800 Subject: [PATCH] feat(v0.1.1): P1 design system + 3 main screens redesigned per spec MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Aligns the desktop client to docs/VISUAL_DESIGN.html. Phase 1 covers the three highest-traffic surfaces — Onboarding, Sidebar (Sessions), and the main Chat/REPL — built on a shared design system. The remaining 6 screens ship in 0.1.2 (P2). **Version bumps (everywhere consistent now):** - tauri.conf.json 0.1.0 → 0.1.1 - apps/desktop/package.json 0.1.0 → 0.1.1 - apps/cli/package.json 0.1.0 → 0.1.1 - src-tauri/Cargo.toml 0.1.0 → 0.1.1 - CHANGELOG.md added **Design tokens (apps/desktop/src/index.css):** - DeepSeek brand blue (#4D6BFE) + soft (#E8EDFF) + mint (#14E4A2) - Dark-mode 4-tier surface (--bg-0/1/2/3) + 4-tier text + line/line-soft - --radius-sm/--radius/--radius-lg + spec --shadow - Inter + JetBrains Mono via @import (matches design) - ~700 lines replacing the old hand-rolled utility classes from M6 **Primitives (apps/desktop/src/components/):** - BrandMark — elephant SVG in gradient .mark / .mark-lg container - Badge — ok / warn / err / info (matches design tool-card statuses) - Pill — chat-header status chips with optional leading dot - ToolCard — head (▸ name · target · badge) + body (mono, scrollable, diff-add / diff-del color) - InspectorRail — 48 px right column with 6 rail buttons **3-column shell (App.tsx rewrite):** - Grid: 240 px sidebar | 1fr main | 48 px inspector rail - Onboarding bypasses shell (standalone hero per design screen #2) - Sidebar lists sessions bucketed by Today/Yesterday/Earlier with active state highlight and relative-time meta column - Inspector rail surfaces Plan badge, context fill, files, info, settings (expand panel deferred to P2) **Chat redesign (Repl.tsx rewrite):** - chat-header with crumb + 3 status pills (connected · model · approval) - chat-stream renders msg.user / msg.assistant rows with 28 px avatars (YO for user, DC gradient for assistant) - Tool calls appear inline as ToolCard components with status badge reflecting running / done / error - Inline approval (Approve / Reject / Always allow) sits IMMEDIATELY under the relevant tool card per spec note ③ (not at screen bottom) - composer is the design's .box + .toolbar: + button · mode badge (clickable to cycle default→plan→bypass) · vim chip (when on) · model picker (clickable to toggle chat ↔ reasoner) · effort dropdown · send button. Below: ctx-bar with token usage + cost estimate - Streaming cursor (.streaming-cursor) appears at end of assistant text while turn is active **Onboarding redesign:** - Standalone hero: radial-gradient bg, big brand chip, gradient-text headline matching design screen #2 - Form panel with .input style + helper link to platform.deepseek.com + Continue button with loading spinner **Conversation fix carries forward:** - dangerouslyAllowBrowser: true is bundled in the new Vite output (verified via grep on dist/assets) - Version bump to 0.1.1 lets users definitively verify they installed the new build via the About screen All workspaces typecheck clean. 533+ tests pass. Bundle is 673 KB (vs. 660 KB before — added ~13 KB for the new components + tokens). Co-Authored-By: Claude Opus 4.7 (1M context) --- CHANGELOG.md | 57 + apps/cli/package.json | 2 +- apps/desktop/package.json | 2 +- apps/desktop/src-tauri/Cargo.toml | 2 +- apps/desktop/src-tauri/tauri.conf.json | 2 +- apps/desktop/src/App.tsx | 102 +- apps/desktop/src/components/Badge.tsx | 15 + apps/desktop/src/components/BrandMark.tsx | 23 + apps/desktop/src/components/InspectorRail.tsx | 91 ++ apps/desktop/src/components/Pill.tsx | 19 + apps/desktop/src/components/Sidebar.tsx | 117 ++ apps/desktop/src/components/ToolCard.tsx | 46 + apps/desktop/src/index.css | 1045 ++++++++++++++--- apps/desktop/src/screens/Onboarding.tsx | 107 +- apps/desktop/src/screens/Repl.tsx | 776 ++++++++---- 15 files changed, 1946 insertions(+), 460 deletions(-) create mode 100644 CHANGELOG.md create mode 100644 apps/desktop/src/components/Badge.tsx create mode 100644 apps/desktop/src/components/BrandMark.tsx create mode 100644 apps/desktop/src/components/InspectorRail.tsx create mode 100644 apps/desktop/src/components/Pill.tsx create mode 100644 apps/desktop/src/components/Sidebar.tsx create mode 100644 apps/desktop/src/components/ToolCard.tsx diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..3e2aad7 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,57 @@ +# Changelog + +All notable changes to DeepCode are documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [0.1.1] — 2026-05-28 + +### Visual redesign — phase 1 +Major UI overhaul aligning the desktop client to `docs/VISUAL_DESIGN.html`. +Phase 1 covers the three highest-traffic surfaces: Onboarding, Sessions +sidebar, and the main Chat / REPL view. Other six screens land in 0.1.2. + +- **Design tokens.** DeepSeek brand blue (`#4D6BFE`) + soft (`#E8EDFF`) + + mint accent (`#14E4A2`) + dark-mode neutral palette baked into CSS vars +- **Brand mark.** Elephant SVG logo (matches the design spec's gradient + brand badge) replaces the previous emoji-free placeholder +- **3-column desktop shell.** 240 px sessions sidebar | 1 fr chat main | + 48 px inspector rail (collapsed by default). Inspector rail shows Plan + badge, context-usage dot, recent files, session info, settings. +- **Chat redesign.** Tool calls are now bordered cards with action · + target · status-badge head + tc-body for output/diff. Inline diff + uses `diff-add` / `diff-del` colors. Approval buttons (Approve / + Reject / Always allow) appear immediately under the relevant tool + card — never at screen bottom. +- **Composer redesign.** New rounded box with toolbar (+ menu / mic / + mode badge / model picker / send) and a context-usage bar showing + tokens used + estimated cost. +- **Onboarding redesign.** Hero gradient + big brand mark + gradient + text headline matching the design spec. + +### Conversation flow +- Carries over the `dangerouslyAllowBrowser: true` fix from 0.1.0 so the + OpenAI SDK's browser-environment guard doesn't trip in the Tauri webview +- Surfaces full error stack traces in the chat stream when the agent + loop throws — easier to diagnose API key / network issues from inside + the app + +## [0.1.0] — 2026-05-28 + +### Mac client + CLI baseline +- **CLI:** agent loop, 30+ slash commands, MCP support, plugin system, + sandbox, hooks, modes, skills, sub-agents, output styles, effort + levels, headless `-p` mode +- **Desktop (Tauri):** 9 screens (Onboarding / REPL / Sessions / + Plugins / Skills / Permissions / MCP / Settings / About), real + `runAgent` in renderer, Tauri auto-updater wired to GitHub + Releases, xterm.js terminal, Monaco file panel with Source / Diff + / History +- **M7/M8 polish:** inline approval UI with Always-allow persistence, + `/rewind` 5-op snapshot rollback, `DEEPCODE_EFFORT_LEVEL` env var, + desktop effort selector, Vim-mode wiring in composer, cron daemon + install/uninstall scripts +- **Apple notarization:** signed + notarized + stapled DMG (4.2 MB + Apple Silicon) +- **VS Code extension + LSP server** calling the real `runAgent` diff --git a/apps/cli/package.json b/apps/cli/package.json index 8cf7751..6e8851b 100644 --- a/apps/cli/package.json +++ b/apps/cli/package.json @@ -1,6 +1,6 @@ { "name": "deepcode-cli", - "version": "0.1.0", + "version": "0.1.1", "description": "DeepCode CLI — DeepSeek-powered AI coding agent, parity with Claude Code", "license": "MIT", "type": "module", diff --git a/apps/desktop/package.json b/apps/desktop/package.json index af2b263..0863f6a 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -1,6 +1,6 @@ { "name": "@deepcode/desktop", - "version": "0.1.0", + "version": "0.1.1", "private": true, "description": "DeepCode Mac desktop client — Tauri + React", "license": "MIT", diff --git a/apps/desktop/src-tauri/Cargo.toml b/apps/desktop/src-tauri/Cargo.toml index 0826990..f1ccd9e 100644 --- a/apps/desktop/src-tauri/Cargo.toml +++ b/apps/desktop/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "deepcode_desktop" -version = "0.1.0" +version = "0.1.1" description = "DeepCode Mac desktop client" authors = ["oratis"] edition = "2021" diff --git a/apps/desktop/src-tauri/tauri.conf.json b/apps/desktop/src-tauri/tauri.conf.json index 009a365..223ca7f 100644 --- a/apps/desktop/src-tauri/tauri.conf.json +++ b/apps/desktop/src-tauri/tauri.conf.json @@ -1,7 +1,7 @@ { "$schema": "https://schema.tauri.app/config/2", "productName": "DeepCode", - "version": "0.1.0", + "version": "0.1.1", "identifier": "dev.deepcode.desktop", "build": { "frontendDist": "../dist", diff --git a/apps/desktop/src/App.tsx b/apps/desktop/src/App.tsx index 924d06c..419e937 100644 --- a/apps/desktop/src/App.tsx +++ b/apps/desktop/src/App.tsx @@ -1,9 +1,10 @@ // Top-level React component for desktop client. // Spec: docs/VISUAL_DESIGN.html -// Milestone: M6-rest — Onboarding gate + Nav + 9 screens +// Milestone: 0.1.1 — design-aligned 3-column shell import { useEffect, useState } from 'react'; -import { Nav, type ScreenName } from './components/Nav.js'; +import { InspectorRail } from './components/InspectorRail.js'; +import { Sidebar } from './components/Sidebar.js'; import { UpdateBanner } from './components/UpdateBanner.js'; import { onUpdateDownloaded, startUpdaterPolling } from './lib/updater.js'; import { AboutScreen } from './screens/About.js'; @@ -16,20 +17,18 @@ import { ReplScreen } from './screens/Repl.js'; import { SessionsScreen } from './screens/Sessions.js'; import { SettingsScreen } from './screens/Settings.js'; import { SkillsScreen } from './screens/Skills.js'; +import type { ScreenName } from './components/Nav.js'; import type { UpdateInfo } from './types/global.js'; export function App(): JSX.Element { - const [version, setVersion] = useState(''); const [hasKey, setHasKey] = useState(null); const [update, setUpdate] = useState(null); const [screen, setScreen] = useState('repl'); + const [activeSessionId, setActiveSessionId] = useState(null); useEffect(() => { - void window.deepcode.version().then(setVersion); void window.deepcode.creds.load().then((c) => setHasKey(c.hasKey)); const offShim = window.deepcode.onUpdateDownloaded((info) => setUpdate(info)); - // Also subscribe to the real Tauri updater (so even if the shim - // surface drifts, the banner still fires). const offReal = onUpdateDownloaded((info) => setUpdate(info)); startUpdaterPolling(); return () => { @@ -40,27 +39,47 @@ export function App(): JSX.Element { if (hasKey === null) { return ( -
+
Loading…
); } + // Pre-onboarding: standalone hero — no shell. + if (!hasKey) { + return setHasKey(true)} />; + } + + // Main shell: 3-column grid. return ( -
+
{update && } -
- DeepCode - v{version} -
- {hasKey &&
); } @@ -74,20 +93,49 @@ function renderScreen( return ; case 'sessions': return ( - setScreen('repl')} onNew={() => setScreen('repl')} /> +
+ setScreen('repl')} + onNew={() => setScreen('repl')} + /> +
); case 'plugins': - return ; + return ( +
+ +
+ ); case 'skills': - return ; + return ( +
+ +
+ ); case 'permissions': - return ; + return ( +
+ +
+ ); case 'mcp': - return ; + return ( +
+ +
+ ); case 'settings': - return ; + return ( +
+ +
+ ); case 'about': - return ; + return ( +
+ +
+ ); case 'repl': default: return ; diff --git a/apps/desktop/src/components/Badge.tsx b/apps/desktop/src/components/Badge.tsx new file mode 100644 index 0000000..2c984df --- /dev/null +++ b/apps/desktop/src/components/Badge.tsx @@ -0,0 +1,15 @@ +// Status badge — three flavors per the design spec (#3 / #6). +// .badge-ok / .badge-warn / .badge-err / .badge-info live in index.css. + +import type { ReactNode } from 'react'; + +export type BadgeKind = 'ok' | 'warn' | 'err' | 'info'; + +interface BadgeProps { + kind: BadgeKind; + children: ReactNode; +} + +export function Badge({ kind, children }: BadgeProps): JSX.Element { + return {children}; +} diff --git a/apps/desktop/src/components/BrandMark.tsx b/apps/desktop/src/components/BrandMark.tsx new file mode 100644 index 0000000..87fc611 --- /dev/null +++ b/apps/desktop/src/components/BrandMark.tsx @@ -0,0 +1,23 @@ +// DeepCode brand mark — elephant silhouette per docs/VISUAL_DESIGN.html. +// Wraps the SVG path in the gradient .mark / .mark-lg container so the +// shell can drop it in anywhere without per-instance styling. + +interface BrandMarkProps { + /** 'sm' = 26 px (default, sidebar / pill); 'lg' = 64 px (onboarding hero). */ + size?: 'sm' | 'lg'; +} + +export function BrandMark({ size = 'sm' }: BrandMarkProps): JSX.Element { + return ( + + + + ); +} diff --git a/apps/desktop/src/components/InspectorRail.tsx b/apps/desktop/src/components/InspectorRail.tsx new file mode 100644 index 0000000..fe3a21a --- /dev/null +++ b/apps/desktop/src/components/InspectorRail.tsx @@ -0,0 +1,91 @@ +// Right-column collapsed inspector (48 px) — design spec screen #3. +// Five rail buttons with optional dot-badge counts: +// ‹ (expand) · ▤ (Plan + N) · ◐ (context %) · 📁 (files) · ⓘ (info) · ⚙ (settings) +// +// For v0.1.1 expand is a stub — clicking ‹ does nothing yet (the +// full-width inspector panel lands in P2 with the rest of the screens). + +import type { ScreenName } from './Nav.js'; + +interface InspectorRailProps { + /** Plan items pending — shown as a badge on ▤. */ + planCount?: number; + /** Context fill 0..1 — drives the ◐ color (mint if < 0.6, warn ≥ 0.8). */ + contextFill?: number; + /** Active screen so settings cog highlights when on settings. */ + activeScreen: ScreenName; + /** Switch screen — only wired for settings cog right now. */ + onChange: (screen: ScreenName) => void; +} + +export function InspectorRail({ + planCount, + contextFill, + activeScreen, + onChange, +}: InspectorRailProps): JSX.Element { + const ctxColor = + contextFill === undefined + ? 'var(--text-2)' + : contextFill > 0.8 + ? 'var(--warn)' + : contextFill > 0.6 + ? 'var(--text-1)' + : 'var(--accent)'; + + return ( + + ); +} diff --git a/apps/desktop/src/components/Pill.tsx b/apps/desktop/src/components/Pill.tsx new file mode 100644 index 0000000..b0fd89c --- /dev/null +++ b/apps/desktop/src/components/Pill.tsx @@ -0,0 +1,19 @@ +// Pill — small rounded chip for the chat header (connected · model · approval). +// Optional leading dot for status indication (e.g. live connection). + +import type { ReactNode } from 'react'; + +interface PillProps { + /** Show a leading mint dot. */ + dot?: boolean; + children: ReactNode; +} + +export function Pill({ dot, children }: PillProps): JSX.Element { + return ( + + {dot && } + {children} + + ); +} diff --git a/apps/desktop/src/components/Sidebar.tsx b/apps/desktop/src/components/Sidebar.tsx new file mode 100644 index 0000000..ad1c86f --- /dev/null +++ b/apps/desktop/src/components/Sidebar.tsx @@ -0,0 +1,117 @@ +// Left-column sessions sidebar — design spec screen #3. +// +// Sections: "Today / Yesterday / Earlier" bucketed by updatedAt; each +// row shows a dot + label + relative-time meta. Active row gets the +// brand-tinted background + 1 px brand-line border (no flat color block +// per the spec note ①). + +import { useEffect, useState } from 'react'; +import { listSessions, type SessionMeta } from '../lib/tauri-api.js'; +import { BrandMark } from './BrandMark.js'; + +interface SidebarProps { + /** Currently active session id; null when on transient/global screens. */ + activeSessionId: string | null; + onPickSession: (id: string) => void; + onNewSession: () => void; +} + +type Bucket = 'Today' | 'Yesterday' | 'Earlier'; + +function bucketFor(updatedAtSecs: number, nowSecs: number): Bucket { + const diffSec = nowSecs - updatedAtSecs; + if (diffSec < 60 * 60 * 24) return 'Today'; + if (diffSec < 60 * 60 * 48) return 'Yesterday'; + return 'Earlier'; +} + +function relTime(updatedAtSecs: number, nowSecs: number): string { + const diffSec = nowSecs - updatedAtSecs; + if (diffSec < 60) return 'now'; + if (diffSec < 3600) return `${Math.floor(diffSec / 60)}m`; + if (diffSec < 86400) return `${Math.floor(diffSec / 3600)}h`; + return '·'; +} + +export function Sidebar({ + activeSessionId, + onPickSession, + onNewSession, +}: SidebarProps): JSX.Element { + const [sessions, setSessions] = useState([]); + const [now, setNow] = useState(Math.floor(Date.now() / 1000)); + + useEffect(() => { + void listSessions() + .then(setSessions) + .catch(() => setSessions([])); + const t = setInterval(() => setNow(Math.floor(Date.now() / 1000)), 30_000); + return () => clearInterval(t); + }, []); + + // Group sessions by bucket, preserving order + const grouped: Record = { + Today: [], + Yesterday: [], + Earlier: [], + }; + for (const s of sessions) { + grouped[bucketFor(s.updated_at_secs, now)].push(s); + } + + return ( + + ); +} + +/** Session ids are `2026-05-28-abc123` — strip the date for display. */ +function shortTitle(id: string): string { + const m = id.match(/^\d{4}-\d{2}-\d{2}-(.+)$/); + return m ? m[1]! : id; +} diff --git a/apps/desktop/src/components/ToolCard.tsx b/apps/desktop/src/components/ToolCard.tsx new file mode 100644 index 0000000..567b31a --- /dev/null +++ b/apps/desktop/src/components/ToolCard.tsx @@ -0,0 +1,46 @@ +// Tool-call card — the design-spec primitive that renders every +// agent-issued tool invocation inline in the chat stream. +// +// Layout per docs/VISUAL_DESIGN.html screen #3: +// ┌──────────────────────────────────────────────┐ +// │ ▸ [status badge] │ ← tc-head +// ├──────────────────────────────────────────────┤ +// │ │ ← tc-body +// └──────────────────────────────────────────────┘ + +import type { ReactNode } from 'react'; +import { Badge, type BadgeKind } from './Badge.js'; + +interface ToolCardProps { + /** Tool name — "Read", "Edit", "Bash", etc. Rendered prefixed with ▸. */ + name: string; + /** Optional sub-text — usually the file path or short args. */ + target?: string; + /** Status badge ('ok' = success, 'warn' = pending approval / running, 'err' = failed). */ + status?: { kind: BadgeKind; label: string }; + /** Body content — pre-formatted (mono, preserves whitespace). */ + body?: ReactNode; + /** If true, body is a diff (line-by-line; preserves whitespace strictly). */ + diff?: boolean; +} + +export function ToolCard({ + name, + target, + status, + body, + diff, +}: ToolCardProps): JSX.Element { + return ( +
+
+ ▸ {name} + {target && {target}} + {status && {status.label}} +
+ {body !== undefined && ( +
{body}
+ )} +
+ ); +} diff --git a/apps/desktop/src/index.css b/apps/desktop/src/index.css index 7c12e1a..7cdafbe 100644 --- a/apps/desktop/src/index.css +++ b/apps/desktop/src/index.css @@ -1,164 +1,917 @@ -/* Tailwind directives — kicks in once postcss + tailwindcss devDeps are - * installed (M6-rest). Until then, the hand-rolled utility classes below - * still cover the skeleton's markup. */ -@tailwind base; -@tailwind components; -@tailwind utilities; +/* DeepCode design system. + * Tokens + base + shell mirrored from docs/VISUAL_DESIGN.html so the + * renderer's chrome matches the design spec without us re-deriving the + * palette by hand. Components live in src/components/; per-screen + * styles in this same stylesheet at the bottom. + * + * Color naming follows the spec: + * --brand DeepSeek blue + * --brand-deep hover / pressed + * --brand-soft tinted backgrounds (chips, active rows) + * --accent success / running mint + * --bg-0..3 dark surfaces (default: dark mode) + * --line/-soft borders + * --text-0..3 text foreground tiers + */ + +@import url("https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;500;600;700&display=swap"); :root { color-scheme: dark; - --bg: #0e0e10; - --bg-elevated: #18181b; - --fg: #f4f4f5; - --muted: #71717a; - --accent: #a3e635; - --error: #f87171; - --border: #27272a; + /* Brand */ + --brand: #4d6bfe; + --brand-deep: #2f49d1; + --brand-soft: #e8edff; + --brand-tint: rgba(77, 107, 254, 0.12); + --brand-line: rgba(77, 107, 254, 0.25); + --accent: #14e4a2; + --warn: #ffb020; + --error: #ff5470; + + /* Dark neutrals (the default) */ + --bg-0: #0b0d12; + --bg-1: #11141b; + --bg-2: #171b24; + --bg-3: #1f2532; + --line: #262c3a; + --line-soft: #1b2030; + --text-0: #f2f4f8; + --text-1: #c7ccd8; + --text-2: #8a92a4; + --text-3: #5b6376; + + --radius-sm: 6px; + --radius: 10px; + --radius-lg: 16px; + --shadow: 0 10px 40px -10px rgba(0, 0, 0, 0.5), + 0 1px 0 rgba(255, 255, 255, 0.03) inset; } * { box-sizing: border-box; } -body, html, #root { +html, +body, +#root { margin: 0; padding: 0; height: 100%; - background: var(--bg); - color: var(--fg); - font-family: ui-sans-serif, -apple-system, BlinkMacSystemFont, system-ui, sans-serif; + background: var(--bg-0); + color: var(--text-0); + font-family: "Inter", -apple-system, BlinkMacSystemFont, system-ui, sans-serif; font-size: 14px; + line-height: 1.55; + -webkit-font-smoothing: antialiased; } -/* Minimum class-equivalents so the skeleton's Tailwind-flavored markup is - * legible without postcss. Real Tailwind replaces this block in M6-rest. */ -.bg-bg { background: var(--bg); } -.bg-bg-elevated { background: var(--bg-elevated); } -.text-fg { color: var(--fg); } -.text-muted { color: var(--muted); } -.bg-accent { background: var(--accent); } -.text-accent { color: var(--accent); } -.bg-error\/10 { background: rgba(248, 113, 113, 0.1); } -.text-error { color: var(--error); } -.border-border { border-color: var(--border); } -.bg-accent\/10 { background: rgba(163, 230, 53, 0.1); } -.bg-accent\/20 { background: rgba(163, 230, 53, 0.2); } - -.flex { display: flex; } -.flex-col { flex-direction: column; } -.flex-1 { flex: 1; } -.gap-2 { gap: 0.5rem; } -.h-full { height: 100%; } -.h-screen { height: 100vh; } -.items-center { align-items: center; } -.justify-center { justify-content: center; } -.justify-between { justify-content: space-between; } -.overflow-hidden { overflow: hidden; } -.overflow-y-auto { overflow-y: auto; } -.whitespace-pre-wrap { white-space: pre-wrap; } -.space-y-3 > * + * { margin-top: 0.75rem; } -.space-y-4 > * + * { margin-top: 1rem; } -.p-3 { padding: 0.75rem; } -.p-4 { padding: 1rem; } -.p-6 { padding: 1.5rem; } -.p-8 { padding: 2rem; } -.px-3 { padding-left: 0.75rem; padding-right: 0.75rem; } -.px-4 { padding-left: 1rem; padding-right: 1rem; } -.py-1 { padding-top: 0.25rem; padding-bottom: 0.25rem; } -.py-2 { padding-top: 0.5rem; padding-bottom: 0.5rem; } -.mt-1 { margin-top: 0.25rem; } -.ml-12 { margin-left: 3rem; } -.mr-12 { margin-right: 3rem; } -.mx-12 { margin-left: 3rem; margin-right: 3rem; } -.mb-1 { margin-bottom: 0.25rem; } -.text-xs { font-size: 0.75rem; } -.text-sm { font-size: 0.875rem; } -.text-xl { font-size: 1.25rem; } -.font-medium { font-weight: 500; } -.font-semibold { font-weight: 600; } -.rounded { border-radius: 0.25rem; } -.rounded-lg { border-radius: 0.5rem; } -.border { border-width: 1px; border-style: solid; } -.border-t { border-top-width: 1px; border-top-style: solid; } -.border-b { border-bottom-width: 1px; border-bottom-style: solid; } -.w-full { width: 100%; } -.max-w-md { max-width: 28rem; } -.outline-none { outline: none; } - -input, button { - font: inherit; +code, +.mono { + font-family: "JetBrains Mono", ui-monospace, Menlo, monospace; +} + +a { + color: var(--brand); + text-decoration: none; +} +a:hover { + text-decoration: underline; +} + +button { + font-family: inherit; color: inherit; + border: 0; + background: transparent; + cursor: pointer; +} +button:disabled { + opacity: 0.5; + cursor: not-allowed; } -input:focus { border-color: var(--accent); } -button:disabled { opacity: 0.5; cursor: not-allowed; } - -code { - font-family: ui-monospace, SFMono-Regular, monospace; - font-size: 0.95em; -} - -/* Additional utility classes used by the M6-rest screens. */ -.border-l { border-left-width: 1px; border-left-style: solid; } -.border-b-2 { border-bottom-width: 2px; border-bottom-style: solid; } -.border-accent { border-color: var(--accent); } -.cursor-pointer { cursor: pointer; } -.hidden { display: none; } -@media (min-width: 1024px) { .lg\:block { display: block; } } -.w-1\/3 { width: 33.333%; } -.max-w-xl { max-width: 36rem; } -.mx-auto { margin-left: auto; margin-right: auto; } -.ml-2 { margin-left: 0.5rem; } -.mt-2 { margin-top: 0.5rem; } -.mt-3 { margin-top: 0.75rem; } -.mt-4 { margin-top: 1rem; } -.p-2 { padding: 0.5rem; } -.gap-1 { gap: 0.25rem; } -.space-y-2 > * + * { margin-top: 0.5rem; } -.text-center { text-align: center; } -.text-left { text-align: left; } -.font-mono { font-family: ui-monospace, SFMono-Regular, monospace; } -.hover\:border-accent:hover { border-color: var(--accent); } -.hover\:text-fg:hover { color: var(--fg); } -table { border-collapse: collapse; width: 100%; } - -/* Additional classes used by the last 5 screens (FilePanel / Plugins / Skills / - * Permissions / About). */ -.flex-row { flex-direction: row; } -.items-baseline { align-items: baseline; } -.ml-1 { margin-left: 0.25rem; } -.ml-auto { margin-left: auto; } -.mb-1 { margin-bottom: 0.25rem; } -.mb-4 { margin-bottom: 1rem; } -.mt-6 { margin-top: 1.5rem; } -.mr-12 { margin-right: 3rem; } -.pr-2 { padding-right: 0.5rem; } -.overflow-auto { overflow: auto; } -.text-error { color: var(--error); } -.hover\:text-error:hover { color: var(--error); } -.hover\:bg-bg-elevated:hover { background: var(--bg-elevated); } -.hover\:underline:hover { text-decoration: underline; } -.cursor-default { cursor: default; } + +input, +textarea, select { - background: var(--bg); - color: var(--fg); - border: 1px solid var(--border); - padding: 0.5rem 0.75rem; - border-radius: 0.25rem; -} -select:focus { outline: none; border-color: var(--accent); } -.block { display: block; } -.bg-error\/80 { background: rgba(248, 113, 113, 0.8); } -.animate-pulse { animation: dc-pulse 1.4s ease-in-out infinite; } -@keyframes dc-pulse { - 0%, 100% { opacity: 1; } - 50% { opacity: 0.3; } -} -.mx-6 { margin-left: 1.5rem; margin-right: 1.5rem; } -.disabled\:opacity-50:disabled { opacity: 0.5; } -.relative { position: relative; } -.absolute { position: absolute; } -.top-3 { top: 0.75rem; } -.right-3 { right: 0.75rem; } -.z-10 { z-index: 10; } -.w-1\/2 { width: 50%; } -.w-0 { width: 0; } + font-family: inherit; + color: inherit; +} + +/* ──────────────────────────────────────────────────────────────────── */ +/* Buttons */ +/* ──────────────────────────────────────────────────────────────────── */ + +.btn { + font-size: 14px; + font-weight: 500; + padding: 9px 16px; + border-radius: var(--radius-sm); + border: 1px solid transparent; + transition: background 0.15s, border-color 0.15s, color 0.15s; + display: inline-flex; + align-items: center; + gap: 8px; +} +.btn-primary { + background: var(--brand); + color: #fff; + border-color: var(--brand); +} +.btn-primary:hover:not(:disabled) { + background: var(--brand-deep); + border-color: var(--brand-deep); +} +.btn-secondary { + background: var(--bg-2); + color: var(--text-0); + border-color: var(--line); +} +.btn-secondary:hover:not(:disabled) { + border-color: var(--text-2); +} +.btn-ghost { + background: transparent; + color: var(--text-2); +} +.btn-ghost:hover:not(:disabled) { + color: var(--text-0); +} +.btn-danger { + background: var(--error); + color: #fff; + border-color: var(--error); +} + +/* ──────────────────────────────────────────────────────────────────── */ +/* Inputs */ +/* ──────────────────────────────────────────────────────────────────── */ + +.input { + width: 100%; + padding: 10px 12px; + border: 1px solid var(--line); + border-radius: var(--radius-sm); + font-family: "JetBrains Mono", monospace; + font-size: 13px; + background: var(--bg-1); + color: var(--text-0); + outline: none; +} +.input:focus { + border-color: var(--brand); + box-shadow: 0 0 0 2px var(--brand-tint); +} + +/* ──────────────────────────────────────────────────────────────────── */ +/* Pills + Badges */ +/* ──────────────────────────────────────────────────────────────────── */ + +.pill { + font-size: 11px; + padding: 3px 9px; + border-radius: 999px; + border: 1px solid var(--line); + color: var(--text-1); + background: var(--bg-2); + display: inline-flex; + align-items: center; + gap: 6px; + white-space: nowrap; +} +.pill .dot { + width: 6px; + height: 6px; + border-radius: 50%; + background: var(--accent); +} + +.badge { + display: inline-flex; + align-items: center; + gap: 4px; + padding: 2px 8px; + border-radius: 999px; + font-size: 10.5px; + font-weight: 600; + font-family: "Inter", sans-serif; +} +.badge-ok { + background: rgba(20, 228, 162, 0.15); + color: #0aa876; +} +.badge-warn { + background: rgba(255, 176, 32, 0.15); + color: #b8770d; +} +.badge-info { + background: var(--brand-tint); + color: #b4c2ff; +} +.badge-err { + background: rgba(255, 84, 112, 0.15); + color: var(--error); +} + +/* ──────────────────────────────────────────────────────────────────── */ +/* Brand mark (elephant) */ +/* ──────────────────────────────────────────────────────────────────── */ + +.mark { + width: 26px; + height: 26px; + border-radius: 7px; + background: linear-gradient(135deg, var(--brand) 0%, #6b86ff 100%); + display: inline-flex; + align-items: center; + justify-content: center; + color: #fff; + flex-shrink: 0; +} +.mark svg { + width: 72%; + height: 72%; + display: block; + color: #fff; +} +.mark.mark-lg { + width: 64px; + height: 64px; + border-radius: 16px; +} + +/* ──────────────────────────────────────────────────────────────────── */ +/* App shell — 3 columns */ +/* ──────────────────────────────────────────────────────────────────── */ + +.app-shell { + display: grid; + grid-template-columns: 240px 1fr 48px; + grid-template-rows: 1fr; + height: 100vh; + background: var(--bg-1); +} + +/* ──────────────────────────────────────────────────────────────────── */ +/* Sidebar (Sessions) */ +/* ──────────────────────────────────────────────────────────────────── */ + +.sidebar { + background: var(--bg-0); + border-right: 1px solid var(--line); + padding: 18px 14px; + overflow-y: auto; + display: flex; + flex-direction: column; +} +.sidebar .brand-row { + display: flex; + align-items: center; + gap: 10px; + padding: 4px 6px 18px; +} +.sidebar .brand-row .name { + font-weight: 700; + color: var(--text-0); + font-size: 14px; + letter-spacing: -0.2px; +} +.sidebar .new-btn { + width: 100%; + margin: 4px 0 8px; + padding: 9px; + background: var(--brand-tint); + color: #b4c2ff; + border: 1px dashed var(--brand-line); + border-radius: var(--radius-sm); + font-size: 12px; + text-align: center; + cursor: pointer; + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; + padding-inline: 12px; +} +.sidebar .new-btn:hover { + background: rgba(77, 107, 254, 0.18); +} +.sidebar .new-btn kbd { + color: var(--text-3); + font-family: inherit; + font-size: 11px; +} +.sidebar .section-title { + font-size: 10.5px; + letter-spacing: 1px; + color: var(--text-3); + text-transform: uppercase; + padding: 14px 8px 6px; + font-weight: 600; +} +.sidebar .item { + display: flex; + align-items: center; + gap: 10px; + padding: 7px 9px; + border-radius: var(--radius-sm); + color: var(--text-1); + font-size: 13px; + cursor: pointer; + border: 1px solid transparent; + margin-bottom: 1px; +} +.sidebar .item:hover { + background: var(--bg-1); +} +.sidebar .item.active { + background: var(--brand-tint); + color: #b4c2ff; + border-color: var(--brand-line); +} +.sidebar .item .dot { + width: 6px; + height: 6px; + border-radius: 50%; + background: var(--text-3); + flex-shrink: 0; +} +.sidebar .item.active .dot { + background: var(--brand); +} +.sidebar .item .label { + flex: 1; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.sidebar .item .meta { + color: var(--text-3); + font-size: 11px; + margin-left: auto; + flex-shrink: 0; +} + +/* ──────────────────────────────────────────────────────────────────── */ +/* Inspector rail (right column) */ +/* ──────────────────────────────────────────────────────────────────── */ + +.inspector-rail { + background: var(--bg-1); + border-left: 1px solid var(--line); + padding: 14px 0; + gap: 10px; + display: flex; + flex-direction: column; + align-items: center; +} +.rail-btn { + width: 30px; + height: 30px; + border-radius: 7px; + display: inline-flex; + align-items: center; + justify-content: center; + color: var(--text-2); + background: var(--bg-1); + border: 1px solid var(--line-soft); + cursor: pointer; + font-size: 13px; + position: relative; +} +.rail-btn:hover { + color: var(--text-0); + border-color: var(--line); +} +.rail-btn.active { + background: var(--bg-2); + border-color: var(--line); + color: var(--text-0); +} +.rail-btn .dot-badge { + position: absolute; + top: -3px; + right: -3px; + min-width: 14px; + height: 14px; + padding: 0 3px; + border-radius: 7px; + background: var(--brand); + color: #fff; + font-size: 9px; + font-weight: 700; + line-height: 14px; + text-align: center; + border: 2px solid var(--bg-0); +} +.rail-divider { + width: 16px; + height: 1px; + background: var(--line-soft); +} +.rail-spacer { + flex: 1; +} + +/* ──────────────────────────────────────────────────────────────────── */ +/* Chat main column */ +/* ──────────────────────────────────────────────────────────────────── */ + +.chat-main { + display: flex; + flex-direction: column; + min-width: 0; + background: var(--bg-1); +} +.chat-header { + padding: 12px 22px; + border-bottom: 1px solid var(--line); + display: flex; + align-items: center; + gap: 10px; + background: var(--bg-1); +} +.chat-header .crumb { + color: var(--text-2); + font-size: 12px; +} +.chat-header .crumb b { + color: var(--text-0); + font-weight: 500; +} +.chat-header .right { + margin-left: auto; + display: flex; + gap: 8px; + align-items: center; +} + +.chat-stream { + flex: 1; + overflow-y: auto; + padding: 22px; + display: flex; + flex-direction: column; + gap: 16px; +} +.chat-stream::-webkit-scrollbar { + width: 8px; +} +.chat-stream::-webkit-scrollbar-thumb { + background: var(--bg-3); + border-radius: 4px; +} + +/* Message rows */ +.msg { + display: flex; + gap: 12px; +} +.msg .avatar { + width: 28px; + height: 28px; + border-radius: 8px; + flex-shrink: 0; + display: flex; + align-items: center; + justify-content: center; + font-size: 12px; + font-weight: 700; +} +.msg.user .avatar { + background: var(--bg-3); + color: var(--text-1); +} +.msg.assistant .avatar { + background: linear-gradient(135deg, var(--brand) 0%, #6b86ff 100%); + color: #fff; +} +.msg.system .avatar { + background: var(--bg-2); + color: var(--text-2); + font-size: 11px; +} +.msg .body { + flex: 1; + min-width: 0; +} +.msg .author { + font-size: 10.5px; + color: var(--text-3); + margin-bottom: 4px; + letter-spacing: 0.5px; + text-transform: uppercase; + font-weight: 600; +} +.msg .content { + color: var(--text-0); + font-size: 13.5px; + line-height: 1.65; + white-space: pre-wrap; + word-break: break-word; +} +.msg .content code { + background: var(--bg-3); + color: #b4c2ff; + padding: 1px 5px; + border-radius: 4px; + font-size: 12.5px; +} + +/* Tool call card */ +.tool-card { + margin: 10px 0; + border: 1px solid var(--line); + background: var(--bg-2); + border-radius: var(--radius); + overflow: hidden; + font-family: "JetBrains Mono", monospace; + font-size: 12.5px; +} +.tool-card .tc-head { + padding: 8px 12px; + display: flex; + align-items: center; + gap: 8px; + background: linear-gradient(180deg, var(--brand-tint), transparent); + border-bottom: 1px solid var(--line); +} +.tool-card .tc-head .name { + color: #c7a8ff; + font-weight: 600; +} +.tool-card .tc-head .target { + color: var(--text-2); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.tool-card .tc-head .badge { + margin-left: auto; + flex-shrink: 0; +} +.tool-card .tc-body { + padding: 10px 14px; + color: var(--text-1); + white-space: pre-wrap; + overflow-x: auto; + max-height: 280px; + overflow-y: auto; +} +.tool-card .tc-body.diff { + white-space: pre; +} +.diff-add { + color: var(--accent); +} +.diff-del { + color: var(--error); +} + +/* Inline approval — sits right under a tool card */ +.approval-row { + margin: 8px 0 4px; + display: flex; + gap: 8px; + flex-wrap: wrap; +} +.approval-row .btn { + font-size: 12.5px; + padding: 7px 14px; +} + +/* ──────────────────────────────────────────────────────────────────── */ +/* Composer */ +/* ──────────────────────────────────────────────────────────────────── */ + +.composer { + border-top: 1px solid var(--line); + padding: 14px 18px 16px; + background: var(--bg-1); +} +.composer .box { + border: 1px solid var(--line); + border-radius: 12px; + padding: 12px 14px; + background: var(--bg-0); + display: flex; + flex-direction: column; + gap: 10px; +} +.composer .box:focus-within { + border-color: var(--brand); + box-shadow: 0 0 0 2px var(--brand-tint); +} +.composer textarea { + width: 100%; + background: transparent; + border: 0; + color: var(--text-0); + font-family: inherit; + font-size: 13.5px; + resize: none; + outline: none; + min-height: 28px; + max-height: 200px; + line-height: 1.5; +} +.composer textarea::placeholder { + color: var(--text-3); +} +.composer .toolbar { + display: flex; + align-items: center; + gap: 8px; +} +.composer .icon-btn { + width: 28px; + height: 28px; + border-radius: 6px; + background: var(--bg-2); + color: var(--text-2); + border: 1px solid var(--line-soft); + display: inline-flex; + align-items: center; + justify-content: center; + font-size: 13px; + cursor: pointer; +} +.composer .icon-btn:hover { + color: var(--text-0); + border-color: var(--line); +} +.composer .mode-badge { + font-size: 11px; + padding: 3px 9px; + border-radius: 999px; + font-weight: 600; + font-family: "Inter", sans-serif; +} +.composer .mode-badge.bypass { + background: rgba(255, 84, 112, 0.12); + color: var(--error); + border: 1px solid rgba(255, 84, 112, 0.3); +} +.composer .mode-badge.default { + background: var(--brand-tint); + color: #b4c2ff; + border: 1px solid var(--brand-line); +} +.composer .mode-badge.plan { + background: rgba(255, 176, 32, 0.12); + color: var(--warn); + border: 1px solid rgba(255, 176, 32, 0.3); +} +.composer .spacer { + flex: 1; +} +.composer .model-picker { + display: inline-flex; + align-items: center; + gap: 7px; + padding: 5px 10px; + background: var(--bg-2); + border: 1px solid var(--line-soft); + border-radius: var(--radius-sm); + cursor: pointer; + font-size: 12px; + color: var(--text-1); +} +.composer .model-picker:hover { + border-color: var(--line); +} +.composer .model-picker .dot { + width: 6px; + height: 6px; + border-radius: 50%; + background: var(--accent); +} +.composer .model-picker .meta { + color: var(--text-3); + font-size: 11px; +} +.composer .send-btn { + width: 32px; + height: 32px; + border-radius: 8px; + background: var(--brand); + color: #fff; + font-size: 14px; + font-weight: 700; + border: 0; + cursor: pointer; + display: inline-flex; + align-items: center; + justify-content: center; +} +.composer .send-btn:hover:not(:disabled) { + background: var(--brand-deep); +} +.composer .send-btn.stop { + background: var(--error); +} +.composer .ctx-bar { + margin-top: 8px; + font-size: 11px; + color: var(--text-2); + display: flex; + align-items: center; + gap: 10px; +} +.composer .ctx-bar .progress { + flex: 1; + height: 3px; + background: var(--bg-3); + border-radius: 2px; + overflow: hidden; + max-width: 220px; +} +.composer .ctx-bar .progress .fill { + height: 100%; + background: var(--brand); + transition: width 0.3s; +} +.composer .vim-chip { + font-family: "JetBrains Mono", monospace; + font-size: 11px; + padding: 2px 7px; + border-radius: 4px; + background: var(--bg-2); + color: var(--text-2); +} +.composer .vim-chip.normal { + background: var(--brand-tint); + color: #b4c2ff; +} +.composer .vim-chip.visual { + background: rgba(255, 84, 112, 0.12); + color: var(--error); +} + +/* ──────────────────────────────────────────────────────────────────── */ +/* Onboarding screen */ +/* ──────────────────────────────────────────────────────────────────── */ + +.onboarding { + min-height: 100vh; + background: radial-gradient( + ellipse at top, + rgba(77, 107, 254, 0.15) 0%, + transparent 50% + ), + var(--bg-0); + display: flex; + align-items: center; + justify-content: center; + padding: 48px 24px; +} +.onboarding .card { + max-width: 480px; + width: 100%; + text-align: center; +} +.onboarding .brand-chip { + display: inline-flex; + align-items: center; + gap: 12px; + padding: 8px 18px; + background: var(--bg-1); + border: 1px solid var(--line); + border-radius: 999px; + font-weight: 600; + color: #b4c2ff; + margin-bottom: 32px; + box-shadow: 0 4px 20px -8px rgba(77, 107, 254, 0.4); +} +.onboarding h1 { + font-size: 44px; + line-height: 1.05; + font-weight: 800; + letter-spacing: -1.5px; + margin: 0 0 14px; + background: linear-gradient(180deg, var(--text-0) 0%, var(--brand) 140%); + -webkit-background-clip: text; + background-clip: text; + color: transparent; +} +.onboarding .tagline { + font-size: 17px; + color: var(--text-1); + margin: 0 0 36px; +} +.onboarding form { + text-align: left; + background: var(--bg-1); + border: 1px solid var(--line); + border-radius: var(--radius-lg); + padding: 24px; + box-shadow: var(--shadow); +} +.onboarding form label { + display: block; + font-size: 12px; + font-weight: 600; + color: var(--text-2); + margin-bottom: 8px; + letter-spacing: 0.5px; +} +.onboarding form .hint { + margin-top: 10px; + font-size: 12px; + color: var(--text-3); +} +.onboarding form .hint a { + color: #b4c2ff; +} +.onboarding form .actions { + margin-top: 18px; + display: flex; + gap: 8px; + justify-content: flex-end; +} +.onboarding form .error { + margin-top: 12px; + padding: 8px 12px; + background: rgba(255, 84, 112, 0.12); + border: 1px solid rgba(255, 84, 112, 0.3); + color: var(--error); + border-radius: var(--radius-sm); + font-size: 12px; +} + +/* ──────────────────────────────────────────────────────────────────── */ +/* Utility — used by screens not yet redesigned (Plugins/Skills/...) */ +/* These keep older screens legible until P2. */ +/* ──────────────────────────────────────────────────────────────────── */ + +.legacy-screen { + padding: 22px; + overflow-y: auto; + height: 100%; +} +.legacy-screen h2 { + font-size: 18px; + font-weight: 700; + margin: 0 0 16px; +} +.legacy-screen table { + width: 100%; + border-collapse: collapse; + font-size: 13px; +} +.legacy-screen th, +.legacy-screen td { + text-align: left; + padding: 8px 10px; + border-bottom: 1px solid var(--line); +} +.legacy-screen th { + color: var(--text-2); + font-weight: 600; + font-size: 11px; + text-transform: uppercase; + letter-spacing: 0.5px; +} + +.muted { + color: var(--text-2); +} +.text-error { + color: var(--error); +} +.text-accent { + color: var(--accent); +} +.font-mono { + font-family: "JetBrains Mono", monospace; +} + +/* Loading spinner */ +.spinner { + display: inline-block; + width: 14px; + height: 14px; + border: 2px solid var(--line); + border-top-color: var(--brand); + border-radius: 50%; + animation: spin 0.7s linear infinite; +} +@keyframes spin { + to { + transform: rotate(360deg); + } +} + +.streaming-cursor { + display: inline-block; + width: 7px; + height: 1em; + background: var(--brand); + vertical-align: text-bottom; + margin-left: 2px; + animation: blink 1.1s steps(2) infinite; +} +@keyframes blink { + 50% { + opacity: 0; + } +} diff --git a/apps/desktop/src/screens/Onboarding.tsx b/apps/desktop/src/screens/Onboarding.tsx index 1c01a91..3fdbd86 100644 --- a/apps/desktop/src/screens/Onboarding.tsx +++ b/apps/desktop/src/screens/Onboarding.tsx @@ -1,8 +1,9 @@ // Onboarding screen — first-run flow capturing the DeepSeek API key. -// Spec: docs/VISUAL_DESIGN.html screen #1 -// Milestone: M6 skeleton +// Design spec: docs/VISUAL_DESIGN.html screen #2 (hero gradient + big mark). import { useState } from 'react'; +import { BrandMark } from '../components/BrandMark.js'; +import { openUrl } from '../lib/tauri-api.js'; interface OnboardingProps { onComplete: () => void; @@ -36,52 +37,74 @@ export function OnboardingScreen({ onComplete }: OnboardingProps): JSX.Element { } return ( -
-
-
-

Welcome to DeepCode

-

- DeepCode talks to the DeepSeek API. Paste your key to get started. -

-
-