diff --git a/apps/desktop/README.md b/apps/desktop/README.md
index 41e188a..7e2855c 100644
--- a/apps/desktop/README.md
+++ b/apps/desktop/README.md
@@ -2,34 +2,64 @@
DeepCode Mac 客户端(Electron + React + Tailwind + xterm + monaco)。
-## 当前状态 — M6 skeleton
+## 当前状态 — M6 skeleton + M6-rest build pipeline
-骨架已落地(type-check 通过):
+骨架 + 构建配置已就位(type-check 通过):
-- `electron/main.ts` — BrowserWindow + IPC 处理(version / creds / settings)+
- electron-updater 钩子(懒加载,没装也不崩)
+- `electron/main.ts` — BrowserWindow + IPC(version / creds / settings)+
+ 懒加载 electron-updater 钩子
- `electron/preload.ts` — `contextBridge` 暴露 `window.deepcode` 给 renderer
- `src/main.tsx` + `src/App.tsx` — React 入口 + Onboarding gate + 更新 banner
-- `src/screens/Onboarding.tsx` — 首次运行的 API key 收集表单
-- `src/screens/Repl.tsx` — 对话占位 UI
-- `src/components/UpdateBanner.tsx` — "Relaunch to update vX.Y.Z" 提示
-- `tsconfig.electron.json` — 等装了 `electron` 之后用这个编译 main/preload
+- `src/screens/Onboarding.tsx` / `src/screens/Repl.tsx`
+- `src/components/UpdateBanner.tsx`
+- `src/index.html` + `src/index.css`(含 Tailwind directives)
+- `vite.config.ts` — renderer 构建(dev server 5173 + prod build → dist/)
+- `tailwind.config.ts` + `postcss.config.js`
+- `tsconfig.json`(renderer)+ `tsconfig.electron.json`(main process)
+- `electron-builder.yml` — universal .dmg + Apple 公证 + GitHub Releases
+- `build-resources/entitlements.mac.plist` — hardened-runtime entitlements
-## 还没做(M6-rest,多个 PR)
+## 装实际依赖(M6-rest 启动)
-- 装 `electron` / `electron-builder` / `vite` / `tailwindcss` 实际依赖(约 250 MB node_modules)
-- Vite dev server + HMR
-- Tailwind PostCSS 流水线
-- xterm.js 终端嵌入
+构建配置以 `*.template.{ts,js}` 后缀存在(避免没装依赖时被 vitest/postcss
+自动加载报错)。要真正能 dev + ship 还需要装这些 ~250 MB 二进制依赖:
+
+```bash
+pnpm add -D --filter @deepcode/desktop \
+ electron electron-builder electron-updater \
+ vite @vitejs/plugin-react \
+ tailwindcss postcss autoprefixer \
+ concurrently wait-on
+
+# 然后把 template 后缀去掉激活
+mv apps/desktop/vite.config.template.ts apps/desktop/vite.config.ts
+mv apps/desktop/postcss.config.template.js apps/desktop/postcss.config.js
+```
+
+之后:
+
+| 命令 | 作用 |
+| ------------------ | --------------------------------------------------- |
+| `pnpm dev` | Vite dev server + electron 自动重载 |
+| `pnpm build:all` | 构建 renderer (dist/) + main process (dist-electron/) |
+| `pnpm pack` | 打包未签名 .app(本地测试) |
+| `pnpm dist` | 完整签名 + 公证 + .dmg(需要 Apple Developer ID) |
+
+## 还没做(M6-rest 余下任务)
+
+- xterm.js + node-pty 嵌入终端
- Monaco 编辑器嵌入
-- electron-builder universal dmg 打包
-- Apple Developer ID + codesign + notarize
-- 11 个屏幕剩余 9 个(Chat / Sessions / Settings / MCPManager / FilePanel 等)
-- Renderer ↔ main process 的 agent loop 流式桥
+- 余下 8 个屏幕(Chat / Sessions / Settings / MCPManager / FilePanel 等 ·
+ Onboarding + REPL 已有)
+- Renderer ↔ main process 的 agent loop 流式桥(让 chat 真能跑)
+- Apple Developer ID 证书 + APPLE_ID/APPLE_APP_SPECIFIC_PASSWORD 写入 CI secrets
+- .github/workflows/release.yml 的 mac build step 解开 `if: false`
+- `electron-updater` 真接 GitHub Releases feed(main.ts 已经有钩子)
+- 11 屏的视觉稿落地(参考 docs/VISUAL_DESIGN.html)
## 为什么 skeleton 故意留小
Electron 二进制装下来 ~250 MB,CI 装包会变慢。把这个负担留给真正开始
-做 M6-rest 的 PR,这样到 M6-skeleton 为止的 monorepo 仍然轻。
+做 M6-rest 的 PR(这一个!)—— 你可以一次安装所有依赖,开始迭代 UI。
详见 `docs/DEVELOPMENT_PLAN.md` §4 + §4a + §4b。
diff --git a/apps/desktop/build-resources/entitlements.mac.plist b/apps/desktop/build-resources/entitlements.mac.plist
new file mode 100644
index 0000000..618b351
--- /dev/null
+++ b/apps/desktop/build-resources/entitlements.mac.plist
@@ -0,0 +1,25 @@
+
+
+
+
+
+ com.apple.security.cs.allow-jit
+
+ com.apple.security.cs.allow-unsigned-executable-memory
+
+ com.apple.security.cs.allow-dyld-environment-variables
+
+ com.apple.security.cs.disable-library-validation
+
+ com.apple.security.inherit
+
+
+ com.apple.security.network.client
+
+ com.apple.security.network.server
+
+
+ com.apple.security.files.user-selected.read-write
+
+
+
diff --git a/apps/desktop/electron-builder.yml b/apps/desktop/electron-builder.yml
new file mode 100644
index 0000000..0aa041e
--- /dev/null
+++ b/apps/desktop/electron-builder.yml
@@ -0,0 +1,57 @@
+# electron-builder config — produces signed .dmg for macOS.
+# Spec: docs/DEVELOPMENT_PLAN.md §4b
+# Milestone: M6-rest
+#
+# Activate by running `electron-builder --mac dmg` from this dir AFTER:
+# 1. `electron` + `electron-builder` are installed as devDeps
+# 2. `vite build` has produced `dist/`
+# 3. `tsc -p tsconfig.electron.json` has produced `dist-electron/`
+# Apple signing requires APPLE_ID + APPLE_APP_SPECIFIC_PASSWORD env vars in CI.
+
+appId: dev.deepcode.client
+productName: DeepCode
+copyright: Copyright © 2026 DeepCode
+
+# Where the packaged app lands locally (gitignored)
+directories:
+ output: release
+ buildResources: build-resources
+
+# Both main + renderer outputs ship inside the .dmg
+files:
+ - dist/**/*
+ - dist-electron/**/*
+ - package.json
+ - '!**/*.{ts,tsx,map}'
+ - '!**/__tests__/**'
+
+# Universal binary (Intel + arm64)
+mac:
+ category: public.app-category.developer-tools
+ target:
+ - target: dmg
+ arch:
+ - universal
+ hardenedRuntime: true
+ gatekeeperAssess: false
+ entitlements: build-resources/entitlements.mac.plist
+ entitlementsInherit: build-resources/entitlements.mac.plist
+ notarize: true
+ icon: build-resources/icon.icns
+
+dmg:
+ title: '${productName} ${version}'
+ icon: build-resources/icon.icns
+ artifactName: '${productName}-${version}-${arch}.dmg'
+
+publish:
+ provider: github
+ releaseType: release
+
+# Linux is a stretch target — disabled until we wire bwrap fully
+linux:
+ category: Development
+ target:
+ - target: AppImage
+ arch:
+ - x64
diff --git a/apps/desktop/package.json b/apps/desktop/package.json
index 448e1e2..82aa7e6 100644
--- a/apps/desktop/package.json
+++ b/apps/desktop/package.json
@@ -2,17 +2,22 @@
"name": "@deepcode/desktop",
"version": "0.0.0",
"private": true,
- "description": "DeepCode Mac desktop client (Electron + React)",
+ "description": "DeepCode Mac desktop client (Electron + React + Tailwind + xterm + monaco)",
"license": "MIT",
"type": "module",
"main": "dist-electron/main.cjs",
"scripts": {
"build": "tsc -p tsconfig.json",
+ "build:renderer": "vite build",
+ "build:electron": "tsc -p tsconfig.electron.json",
+ "build:all": "pnpm build:renderer && pnpm build:electron",
+ "dev": "concurrently \"vite\" \"wait-on http://localhost:5173 && tsc -p tsconfig.electron.json --watch & electron .\"",
+ "pack": "pnpm build:all && electron-builder --dir",
+ "dist": "pnpm build:all && electron-builder --mac dmg",
"typecheck": "tsc -p tsconfig.json --noEmit",
"test": "vitest run --passWithNoTests",
"lint": "echo 'lint: configured in M1' && exit 0",
- "clean": "rm -rf dist dist-electron release *.tsbuildinfo",
- "// notes": "M6-rest will add: dev (vite + concurrent electron), pack (electron-builder), dist (dmg)"
+ "clean": "rm -rf dist dist-electron release *.tsbuildinfo"
},
"dependencies": {
"@deepcode/core": "workspace:*",
@@ -27,6 +32,7 @@
"typescript": "^5.7.0",
"vitest": "^2.1.9"
},
+ "//notes": "Real build deps (electron, electron-builder, vite, @vitejs/plugin-react, tailwindcss, postcss, autoprefixer, concurrently, wait-on) install separately via `pnpm add -D --filter @deepcode/desktop electron electron-builder vite ...` — they pull ~250 MB and slow CI. Configs above are in place; just install the deps when ready to ship the Mac client.",
"engines": {
"node": ">=22"
}
diff --git a/apps/desktop/postcss.config.template.js b/apps/desktop/postcss.config.template.js
new file mode 100644
index 0000000..cbfab53
--- /dev/null
+++ b/apps/desktop/postcss.config.template.js
@@ -0,0 +1,8 @@
+// PostCSS — wires Tailwind + Autoprefixer for the Vite renderer build.
+
+export default {
+ plugins: {
+ tailwindcss: {},
+ autoprefixer: {},
+ },
+};
diff --git a/apps/desktop/src/index.css b/apps/desktop/src/index.css
index eeffd3a..0eefd44 100644
--- a/apps/desktop/src/index.css
+++ b/apps/desktop/src/index.css
@@ -1,8 +1,9 @@
-/* Tailwind / global styles entry — M6 skeleton.
- * Real Tailwind setup (postcss + vite-plugin) lands in M6-rest. The skeleton
- * relies on CSS variables + plain styles so the React tree renders without
- * a build pipeline beyond `tsc`.
- */
+/* 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;
:root {
color-scheme: dark;
diff --git a/apps/desktop/src/index.html b/apps/desktop/src/index.html
new file mode 100644
index 0000000..11a72e9
--- /dev/null
+++ b/apps/desktop/src/index.html
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+ DeepCode
+
+
+
+
+
+
diff --git a/apps/desktop/tailwind.config.ts b/apps/desktop/tailwind.config.ts
new file mode 100644
index 0000000..e7a5475
--- /dev/null
+++ b/apps/desktop/tailwind.config.ts
@@ -0,0 +1,31 @@
+// Tailwind config — DeepCode desktop client.
+// Milestone: M6-rest
+//
+// Color tokens match docs/VISUAL_DESIGN.html. Dark theme is the only mode
+// shipped at v1 (matches Claude Code's look).
+
+import type { Config } from 'tailwindcss';
+
+const config: Config = {
+ content: ['./src/**/*.{ts,tsx,html}'],
+ theme: {
+ extend: {
+ colors: {
+ bg: '#0e0e10',
+ 'bg-elevated': '#18181b',
+ fg: '#f4f4f5',
+ muted: '#71717a',
+ accent: '#a3e635',
+ error: '#f87171',
+ border: '#27272a',
+ },
+ fontFamily: {
+ sans: ['ui-sans-serif', '-apple-system', 'BlinkMacSystemFont', 'system-ui', 'sans-serif'],
+ mono: ['ui-monospace', 'SFMono-Regular', 'Menlo', 'monospace'],
+ },
+ },
+ },
+ plugins: [],
+};
+
+export default config;
diff --git a/apps/desktop/vite.config.template.ts b/apps/desktop/vite.config.template.ts
new file mode 100644
index 0000000..70af9a1
--- /dev/null
+++ b/apps/desktop/vite.config.template.ts
@@ -0,0 +1,36 @@
+// Vite config for the DeepCode desktop renderer.
+// Spec: docs/DEVELOPMENT_PLAN.md §4
+// Milestone: M6-rest
+//
+// Renderer is a single-page React app served from dist/. In dev,
+// `pnpm dev` starts the vite dev server on 5173; the Electron main
+// process points BrowserWindow at http://localhost:5173. In prod,
+// electron-builder packages dist/ alongside dist-electron/.
+
+import { defineConfig } from 'vite';
+import react from '@vitejs/plugin-react';
+import { resolve } from 'node:path';
+
+export default defineConfig({
+ plugins: [react()],
+ root: resolve(__dirname, 'src'),
+ base: './',
+ publicDir: resolve(__dirname, 'public'),
+ server: {
+ port: 5173,
+ strictPort: true,
+ },
+ build: {
+ outDir: resolve(__dirname, 'dist'),
+ emptyOutDir: true,
+ sourcemap: true,
+ rollupOptions: {
+ input: resolve(__dirname, 'src', 'index.html'),
+ },
+ },
+ resolve: {
+ alias: {
+ '@deepcode/core': resolve(__dirname, '..', '..', 'packages', 'core', 'src', 'index.ts'),
+ },
+ },
+});
diff --git a/apps/desktop/vitest.config.ts b/apps/desktop/vitest.config.ts
new file mode 100644
index 0000000..ae513bf
--- /dev/null
+++ b/apps/desktop/vitest.config.ts
@@ -0,0 +1,12 @@
+// Desktop has no renderer tests yet (lands once Electron + Vite deps are
+// installed). Explicitly disable vite + css processing so vitest doesn't
+// try to load vite.config.ts / postcss.config.js with deps absent.
+
+export default {
+ test: {
+ include: ['src/**/*.test.{ts,tsx}'],
+ css: false,
+ },
+ // Disable vite config discovery entirely
+ configFile: false,
+} as const;