# Pawcast Architecture · v0.1 dev

> **Doc type:** ARCHITECTURE（项目架构设计）
> **Refers from:** `2026-05-02-pawcast-roadmap.md`
> **Project root:** `/Users/luzhipeng/projects/pawcast`

**Goal:** 设计一个能在 macOS 开发机上完整跑通的 Electron + Bun 桌面应用骨架，4 大功能模块（计票 / 探索 / 私信 / 监控）共享同一份采集层、AI orchestrator 和挂件 server。

---

## 1. 顶层架构（4 进程模型）

```
┌──────────────────────────────────────────────────────────────────┐
│                    Pawcast.app (macOS)                           │
│                                                                   │
│  ┌─────────────────┐    ┌─────────────────────────────────────┐ │
│  │ Renderer Process│    │       Main Process (Bun)            │ │
│  │   (React 19)    │    │                                     │ │
│  │                 │◀──▶│  ┌─────────┐ ┌──────────────────┐   │ │
│  │  - 5 Tab UI     │IPC │  │  IPC    │ │ Domain Modules   │   │ │
│  │  - Settings UI  │    │  │ Router  │ │  - vote-engine   │   │ │
│  │  - All forms    │    │  └─────────┘ │  - explorer      │   │ │
│  └─────────────────┘    │              │  - dm-center     │   │ │
│                         │              │  - live-monitor  │   │ │
│  ┌─────────────────┐    │              └──────────────────┘   │ │
│  │ Worker Threads  │◀───┤                       │             │ │
│  │  (per room WS)  │    │              ┌────────┴─────────┐   │ │
│  └─────────────────┘    │              │ tiktok-engine    │   │ │
│                         │              │ ai-orchestrator  │   │ │
│  ┌─────────────────┐    │              │ widget-server    │   │ │
│  │ Playwright      │◀───┤              │ persistence (db) │   │ │
│  │  (sub process)  │    │              └──────────────────┘   │ │
│  └─────────────────┘    └─────────────────────────────────────┘ │
│                                                                   │
│                         ┌─────────────────────────────────────┐ │
│                         │   Widget Server (Hono on Bun)       │ │
│                         │   127.0.0.1:17777                   │ │
│                         │                                     │ │
│                         │   GET /widget/<type>?preset=…       │ │
│                         │   WS  /ws/widget/<type>/<id>        │ │
│                         └─────────────────────────────────────┘ │
│                                       ▲                          │
└───────────────────────────────────────┼──────────────────────────┘
                                        │
                              ┌─────────┴─────────┐
                              │  OBS / Live Studio│
                              │  Browser Source   │
                              └───────────────────┘
```

### 4 类进程

| 进程 | 个数 | 职责 |
|---|---|---|
| **Renderer** | 1 | React UI · 用户交互 · 通过 IPC 调主进程 RPC |
| **Main** | 1 | 业务逻辑核心 · DB · IPC · 调度 worker / playwright · 内嵌 widget server |
| **Worker Threads** | 0~16 | 每个 TikTok 直播间一个 worker 跑 WebSocket，避免阻塞主线程 |
| **Playwright child** | 0~3 | 探索抓页 / DM 发送 / 弹窗模式登录，用子进程隔离崩溃 |

---

## 2. Monorepo 目录结构

