Skip to content
Merged
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
57 changes: 57 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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`
2 changes: 1 addition & 1 deletion apps/cli/package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
2 changes: 1 addition & 1 deletion apps/desktop/package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
2 changes: 1 addition & 1 deletion apps/desktop/src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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"
Expand Down
2 changes: 1 addition & 1 deletion apps/desktop/src-tauri/tauri.conf.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
102 changes: 75 additions & 27 deletions apps/desktop/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
// Top-level React component for desktop client.
// Spec: docs/VISUAL_DESIGN.html
// Milestone: M6-restOnboarding gate + Nav + 9 screens
// Milestone: 0.1.1design-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';
Expand All @@ -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<string>('');
const [hasKey, setHasKey] = useState<boolean | null>(null);
const [update, setUpdate] = useState<UpdateInfo | null>(null);
const [screen, setScreen] = useState<ScreenName>('repl');
const [activeSessionId, setActiveSessionId] = useState<string | null>(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 () => {
Expand All @@ -40,27 +39,47 @@ export function App(): JSX.Element {

if (hasKey === null) {
return (
<div className="flex h-screen items-center justify-center bg-bg text-fg">
<div
style={{
height: '100vh',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
color: 'var(--text-2)',
background: 'var(--bg-0)',
}}
>
Loading…
</div>
);
}

// Pre-onboarding: standalone hero — no shell.
if (!hasKey) {
return <OnboardingScreen onComplete={() => setHasKey(true)} />;
}

// Main shell: 3-column grid.
return (
<div className="flex h-screen flex-col bg-bg text-fg">
<div className="app-shell">
{update && <UpdateBanner info={update} />}
<header className="flex items-center justify-between border-b border-border px-4 py-2 text-sm">
<span className="font-semibold">DeepCode</span>
<span className="text-muted">v{version}</span>
</header>
{hasKey && <Nav active={screen} onChange={setScreen} />}
<main className="flex-1 overflow-hidden">
{!hasKey ? (
<OnboardingScreen onComplete={() => setHasKey(true)} />
) : (
renderScreen(screen, setScreen)
)}
</main>
<Sidebar
activeSessionId={activeSessionId}
onPickSession={(id) => {
setActiveSessionId(id);
setScreen('repl');
}}
onNewSession={() => {
setActiveSessionId(null);
setScreen('repl');
}}
/>
<main className="chat-main">{renderScreen(screen, setScreen)}</main>
<InspectorRail
activeScreen={screen}
onChange={(s) => setScreen(s)}
contextFill={undefined}
/>
</div>
);
}
Expand All @@ -74,20 +93,49 @@ function renderScreen(
return <ChatScreen />;
case 'sessions':
return (
<SessionsScreen onPick={() => setScreen('repl')} onNew={() => setScreen('repl')} />
<div className="legacy-screen">
<SessionsScreen
onPick={() => setScreen('repl')}
onNew={() => setScreen('repl')}
/>
</div>
);
case 'plugins':
return <PluginsScreen />;
return (
<div className="legacy-screen">
<PluginsScreen />
</div>
);
case 'skills':
return <SkillsScreen />;
return (
<div className="legacy-screen">
<SkillsScreen />
</div>
);
case 'permissions':
return <PermissionsScreen />;
return (
<div className="legacy-screen">
<PermissionsScreen />
</div>
);
case 'mcp':
return <MCPManagerScreen />;
return (
<div className="legacy-screen">
<MCPManagerScreen />
</div>
);
case 'settings':
return <SettingsScreen />;
return (
<div className="legacy-screen">
<SettingsScreen />
</div>
);
case 'about':
return <AboutScreen />;
return (
<div className="legacy-screen">
<AboutScreen />
</div>
);
case 'repl':
default:
return <ReplScreen />;
Expand Down
15 changes: 15 additions & 0 deletions apps/desktop/src/components/Badge.tsx
Original file line number Diff line number Diff line change
@@ -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 <span className={`badge badge-${kind}`}>{children}</span>;
}
23 changes: 23 additions & 0 deletions apps/desktop/src/components/BrandMark.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<span className={size === 'lg' ? 'mark mark-lg' : 'mark'}>
<svg
viewBox="0 0 100 100"
xmlns="http://www.w3.org/2000/svg"
fill="currentColor"
aria-hidden="true"
>
<path d="M18 36 L24 10 L42 30 Q50 28 58 30 L76 10 L82 36 Q90 52 86 70 Q76 88 50 88 Q24 88 14 70 Q10 52 18 36 Z" />
</svg>
</span>
);
}
91 changes: 91 additions & 0 deletions apps/desktop/src/components/InspectorRail.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<aside className="inspector-rail">
<button
type="button"
className="rail-btn"
title="Expand inspector (⌘\\) — coming in P2"
disabled
>
</button>
<div className="rail-divider" />
<button
type="button"
className="rail-btn"
title={planCount ? `Plan · ${planCount} pending` : 'Plan'}
>
{planCount !== undefined && planCount > 0 && (
<span className="dot-badge">{planCount}</span>
)}
</button>
<button
type="button"
className="rail-btn"
title={
contextFill === undefined
? 'Context: idle'
: `Context: ${Math.round(contextFill * 100)}% used`
}
style={{ color: ctxColor, borderColor: 'rgba(20,228,162,.18)' }}
>
</button>
<button
type="button"
className="rail-btn"
title="Recent files"
onClick={() => onChange('repl')}
>
📁
</button>
<button type="button" className="rail-btn" title="Session info">
</button>
<span className="rail-spacer" />
<button
type="button"
className={'rail-btn' + (activeScreen === 'settings' ? ' active' : '')}
title="Settings"
onClick={() => onChange('settings')}
>
</button>
</aside>
);
}
19 changes: 19 additions & 0 deletions apps/desktop/src/components/Pill.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<span className="pill">
{dot && <span className="dot" />}
{children}
</span>
);
}
Loading
Loading