# Openspeech Dev

> OpenSpeech 项目（跨平台 AI 语音输入桌面应用，Tauri 2 + React 19 + Rust）的开发规约入口。**当用户在 OpenSpeech 仓库里提出任何开发请求时都应触发**——加功能、改 UI、调快捷键/录音/注入、装依赖、读 docs/ 业务规则都先加载本技能。触发关键词：OpenSpeech、语音输入、听写、录音、快捷键、Tauri、shadcn、Rust 插件、业务规则、词典、历史记录、TypeLess、REST STT、realtime ASR。

- Skill: `openloaf/openspeech-dev` (Agent Skill, multi-file: 5 files)
- Install (CLI): `npx skillmds@latest add openloaf/openspeech-dev`
- Raw SKILL.md: https://api.skillmd.com/api/skills/openloaf/openspeech-dev/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Web & Frontend
- Author: openloaf (https://skillmd.com/u/openloaf)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/openloaf/openspeech-dev

---


# OpenSpeech 开发技能

跨平台 AI 语音输入桌面应用：按住快捷键说话 → 录音 → OpenLoaf SaaS realtime ASR（默认）转写 → 把文字注入当前焦点输入框。

---

## ⚠️ 铁律：SKILL.md 只写源码读不出的事实

**写入本技能前先问自己一句：这条信息能不能用一次 grep / 一次 Read 从源码里直接拿到？能，就不要写。**

| ❌ 不要写进来 | ✅ 应该写进来 |
|---|---|
| 函数 / 类 / 字段 / 模块名（grep 即得） | 决策理由、踩过的坑、外部约束 |
| 配置项的值（读 `package.json` / `Cargo.toml` / `tauri.conf.json` / `capabilities/*.json` 即得） | "为什么必须这样选"——用源码无法表达的取舍 |
| 流程的逐步描述（读对应模块源码即得） | 跨文件 / 跨进程的隐性协作契约 |
| 事件名、命令名、payload 结构（读源码即得） | 哪些 docs/ 文件是该话题的 SSoT |
| 业务规则细节（已在 `docs/*.md` 里） | 指向 docs/ 的索引 |

**当源码已经是 SSoT 时，本技能只列路径 + 一行用途，让 Claude 自己去读。**

---

## 任务路由（先按类别加载对应 reference，再动手）

| 任务类别 | 触发词 | 先读 |
|---|---|---|
| 改 UI / 加文案 / 改样式 / 新建组件 / 新建 Dialog | i18n、文案、TE 风、样式、Logo、drag region、Dialog | 私仓 OpenSpeech-Frontend 的 `openspeech-frontend` skill（src/.claude/skills/openspeech-frontend/） |
| 改窗口 / 托盘 / 关闭行为 / 加 invoke / 加系统权限 / 改开机自启 / 加新 quick panel 模式 | 窗口、托盘、关闭、Cmd+Q、Dock、capability、autostart、overlay、quick panel、Spotlight、nonactivating panel、drag region | `references/desktop-runtime.md` |
| 改录音 / STT / 调 SaaS realtime ASR / 改触发录音 gate / 新增直连 SaaS 链路 | 录音、STT、ASR、PCM、SaaS、未登录、saas-file、chat completions、token 过期、401、被踢登录 | `references/asr-recording.md` |
| 发版 / 改签名 / 测 updater / 加 NSIS 语言 | 发版、签名、updater、NSIS、entitlement | `references/release.md`（执行流程走 `openspeech-release` skill） |
| 让 Claude 自己看 / 操作运行中的 UI | 截图、看一下界面、控制 UI、读 console | `references/tauri-mcp.md` |
| 改业务规则 / 实现新功能 | 录音状态机、快捷键策略、词典、历史、隐私、订阅 | 见下方"业务规则索引" |

---

## 自更新约定

**本技能必须与项目规则保持同步。** 改动以下任一项时，同会话内一并更新本文件或对应 reference：

| 项目变更 | 更新位置 |
|---|---|
| 升降级 / 增删 **前端依赖** 或 **Rust crate / Tauri 插件** | 本文"技术栈关键约束" |
| 变更 **目录结构**（新增 src 子目录、移动文件） | 本文"目录索引" |
| 变更 **构建脚本 / 包管理器 / 工具链** | 本文"包管理与脚本" |
| **新增 / 重命名 / 删除 `docs/*.md`** | 本文"业务规则索引" |
| 出现一条新的、源码读不出的决策 / 跨文件协作 / 踩坑 | 找最贴近的 reference 加一行；若无对应 reference 再开 |

不确定要不要更新时，默认更新。

---

## 项目本质

| 维度 | 约定 |
|---|---|
| 形态 | 桌面应用（Win / macOS / Linux），非 Web、非 CLI |
| 后端 | Rust（`src-tauri/`）：全局快捷键、录音、文本注入、本地 SQLite |
| 前端 | React（`src/`）：主窗口（首页/历史/词典/设置）+ 悬浮录音条子窗口 |
| 模型 | **默认走 OpenLoaf SaaS realtime ASR**（WebSocket 流式）；保留"BYO REST 端点"扩展槽，MVP 仅 SaaS 一路 |
| 隐私立场 | 录音仅落盘本机（`app_data_dir/recordings/<id>.wav`）；除登录态下的 SaaS realtime ASR 外不发任何服务器；详见 `docs/privacy.md` |

---

## 技术栈关键约束（不可随意改）

> 真相来源：`package.json` + `src-tauri/Cargo.toml`。本节只列"读不出的取舍"。

- **Tailwind v4**（不再走 PostCSS，入口 `src/App.css` 用 `@import "tailwindcss"`）
- **React Router v7**（API 与 v6 不兼容，禁止降级）
- **Rust edition 2024**
- **`rdev` = `openloaf-rdev` 0.5.1（OpenLoaf 自有 fork via crates.io，crate 通过 `package = "..."` 重命名）**
  - fork 修的是 macOS event tap 在 `kCGEventTapDisabledByTimeout` / `ByUserInput` 时的自愈，解决 "按下第一次没反应、得再按一次" 偶发 bug。
  - crate 改名因上游名 `rdev` 被 Narsil 占着；业务代码里仍 `use rdev::...`，不用动。
  - `rdev::listen` **进程内只能调一次** —— 新订阅扩展 `src-tauri/src/hotkey/modifier_only.rs`，不要另起 listen。
  - **Windows 上双修饰键同时释放（Win+Ctrl 等）OS 会吞 KEYUP**：这是 Windows 抑制开始菜单的副作用，rdev 忠实转发，所以 hook 队列里就缺那条 release。`modifier_only.rs` 的 OS sync correction 就是兜这个，不是兜 rdev。看到 "OS sync removed stale modifier" 不是 bug。
- **`reqwest` 走 `rustls`**（关 default-features，避免拖入 native-tls）
- **`tauri` 启用 `tray-icon` feature**（托盘依赖，不可移除）
- **`openloaf-saas` 跟随 `@openloaf-saas/sdk` Node 包对齐版本号**
- **桌面前端依赖必须在 `src/package.json` 独立声明**：仅加到主仓不能保证私仓 npm 包构建成功；会议虚拟列表依赖曾因此漏装。

### 添加依赖的规范动作

| 类型 | 命令 |
|---|---|
| Tauri 插件 | `pnpm tauri add <name>` —— 同步处理 Cargo / npm / capabilities / `lib.rs` 注册 |
| Rust 非 Tauri crate | `cargo add --manifest-path src-tauri/Cargo.toml <crate>` |
| shadcn 组件 | `pnpm dlx shadcn@latest add <name>` |

> 任何能被脚手架生成的东西禁止手写同等内容。

### 字体 / UI 体系

- shadcn `base-nova` 风格、`neutral` 基色
- 正文 `@fontsource-variable/inter`，mono `@fontsource/space-mono`
- 视觉语言 = **TE 工业风**（详细规则在私仓 `openspeech-frontend` skill 与软链 `te-industrial-frontend` skill）

---

## 目录索引

```
docs/                           业务规则 SSoT（见"业务规则索引"）
src/                            前端源码（私仓 OpenSpeech-Frontend，公开仓 .gitignore；见私仓
                                openspeech-frontend skill 取详细约定）
src-tauri/
├── src/
│   ├── lib.rs                  tauri::Builder + 插件注册 + setup（纯装配壳，业务已外溢）
│   ├── {events,logging,macos_native,commands,window,tray}.rs  lib.rs 装配辅助（事件常量/日志生命周期/objc/零散命令/窗口显隐/托盘菜单）
│   ├── audio/                  cpal 采集 + WAV 落盘 + PCM16 喂 stt
│   ├── stt/                    OpenLoaf SaaS realtime ASR worker
│   ├── hotkey/                 combo / modifierOnly / doubleTap 三路编排
│   ├── permissions/            macOS 系统权限检测 / 请求 / 跳转
│   ├── secrets/                keyring 包装
│   ├── inject/                 文本注入（剪贴板 + 粘贴）
│   ├── openloaf/               SaaS 登录 / token / 用户档案 / 支付
│   └── db/                     SQLite 迁移 + recordings_dir 帮手
├── capabilities/               权限声明（default.json + desktop.json）
├── examples/                   离线诊断脚本（如 test_realtime_asr）
└── tauri.conf.json
.claude/skills/
├── openspeech-dev/             本技能
│   ├── SKILL.md                入口（本文）
│   └── references/             按任务类别拆分的细则
├── te-industrial-frontend/     软链：TE 风实现指南
└── openloaf-saas-sdk-rust/     软链：SaaS SDK 用法
```

> 子模块的具体函数 / 字段 / 事件名 **直接读对应文件**，不在本技能复述。

---

## 包管理与脚本

包管理器：**pnpm**（corepack 已启，10+）。不要换 npm/yarn/bun。

| 命令 | 作用 |
|---|---|
| `pnpm tauri dev` | **桌面开发主命令** |
| `pnpm tauri build` | 打安装包 |
| `pnpm dev` | 仅 Vite，纯调 UI 时用 |
| `pnpm build` | `tsc && vite build`，验前端类型 |
| `pnpm version patch\|minor\|major` | 唯一发版动作（详见 `references/release.md`） |

---

## 通用开发规约

- 路径别名 `@/` 已配；新建文件优先用 `@/...`。
- Rust 新模块在 `src-tauri/src/<domain>/mod.rs`；`lib.rs` 只装配。
- 任何面向用户的行为**先读 `docs/` 对应文件**，不要凭感觉实现。
- 状态管理：跨窗口持久化 → `tauri-plugin-store` 或 SQLite；单窗口 UI → Zustand；**不用 Redux / Jotai / Context 大杂烩**。

---

## 业务规则索引（docs/）

**代码实现前先读对应 docs 文件。** 业务规则 SSoT 在这里，本技能不复述。

| 任务 | 先读 |
|---|---|
| 产品定位 / TypeLess 对比 | `docs/product.md` |
| 实现或讨论任一功能 | `docs/features.md` |
| **录音 / 快捷键触发 / 转写 / 注入** 状态机与边界 | `docs/voice-input-flow.md` |
| 设计或改动快捷键 | `docs/hotkeys.md` |
| 历史记录 + SQLite schema | `docs/history.md` |
| 词典 + hints | `docs/dictionary.md` |
| 设置页任一项 | `docs/settings.md` |
| 数据流 / 本地存储 / 上传边界 | `docs/privacy.md` |
| 麦克风 / 辅助功能 / Wayland 等权限 | `docs/permissions.md` |
| 首次启动向导 | `docs/onboarding.md` |
| 计费 / 试用 / 账户（MVP 暂缓） | `docs/subscription.md` |
| 多 Provider / Adapter / 不登录可用 / 开源接入 | `docs/speech-providers.md` |

新增或重命名 `docs/*.md` 时回来更新此表。

---

## 反模式

- ❌ 在 SKILL.md 或 reference 里复述能从源码 grep 出来的事实（违反铁律）
- ❌ 直接在主窗口写内联颜色 / 硬编码 `bg-black` 等
- ❌ 跳过 `pnpm tauri add` 手动改 Cargo.toml + lib.rs
- ❌ 手写 `src/components/ui/*.tsx` 而不用 shadcn CLI
- ❌ 为了"兼容" v6 API 降级 react-router-dom
- ❌ API Key 明文存 `tauri-plugin-store`（应走 `keyring`，详见 `references/desktop-runtime.md`）
- ❌ Rust 侧重写 Tauri 官方插件已覆盖的能力
- ❌ 改了技术栈 / 目录 / docs 不同步本技能

---

## 协作技能

- **`openspeech-frontend`**（私仓 OpenSpeech-Frontend `.claude/skills/`，仅本机 src/ 在时可见）—— 前端规约（i18n / TE 样式 / Drag region / Dialog / Logo），做 UI / 改文案 / 调样式时先加载。
- **`te-industrial-frontend`**（软链）—— TE 工业风实现指南，做 UI 时优先激活。
- **`openloaf-saas-sdk-rust`**（软链）—— 任何登录 / 用户档案 / AI 工具 / realtime ASR 实现先读它。
- **`openspeech-release`** —— 发版执行流程入口（本技能 `references/release.md` 只讲"为什么这样"）。
- 写 PRD / 提案 → `create-prd` / `create-proposal`，但 OpenSpeech 业务规则主位 `docs/`。
- Tauri / Claude API / 前端通用最佳实践 → `claude-api` / `vercel-react-best-practices` / `document-skills:frontend-design`，**本技能优先**。