```
pawcast/
├── design/                          # 9 份 HTML 设计稿（已部署 pawcast.catclaw.cat）
├── docs/
│   └── superpowers/
│       ├── plans/                   # 本 plan + roadmap + 各 phase 详细 plan
│       └── specs/                   # 之前的 spec.html 转 .md
│
├── apps/
│   ├── desktop/                     # Electron app
│   │   ├── electron/
│   │   │   ├── main.ts              # Electron main entry
│   │   │   ├── preload.ts           # contextBridge 安全桥
│   │   │   └── window.ts            # BrowserWindow 创建
│   │   ├── src/                     # Renderer (React)
│   │   │   ├── main.tsx             # React entry
│   │   │   ├── App.tsx              # 5 Tab 主壳 + 路由
│   │   │   ├── pages/
│   │   │   │   ├── Dashboard/
│   │   │   │   ├── Vote/
│   │   │   │   ├── Explore/
│   │   │   │   ├── DM/
│   │   │   │   ├── Monitor/
│   │   │   │   └── Settings/
│   │   │   ├── components/          # 共享 UI（Button / Card / Modal / Toggle…）
│   │   │   ├── hooks/               # useIPC / useLiveQuery / …
│   │   │   ├── stores/              # Zustand stores（一模块一 store）
│   │   │   ├── lib/
│   │   │   │   ├── ipc-client.ts    # 类型化 IPC 客户端（包装 contextBridge）
│   │   │   │   └── tokens.ts        # Tailwind 变量映射
│   │   │   └── styles/
│   │   │       └── globals.css      # Tailwind import + 全局样式
│   │   ├── public/
│   │   ├── index.html               # Vite entry
│   │   ├── vite.config.ts
│   │   ├── tailwind.config.ts
│   │   ├── tsconfig.json
│   │   └── package.json
│   │
│   └── widgets/                     # 挂件 HTML（独立的 Vite app，构建到 widget-server 服务）
│       ├── src/
│       │   ├── sticker-dance/
│       │   ├── duel-dance/
│       │   ├── multi-pk/
│       │   ├── solo-stage/
│       │   ├── freedom/
│       │   ├── ranking/
│       │   ├── user-round-rank/
│       │   ├── profile-border/
│       │   ├── voting-tool/
│       │   ├── lucky-game/
│       │   ├── lucky-fans/
│       │   └── shared/              # 共用 component（pulse 动画 / 数字滚动）
│       ├── vite.config.ts           # multi-page build → 输出多个独立 HTML
│       └── package.json
│
├── packages/                        # 内部共享包，所有 app 都能 import
│   │
│   ├── core/                        # 业务核心 · 不依赖 UI / Electron
│   │   ├── src/
│   │   │   ├── tiktok-engine/       # WebCast 协议封装
│   │   │   │   ├── connection.ts    # RoomConnection 类
│   │   │   │   ├── manager.ts       # 多房并发管理
│   │   │   │   ├── poller.ts        # 30s 巡检
│   │   │   │   ├── recorder.ts      # JSONL 录制器
│   │   │   │   ├── replayer.ts      # 回放器
│   │   │   │   └── types.ts         # GiftEvent / ChatEvent / PKEvent…
│   │   │   ├── playwright-engine/   # 浏览器自动化
│   │   │   │   ├── pool.ts          # 子进程池管理
│   │   │   │   ├── route.ts         # Route One/Two 切换
│   │   │   │   ├── cookie-store.ts  # macOS Keychain 加密
│   │   │   │   └── tasks/
│   │   │   │       ├── crawl-creator.ts
│   │   │   │       ├── send-dm.ts
│   │   │   │       └── extract-frames.ts
│   │   │   ├── ai-orchestrator/     # 3 Provider 抽象
│   │   │   │   ├── provider.ts      # Provider 接口
│   │   │   │   ├── anthropic.ts
│   │   │   │   ├── openai.ts
│   │   │   │   ├── qwen.ts
│   │   │   │   └── usage-tracker.ts
│   │   │   ├── vote-engine/         # 5 玩法引擎
│   │   │   │   ├── base.ts
│   │   │   │   ├── sticker-dance.ts
│   │   │   │   ├── duel-dance.ts
│   │   │   │   ├── multi-pk.ts
│   │   │   │   ├── solo-stage.ts
│   │   │   │   ├── freedom.ts
│   │   │   │   ├── gift-binding.ts
│   │   │   │   └── interactive-vote.ts
│   │   │   ├── explorer/            # 探索主播业务
│   │   │   │   ├── scan.ts
│   │   │   │   ├── score-7d.ts
│   │   │   │   └── analyze-video.ts
│   │   │   ├── dm-center/           # DM 业务
│   │   │   │   ├── queue.ts
│   │   │   │   ├── rate-limiter.ts
│   │   │   │   ├── risk-monitor.ts
│   │   │   │   ├── template-engine.ts
│   │   │   │   ├── ai-drafter.ts
│   │   │   │   └── follow-up.ts
│   │   │   ├── live-monitor/        # 监控业务
│   │   │   │   ├── grid-state.ts
│   │   │   │   ├── pk-tracker.ts
│   │   │   │   ├── alert-engine.ts
│   │   │   │   └── heatmap.ts
│   │   │   └── index.ts
│   │   ├── tsconfig.json
│   │   └── package.json
│   │
│   ├── db/                          # SQLite 持久化层
│   │   ├── src/
│   │   │   ├── client.ts            # bun:sqlite 单例 + WAL 配置
│   │   │   ├── migrations/
│   │   │   │   ├── 001-initial.sql  # 初始 10 张表
│   │   │   │   └── runner.ts        # 顺序执行 migration
│   │   │   ├── repositories/        # 一表一 repository
│   │   │   │   ├── broadcasters.ts
│   │   │   │   ├── presets.ts
│   │   │   │   ├── live-sessions.ts
│   │   │   │   ├── gift-events.ts
│   │   │   │   ├── chat-events.ts
│   │   │   │   ├── pk-battles.ts
│   │   │   │   ├── dms.ts
│   │   │   │   ├── dm-templates.ts
│   │   │   │   ├── tt-users.ts
│   │   │   │   └── settings.ts
│   │   │   └── types.ts             # 表 row 类型
│   │   └── package.json
│   │
│   ├── widget-server/               # 主进程内嵌 Hono server
│   │   ├── src/
│   │   │   ├── server.ts            # Hono app + listen 17777
│   │   │   ├── routes/
│   │   │   │   ├── widget.ts        # GET /widget/:type
│   │   │   │   └── ws.ts            # WS /ws/widget/:type/:id
│   │   │   ├── pubsub.ts            # in-memory event bus
│   │   │   └── types.ts             # WidgetEvent
│   │   └── package.json
│   │
│   ├── ipc-contract/                # IPC RPC schema · 主↔渲染共用
│   │   ├── src/
│   │   │   ├── methods.ts           # 所有 RPC method 定义（Zod schema）
│   │   │   ├── events.ts            # 主→渲染 push event 定义
│   │   │   └── index.ts             # 导出类型 ContractType
│   │   └── package.json
│   │
│   └── ui-kit/                      # 跨 desktop / widgets 共享的视觉 token + 基础组件
│       ├── src/
│       │   ├── tokens.css           # CSS variables（紫橙双色 / spacing / font）
│       │   ├── components/
│       │   │   ├── Pill.tsx
│       │   │   ├── Toggle.tsx
│       │   │   ├── Button.tsx
│       │   │   └── …
│       │   └── icons/               # 内嵌 SVG icon 集合
│       └── package.json
│
├── tests/
│   ├── e2e/                         # Playwright 端到端测试
│   ├── fixtures/                    # 录制的 TikTok 事件流回放数据
│   └── utils/
│
├── scripts/
│   ├── dev.ts                       # `bun dev` 入口（并发启 vite + electron + widget server）
│   ├── seed-db.ts                   # 灌测试数据
│   └── replay-recording.ts          # 用录制 JSONL 喂事件流测试
│
├── .gitignore
├── .biome.json                      # 代码格式化 + lint
├── tsconfig.base.json               # 所有 package 继承
├── package.json                     # 根 workspace（bun workspaces）
└── README.md
```

