发布记录
写写贴 (wpaste) 的产品发布历程。每条目按发布时间倒序排列。
说明:由于早期提交历史被 squash 成单个 commit,初期版本的具体时间线未能完整保留。本文档从当前可还原的里程碑开始记录。
🆕 2026-04-11(下午)· 镜像体系完善 + 迁移链修复
在上午交付的镜像 MVP 基础上,补齐了四件事:长文档分段翻译、增量翻译缓存、术语表、套餐配额。HTML 站点镜像暂不做,其余全部上线。
1. 长文档分段翻译 + 增量翻译缓存
核心改动:lib/mirror.ts 的 translateMarkdown 从"一次性 LLM 调用"改成"按 H2 边界切分的块级流水线"。
分块策略 (splitIntoBlocks):
- 按
^##切分文档;跳过 fenced code block 内的##避免误判 - Block 0 = 文档开头到第一个 H2(frontmatter + H1 + 引言)
- Block 1..N = 每个 H2 到下一个 H2
- 没有 H2 的短文档 → 单块路径(行为与之前一致)
增量缓存 (document_mirrors.block_cache):
- 新列,JSON 格式
{ sha256(blockContent): translatedBlock } - 每次
regenerateMirror前加载历史缓存 - 每个块:命中缓存 → 0 LLM 调用直接复用;未命中 → 翻译并写入新缓存
- 翻译完成后把新缓存持久化回
block_cache列
效果:
- 长文档(多 H2)天然被切成多次小 LLM 调用,不再受上下文上限约束
- 完全未改的源文档重译 → 0 次 LLM 调用
- 只改一个段落 → 只有那一个块被重译,其他块复用
wpaste mirror <id> --lang en --regenerate(force: true)清空缓存读,强制从头重译
2. 术语表 (Glossary)
数据模型:新表 user_glossaries,唯一索引 (user_id, source_term, language)。每用户每目标语种维护一份术语表。
REST API /api/v1/glossary:
GET [?language=en]— 列出(可按目标语种过滤)POST { sourceTerm, translation, language }— upsert:已存在 → 200updated: true;新建 → 201created: trueDELETE ?id=...
CLI:
wpaste glossary list [--lang zh|en|ja]
wpaste glossary add "写写贴" "wpaste" --lang en
wpaste glossary remove <id>
Prompt 注入:regenerateMirror 自动 loadGlossary(userId, language) 读出用户在目标语种下的所有术语,追加到翻译 system prompt 的 Glossary — you MUST use these exact translations ... 段落里。
注意:术语表只对新翻译的块生效,历史缓存不会被自动 re-apply。想让新术语完整生效:跑一次 wpaste mirror <id> --lang en --regenerate。
3. 套餐配额 (Plan Quotas)
在 lib/plan-limits.ts 加两项限额:
| Plan | maxMirrors | maxGlossaryEntries |
|---|---|---|
| free | 5 | 10 |
| pro | 500 | 200 |
| team | 5000 | 2000 |
执行点:
POST /api/v1/documents/:id/mirrors— 仅在新建时检查;超限返回403+code: "LIMIT_MIRRORS"+upgrade_urlPOST /api/v1/glossary— 同理,upsert 更新免配额,只有created=true的 insert 触发LIMIT_GLOSSARY- 幂等行为:已有
(source, language)镜像或(term, language)术语再 POST 不消耗额度
4. 迁移链修复
背景:fresh local SQLite init 一直是坏的 —— 0000 → 0002 的迁移链里丢了一个加 documents.tags 的迁移,导致 0002 的 INSERT INTO __new_documents ... SELECT tags FROM documents 在新库上炸 no such column: tags。
修复:scripts/init-db.ts 从"按序 replay 所有 migration SQL"改成 shell out 到 drizzle-kit push,绕过坏掉的链直接从 db/schema.ts 同步 schema。drizzle.config.ts 加 dbCredentials.url 读 DATABASE_PATH 环境变量,让 self-hoster 能指定自定义路径。
Cloudflare D1 不受影响:线上 D1 早在 bug 出现之前就 bootstrap 过了,仍然走 wrangler d1 migrations apply。今天顺手把 0014 / 0015 / 0016 一起 apply 到了 production D1。
5. CLI Marp 误判修复
症状:今天上午发"产品功能总览"到 wpaste 时,CLI 把它误判成 Marp 幻灯片(docType: "slides")。触发条件是文档里字面包含了 marp: true(作为功能说明)+ 文档有多个 --- 水平线分隔。
根因:cli/src/commands/publish.ts 的检测正则 /^---[\s\S]*?marp:\s*true[\s\S]*?---/m 因为 [\s\S]*? 跨越 frontmatter 边界,把"正文提到 marp: true + 后面有 ---"的任何文档都判成幻灯片。
修复:抽成独立纯函数 detectMarpSlides(content),先严格匹配文档开头的 ^---\s* ([\s\S]*?) ---,再在提取出的 frontmatter 块内用 ^\s*marp:\s*true\s*$ 行匹配。锁 14 个回归测试(包括今天真实触发的那种文档)。
6. 测试体系
- 主项目:从 202 → 241 个测试(+35 个新增,覆盖 splitIntoBlocks、缓存行为、glossary CRUD、配额)
- CLI:独立 vitest 配置 + 14 个
detectMarpSlides回归测试 - 总计:255 个测试,0.5 秒跑完 CLI,2 秒跑完主项目
- 新增
tests/api/glossary.test.ts(18 个 HTTP 层测试) - 覆盖率(在
lib/+ 镜像路由):~45% lines,镜像相关代码 >95%
7. 文档更新
docs/mirror.md— 新增"分块翻译与增量缓存"、"术语表"、"配额与计费"三大段;"未来方向"缩到只剩 HTML 镜像和若干 nice-to-havedocs/api.md— 新增/api/v1/glossary段落 +LIMIT_MIRRORS/LIMIT_GLOSSARY错误码docs/cli.md— AI 能力表追加wpaste mirror和wpaste glossarydocs/database.md— 记录迁移链修复的历史和 workflow 变化
本次交付的 Git Commit
32d820d feat(db): mirror block cache, user glossary table, plan quotas
8bec54b feat(mirror): block-aware translation with incremental cache + glossary
576088f feat(api): enforce mirror plan quota on create endpoint
eea76f4 feat(glossary): REST API + wpaste glossary CLI
1b07e84 docs: update mirror / api / cli for chunking, cache, glossary, quotas
1540a1a fix(db): bypass broken migration chain in init-db.ts
🆕 2026-04-11(上午)· 镜像功能 + 技术 Wiki + 测试基础设施
镜像(Mirror)— 自动外语翻译
给知识库加了"镜像"能力:一份文档可以自动生成其他语言的副本,作为关联的只读文档保留在同一个 workspace 里。
交付内容:
- 支持 中 / 英 / 日 三种目标语言
- 新增
document_mirrors关联表 +documents.language列 - 4 个新 API 端点:
POST /api/v1/documents/:id/mirrors— 创建镜像(首次同步翻译)GET /api/v1/documents/:id/mirrors— 列出已有镜像POST /api/v1/documents/:id/mirrors/:lang— 强制重生DELETE /api/v1/documents/:id/mirrors/:lang
- CLI 新命令:
wpaste mirror <id> --lang en [--list|--regenerate|--delete] - 阅读页新增 语言切换器 组件(pill 样式,按主题配色)
- Wiki-link 跨语言解析:镜像里的
*Title*自动链到同语言的其他镜像(有则指向镜像,无则回退到原文)
核心行为:
- 首次创建阻塞翻译:
POST请求等翻译完成才返回,调用方拿到的 URL 是可直接访问的 - 源文档更新后自动重生:PATCH 源 → 标记所有镜像
stale→waitUntil后台重译 - 镜像只读:
PATCH到doc_type=mirror一律 400,防止改动被下次重译覆盖 - 代码块保护:fenced 和 inline code 在发送给 LLM 前替换为
{{CODE_N}}占位符,不参与翻译 - H1 自动同步:翻译后提取第一个
#更新documents.title - 删除级联:删除源文档时先删所有镜像 doc,再删源,
document_mirrors关联行靠 FK CASCADE 清理 - 过滤 title 冲突:
POST /api/v1/documents的按 title 幂等覆盖逻辑排除doc_type=mirror,避免误覆盖镜像 - doc_type 限制:
raw/wiki/index/answer可镜像,html/slides不行
数据迁移:db/migrations/0015_noisy_korg.sql
技术 Wiki(docs/)
新增 13 篇技术文档,从入门到参考完整覆盖:
| 文档 | 内容 |
|---|---|
docs/README.md | 索引 + 技术栈速览 + 阅读顺序建议 |
docs/architecture.md | 系统总体架构、双形态部署拓扑、核心抽象 |
docs/database.md | Drizzle schema、表间关系、迁移流程 |
docs/api.md | 全部 REST API 参考(含 /api/v1/* 和 /api/auth/*) |
docs/auth.md | GitHub OAuth + Bearer Token 双重认证细节 |
docs/ai.md | 多后端 AI Provider 抽象 + Job 流水线模式 |
docs/mirror.md | 镜像功能完整设计文档 |
docs/storage.md | R2 / 本地 FS / S3 存储抽象 |
docs/cli.md | wpaste CLI 命令参考与架构 |
docs/themes.md | 18 套主题 + 自定义主题系统 |
docs/workers.md | marp / hn-scraper / static 三个辅助 Worker |
docs/deployment.md | Cloudflare 边缘部署 + Docker 自托管 |
docs/development.md | 本地开发、常见坑、调试技巧 |
测试基础设施
从 0 到 202 个测试、13 个测试文件:
| 类别 | 文件数 | 测试数 |
|---|---|---|
| 纯函数单元测试 | 8 | 154 |
| 集成测试(内存 SQLite) | 2 | 26 |
| API 路由测试 | 3 | 38 |
| 总计 | 13 | 202 |
覆盖率(在 lib/ + 被测 API 路由范围):
- 镜像核心
lib/mirror.ts:95.65% lines - 两个镜像路由文件:100% lines
lib/wiki-links.ts/encrypt.ts/frontmatter.ts/mirror-prompts.ts/plan-limits.ts/toc.ts:100%lib/custom-theme.ts:93.54%- 总体加权:45.08% lines
技术选型:
vitestv4 +@vitest/coverage-v8- 集成测试用
better-sqlite3(':memory:')+ 提交的tests/schema.sql快照 - 测试 harness:
makeTestDb()/seedUser()/seedDocument()/callRoute()/stubProvider()/failingProvider() - API 路由测试用
vi.hoisted+vi.mock注入 fake DB / auth / AI provider
生产代码的一处小改动:lib/mirror.ts 的 regenerateMirror() 增加可选 opts.provider?: AIProvider 参数,支持依赖注入以便测试。生产路径继续走 getAIProvider 工厂,行为不变。
新增脚本:
npm test # 一次性跑
npm run test:watch # 开发时 watch
npm run test:coverage # v8 覆盖率报告
🚀 初始上线(0f015a5 "feat: initial commit")
一次性交付了一个完整的 markdown 文档托管平台。此前的 commit 87d9387 是 create-next-app 的脚手架。
基础设施
- Next.js 16 App Router + React 19 + TypeScript 5
- 双形态部署:Cloudflare Workers(
@opennextjs/cloudflare)+ Docker/VPS(standalone) - 数据库双态:Cloudflare D1 / 本地 better-sqlite3,通过 Drizzle ORM 共享 schema
- 存储抽象:R2 / 本地文件系统 / S3(简化版),可切换
- AI Provider 抽象:Workers AI / OpenAI / Anthropic / Ollama 四种后端
核心功能
- Markdown 文档发布、读取、更新、删除
- 全文搜索(
LIKE模糊匹配,含标题 + 内容) - Workspace / Folder 层级组织
- Wiki-link
*Title*语法 + 反向链接 - 文档片段(Data Fragments)交互组件
- HTML 静态站点托管(多文件 → R2)
- Marp 幻灯片支持(独立 Worker 即时渲染)
18 套主题
- 8 套通用:github / academic / night-owl / ink / terminal / notion / craft / editorial
- 10 套国家主题:china / japan / korea / france / britain / germany / italy / usa / brazil / india
- Pro+ 支持自定义主题(JSON 配置 + 可选的 AI 生成)
认证体系
- GitHub OAuth(
arctic+ 自实现 HMAC session) - API Token 体系(
wpaste_前缀,SHA-256 存库) - 双认证中间件(Bearer 优先,Session 兜底)
- 套餐体系:free / pro / team,过期自动降级
AI 能力
- compile:原始笔记 → 结构化 wiki 文章 + 索引页 + 交叉引用
- organize:未整理文档 → AI 归类提案
- ask:RAG 问答,带
*引用* - lint:知识库健康检查
- context:知识库摘要导出
- BYOK:用户自带 API key(AES-256-GCM 加密)
- 按 plan 分级模型(free 小 / pro+ 大)
- 异步 Job 流水线(客户端轮询 + 分步 process,绕开 Workers CPU 时限)
- 结果缓存:
document_summaries按content_hash跳过重复调用
CLI (wpaste)
独立 npm 包,commander + ink TUI:
- 认证:
auth/whoami - 文档 CRUD:
publish/list/view/open/update/append/prepend/delete/move - 标签:
tag add/remove/list - 组织:
workspace create/list/delete/folder create/list/delete - 搜索:
search - AI:
organize/compile/ask/lint/context - 其他:
image/daily/ui/install-skills - 支持 stdin 输入、目录输入(HTML 站点)、JSON 输出
Claude Code 集成
- 7 个 skill:
publish/save/search/ask/compile/context/daily - 通过
wpaste install-skills自动安装到~/.claude/plugins/
社交 / 通知
- 用户主页
/u/{username}+ 子域名{username}.wpaste.io - 关注 / 粉丝体系
- 公共文档评论
- Web Push(VAPID)+ 站内通知中心
- Workspace 订阅(付费知识库)
辅助 Workers
- marp.wpaste.io — Marp 幻灯片即时渲染(带 CF 边缘缓存)
- static.wpaste.io — 静态站点回源
- hn-scraper — HN TOP 20 每小时定时抓取 + AI 总结,自动发布
浏览器扩展
extension/ 目录下的 Chrome 扩展:网页内容 → turndown.js → wpaste 文档
自托管
docker-compose.yml+Dockerfilescripts/init-db.ts初始化本地 SQLite- 完整的
.env.example
📋 累计变更统计(2026-04-11 全日)
| 指标 | 数量 |
|---|---|
| 新增数据库表 | 2(document_mirrors、user_glossaries) |
| 新增数据库列 | 2(documents.language、document_mirrors.block_cache) |
| 新增 API 路由 | 3(/mirrors 集合、/mirrors/[language]、/glossary) |
| 修改 API 路由 | 2(documents/route.ts、documents/[id]/route.ts) |
新增 lib/ 模块 | 2(mirror.ts、mirror-prompts.ts) |
| 新增 React 组件 | 1(language-switcher.tsx) |
| 修改页面 | 1(app/d/[slug]/page.tsx,语言切换器 + 跨语言 wiki-link) |
| 新增 CLI 命令 | 2(wpaste mirror、wpaste glossary) |
| 新增 Wiki 文档 | 13(docs/*.md) |
| 新增测试文件 | 15 |
| 新增测试数量 | 255(241 主项目 + 14 CLI) |
| 新增生产代码(估算) | ~2,500 行 |
| 应用的 D1 迁移 | 3(0014、0015、0016) |
🔮 已知未做 / 未来方向
- HTML 站点镜像 —— 静态站点(
doc_type='html')的lang="en"副本;需要处理多文件、外链、JS 字符串等,工程量大 - 正则术语表 —— 术语表目前只做字面匹配,未来可加
{regex}匹配词形变化 - Glossary workspace 共享 —— 团队共用术语表
- 翻译评审界面 —— dashboard 里 diff 新旧译文并选择采纳