### 关键设计原则

1. **monorepo via `bun workspaces`**：`apps/*` + `packages/*` 都在一个 git repo，互相 import 用 `@pawcast/core`、`@pawcast/db` 这样的别名。
2. **`packages/core` 不依赖 UI / Electron**：纯业务逻辑，可在测试里 import 跑（不开 Electron）。
3. **`packages/db` 单一职责**：所有 SQLite 操作必须经过 repository，业务层不直接拼 SQL。
4. **`packages/ipc-contract` Zod schema 双端共用**：主进程和渲染进程用同一份 schema 定义，类型 100% 对齐 + runtime 校验。
5. **Vite 多入口构建挂件**：`apps/widgets` 一次 build 出 11 个独立 HTML（每个挂件一个），widget-server 直接 serve 这些 HTML。
6. **测试基础设施靠 Vitest**：`packages/core/**/*.test.ts` 跑核心逻辑测试；`apps/desktop/src/**/*.test.tsx` 跑 React 组件测试；`tests/e2e` 跑端到端。

---

## 3. 数据流详细图

### 3.1 TikTok 礼物事件 → 计票挂件渲染

```
TikTok WebCast Server
         │ WebSocket
         ▼
┌────────────────────────┐
│ Worker Thread           │   一个 thread 一个直播间
│ (room: @link8_kr)       │
│                         │
│ tiktok-live-connector   │
│   ↓ raw event           │
│ recorder (写 JSONL)     │
│   ↓                     │
│ post message → main     │
└──────────┬──────────────┘
           │ structured event
           ▼
┌────────────────────────────────────────┐
│ Main Process                            │
│                                          │
│ tiktok-engine.manager.onGiftEvent()    │
│   ↓                                      │
│ db.giftEvents.insert(event)            │
│   ↓                                      │
│ vote-engine.processGift(event)         │
│   ↓ updates score/binding              │
│ widget-server.pubsub.publish('vote_update', payload)
│   ↓                                      │
│ ipc.send('vote.score_update', payload) │
└──────────┬──────────────┬──────────────┘
           │              │
           ▼              ▼
   Renderer (React)   Widget Page (Browser Source)
   - 计票器运行态     - 礼物贴纸脉冲
     UI 数字跳动        + 数字增量动画
```

### 3.2 探索主播完整链路

```
用户在 Explorer 输入关键词 "dance cover"
         │ ipc('explorer.startScan', {keywords})
         ▼
Main: explorer.scan() {
  for each keyword:
    playwright.crawl-creator → 抓 50 位主播 metadata
    for each creator:
      ffmpeg.extract-frames → 抽 12 帧
      ai-orchestrator.score7d → GPT-4o Vision + Claude
      db.broadcasters.upsert(creator)
      ipc.send('explorer.creator_scored', creator)  // 渐进式推到 UI
}
         │
         ▼
Renderer: zustand store 接增量 → 主页瀑布流自动 re-render
```

### 3.3 私信发送（带速率 + 风控）

```
用户点击「采纳并发送」
         │ ipc('dm.queueMessage', {to, content})
         ▼
Main: dm-center.queue.add(job)
         │
         ▼
dm-center.rate-limiter loop {
  every 3-7 minutes:
    next = queue.peek()
    if rate-limit ok:
      playwright.send-dm(next.to, next.content)
      if success: db.dms.update({sent_at})
      if captcha: risk-monitor.trigger() → ipc.send('dm.risk_alert')
                  pause queue 12h
}
         │
         ▼
Renderer: AI 起草 banner 消失，UI 显示「已加入队列」+ 预计发送时间
```

---

## 4. SQLite Schema（10 张核心表）

按 spec §6.2 落地。完整 SQL 写在 Phase 1 的 `001-initial.sql`。

| 表 | 字段要点 | 用途 |
|---|---|---|
| `broadcasters` | id, tt_user_id, status (discovered/contacted/signed/declined), scores_json | 探索池 + 团员库共用 |
| `presets` | id, name, mode, config_json, cast_json, is_active | 12 玩法预设 |
| `live_sessions` | id, broadcaster_id, tt_room_id, start_at, end_at, total_gifts, total_coins | 一次开播一行 |
| `gift_events` | id, session_id, user_id, gift_id, count, coins, bound_to, ts | 礼物明细 |
| `chat_events` | id, session_id, user_id, text, vote_for, ts | 评论（互动投票用） |
| `like_events` | id, session_id, user_id, count, ts | 点赞 |
| `pk_battles` | id, my_room, other_room, start_at, end_at, my_score, other_score, winner | PK 战役 |
| `dms` | id, to_broadcaster, template, draft, final, timeline_json, outcome | 私信 |
| `dm_templates` | id, name, category, content, variants_json, usage_count, reply_rate | 模板 |
| `tt_users` | id, tt_user_id, display, avatar_url, total_coins, last_seen | 送礼者档案 |
| `settings` | key, value | k-v 设置（AI Key / 路由 / 主题…） |

**索引**：
- `gift_events(session_id)` / `(user_id)`
- `dms(to_broadcaster)` / `(outcome)`
- `broadcasters(status)` / `(tt_user_id UNIQUE)`

---

## 5. IPC Contract（Zod schema 共用）

`packages/ipc-contract/src/methods.ts` 定义所有 RPC method：

```typescript
import { z } from 'zod';

export const VoteContract = {
  'vote.listPresets': {
    input: z.void(),
    output: z.array(PresetSchema),
  },
  'vote.savePreset': {
    input: PresetSchema,
    output: z.object({ id: z.number() }),
  },
  'vote.startCollecting': {
    input: z.object({ presetId: z.number() }),
    output: z.object({ sessionId: z.number() }),
  },
  // ...
};

export const ExplorerContract = { /* ... */ };
export const DMContract = { /* ... */ };
export const MonitorContract = { /* ... */ };
export const SettingsContract = { /* ... */ };

export type ContractType = typeof VoteContract & typeof ExplorerContract & /* ... */;
```

**主进程注册：**

```typescript
// apps/desktop/electron/main.ts
import { VoteContract } from '@pawcast/ipc-contract';
import { voteEngine } from '@pawcast/core/vote-engine';

ipcMain.handle('vote.listPresets', async () => {
  const result = await voteEngine.listPresets();
  return VoteContract['vote.listPresets'].output.parse(result);  // runtime 校验
});
```

**渲染进程调用：**

```typescript
// apps/desktop/src/lib/ipc-client.ts
import type { ContractType } from '@pawcast/ipc-contract';

export const ipc = {
  invoke: <K extends keyof ContractType>(
    method: K,
    input: z.infer<ContractType[K]['input']>,
  ): Promise<z.infer<ContractType[K]['output']>> => {
    return window.pawcast.invoke(method, input);
  },
};

// 业务代码
const presets = await ipc.invoke('vote.listPresets', undefined);
//    ^ 自动推导为 Preset[]
```

类型 100% 对齐，零手写 type duplication。

---

## 6. 模块边界与依赖规则

```
                  ┌─────────────────┐
                  │  apps/desktop   │
                  │  apps/widgets   │
                  └────────┬────────┘
                           │ 仅 import packages，不互相 import
                           ▼
        ┌──────────────────┬──────────────────┐
        │                  │                  │
        ▼                  ▼                  ▼
┌──────────────┐  ┌────────────────┐  ┌──────────────┐
│   ui-kit     │  │ ipc-contract   │  │    core      │
│              │  │                │  │              │
│ (共享视觉)    │  │ (共享 Zod)     │  │ (业务核心)    │
└──────────────┘  └────────────────┘  └───────┬──────┘
                                              │
                                              ▼
                                      ┌──────────────┐
                                      │  db          │
                                      │              │
                                      │ widget-server│
                                      └──────────────┘
```

**规则**：
- ✅ `apps/*` 可以 import `packages/*`
- ✅ `packages/core` 可以 import `packages/db` 和 `packages/widget-server`
- ❌ `packages/db` / `packages/widget-server` / `packages/ui-kit` / `packages/ipc-contract` 不能 import `packages/core`（避免循环）
- ❌ 任何 `packages/*` 都不能 import `apps/*`
- ✅ 同一 package 内自由 import

ESLint 规则强制 enforce（用 `eslint-plugin-boundaries` 或 Biome 规则）。

---

## 7. 测试策略

### 单元测试（Vitest，每 package 内）

- `packages/core/**/*.test.ts`：业务逻辑测试，可用录制的 JSONL 模拟 TikTok 事件流
- `packages/db/**/*.test.ts`：repository 单元测试，每测试 spawn 一个内存 SQLite
- `apps/desktop/src/**/*.test.tsx`：React 组件测试 + zustand store 测试

### 集成测试（Vitest，跨 package）

- `tests/integration/`：模拟 IPC 流程（main + renderer 通信）
- `tests/integration/widget-flow.test.ts`：模拟 widget WS 推送 → 浏览器接收

### 端到端（Playwright，可选 v0.2 加）

- `tests/e2e/`：启动真 Electron app，模拟用户点击，验证全链路

### Test Fixtures

- `tests/fixtures/recorded-events/yoochan-2025-06-15.jsonl`：真实 TikTok 录制
- `tests/fixtures/mock-creators/`：探索主播 mock metadata
- 所有外部依赖（TikTok WS / Playwright / AI API）都可用 fixture 替换

---

## 8. 性能预算（目标）

| 维度 | 目标 | 限制理由 |
|---|---|---|
| 启动时间 | < 3s 到 UI 可点击 | 用户体验底线 |
| 8 房间并发 | CPU < 30%, RAM < 800MB | M1 Mac 上验证 |
| 礼物事件延迟 | TikTok 收到 → UI 显示 < 200ms | 直播实时感 |
| 挂件 WS 延迟 | 主进程 push → 挂件渲染 < 50ms | OBS 端流畅 |
| .xlsx 导出 | 1000 条礼物 < 2s | 用户等待容忍 |

---

## 9. 关键技术决策（带理由）

### 9.1 Bun 而非 Node.js
- **理由**：bun:sqlite 内置（不需 better-sqlite3 + native build）+ TypeScript 原生 + 启动快
- **风险**：Bun 在 Electron main 进程兼容性需验证（spec 假设可行；Phase 1 第一个 task 就是验证）
- **回退**：如不行回退到 Node + better-sqlite3，但所有 `bun:` API 需替换。预估回退成本 1 天

### 9.2 Zustand 而非 Redux / Jotai
- **理由**：API 极简、无 boilerplate、TypeScript 友好、bundle 小
- **每个模块一个 store**：`useVoteStore`、`useExploreStore`、`useDMStore`、`useMonitorStore`、`useSettingsStore`

### 9.3 TanStack Query 处理 IPC 数据
- **理由**：缓存 + 重试 + 后台刷新都内置；让 IPC 调用也享受 React Query 生态
- **集成**：`useQuery({ queryFn: () => ipc.invoke('vote.listPresets') })`

### 9.4 Tailwind 而非 CSS-in-JS
- **理由**：和挂件页面共用 token 一致，避免双套样式系统；构建时静态生成 CSS，运行时零开销
- **黑暗主题为默认**：所有变量都是深色，浅色主题 v0.5 再加

### 9.5 Hono 而非 Express / Koa
- **理由**：Bun 原生支持 Hono，性能最好；TypeScript 一等公民；适合 widget 这种"少量 endpoint + 大量 WS"场景

### 9.6 contextBridge + Zod 而非 IPC 自由调用
- **理由**：安全（防止 XSS 攻击逃逸到主进程）+ 类型安全（编译期 + 运行期双重校验）
- **代价**：每个新 RPC 都要在 contract 文件加一行，但对长期可维护性值得

### 9.7 Worker Thread 而非主线程跑 WS
- **理由**：8 个 TikTok WS 并发解析事件可能有计算尖峰，避免阻塞主进程的 IPC 响应
- **代价**：worker 与 main 通信走 message passing，需要 serialize；事件流大但每条小，开销可接受

### 9.8 Playwright 子进程而非主进程
- **理由**：Playwright 浏览器进程崩溃概率非零，子进程隔离避免拖死整个 app
- **代价**：跨进程 IPC 复杂度上升

---

## 10. macOS 开发环境前提

| 工具 | 版本 | 安装 |
|---|---|---|
| Node.js | ≥ 20（Electron 要） | `nvm install 20` |
| Bun | ≥ 1.2 | `curl -fsSL https://bun.sh/install \| bash` |
| Playwright browsers | latest | `bunx playwright install chromium` |
| ffmpeg | latest | `brew install ffmpeg` |
| Git | any | macOS 自带 |
| Xcode CLI | for native modules | `xcode-select --install` |

dev mode 不需要 license / 公证，只要 `bun dev` 跑起来即可。

---

## 11. 命名规范

| 类型 | 规则 | 例 |
|---|---|---|
| 文件名 | `kebab-case.ts` | `vote-engine.ts` |
| React 组件文件 | `PascalCase.tsx` | `VoteConfig.tsx` |
| 类 | `PascalCase` | `RoomConnection` |
| 函数 / 变量 | `camelCase` | `processGift()` |
| 常量 | `SCREAMING_SNAKE` | `DEFAULT_RATE_LIMIT` |
| TypeScript 类型 | `PascalCase` | `GiftEvent`、`VoteContract` |
| 测试文件 | `*.test.ts` 同目录 | `vote-engine.test.ts` |
| Zustand store | `useXxxStore` | `useVoteStore` |
| Hook | `useXxx` | `useLiveQuery` |
| IPC method | `module.action` | `vote.listPresets` |
| DB 表 | `snake_case` 复数 | `gift_events` |
| DB 列 | `snake_case` | `tt_user_id` |
| Git branch | `feat/phase-N-xxx` 或 `fix/xxx` | `feat/phase-1-foundation` |
| Commit | conventional commits | `feat(vote): add sticker dance engine` |

---

## 12. 错误处理策略

### 主进程内
- 业务错误：用 `Result<T, E>` 类型（neverthrow 库）传递
- 程序错误：throw → 顶层 IPC 处理器 catch → 推 `error` event 到渲染层

### 渲染进程内
- TanStack Query 自动 retry
- ErrorBoundary 包整个 App，挂掉时显示 fallback 页面

### 不允许
- 在业务逻辑里 `try { … } catch { /* 吞了 */ }` 隐藏错误
- 任何 `console.log` 残留在生产代码（统一用 `packages/core/logger.ts`）

---

## 13. 日志策略

`packages/core/logger.ts` 提供统一 logger：

```typescript
import { logger } from '@pawcast/core/logger';

logger.info('vote-engine', 'Preset 2 started', { presetId: 2 });
logger.warn('dm-center', 'Rate limit reached');
logger.error('tiktok-engine', 'WS disconnect', { error });
```

输出：
- dev mode: 控制台 colored
- 实际写盘：`~/.pawcast/logs/main-YYYY-MM-DD.log`，按天 rotate
- 错误级别走单独文件 `error.log`

---

## 14. 开发命令

```bash
# 第一次 setup
bun install

# 开发
bun dev             # 同时启动: vite (renderer) + electron (main) + widget server
bun dev:renderer    # 只启 renderer，方便用浏览器调试 UI
bun dev:main        # 只启 main，配合现有 renderer
bun dev:widgets     # 只启 widget server + 挂件 vite

# 测试
bun test            # 跑全部测试
bun test:watch      # watch mode
bun test:integration

# 类型检查 + lint
bun typecheck
bun lint
bun fmt             # biome format

# DB
bun seed            # 灌测试数据
bun db:reset        # 删 + 重建
bun replay <file>   # 用录制 JSONL 喂事件流

# Build (v0.1 dev 不需要，v0.2 加)
# bun run build
# bun run dist:mac
```

---

## 15. v0.1 dev 不实现的（明确边界）

- ❌ License / 设备绑定 / 试用期
- ❌ Stripe / 支付集成
- ❌ Windows 打包 / 自动更新
- ❌ macOS 公证 / 签名（仅 dev 跑）
- ❌ 多语言（i18n 框架预留 hook，但实际只写中文）
- ❌ 主题切换（写死深色）
- ❌ 错误自动上传服务器
- ❌ 匿名使用统计
- ❌ 云端备份 / 多设备同步
- ❌ 挂件 SDK / 挂件市场

明确这些不做，能让 Phase 1 的脚手架尽量精简，不引入早期不必要的抽象。

---

## End

下一步：基于本架构写 Phase 1 详细 plan。
