# Henry Chen — 全站内容

> Agentic engineer · Fullstack developer · Photographer，常驻 Sydney, New South Wales，澳大利亚。 以下内容由线上站点实时生成，不会落后于页面本身。文章正文为全文收录。

## 个人信息

- **姓名:** Henry Chen
- **所在地:** Sydney, New South Wales, 澳大利亚
- **时区:** Australia/Sydney
- **身份:** Agentic engineer · Fullstack developer · Photographer
- **毕业院校:** The University of Western Australia; The University of Sydney
- **网站:** https://misoto22.com/zh

## 技术栈分层

- **frontend:** React, Tailwind CSS, HTML5, CSS3, Figma, TypeScript, Next.js, JavaScript
- **backend:** Python, Node.js, Java, C
- **data:** PostgreSQL, SQL Server
- **devops:** Docker, GitHub Actions, AWS, Vercel, CI/CD, Linux, Terraform
- **tooling:** Git, VS Code, Agile, Jira, Lightroom, Claude Code, Codex

## 工作经历

- **软件工程师**, Inovit Pty Ltd (Jun 2025 – Present, Sydney, NSW) — React, Django, SQL Server, Git, Agile
- **IT 与数据实习生**, Path of Hope Foundation (Jul 2024 - Oct 2024, Perth, WA) — ETL, Data Visualization, Web Development, IT Strategy, Data Management
- **高级数学辅导老师**, HD Education (Jul 2019 - Jan 2023, Sydney, NSW) — Curriculum Development, Student Assessment, Educational Technology, Communication, Problem Solving

## 教育经历

- **信息技术硕士**, The University of Western Australia (2023 - 2024)
- **计算机学士**, The University of Sydney (2020 - 2022)

## 项目（8）

- [INOVIT 经销商门户](https://misoto22.com/zh/projects/dealer-portal): B2B 电商平台，替换遗留 .NET ERP —— 49 个 API 端点、4 区域数据库路由、跨仓库 OpenAPI 类型生成、共享生产 SQL Server 上的零停机迁移。 — Stack: React 19, TypeScript, Django 5, DRF, SQL Server, Ant Design 6
- [EFFICIENT 系统](https://misoto22.com/zh/projects/efficient-system): 一套多租户的轮毂轮胎 ERP 与电商平台 —— 一个 Django 后端、一套共享 React UI kit、五个门户、一个店面、一个带 MCP server 的 Rust CLI。我负责的是它们下面那一层：一份 160 条规则、任何仓库都不许复制的工程契约，以及让十五个仓库真正守住它的 agent 工作流。 — Stack: Agent Skills, Claude Code, Python, Django, React
- [SlateCourt 羽毛球场馆与约球应用](https://misoto22.com/zh/projects/slatecourt): 面向悉尼羽毛球玩家的移动优先 PWA：54 个场馆目录与实时场地空位（汇总自场馆公开的预订页面），打球账本支持费用分摊与装备记录，并带好友、小组与通知。个人非商业演示项目 —— 双语、自托管，注册仅限邀请码。 — Stack: Next.js 16, TypeScript, FastAPI, Python, PostgreSQL, Redis
- [个人网站](https://misoto22.com/zh/projects/personal-website): 双语作品集网站，包含基于站点自身内容的 AI 助手、隐私优先的分析、博客、摄影画廊与 WCAG AAA 无障碍 —— 17 个页面、17 个 API 路由，自托管 Docker 部署，独立开发。 — Stack: Next.js 16, React 19, TypeScript, Tailwind CSS, PostgreSQL + pgvector, Docker
- [Lumia Crystal 电商平台](https://misoto22.com/zh/projects/lumia-crystal): 水晶珠宝品牌的 Headless 电商平台。基于 Next.js 15 和 Shopify Storefront API，支持实时商品目录、购物车、搜索筛选，响应式设计。 — Stack: Next.js, TypeScript, Tailwind CSS, Shopify API, React
- **Smart Vision Hat 智能视觉帽**: 面向视障人士的 IoT 可穿戴设备。基于树莓派和 YOLOv8 目标检测，提供实时语音反馈和紧急警报。 — Stack: Raspberry Pi, Python, Flask, Firebase, YOLOv8, OpenCV
- **并行鱼群搜索算法**: 高性能计算项目，用 C、OpenMP 和 MPI 模拟鱼群行为。在 Setonix 超算上通过线程和进程级优化实现了大幅加速。 — Stack: C, OpenMP, MPI, Bash
- **澳洲 EOI 移民打分器**: 澳洲技术移民 EOI 打分计算器。支持实时计算、中英双语、响应式设计和深浅色主题。 — Stack: Next.js, TypeScript, Tailwind CSS, i18next, Framer Motion

## 文章（8）

### 关于我的数据，应该属于哪里？

https://misoto22.com/zh/blog/where-should-my-data-live · 2026 · 约 18 分钟读完 · 开发

> 限制我们用好 AI 的，早就不是模型能力，而是关于我的数据不在我手里。产品随时可以被换掉，唯一让你换不动的，是沉淀在里面的上下文。我为此做了一个个人数据库，做完才发现它只是一面镜子。

> 限制我们用好 AI 的，早就不是模型能力了，是关于我的数据不在我手里。

#### AI 不认识我

我每天都在用 LLM，但 LLM 并不认识我。

模型可以帮我写代码、分析架构、修改简历，在拿到工具之后还能替我完成一些真实的操作。但每次打开一个新的 Provider，我都要重新解释自己是谁：我用哪些语言，做什么工作，手上有哪些项目，习惯怎样组织代码，哪些技术限制不能被忽略。

一开始我把它当成一件麻烦事。后来我意识到，它是天花板。

#### 瓶颈换过很多次，但不是我这一侧的

7 月 28 日那期 [Invest Like the Best](https://colossus.com/episode/how-to-make-an-abundant-future/)，Patrick O'Shaughnessy 和 Sam Altman 聊了将近一小时。中间有一段专门讲瓶颈（22:27）：过去几年，限制 AI 前进的东西一直在轮换——有时候是 research ideas，有时候是算力，有时候是数据，然后又转回算力。

那是造模型的人看到的瓶颈。

我这一侧的瓶颈从来没有轮换过。

模型每几个月跳一级，推理成本掉一个数量级，能力边界持续外扩。而我每打开一个新对话，仍然要从头解释我是谁。模型侧的进步是指数的，我这一侧的起点始终是零。

所以真正的问题不是“AI 够不够聪明”，而是：一个足够聪明的系统，能拿到关于我的多少事实？

答案是：非常少，而且都不在我手里。

#### 每个产品都存着一份“我”

Provider 通常会提供一个类似 `things about me` 的地方。写进去，问题看上去就解决了。但真的开始维护它，我发现自己只是在建立又一份关于我的数据副本。

我的个人网站保存了工作经历、技能、项目和简介。修改简历的应用需要同样的数据，于是再 import 一份。LLM Provider 有自己的个人资料，下一个产品还会再要一份 profile。

这些系统保存的不是不同的数据，而是同一个人的不同投影。问题是，每一个投影都逐渐开始把自己当成源头。

只要数据被复制，漂移就会发生。我更新了一段工作经历，应该先改哪一个？某个项目换了技术栈，哪个系统里的版本才算正确？Provider 记住的偏好过期以后，我甚至不一定知道它仍在用一条旧信息影响回答。

最初我把它理解成 context 问题：也许我需要一份更好的 system prompt，或者一份可以导入不同模型的 JSON。后来我意识到，这只是换了一种复制方式。

Context 是数据被某个消费者使用时的形态，不是数据本身。Prompt、RAG 文档、简历页面和网站 API 都可以是消费方式，但它们不该各自拥有一份事实来源。

#### 换不动的从来不是产品，是沉淀在里面的上下文

同一期访谈里还有一个更尖锐的问题：如果智能本身变成纯粹的商品，还剩下什么护城河？

Sam 给出的排序大致是：算力规模最耐久；工作流和集成产生的黏性次之；品牌和分发中等，他形容 ChatGPT 的捆绑效应“very very tiny”；而**产品本身最弱**——有人做出更好的，用户就会走。

我想了很久，因为它正好从另一侧说出了我一直在想的事：

> 如果产品可以随时被换掉，那唯一让你换不动的就是沉淀在里面的上下文。

Sam 是站在公司那一侧说的：产品会被替换，所以护城河必须在别处。站在我这一侧，同一件事只是换了个名字——那不是护城河，是锁。

我在一个平台上积累的上下文越多，它的护城河越深，我越走不掉。而这份上下文，本来就是我的生活：我做过的项目、写过的笔记、修过的简历、解释过无数次的技术偏好。

所以问题不是“AI 能不能记住我”，而是：**记住我的那份东西，产权归谁。**

上下文是结果，所有权才是前提。谁拥有数据，谁就决定了这份上下文能被谁使用、能不能被带走，以及在你换掉产品的那天它是否还存在。

#### 数据边界的方向是反的

今天的大型平台通常这样组织数据：

> 一个领域 × 大量用户

健康平台收集许多人的健康数据，音乐平台收集许多人的收听记录，招聘平台收集许多人的职业经历。它们的领域边界非常清楚，但同一个人的数据被垂直切开，分别留在不同公司里。

个人 AI 需要的是相反的方向：

> 一个人 × 多个生活领域

职业、知识、照片、音乐、健康、旅行和浏览记录属于同一个人。它们可能来自不同产品，却不该永远以产品为最终边界。

这个方向听起来很自然，也很容易低估它的难度。我试过。

#### 我做过一次，做完才知道它只是镜像

我给自己写了一个个人数据库，叫 Kioku：一个 PostgreSQL，收着职业、知识、照片、音乐、健康和浏览记录，网站通过 API 读，LLM 通过 MCP 读。它确实解决了副本和漂移的问题——现在只有一个地方是源头，其他都是派生。

但做完之后我很清楚它是什么：**它目前只是其他平台数据的镜像和同步。**

数据仍然产生在别人的系统里。音乐记录来自流媒体，健康数据来自手机和手表，代码活动来自 Git 托管商，阅读和观看记录来自各自的 App。Kioku 做的是定时把它们拉过来、对齐、存下。我拥有的是一份可靠的副本，不是源头。

这意味着几件很实际的事。平台改了导出接口，我这边就断；平台不提供增量接口，我拿到的就只是一次性归档；平台从来没记录过的东西，我这里也不会凭空出现。所有权在纸面上回到了我这里，产生数据的那一端并没有。

我不后悔做它。恰恰是做完这一层，我才看清楚问题的真实位置：**难的从来不是存，是源头。**

#### 还缺一只眼睛

把散落的数据收回来，只解决了一半。

能收回来的东西，几乎全部是数字足迹：我点过什么、买过什么、听过什么、提交过什么代码。这些是平台记下的我，是二手投影。

我在现实世界里做了什么，没有任何东西在记。今天和谁见了面、聊到什么、看到什么、当时在想什么——这一层最能解释我，也完全缺失。没有任何设备像我的眼睛那样，实时地接收现实世界并把它留下来。

同一期访谈里，Sam 用了不小的篇幅谈 robotics（35:33），认为未来两三年会出现 robotics 的“ChatGPT 时刻”，并强调如果人类最后的角色是充当云端 AI 的执行器，那会非常糟糕。那是在给云端智能装上手。

但对个人 AI 来说，缺的不是手，是眼睛。

这个缺口迟早会被填上，而且大概率会被一台设备填上。我在意的不是它什么时候出现，而是它出现的时候，数据的默认归属是谁。如果最私密、最能解释一个人的那一层数据，从被记录的第一秒起就存在别人的数据库里，那么前面所有关于数据所有权的讨论，都只是在收拾一个更小的摊子。

所以这不是一个“以后再说”的问题。而且它也不是“记得越多越好”的问题——缺口不在于记录得不够全，而在于**入口在谁手上**。边界应该在数据出现之前就画好，而不是等它已经躺在别人那里之后再去要回来。

#### 这条路以前有人走过

“一个人的数据库”并不是新想法。

Vannevar Bush 在 1945 年提出 Memex，想象一种能够保存并连接个人资料的外部记忆。2001 年，Gordon Bell、Jim Gemmell 和 Roger Lueder 在微软研究院开始了 [MyLifeBits](https://www.microsoft.com/en-us/research/project/mylifebits/)：一个基于 SQL 的 “personal database for everything”，保存文档、邮件、照片、网页、录音以及传感器产生的生活记录，并探索全文检索、标注、链接和相似性。

之后，Personal Data Store 和 Solid 从另一个方向处理相同矛盾：数据不该被绑定在应用里，应用应当在获得授权后访问由个人选择的存储。到 2026 年，W3C 仍在推进 [Linked Web Storage](https://www.w3.org/TR/lws10-core/)，尝试标准化应用对外部存储的安全、授权访问。

还有一条更务实的工程路线。[HPI](https://github.com/karlicoss/HPI) 不等待整个互联网先接受统一协议，而是通过导出文件和适配器，把聊天、音乐、浏览、位置和健康等数据转换成可以由个人程序查询的接口。

这些项目不是一个连续、统一的谱系，但它们反复回答着同一个问题：当一个人的数字生活横跨许多应用时，数据是否可以重新围绕这个人组织？

#### 为什么它没有成为互联网的默认架构

这条路线反复出现，也反复停留在研究项目、开源工具和少数自托管用户之间。障碍从来不只是硬盘容量。

首先，平台没有很强的动力交出持续、完整、机器可读的数据。即使提供导出，得到的也经常是一次性归档，而不是稳定的增量接口。数据可以被取回，不代表它可以被可靠同步——这正是我自己撞上的那堵墙。

其次，跨来源的数据很难自动统一。同一个地点、联系人或活动在不同系统里可能没有共同标识；时间戳包含不同的时区假设；设备可能重复记录同一次运动。把文件放进同一个目录很容易，让它们在语义上成为同一个数据库则困难得多。

再次，集中化会扩大安全爆炸半径。分散的数据不方便使用，但聚合后的健康、位置、职业和浏览记录一旦被错误授权，损失也更集中。数据主权不能只意味着“数据都在我这里”，还必须包括最小权限、审计、备份和可恢复性。

最后，普通人不应该为了拥有自己的数据而成为数据库管理员。对开发者来说，一人一个 PostgreSQL 是可以接受的；如果这种模式要服务更多人，合理的形态更可能是托管的个人空间或逻辑隔离的数据账户，而不是要求每个人维护一台服务器。

#### AI 让这件事从“有价值”变成“紧迫”

过去，一个跨领域个人数据库缺少足够强的日常消费者。做一张健康图表或搜索旧消息很有用，却未必足以支撑整个生态改变数据边界。

个人 AI 改变了这件事。一个能帮我规划工作、修改简历、回顾项目或分析生活状态的 Agent，不能只知道某一个领域。它需要跨越职业、知识、偏好和历史，但每次任务只应获得完成任务所需的那部分。这是第一个天然需要全部数据的消费者，也是第一次，数据所有权不再只是原则问题，而是直接决定了这套系统能力的上限。

外部条件也在变化。欧盟 [Data Act](https://digital-strategy.ec.europa.eu/en/factpages/data-act-explained) 已经推动联网产品向用户提供其产生的数据和必要元数据；DMA 也在推动大型平台提供更及时的数据可携带接口。这些规则不会自动创造一个个人数据库，但它们让“把数据拿回来”逐渐从逆向工程变成可以被要求的能力。

不过，AI 只是让需求变强，并没有替我们解决数据治理。

LLM 不能成为唯一真相来源。向量索引不保留精确数量，模型推断出的关系也不等于事实。一个 Agent 可以建议两条记录可能有关联，却不该因为一次生成就重写我的职业经历。自然语言让数据更容易使用，但来源、权限和确定性仍然必须由系统明确表达。

#### 智能会变成商品，上下文不会

如果智能真的会变成纯粹的商品，那么唯一不会被商品化的，就是关于我的那份上下文。它决定了同样的模型在我手上能做到什么，也决定了我离开一个产品时能带走什么。

我手上现在只有一份镜像。它比散落在十几个平台里要好，但它不是终点——源头仍在别人那里，现实世界那一层根本还没有入口。

所以这篇文章不是一个解决方案，是一个位置的确认：限制我们把 AI 用到最大的，不是模型，是关于我们自己的那份数据究竟归谁。

模型会一直变强。这个问题不会自己消失。

### 从网站助手到个人知识接口

https://misoto22.com/zh/blog/building-a-rag-chatbot · 2026 · 约 19 分钟读完 · 人工智能

> 本站原本只有一个带引用的 RAG 问答助手。后来它演变成 Kioku 上的两条 AI 接口：访客使用公开检索，我自己的 AI 通过 MCP 使用私人搜索与结构化工具；两者共享数据，但不共享权限。

这个网站上的助手，最初只有一个很窄的任务：访客可以问我的经历和项目，它要根据我真正发布过的内容回答，并给出可以点开的来源。看起来，这只是给网站加了一个聊天框。可做到后来，它逼我回答了一个更重要的问题：当 AI 开始使用我的数据时，围绕这些数据的检索、权限和工具，究竟应该放在哪里？

现在我的答案是 Kioku，也就是这个网站背后的个人数据系统。公开助手依然存在，但它只是 Kioku 的一个客户端。我自己使用的 AI 则通过另一条 MCP 通道，搜索私人知识、查询结构化记录、保存新材料，以及发起受控的工作流。两条路径最终使用同一份数据，却不会获得相同的权限。

对我来说，这个区别比选了哪一家模型、哪一种向量数据库更重要。模型会不断替换，个人数据的边界不应该跟着一起重做。

#### 它最初只是网站上的问答助手

大语言模型知道很多公开知识，却不会自动知道我上个月做了什么、为什么选择某个架构，或简历上的哪段描述才是最新版本。让它只凭模型记忆回答，往往会得到语言流畅的猜测；每次都把整个网站塞进提示词，又会越来越慢、越来越贵，内容多起来后也根本放不下。

RAG，也就是“检索增强生成”，解决思路其实很朴素：在请模型回答之前，先从我控制的内容里找出几段真正相关的材料，再把这些材料连同问题一起交给模型。它不是重新训练一个“懂我”的模型，而是为眼前这个问题补上模型原本没有的上下文。

Embeddings（向量表示）让搜索不必只依赖完全相同的关键词。它会把文字转换成大致保留语义关系的坐标，因此“职业变化”也有机会找到写着“从一个岗位转到另一个岗位”的段落。PostgreSQL 仍是主要数据库，pgvector 只负责在其中补上向量相似度搜索。找到材料之后，模型根据这些段落组织答案，并把来源页面附在回答里。

引用并不是回答末尾的装饰，而是这个功能最重要的约束之一。访客应该能从一句结论回到支持它的原文。如果公开资料不足，克制地说“我不知道”，比生成一个看起来很专业的答案更有价值。

访客看到的交互仍然很简单：问题发往 `/ask`，答案逐步出现，相关来源随后到达。SSE 是一种从服务端持续向浏览器发送事件的方式，让用户不必等完整答案生成后才看到第一句话。屏幕后面可以很复杂，但它和访客的约定只有一句：回答要来自可以检查的公开资料。

#### 为什么 RAG 应该搬进 Kioku

最早的实现把检索当成网站自身的功能。当时只有网站会使用它，这个决定很自然。后来 Kioku 开始保存文章、项目、知识条目，以及越来越多结构化的个人记录，问题就出现了。

如果索引由网站拥有，每增加一个客户端，都要依赖网站或重新实现一遍检索规则。可见性修改后，多个地方都要同步；后台导入已经更新了 Kioku，网站却可能仍在搜索昨天的副本。负责展示数据的网站，慢慢变成了必须理解所有数据的后端。

所以我把语料整理、切块、向量化、检索和回答编排都移进 Kioku。网站只保留真正属于网站的职责：确认流量经过预期的边缘防护、限制单个访客的突发请求，再把 Kioku 返回的事件流原样交给浏览器。问题校验、公开检索、有限轮次的工具调用、模型网关和引用整理，都由 Kioku 完成。网站是客户端，不再是第二套知识后端。

最近一次后端重构又把这层关系明确了一步。REST、MCP 和命令行任务是并列的入口：它们分别处理自己的认证和通信格式，然后调用同一组与协议无关的 application use cases。MCP 不需要绕回去调用 REST，REST 也不需要复制一套业务逻辑。这听起来只是普通的应用分层，却带来一个很实际的结果：数据规则可以复用，同时不依附于某一种客户端协议。

```diagram
{
  "caption": "同一份数据，通过两条可以强制执行的访问路径使用。",
  "direction": "column",
  "nodes": [
    {
      "id": "callers",
      "label": "两类调用方，两条路径",
      "note": "同一份数据，不同权限",
      "direction": "column",
      "children": [
        {
          "id": "v",
          "label": "访客",
          "note": "没有账号",
          "direction": "row",
          "children": [
            {
              "id": "v1",
              "label": "misoto22.com /ask"
            },
            {
              "id": "v2",
              "label": "公开检索",
              "note": "只含公开内容的视图"
            },
            {
              "id": "v3",
              "label": "带引用的回答"
            }
          ]
        },
        {
          "id": "o",
          "label": "我自己的 AI",
          "note": "已认证",
          "direction": "row",
          "children": [
            {
              "id": "o1",
              "label": "Kioku MCP"
            },
            {
              "id": "o2",
              "label": "私人搜索",
              "note": "另一个数据库角色"
            },
            {
              "id": "o3",
              "label": "结构化工具"
            }
          ]
        }
      ]
    },
    {
      "id": "core",
      "label": "Kioku 共享服务与数据",
      "note": "唯一的真值来源",
      "accent": true,
      "footnote": "没有 include_private 这种开关 —— 边界在对话层以下"
    }
  ],
  "edges": [
    {
      "from": "v1",
      "to": "v2"
    },
    {
      "from": "v2",
      "to": "v3"
    },
    {
      "from": "o1",
      "to": "o2"
    },
    {
      "from": "o2",
      "to": "o3"
    },
    {
      "from": "callers",
      "to": "core",
      "label": "两条都读它"
    }
  ]
}
```

#### 同一份数据，两条读取边界

“告诉公开助手只能搜索公开内容”并不是可靠的安全设计。模型可能误解提示词，应用参数可能传错，未来的代码也可能无意中放宽查询。我希望公开与私人的区别在对话层以下就已经成立。

因此，Kioku 里有两条独立的检索路径。公开 `/ask` 使用受限的数据库角色和只包含公开内容的视图；面向所有者的知识搜索，则通过另一项数据库能力读取私人索引。公开函数没有 `include_private` 这样的开关，也不存在一个传错就能把访客查询变成所有者查询的特殊参数。

它们仍然共享合适的基础设施：文本按照稳定规则切成段落，向量使用一致的方法生成，索引也都保存在同一个 PostgreSQL 实例中。但共享基础设施不等于共享权限。在模型看到任何上下文之前，数据库就已经决定了这条路径能接触什么。

这样一来，系统更容易解释，也更容易审查。公开模型当然仍会收到“不要泄露私人信息”的提示，但更关键的是，它根本收不到私人内容。有人尝试诱导它复述一条私人笔记时，公开检索没有任何私人段落可以返回。Kioku 的双语质量检查也会把这类隐私诱导问题，与普通问答、引用正确性和无答案场景放在一起测试。评估无法替代权限控制，但可以发现权限边界之上的体验是否退化。

外层限制也遵循同样的思路。网站最清楚访客从哪里来，因此先拦截异常流量，避免它浪费向量化和生成额度；Kioku 真正连接模型服务，所以全局并发与成本上限由 Kioku 执行。每一层只承担自己能够准确判断的限制。

这也让未来的新客户端必须诚实面对权限。移动应用、另一个网站或本地工具，以后都可以接入 Kioku，但它们仍需要明确的身份和能力。“使用同一份数据”不再自动意味着“能够看到同样的数据”。

#### MCP 让 AI 不只会搜索

答案藏在文章段落里时，RAG 很好用；问题需要精确、结构化结果时，它就不是最佳工具。“我写过哪些关于数据库所有权的想法”适合搜索，而“去年记录了多少次飞行”或“哪些项目使用 Rust”，应该从记录和关系中直接计算，不该让模型根据几段语义相近的文字估算。

MCP 改变了这个项目的形状。Model Context Protocol 让 AI 客户端看到一组有清晰描述的工具，而不是把大量个人数据塞进一个超长提示词。通过 Kioku 的私人 MCP 入口，我的 AI 可以搜索知识、读取权威记录、保存新笔记、连接相关条目，或启动一个后台工作。重点不是现在究竟有多少个工具，而是每项操作都有明确的输入、结果和权限。

连“搜索”本身也需要两种能力。语义搜索擅长找到用词不同但意思相关的内容；字面搜索更适合精确的人名、标识符或短语。Kioku 会合并两边的结果，而不是让模型在尚未看到答案前猜应该选哪一种。遇到数字、日期或分类问题时，结构化工具则可以完全绕过向量检索，直接查询相应领域的数据。

这让模型回到一个更健康的位置。它负责选择和组合能力，却不成为事实来源。数据库查询确定精确事实，检索提供用于解释的原文，模型把结果组织成对人有用的回答。每一层都做自己擅长的事情。

MCP 还让 Kioku 能参与工作过程，而不只是保存最终发布的文章。和 AI 讨论问题时，如果遇到值得留下的资料，对话可以把它保存到知识库，不必让它永远困在聊天记录里。系统之后可以建议相关条目，但真正建立关系仍是一项明确的写操作。类似模式也适用于个人记录和需要较长时间完成的任务。

私人客户端能力更强，却不会被当成一个无限制的所有者会话。它使用可单独撤销的凭据和数据库角色；每个工具都会声明它是读取、写入、启动任务，还是会产生外部影响；调用需要通过能力检查，危险动作可以等待确认，写入也留下审计记录。模型是代表我执行工作的 agent，但它并不等于我本人。

#### 让数据保持新鲜，也保持私密

如果搜索索引只是某一天生成后就被忘记的快照，知识接口很快就会失去可信度。每次编辑都重新生成全部向量当然正确，却会浪费时间和调用额度；等到下一次部署才更新，又会让刚保存的内容像是“坏掉了”。

Kioku 会为每个索引来源记录内容哈希。文字或可见性变化时，只重新处理真正受影响的段落。新捕获的资料会安排后续索引，因此不需要发布新版本或重建全部内容，也能进入搜索。完整重建仍然保留，用于修复和验证，而不是每次小改动都必须经过的日常流程。

可见性也是索引身份的一部分。一条笔记从私人改为公开，即使正文一个字都没动，对检索来说也是重要变化。页面地址重命名时，旧身份会先被删除，再启用新身份，避免旧网址对应的公开结果长期残留。

数据新鲜只是信任的一半。新捕获内容默认是私人的；MCP agent 即使可以创建内容，也不能因此直接发布；公开检索始终走受限路径；有外部影响的操作可以要求确认；审计记录则让写入在事后仍可检查。这些并不是神奇的“AI 安全方案”，而是围绕 AI 接口重新应用了一组普通、明确而且可执行的控制。

模型服务也被留在数据所有权边界之外。Kioku 只发送向量化或回答当下所需的最少上下文，并通过可以替换的网关连接模型。长期保存的数据、可见性、条目关系、修订历史和结构化记录，仍留在我自己的数据库里。更换模型应该只是替换集成，而不是搬一次家。

系统当然仍有取舍。语义索引可能漏掉最合适的段落，流式生成可能在中途失败，一个真正有用的个人系统也必然包含绝不能公开的内容。目标不是用“AI”这个标签把风险藏起来，而是让每个风险都有看得见的边界、失败后的退路，以及可以持续检查的位置。

#### 我做的已经不只是聊天机器人

`/ask` 仍是这项工作最容易被看到的部分，而且我希望它一直保持简单：询问我的公开项目，获得有来源的回答，需要完整上下文时再点进原文。但它背后的系统已经不再围绕一个聊天框设计。

Kioku 现在为同一份个人数据提供两种视图。公开视图范围很窄，回答必须带引用，也无法接触私人材料；所有者视图可以搜索更广的知识、使用精确的领域工具、保存内容，并协调受控任务。共享的 application layer 避免重复实现，分离的数据库能力则避免共享权限。

这也是我真正关心的 roadmap：不是为了记录而记录一切，也不是因为模型能够调用工具，就不断往里面堆工具。我想做的是一个耐久的个人知识接口——数据能够由我保留，规则能够由我检查，而不同的 AI 客户端始终可以被替换。

实现会继续在 [Kioku](https://github.com/Misoto22/kioku) 中演进，公开客户端则位于 [misoto22-site](https://github.com/Misoto22/misoto22-site)。比两个代码库都更持久的设计原则其实很简单：让智能靠近数据，让权限比智能离数据更近。

### Ghostty + Tmux：一个 Tab 一个项目，一屏 Claude 一屏应用

https://misoto22.com/zh/blog/my-ghostty-setup · 2026 · 约 8 分钟读完 · 开发

> Ghostty 的 tab 当项目槽，tmux 当分屏管理器 —— 为什么 tab 要看得见、为什么分屏交给 tmux、以及让八个项目同时开着还不会乱的那些配置选择。

最好的终端不是功能最多的那个，是你同时开着八个项目还能保持头脑清醒的那个。

我现在落下来的配置刻意做得很无聊：Ghostty 负责宿主窗口，macOS tab 负责分项目，每个项目内部是一个 tmux session，分屏里永远是同样两件事 —— 左边 Claude Code，右边应用跑着。没有花哨的 Ghostty 分屏、没有工作区管理器、也没有三个月后自己看不懂的 tmuxinator 配置文件。

这篇文章就是这套配置的走读，以及背后的几个选择。完整的 `~/.config/ghostty/config` 在 [github.com/Misoto22/ghostty-config](https://github.com/Misoto22/ghostty-config)，想直接拿走用的话过去抄。

---

#### 心智模型

三条规则决定了每一个配置项：

| 规则 | 含义 |
|---|---|
| **Tab = Project** | 每个 Ghostty tab 绑定一个项目。切项目 = `⌘+1/2/3`，不用去找窗口。 |
| **Tmux = Role** | 每个 tab 内用 tmux 分屏，分屏角色固定：左边 Claude Code，右边应用 / 测试 / 日志。 |
| **Ghostty 只做宿主** | Ghostty 负责字体、颜色、tabs、快捷键。不负责分屏、不负责会话恢复。那是 tmux 的活。 |

为什么分屏用 tmux 不用 Ghostty 原生的？**可 detach、可 reattach、可以在 SSH 里用同一套。** 机器重启之后 `tmux attach` 一切回来，Claude Code 那边 `claude --continue`，应用那边历史都还在。Ghostty 的原生分屏给不了这个。

---

#### 快捷键速查

**Ghostty —— 只管窗口和 tab：**

| 快捷键 | 动作 |
|---|---|
| `⌘+T` | 新 tab（新的项目槽位） |
| `⌘+W` | 关当前 surface |
| `⌘+1~9` | 直达第 N 个 tab |
| `⌘+Shift+Enter` | 全屏切换 |
| `⌘+K` | 清屏 |
| `⌘+` `` ` `` | Quick Terminal（全局热键） |
| `⌘+D` / `⌘+Shift+D` | 原生分屏（没开 tmux 时的 fallback） |

**Tmux —— 项目内部的一切。** 我用 `Ctrl+a` 当 prefix（个人喜好，默认的 `Ctrl+b` 也行）：

| 快捷键 | 动作 |
|---|---|
| `prefix + \|` | 左右分屏 |
| `prefix + -` | 上下分屏 |
| `prefix + h/j/k/l` | pane 间跳转 |
| `prefix + z` | 放大当前 pane |
| `prefix + d` | detach（session 继续跑） |
| `tmux a -t <name>` | 重新 attach |

---

#### 配置详解

##### 字体：JetBrains Mono Nerd Font

```text
font-family = JetBrainsMono Nerd Font
font-size = 14
font-thicken = false
```

为什么不是 Maple Mono？我很少需要中文等宽，反而更吃 JetBrains Mono 的比例和连字。Nerd Font 版本自带图标，tmux 状态栏和 starship prompt 直接就能用，省了一套字体回退。

`font-thicken = false` 是因为我不想补偿 macOS 的渲染。原生那种稍微偏细的字，在高 opacity 深色背景下反而更清爽。

##### 主题：Cobalt Next Dark，一套到底

```text
theme = Cobalt Next Dark
```

没有 `light:…,dark:…` 的自动切换。原因是我**几乎不在浅色终端里写代码**：每天泡在里面的 6–8 小时房间光线是恒定的，切主题只会打乱我在 tmux 状态栏里建立起来的颜色记忆。一套固定方案，脑子省一格。

Cobalt Next Dark 的对比度比 Catppuccin Mocha 高一点 —— diff 里的红色更跳。比起咖啡厅美学，我更看重这个。

##### 窗口：Tab 一定要看得见

```text
background-opacity = 0.96
macos-titlebar-style = tabs
window-padding-x = 12
window-padding-y = 12
```

和常见的"让终端消失"流派有两点分歧：

- **`background-opacity = 0.96`** —— 不是 0.88。我底部有 tmux 状态栏，太透的话桌面壁纸会和状态栏文字串在一起，反而读着费劲。0.96 保留一点点通透感，但读起来不打架。功能性优先。
- **`macos-titlebar-style = tabs`** —— 不 hidden。**Tab 就是我的项目切换器，必须看得见。** `⌘+1/2/3` 是肌肉记忆，但用肉眼确认"我在哪个项目里"这个动作每几分钟就要做一次。为了省那点像素而隐藏标题栏，不值。

##### 光标与剪贴板

```text
cursor-style = block
cursor-style-blink = false
copy-on-select = clipboard
scrollback-limit = 100000
```

Block 光标不是怀旧 —— 我在 tmux + vim 里切得多，block 和 vim 的 normal mode 光标视觉上一致，少一次认知切换。

`scrollback-limit = 100000` —— 比很多指南小两个数量级。够翻一次构建日志就行。真要仔细看的东西我 `| tee build.log` 存盘，不指望 scrollback 当日志系统。

##### macOS 集成

```text
macos-option-as-alt = true
quit-after-last-window-closed = true
confirm-close-surface = false
```

`macos-option-as-alt = true` 是刚需。shell 里 `Option+←/→` 按单词跳、`Option+Backspace` 删单词，没这个等于废了半个 readline。

##### Quick Terminal：写文档时的急救包

```text
keybind = global:cmd+grave_accent=toggle_quick_terminal
quick-terminal-position = top
quick-terminal-animation-duration = 0.1
```

用途单一：我在 VS Code / 浏览器 / Notion 里，突然要跑一下 `git status` 或 `pbpaste | wc -l`。按 `⌘+` `` ` `` 从顶部滑出一条终端，敲完再按一次收回去。**不开新 tab、不污染任何项目上下文。**

##### 原生分屏：保留但很少用

```text
keybind = cmd+d=new_split:right
keybind = cmd+shift+d=new_split:down
keybind = cmd+opt+left/right/up/down=goto_split:...
```

保留这些只是为了**没开 tmux 的场景** —— 偶尔开一个临时 tab 跑个一次性脚本，不值得起 tmux session。平时进项目 tab 第一件事就是 `tmux a` 或 `tmux new -s <project>`，之后分屏全归 tmux。

---

#### 一个项目的日常流程

**开项目：**

```bash
⌘+T                             # 新 tab
cd ~/code/efision
tmux new -s efision             # 或 `tmux a -t efision`
```

**进 tmux 后的标准布局：**

```diagram
{
  "caption": "一个窗口，两个 pane —— 左边是代理，右边是它驱动的东西。",
  "nodes": [
    {
      "id": "window",
      "label": "Ghostty",
      "note": "fullscreen",
      "footnote": "一个 tmux window",
      "children": [
        {
          "label": "Claude Code",
          "note": "claude"
        },
        {
          "label": "cargo run",
          "note": "或者 pytest、tail -f"
        }
      ]
    }
  ]
}
```

左边 Claude Code。右边是这个项目的长驻伴侣：

- Rust 项目：`cargo watch -x run`
- Django：`uv run manage.py runserver`
- 前端：`pnpm dev`

需要第三个 pane 看 `git diff` 或 `psql`，`prefix + -` 下面再切一刀。

**切项目：** `⌘+2` → 另一个 tab → 那边的 tmux session 原样躺着。

**关机 / Ghostty 崩溃：** 无所谓。tmux server 还在（除非系统重启）。重开 Ghostty 每个 tab 里 `tmux a -t <name>`，**所有 pane 都回来，Claude Code 也在**。然后 `claude --continue` 接上对话。

---

#### 场景速查

| 场景 | 操作 |
|---|---|
| 切项目 | `⌘+1/2/3` |
| 加个 pane 看日志 | `prefix + -` |
| Claude 输出太长 | `prefix + z` 放大 pane |
| 快速查个东西不污染当前项目 | `⌘+` `` ` `` Quick Terminal |
| 重启之后恢复 | `tmux a` + `claude --continue` |
| 一次性试验 | `⌘+T` 开 tab，不起 tmux |

---

#### 为什么这套比原生分屏好

| 维度 | Ghostty 原生分屏 | Tmux 分屏 |
|---|---|---|
| 会话持久化 | 重启丢 | `tmux a` 回来 |
| SSH 一致性 | 远端用不了 | 本地远端同一套肌肉记忆 |
| 布局脚本化 | 手动拖 | `tmuxinator` / shell 脚本一键起 |
| 命名与切换 | 无 | `prefix + $` 重命名 window |

代价是一层抽象加一个 prefix key。对我来说值。

---

#### 相关

- **[我的 Ghostty 配置仓库](https://github.com/Misoto22/ghostty-config)** —— 完整 `config` 文件，可直接复制
- [Ghostty](https://ghostty.org)
- [tmux](https://github.com/tmux/tmux)
- [JetBrains Mono Nerd Font](https://www.nerdfonts.com)
- Cobalt Next 主题 —— Ghostty 内置，`ghostty +list-themes | grep -i cobalt`

### Claude Code 并行开发的三种方式

https://misoto22.com/zh/blog/claude-code-parallel-development · 2026 · 约 10 分钟读完 · 人工智能

> Worktree、Agent Teams、对话内 Subagent —— 各自什么时候用、底层到底在干什么，以及我日常用下来踩到的取舍。

越是重度依赖 Claude Code，瓶颈就越不是"它能不能写这个"，而是"能同时写几个"。一个后端模块、一个前端 feature、一轮文档梳理，本来就彼此独立 —— 难点只是让它们的文件系统、分支、上下文不要互相踩。

Claude Code 给了三条路走，而且它们并不能互相替代。这篇把三种方式都过一遍，底层到底在做什么，以及我自己取舍时用的那几条规则。

---

#### 1. `claude --worktree` —— 多个终端，同一个仓库

最简单、最可预期。每个终端启动一个独立的 Claude session，各自对应一个 git worktree。

```bash
# 终端 1
claude --worktree feat-dashboard

# 终端 2
claude -w feat-customers   # -w 是短写

# 终端 3
claude -w feat-assets
```

实际发生的事：Claude 在 `.claude/worktrees/<name>/` 下创建 worktree，checkout 一个新分支，分支名是 `worktree-<name>`。Base 是本地 `origin/HEAD` 指向的那个分支 —— **不是你当前的分支**。如果 GitHub 上默认分支变过而本地没同步，你会从一个旧 ref 开分叉。用这条命令重新对齐：

```bash
git remote set-head origin -a
```

把 `.claude/worktrees/` 加进 `.gitignore`，免得主 checkout 里出现一堆 untracked 文件。

**复制 gitignore 文件。** 新建的 worktree 没有 `.env`、没有本地配置，因为那些本来就不跟踪。在仓库根目录加一个 `.worktreeinclude`，用 gitignore 语法列要复制的文件，Claude 每次建 worktree 都会自动拷过去：

```
.env
.env.local
config/secrets.json
```

只有"匹配 pattern 且被 gitignore"的文件会被复制，已跟踪的文件绝不会重复。

**清理。** 会话退出时若没改动，worktree 和分支会自动删除。有 commit 或脏文件时会提示是保留还是丢弃。在会话外手动创建的 worktree 用 `git worktree list` / `git worktree remove` 管。

我 80% 的情况都用这个。每个 session 完全隔离，像切换编辑器标签一样在终端间跳，没有任何协调成本。

---

#### 2. Agent Teams —— 多个 Claude 之间互相对话

Agent Teams 是更新、更激进的模式。一个 session 做 **lead**，它会 spawn 出 teammate，每个 teammate 有自己独立的 context window，而且彼此之间可以直接发消息，不必都经过 lead。需要 **Claude Code v2.1.32 或更高**，属于实验性功能，要主动开启 —— 把这段加进 `settings.json` 或环境变量：

```json
{
  "env": {
    "CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS": "1"
  }
}
```

然后用自然语言描述这个团队就行：

```
Create an agent team to build the dealer portal. Spawn three teammates:
- 一个负责 dashboard 后端（apps/dashboard/）
- 一个负责 customers 模块（apps/customers/）
- 一个负责 assets 模块（apps/assets/）
Have them share findings on shared types before touching overlapping files.
```

**显示模式。** 两种：

- **In-process** —— 所有 teammate 都跑在 lead 的同一个终端里。`Shift+Down` 轮询 teammate；`Enter` 进入某个 teammate 的会话；`Esc` 打断它当前这一轮；`Ctrl+T` 切换共享任务列表。任何终端都能用。
- **Split panes** —— 每个 teammate 一个独立面板。需要 **tmux**，或者 **iTerm2 + `it2` CLI**（并在 iTerm2 设置里打开 Python API）。macOS 上推荐组合是在 iTerm2 里 `tmux -CC`。

默认值是 `"auto"`：已经在 tmux session 里就用 split panes，否则走 in-process。全局覆写在 `~/.claude.json` 里设 `teammateMode`，单次覆写就 `claude --teammate-mode in-process`。

**共享了什么。** 一个带依赖追踪的任务列表（被 block 的任务会在父任务完成后自动解锁），以及一个 mailbox，teammate 可以 `message` 点对点发给一个同伴，也可以 `broadcast` 发给所有人。Teammate 和普通 session 一样会加载 `CLAUDE.md`、MCP、skills 这些项目上下文，但 **不会** 继承 lead 的对话历史。任务相关的细节得写进 spawn prompt。

**已知的坑。** `/resume` 恢复不了 in-process teammate。任务状态偶尔会滞后（teammate 忘了 mark complete，下游任务就卡住）。一个 lead 一次只能带一个团队，不支持嵌套团队，lead 身份在整个团队生命周期内固定不变。Split-pane 模式在 VS Code 集成终端、Windows Terminal、Ghostty 下不支持。

它真正值回票价的场景是 **研究和对抗性 review** —— 多个并行的调查员互相证伪对方的假设，比单个 agent 锚定在第一个看起来合理的解释上更快收敛到根因。对那种 teammate 之间真的需要沟通的实现任务，它确实很强；但如果任务本质只是"做这三件事"，worktree 更便宜。

---

#### 3. 对话内 Subagent

最轻量的一种。在同一个对话里让 Claude 并行 spawn 多个 subagent：

```
并行开发以下模块：

模块 A —— Dashboard：实现 KPI 统计 API……
模块 B —— Customer profile：GET/PUT /api/customers/profile/……
模块 C —— Assets：tag filtering 和 detail with items……
```

Claude 会给每个 subagent 分配独立的 context window，结果汇总回主对话。它们彼此之间不通信。想让它们也跑在隔离的 worktree 里？在 subagent 的 frontmatter 里加 `isolation: worktree`，每个 subagent 会拿到自己的 worktree，如果结束时没有任何改动就自动清理。

Subagent 便宜，因为回到主对话的只有摘要。代价是最脆弱的一种 —— subagent 一旦卡住或跑偏，你能介入的余地远不如 teammate，后者你可以 `Shift+Down` 直接进去改方向。

---

#### 真正重要的对比

|              | `--worktree` | Agent Teams | 对话内 Subagent |
| ------------ | ------------ | ----------- | --------------- |
| 通信         | 无 —— 完全独立 | Teammate 之间 + lead 互发消息 | 只向主对话汇报 |
| 协调         | 手动（切终端） | 共享任务列表、自动依赖解锁 | Lead 手动委派 |
| Token 成本   | 中（独立 session） | **高**（每个 teammate 都是完整实例） | 低（只回摘要） |
| 配置         | 无 | `CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1`，需 v2.1.32+ | 无 |
| 最适合       | 独立模块、长任务 | 对抗 review、跨层 feature、假设式 debug | 轻量并行小任务 |
| 最大风险     | 忘了哪个终端是哪个 | Teammate 悄悄 idle、任务状态滞后 | Subagent 卡住没救 |

---

#### 我自己遵守的规则

**适合并行：**
- 模块各在自己的目录下（`apps/dashboard/`、`apps/customers/`、`apps/assets/`）。
- 同时碰前后端，且前后端各自独立仓库或 package。
- 并行 code review，每个 reviewer 用不同 lens —— 安全、性能、测试覆盖。

**不要并行：**
- 多个 agent 会动同一个共享文件（`shared/types/`、顶层 `urls.py`、一个单体 schema）。
- 有真实依赖链 —— B 需要 A 的 API shape 先存在。
- 涉及数据库 migration。让一个 session 独占；worktree 共享同一个数据库，两个 agent 同时跑 migration 是你不需要的痛苦。

**合并策略**，并行分支跑完之后：

1. 先合基础分支（其他分支实际上依赖的那个）。
2. 逐个合并，每次合完跑一遍测试。
3. 冲突手动解，或者回到 Claude Code 里把两个分支都拉进上下文让它做一次 three-way 阅读。

---

#### 一个具体例子

前段时间重写 dealer portal 的完整流程：

```bash
# 终端 1 —— Dashboard 后端
claude -w feat-dashboard
> 在 apps/dashboard/ 下实现 KPI 聚合：recent orders、total revenue、
> pending count。参考 apps/orders/ 里的 repository 模式。

# 终端 2 —— Customer 后端
claude -w feat-customers
> 在 apps/customers/ 下实现 sub-user CRUD：list、create、update、delete。
> 用现有的 JWT 中间件鉴权。

# 终端 3 —— Assets 后端
claude -w feat-assets
> 在 apps/assets/ 下完善 tag filtering 和 detail-with-items 接口。
> 序列化风格对齐 apps/orders/serializers.py。
```

三个终端、三个 worktree、三个分支。每个跑完我 review diff、跑测试、按顺序合并。原本要一周顺序做完的活，一个下午搞定。

---

#### 到底该用哪个？

刚上手：**直接用 `--worktree`**。这是你之后也不会抛弃的那个。

当问题真的受益于 teammate 之间对话时 —— 多个假设 PK 的 debug、一次需要安全/性能/测试各过一遍的 PR review —— 再上 Agent Teams。Token 账单是真的，协调开销也是真的，单 session 更快能搞定的任务别拿来用。

对话内 Subagent 适合你想把结果汇总回当前在用的 session，而且能接受中途没法介入它们。

三种底层都是同一个把戏 —— 多个上下文，在同一个仓库上各自隔离工作。区别只在于它们之间需要多少沟通，以及你愿意为那些沟通付多少钱。

### 我的 Claude Code IDE：Zed 看改动，Ghostty + tmux 跑代理

https://misoto22.com/zh/blog/my-claude-code-terminal · 2026 · 约 15 分钟读完 · 人工智能

> 为什么我把编辑器和代理分在两个 app 里、tmux 怎么让 Claude Code 会话不随窗口关闭而丢、以及那些真正让整套环境顺手的小配置。

刚开始认真用 Claude Code 的时候，我以为会把它和 VS Code 的 Claude Code 插件一起用。试了几周，能用，但总像两个工具在抢同一块地盘。编辑器想让我看代码，代理想让我全神贯注，两个都在争窗口下半屏。

现在落下来的配置，是把这两个角色拆成两个 app：终端跑代理，编辑器负责看代理改了什么。这一拆反而成了整个工作流顺起来的关键。这篇文章就是这套配置的走读 —— Ghostty + tmux 负责代理那半边，Zed 负责看改动那半边，外加那些真正要解开才能用顺的小配置结。

---

#### 为什么是两个 app 而不是一个

基于插件的方案（VS Code + Claude Code 扩展）把代理和编辑器塞进一个窗口，方便，但有两件事一直别扭：

1. **代理那格永远太窄。** 默认编辑器占大头，而我真要看一段长 diff 时想让编辑器最大，不是代理最大。可拖的分隔条解决不了这个问题 —— 每次重排都是一个判断。
2. **会话和窗口绑死。** 关 VS Code，对话没了；重启，全没了。虽然有恢复功能，但那不等于会话真的还在跑。

把两个 app 拆开就把这两件事都解决了。代理在 Ghostty 里全屏跑（需要两个代理并行时就用 tmux 把它拆成多个 pane）。编辑器单独开，眼里只有代码。编辑器关了代理还在跑，代理改了文件编辑器自己会刷新。

心智模型很简单：**Claude Code 是一个进程，不是一个面板。** 一旦你这样想它，把它放到终端复用器里就是顺理成章的选择。

---

#### 整套配置一眼看

```diagram
{
  "caption": "Ghostty 里跑 tmux，tmux 里跑会话；Zed 在旁边开着，用来读改动。",
  "nodes": [
    {
      "id": "ghostty",
      "label": "Ghostty",
      "note": "全屏",
      "footnote": "由 tmux + resurrect 持久化",
      "children": [
        {
          "label": "tmux session: misoto22",
          "direction": "row",
          "children": [
            {
              "label": "claude",
              "note": "代理"
            },
            {
              "label": "pnpm dev",
              "note": "或者一个 REPL"
            }
          ]
        }
      ]
    },
    {
      "id": "zed",
      "label": "Zed",
      "note": "项目 diff · Git 面板",
      "footnote": "想看改动的时候才开",
      "children": [
        {
          "label": "行内 diff 标记",
          "note": "读 Claude 改了什么"
        }
      ]
    }
  ]
}
```

Ghostty 里跑 tmux，tmux 里跑 Claude Code 还有一个 dev server 或者 REPL。Zed 单独开，用来看 Claude 改了什么。关掉 Ghostty 并不会杀掉 Claude 会话 —— tmux 只是把它 detach 了，tmux-resurrect 之后还能把它搬回来。

下面逐项展开。

---

#### Zed —— 审阅那一半

之前一直在用 VS Code。把 Zed 换上来做我"以读为主"的编辑器，理由只有两条：它快到开起来我不会察觉，它的 git diff 集成好到"看代理改了啥"这件事做起来顺手。

**Git 集成**是我真正在用的。Zed 提供三种看改动的视图：

- **边栏彩色条**（新增 / 修改 / 删除）
- **行内 blame**，显示当前行的提交，挺适合区分"Claude 刚写的"和"本来就在那的"
- **Project Diff 视图**，把工作区里所有改动文件作为一个可编辑的 multibuffer 列出来 —— 可以直接用 `cmd-y` / `alt-y` 暂存或撤销某个 hunk

Git 面板和 Project Diff 都会在 Claude 写盘时立刻刷新，所以我能看着改动随代理出现。有一个要知道的坑：**已经打开的 buffer 不会自动 reload**。如果某个文件已经在 Zed 标签页里开着，Claude 又改了它，Zed 注意到磁盘变了，但标签页里的内容还是旧的，除非你手动触发 `editor::ReloadFile`。社区早就提了"未修改 buffer 自动刷新"的需求，但还没实装。

我的习惯是：用 Git 面板和 Project Diff *找到* Claude 改了哪些文件，然后重新打开（不是复用已开的标签）去读内容。这样就绕开了 stale buffer 的问题。

**拆分视图** —— 适合一边是 diff、一边是当前文件的场景，或者两个相关文件并排。Zed 拆分够轻，我会下意识就拆，而不是不停切标签。

Zed 我不用来写代码。代码 Claude 基本都写了，Zed 就是读、批、偶尔改一两行比解释给 Claude 还快的地方。

---

#### Ghostty —— 终端这一层

Ghostty 是 GPU 加速、零配置哲学的终端。功能上对标 iTerm2 和 Alacritty，但默认就够好用，我除了改字体大小之外基本没动它的配置。

专门为 Claude Code 选它的理由：

- **`Shift+Enter` 原生就支持**多行输入。大部分终端需要改键绑定；文档里明确列出能开箱即用的只有 Ghostty、iTerm2、WezTerm、Kitty 四个。
- **桌面通知开箱即用。** Claude 长任务完成后发通知 hook，Ghostty 会像其它 macOS 通知一样弹出来。
- **GPU 渲染**让 scrollback 和长输出不卡。Claude 往终端里倒几千个 token 的时候，你会明显感受到这点比想象中重要。

Ghostty 有意做得"无聊" —— 没有高级标签 UI、没有插件系统、没有脚本层。要 session 和拆分就去用 tmux。它就是这个意思。

---

#### tmux —— session、window、pane

tmux 是终端复用器。没用过的话，三个概念要搞清楚：

- **pane** —— 一块终端区域，里面跑一个 shell 或一个程序。
- **window** —— 一个标签页，包含一个或多个 pane。
- **session** —— 一组 window。这是能跨终端崩溃、重启、SSH 断开而保留的单位。

核心动作是 **detach**。从 session detach 之后，里面所有东西都还在跑 —— Claude Code 对话、dev server、REPL、长时间编译都还在。之后 `tmux attach -t <name>` 就能回到你离开时的样子。整个关掉 Ghostty 再开 —— 还在跑；部署做一半 SSH 掉了 —— 还在跑。

对 Claude Code 来说，这恰好解决了终端代理最烦人的一件事：关窗口就丢对话。

我默认的项目 session 是两个 pane：左边 Claude Code，右边 dev server 或者测试 watcher。同时在不同分支上跑两个代理的时候，就变成一个 window 里上下两个横 pane，一个项目一个 session。

---

#### 会话持久化 —— 让重启也扛得住

detach / reattach 解决关窗口和 SSH 断开的问题，但扛不住重启 —— tmux session 存在内存里，机器一重启就没了。要让它扛住重启需要两个插件；对 Claude Code 来说，还要加第三个。

**tmux-resurrect** 负责保存环境：session 列表、window 布局、pane 排列、工作目录，很多情况下连 pane 里跑的程序也能恢复。`prefix + Ctrl-s` 保存，`prefix + Ctrl-r` 恢复。

**tmux-continuum** 在 resurrect 之上再自动化一层。每 15 分钟在后台自动保存，加一行配置还能在 tmux 启动时自动恢复：

```text
set -g @continuum-restore 'on'
```

设完之后重启机器、敲 `tmux`，自动就回到上次的样子。

但有个缺口：普通的 resurrect 能记住某个 pane 里"跑着 Claude Code"，但记不住"跑的是哪段对话"。恢复回来的 pane 打开的是全新会话，不是你之前那段。对 shell 和 dev server 来说没关系，想续一段长对话就不够用了。

**tmux-assistant-resurrect** 就是补这个缺口的配套插件。它挂到 Claude Code 的 `SessionStart` 事件上记下每个在跑会话的 UUID，再挂到 tmux-resurrect 的保存 / 恢复生命周期里，恢复时自动在对应 pane 里跑 `claude --resume <id>`。它重建的命令会保留你原来的 CLI 参数、环境变量和会话历史。通过 TPM 和另外两个一起装：

```text
set -g @plugin 'tmux-plugins/tmux-resurrect'
set -g @plugin 'tmux-plugins/tmux-continuum'
set -g @plugin 'timvw/tmux-assistant-resurrect'
```

实际效果是：重启机器，Ghostty 开起来，tmux 自动 attach，三个 Claude Code 窗口都回到原来的状态 —— 同一段对话、同一个工作目录、旁边的 dev server 也还在跑。

---

#### 真正要改的几条配置

`~/.tmux.conf` 大部分是个人偏好。但有几行专门为 Claude Code 是必须的。

**Shift+Enter 能换行。** tmux 默认会吃掉扩展按键，所以 `Shift+Enter` 在 Claude Code 里会变成"发送消息"而不是"插入换行"。两行解决：

```text
set -s extended-keys on
set -as terminal-features 'xterm*:extkeys'
```

第一行让 tmux 在 Claude Code 请求时承认扩展按键；第二行告诉 tmux 外层终端（Ghostty）能传这些序列进来。

**桌面通知打通。** Claude Code 通知 hook 发的是一段逃逸序列，外层终端把它转成系统通知。tmux 默认会拦掉，所以通知到不了 Ghostty。一行放开：

```text
set -g allow-passthrough on
```

同一个设置也让 Claude Code 的终端进度条能穿透到外层终端。

**合理的默认。** 我用了标准的 `tmux-sensible` 插件，省得一条条记那些古怪的默认值。在它之上我只多改两样：提高 history 上限（Claude 有时会倒出很长的 trace）、开 `set -g mouse on` 让我能点击切 pane 而不是每次都先 prefix。

剩下的 —— 状态栏、颜色、键重映 —— 都是口味，跟 Claude 无关。

---

#### 日常怎么用

习惯成自然之后的流程：

1. `tmux attach`（或者 continuum 已经恢复好的话直接 `tmux`）—— 瞬间回到离开时的状态。
2. 切到跑 Claude Code 的那个 pane。对话还在。
3. 说要做什么。Claude 改文件、跑测试、提交。
4. 要审阅的时候 `cmd-tab` 到 Zed。Project Diff 里能看到所有改动。读、批、或者把修改意见发回给 Claude。
5. 终端不好看清的东西 —— 一段很长的日志、pane 放不下的 diff —— 也 `cmd-tab` 去 Zed 里看。
6. 晚上合上笔记本，早上掀开。同样的 session、同样的对话、dev server 还在跑。

两个 app、职责清楚、切换不痛。到这个阶段就不再像是"配置"，而是一个完整的环境。

---

#### 从零开始，先配这几样

如果你要从 VS Code + Claude Code 扩展切到终端托管方案，按投入产出比排：

1. **装一个 `Shift+Enter` 原生就能用的终端。** Ghostty、iTerm2、WezTerm、Kitty 四选一。
2. **装 tmux。** 插件先放一边，光 detach / reattach 这一项就值了。
3. **装 `tmux-resurrect`、`tmux-continuum`、`tmux-assistant-resurrect`。** 通过 TPM 十五分钟能搞完，这是让整套环境真正稳固的一环。
4. **加那三条必须的 tmux 配置**（`extended-keys`、`terminal-features`、`allow-passthrough`）。
5. **挑一个用来审阅的编辑器。** 我用 Zed，不装 Claude 扩展的 VS Code 也行。关键是让编辑器变成"以读为主的配角"，不是代理的宿主。
6. **别再忙着拉 pane，学会信任 `cmd-tab`。** 两个全屏窗口一开始会觉得怪，给一周时间。

AI 驱动的工作流里大部分摩擦，都来自工具跟你抢注意力。把代理和编辑器拆成两个 app 就结束了这场争夺。其它的 —— 会话持久化、键绑定、diff 视图 —— 都是这一个决定之上的抛光。

---

#### 参考资料

下面每个链接都是写这篇时实际翻过的页面。一句话注释说的是"点进去你真正能拿到什么"。

1. **[Optimize your terminal setup](https://code.claude.com/docs/en/terminal-config)** —— Claude Code 官方终端指南。`Shift+Enter` 配置、tmux extended-keys 要求、通知穿透的权威参考。动 `~/.tmux.conf` 之前先读它。
2. **[Zed Git documentation](https://zed.dev/docs/git)** —— Git 面板、Project Diff、行内 blame、两种 diff 视图。边栏和词级高亮的说明也在这儿。
3. **[Ghostty documentation](https://ghostty.org/docs)** —— Ghostty 的设计哲学和支持平台。文档故意写得短，因为它本来就没什么需要学的。
4. **[tmux-resurrect](https://github.com/tmux-plugins/tmux-resurrect)** 和 **[tmux-continuum](https://github.com/tmux-plugins/tmux-continuum)** —— tmux 会话持久化的两个标准插件。两篇都要读：resurrect 讲"保存了什么"，continuum 讲"怎么让它自动"。
5. **[tmux-assistant-resurrect](https://github.com/timvw/tmux-assistant-resurrect)** —— Claude Code 专属的那一层胶水，把"tmux 回来了"变成"我的 Claude 对话也回来了"。README 讲清楚了它怎么挂 `SessionStart` 跟踪 session ID、怎么在恢复时跑 `claude --resume`。

### 我的 2026 年 Mac 应用清单

https://misoto22.com/zh/blog/my-2026-mac-apps · 2026 · 约 16 分钟读完 · 开发

> 十三个让我每天开发更顺手的 Mac 应用——从终端、编辑器到语音输入、远程连接和密码管理，每个都解决一个明确的问题。

用 Mac 做开发几年了，工具换过不少轮。2026 年稳定下来的这套，是真正每天都在用、离不开的。不搞“百大应用推荐”，就这十三个：有的负责写代码，有的让两台电脑不在同一个局域网时也能互相连接，还有一个把背后的账号和项目密钥管好。每个都有留下来的理由。

---

#### Ghostty — 终端

[Ghostty](https://ghostty.org/) 是 Mitchell Hashimoto（HashiCorp 创始人）用 Zig 写的终端模拟器。快，是真的快 —— GPU 加速渲染，冷启动几乎无感。配置极简，一个 `~/.config/ghostty/config` 文件搞定一切，不需要 YAML/TOML/JSON 之争。

我从 iTerm2 切过来之后就没回去过。iTerm2 功能多但启动慢、配置繁杂；Ghostty 做减法，只留终端该有的东西，但每一项都做到极致。原生 macOS 渲染、字体 fallback 完美、分屏也够用。

对我来说终端就该是这样：打开就能用，不挡路。

---

#### Zed — 代码编辑器

[Zed](https://zed.dev/) 是 Atom 原班团队用 Rust 重写的编辑器。启动秒开，大文件不卡，这两件事就已经赢了。

我把大部分轻量编辑都交给了 Zed：快速改配置、看日志、写 Markdown。VS Code 我还留着，主要用它的插件生态（Remote SSH、特定语言的 debugger）。但日常"打开一个文件改几行"这种事，Zed 比 VS Code 快太多了。

多人实时协作编辑是 Zed 的核心功能之一，不过我还没怎么用到。AI 集成做得也不错。内置终端、文件树、搜索都够用。说白了就是一个不臃肿的现代编辑器，该有的都有，不该有的不塞。

---

#### Warp — AI 终端

等一下，已经有 Ghostty 了为什么还要 [Warp](https://www.warp.dev/)？

因为它们解决的问题不一样。Ghostty 是纯粹的终端模拟器，快且稳；Warp 是"终端 + IDE"的混合体。Warp 的输入框是真正的编辑器，支持光标自由移动、多行编辑、命令历史搜索。每条命令的输出是独立的 block，可以折叠、复制、滚动，不会和上下文混在一起。

我用 Ghostty 跑长期进程（dev server、docker compose），用 Warp 做交互式操作（git、调试、临时脚本）。两个并行用，各取所长。

Warp 还有 AI 命令解释和生成功能。虽然我不常用（Claude Code 已经够了），但偶尔忘了某个 `ffmpeg` 参数的时候确实方便。

---

#### Typeless — 给编程 Agent 的语音输入

[Typeless](https://www.typeless.com/) 是一个全局可用的 AI 语音输入工具。我可以按平时说话的方式表达，中间有停顿、改口也没关系，它会去掉口头禅和重复，把结果整理成干净的文字，直接放进当前应用的输入框。

它对我帮助最大的场景是 agentic coding。给 Codex 或 Claude Code 的好任务，通常不是一句“帮我修这个 bug”，而是要把目标、现状、限制条件和验收标准讲清楚。这样的上下文用说的比用键盘敲更自然，特别适合一轮任务的开场、看完 diff 后给 review 意见，或者读完 agent 的计划再补充要求。

代码、Shell 命令、文件路径和凭据我还是会手打。语音负责表达意图，键盘负责精确语法。边界清楚之后，Typeless 才真正进入了我的开发流程，而不只是一个新鲜的输入方式。

---

#### DataGrip — 数据库 IDE

[DataGrip](https://www.jetbrains.com/datagrip/) 是 JetBrains 出的数据库 IDE。工作中要连 SQL Server 2025，个人项目用 Supabase (PostgreSQL)，DataGrip 一个工具全搞定。

SQL 自动补全、表结构可视化、查询结果导出、多数据源同时连接 —— 这些功能听起来平平无奇，但 DataGrip 的补全质量是我用过所有数据库工具里最好的。它真的理解你的 schema，不是简单的关键字匹配。

唯一缺点是 JetBrains 系一贯的问题：吃内存。但对于数据库工作来说，没有更好的替代品了。DBeaver 免费但体验差不少。

---

#### Typora — Markdown 编辑器

[Typora](https://typora.io/) 是所见即所得的 Markdown 编辑器，一直是我写长文的首选。

很多人觉得"VS Code / Zed 也能写 Markdown"，确实可以，但体验完全不同。Typora 的实时预览不是分屏，而是直接在你打字的地方渲染。写作时不会被语法符号打断思路。

我用 Typora 写博客草稿、项目文档、会议记录。最后再拷到 MDX 文件里。也许有点"多此一举"，但写作这件事，工具的手感比效率更重要。Typora 的手感是最好的。

图片直接拖入、表格编辑、主题自定义、导出 PDF —— 该有的都有，而且一直稳定，几乎不出 bug。

---

#### PDF Expert — PDF 阅读与标注

[PDF Expert](https://pdfexpert.com/) 是 macOS 上最好的 PDF 工具，没有之一。

Preview.app 能应付简单查看，但一旦需要标注、签名、编辑文字、合并文件，就完全不够用了。PDF Expert 启动快、渲染准确、标注工具齐全。我每天用它看技术文档和论文，高亮 + 批注的工作流非常顺畅。

表单填写、签名也是高频场景。比 Adobe Acrobat 轻量得多，价格也合理（买断制）。

---

#### Docker Desktop — 容器

[Docker Desktop](https://www.docker.com/products/docker-desktop/) 不需要过多介绍。本地开发需要跑数据库、消息队列、各种服务的时候，Docker Compose 一键搞定。

我主要用它来：
- 本地跑 PostgreSQL、Redis
- 测试多服务架构
- 复现生产环境问题

macOS 上 Docker Desktop 的性能一直被诟病（因为要跑 Linux VM），但最近几个版本改善了不少。VirtioFS 文件系统让 bind mount 的速度可以接受了。

OrbStack 是一个更轻量的替代品，但 Docker Desktop 对我来说够用了，而且和 VS Code Dev Containers 的集成更成熟。

---

#### Tailscale — 两台电脑之间的私人网络

[Tailscale](https://tailscale.com/) 解决的是一个很具体的问题：我的两台笔记本不在同一个局域网时，怎么继续安全地互相访问。每台设备都有稳定的私有地址和 MagicDNS 名称，所以一台留在家里，另一台在公司或外面，也不需要配置路由器端口转发，更不用把服务直接暴露到公网。

我会通过这条私人连接用 SSH、访问开发服务器和内部 dashboard，偶尔也传文件。目标电脑上仍然要先运行对应的服务——Tailscale 提供的是网络通道，不会凭空变出 SSH 或 Web 服务——但两台电脑加入同一个 tailnet 之后，换 Wi-Fi 不再改变我的连接方式。

网络条件允许时，两台设备会直接连接；直连不通时则自动走中继。对我来说这部分是透明的，同一个私有主机名始终能用。

---

#### 1Password — 账号与项目密钥

[1Password](https://1password.com/) 先管好我个人 AI 工作流的入口：ChatGPT 和 Claude 的登录信息、passkey、恢复码以及相关账号资料。它们不会散落在浏览器备注、Shell 历史或某个迟早会被复制错地方的文本文件里。

现在它也是这个项目的凭据来源。仓库里可以提交一个不包含真实值的引用：

```dotenv
LLM_API_KEY=op://.../LLM_API_KEY
```

真正的值留在 1Password。本地开发时，`op run` 解析引用，只把凭据交给当前启动的子进程；生产环境也在部署前走同样的思路，让运行时拿到需要的值，同时避免把 GitHub 或代码仓库变成密钥的最终来源。

Codex 和 Claude Code 能替我执行命令之后，这条边界更重要。我不会给 agent 整个保险库的读取权限，而是只给当前命令需要的环境变量。1Password 不只是“保存密码的地方”，它隔开了“agent 能完成工作”和“agent 能看到我所有凭据”这两件事。

---

#### Raycast — Spotlight 替代品

[Raycast](https://www.raycast.com/) 是我装完 Mac 第一个配置的应用。装完之后，Spotlight 就再也没打开过。

##### 为什么替代 Spotlight

Spotlight 的问题不是不能用，而是太慢、太笨。搜索应用时经常排序不对，搜文件更是一言难尽。Raycast 的模糊匹配又快又准，打两三个字母就能定位到想要的应用。而且 Raycast ���只是启动器 —— 它是一个可编程的命令面板，装上插件之后能取代一堆独立小工具。

我把 Raycast 绑定到 `Cmd+Space`，完全替代系统 Spotlight。

##### Snippets —— 最高频的功能

Snippets 是我在 Raycast 里用得最多的功能。原理很简单：定义一个关键词，输入时自动展开成完整内容。

我的常用 Snippets：

| 关键词 | 展开内容 | 场景 |
|--------|----------|------|
| `;phone` | 我的手机号 | 填表、注册账号 |
| `;email` | 常用邮箱地址 | 各种登录、联系表单 |
| `;addr` | 家庭地址 | 快递、外卖、注册 |
| `;waddr` | 公司地址 | 工作相关表单 |
| `;card` | 银行卡尾号（不存完整卡号） | 提醒自己用哪张卡 |
| `;sig` | 邮件签名 | 写邮件 |
| `;zoom` | Zoom 会议链接 | 约会议 |

这些信息每天都要输入好几次。没有 Snippets 之前，要么手动打，要么从备忘录里复制粘贴。现在打 `;phone` 瞬间展开，在任何应用里都能用。

你也可以用它做代码模板（比如 `;log` 展开成 `console.log()`），但我个人代码补全交给编辑器和 Claude Code，Snippets 主要存个人信息和常用文本。

##### Clipboard History —— 不再丢失复制的内容

macOS 原生剪贴板只保留最后一条。复制了一段代码，再复制一个链接，代码就没了。

Raycast 的 Clipboard History 解决了这个痛点：
- **无限历史**：所有复制过的内容都保留，文字、图片、文件都支持
- **搜索**：`Cmd+Shift+V` 打开历史面板，直接搜索关键词定位
- **置顶**：常用的内容可以 pin 住，随时粘贴

这个功能听起来简单，但用过就回不去了。特别是开发时经常需要在多段代码之间复制粘贴，有历史记录不用来回切窗口了。

##### 其他好用的功能

- **窗口管理**：快捷键把窗口移到左半/右半/全屏，不需要额外装 Magnet 或 Rectangle
- **计算器**：直接在搜索框算，支持单位换算和汇率
- **插件生态**：Jira、GitHub、Linear、Notion 都有高质量的社区插件

免费版已经覆盖了 90% 的使用场景。Pro 版的 AI 功能不错但不是必需。

---

#### Shottr — 截图

[Shottr](https://shottr.cc/) 是一个轻量级截图工具。

macOS 自带的截图（`Cmd+Shift+4`）够用，但 Shottr 多了几个关键功能：
- **滚动截图**：长网页、长对话一键截全
- **标注**：箭头、方框、文字、马赛克，截完直接标注
- **OCR**：截图后直接识别文字，复制出来
- **测量**：量像素间距、颜色取值，做 UI 开发很方便
- **钉图**：把截图钉在屏幕最上层，方便对照

免费、轻量、原生 macOS 体验。CleanShot X 功能更多但要付费，Shottr 对我来说刚好够用。

---

#### Bob — 翻译

[Bob](https://bobtranslate.com/) 是 macOS 上的翻译工具，支持划词翻译、截图翻译和输入翻译。

作为一个需要在中英文之间频繁切换的开发者，Bob 对我来说是刚需。看英文文档遇到不确定的词，划一下就出翻译结果，不用切到浏览器开 Google Translate。

Bob 的强大在于它支持多翻译源：可以同时显示 DeepL、OpenAI、Google Translate 的结果，对比着看哪个翻译更准确。我现在主要用 DeepL + OpenAI 的组合。

截图翻译也很实用 —— 遇到图片里的文字（比如 UI 截图、PDF 扫描件），直接截图就能翻译，不用手动打字。

---

#### 总结

这十三个应用覆盖了我日常开发里那些“工具选得对不对，体验真的会不一样”的场景：

| 场景 | 工具 |
|------|------|
| 终端 | Ghostty（纯终端）+ Warp（交互式） |
| 代码编辑 | Zed（轻量）+ VS Code（重型） |
| 数据库 | DataGrip |
| 写作 | Typora |
| PDF | PDF Expert |
| 容器 | Docker Desktop |
| 启动器 | Raycast |
| 截图 | Shottr |
| 翻译 | Bob |
| 语音输入 | Typeless |
| 远程连接 | Tailscale |
| 密码与项目密钥 | 1Password |

选工具我的原则还是：**解决一个问题，解决得彻底**。我不在意它是不是功能最多、是不是刚好最流行；真正会留下来的，是那些我会反复打开，而且能用一句话说清楚为什么需要它的工具。这十三个现在刚好组成了我 Mac 工作流外面那一层：表达意图、让 agent 工作、连接设备、保护凭据，再把写代码之外的事情处理好。

### 我如何配置 Claude Code 作为日常开发伙伴

https://misoto22.com/zh/blog/my-claude-code-setup · 2026 · 约 25 分钟读完 · 人工智能

> 从零开始介绍 Claude Code —— 它是什么、配置分几层作用域、全局和项目层面的规则 / 插件 / Hooks / 自动记忆 / CLI 组合起来如何支撑日常开发。

Claude Code 是我每天花时间最多的工具 —— 一小时写 Rust，下一小时跳到 Python，再下一小时进 TypeScript 项目。一天几百条消息，横跨四五个仓库。折腾了几个月，围绕它的这套配置已经远不止是代码补全：它会执行规约、跨会话记住上下文，还能代我驱动真实的基础设施。这篇文章从头把它讲一遍。如果你从没打开过 Claude Code，前半部分就是写给你的；如果已经在用了，直接跳到后面的配置章节。

---

#### Claude Code 到底是什么

Claude Code 是 Anthropic 的官方 CLI。在终端敲 `claude` 就会打开一个交互式 Agent，能读写文件、执行 shell、搜网页、调用 [MCP server](https://code.claude.com/docs/en/mcp)、把任务分派给专门的子代理 —— 而且始终以你启动时所在的目录为根。

底层还是和 API 一样的 Claude 模型，让它区别于聊天窗口的是模型之外的那一圈东西：工具、权限、Hooks、记忆、插件。模型本身是固定的，你能调的是外面那层壳。

我脑子里的模型是这样的：**Claude Code 是一个套在 Agent 外面的壳。** Agent 很聪明，壳是你要配的东西。

---

#### 配置放在哪里

四层作用域，遇到冲突时下面覆盖上面：

| 作用域 | 位置 | 影响 | 是否共享 |
|-------|------|-----|---------|
| 受管策略 | 操作系统级别的策略目录 | 机器上所有用户 | IT 部署 |
| 用户 | `~/.claude/` | 你自己所有项目 | 否 |
| 项目 | 仓库根的 `.claude/` + `CLAUDE.md` | 仓库里所有协作者 | 是（提交进 git） |
| 本地 | `.claude/settings.local.json` | 你在这个仓库 | 否（在 `.gitignore` 里） |

后面讲到的每一样东西都落在这个阶梯的某一层。规则对所有项目成立 → 用户层；约定只对这个仓库成立 → 项目层；和你本机相关的差异（某个工具的本地路径、私人 API key）→ 本地层。受管策略是组织层面推下来、个人覆盖不掉的那一档。

这种分层看似琐碎，但真的重要。用户层规则是你"不用再重复自己"的地方；项目层规则是你告诉 Claude "这个仓库里有哪些非显式约定"的地方，而不用把它塞进每个会话的系统提示里。

---

#### 全局目录结构

我的用户作用域长这样：

```
~/.claude/
├── CLAUDE.md              # 全局指令，每次会话自动加载
├── settings.json          # 权限、Hooks、插件、状态栏
├── rules/                 # 按主题拆分的指令文件
├── skills/                # 用 /命令 触发的自定义工作流
├── projects/              # 按项目的自动记忆（Claude 自己维护）
│   └── <project>/memory/
│       ├── MEMORY.md
│       └── ...
├── statusline-command.sh  # 状态栏脚本
├── update-pricing.sh      # 每日拉 Anthropic 定价
└── cache/
    └── model-pricing.env
```

后面几节按实际搭建顺序依次展开。

---

#### CLAUDE.md —— 系统指令

这是 Claude 每次会话开头都会读的文件，相当于常驻 briefing：你是谁、在做什么、要遵守哪些约定。

可以在 `~/.claude/CLAUDE.md`（到处生效）放一份，在每个仓库的 `./CLAUDE.md` 或 `./.claude/CLAUDE.md`（只在本仓库生效）再放一份，还可以在项目里放一份不进 git 的 `./CLAUDE.local.md`。所有找到的文件会直接拼接进上下文，不会互相覆盖。新项目第一次用时，可以用 `/init` 让 Claude 扫描代码、生成一份起点。

我的全局版本写得很短，是索引而不是手册：

- **身份** —— 全栈开发，Rust / C# / .NET / Python / TypeScript。
- **环境** —— macOS 宿主机 + Parallels 里的 Windows 虚拟机，用来跑 .NET Framework 4.8 项目。
- **行为** —— 先读代码再改、不确定就问、匹配现有风格、永远不对 `main` 做 force-push。
- **规则索引** —— 每个领域指向 `rules/` 下对应的文件。

之所以写短，是因为一大坨文字的系统提示容易被忽略 —— 对模型是这样，对未来想来编辑的自己也是这样。官方文档自己的建议是每份 `CLAUDE.md` 控制在 200 行以内。索引告诉 Claude 去哪找，实际内容就地贴近被使用的地方。

还可以用 `@路径` 导入其它文件 —— 引用 `README`、`package.json`、或者从家目录里引用一份共享规则文件都很好使。导入是递归的，所以一份项目 `CLAUDE.md` 可以引用你个人的偏好文件而不用重复写。

项目级 `CLAUDE.md` 装的是仓库特有的约定 —— 包管理器、内容模型、导入别名、测试命令。我个人网站那份大致是：

- 用 `pnpm`（不用 `npm`）。
- 博客是 `content/blog/` 下的 MDX 文件。
- 导入用 `@/i18n/navigation`（不用 `next/link`）。
- API 响应遵循全局 RFC 9457 规则。

`cd` 进另一个仓库时，项目级 `CLAUDE.md` 自动加载，叠在全局那份上面。不用每次重新交代。

---

#### 规则文件 —— 作用域化的可重载标准

Claude Code 专门给模块化指令留了一个目录：项目层的 `.claude/rules/` 和用户层的 `~/.claude/rules/`。目录里的每个 `.md` 文件都会被递归发现，和 `CLAUDE.md` 一起加载。这是内建功能，不是你要自己拼的。

比"直接往 `CLAUDE.md` 里加内容"强的一点在这里：规则可以通过 frontmatter 绑定到特定文件。一条 `paths: ["src/api/**/*.ts"]` 的规则，只在 Claude 真的动 API 文件时才进入上下文，其它时候不占位置。

我用户层的规则集是十个文件，每个管一件事：

| 文件 | 管什么 |
|------|-------|
| `coding-style.md` | 单一职责、函数不超过 50 行、注释写"为什么" |
| `git-workflow.md` | 祈使句 commit、feature 分支、提交前跑 linter |
| `security.md` | 不提交密钥、参数化 SQL、只走 HTTPS |
| `testing.md` | 正常路径 + 边界 + 错误路径，各语言各自的测试框架 |
| `rust.md` | `anyhow` / `thiserror`、禁用 `unwrap()`、`Service<R: Repository>` 模式 |
| `python-django.md` | Django 5 + DRF、`mssql-django`、`managed = False`、用 `uv` 不用 pip |
| `api-design.md` | RFC 9457 错误格式、游标分页、所有路径带限流 header |
| `patterns.md` | Repository 模式、分层错误处理 |
| `performance.md` | 模型选择、上下文预算、压缩策略 |
| `agents.md` | 什么时候分派子代理、并行执行、插件与任务的映射 |

这样做换来两个好处：

1. **规则扛得住压缩。** Claude 在会话过程中学到的东西，被压缩时可能丢；项目根的 `CLAUDE.md` 和 `.claude/rules/` 文件会在压缩后从磁盘重新注入。
2. **它们可以 diff。** 改一个标准只改一个文件，不用翻四条散落在 shell 历史里的提示。

效果日常就能看到。Claude 会在 Rust 提交前自己跑 `cargo clippy`，Python 项目默认用 `uv`，API 错误格式严格走 RFC 9457 —— 我一句不用说。规则写一次，就不用再重复自己。

---

#### settings.json —— 权限 / Hooks / 插件 / 状态栏

`settings.json` 是全局配置的另一半。它决定 Claude *能做什么*，以及它做事前后*周围发生什么*。下面每一节都够单独展开。

---

#### 权限 —— 纵深防御

权限分成允许列表（自动批准，不弹窗）和拒绝列表（硬性封堵，不可覆盖）。

**允许**（自动批准）：

- 文件操作：`Read`、`Edit`、`Write`、`Glob`、`Grep`
- Shell：`Bash`，由拒绝列表兜底
- 网络：`WebFetch`、`WebSearch`
- Playwright 和 Context7 的所有工具

**拒绝**（绝对不能跑，哪怕模型觉得该跑）：

- 破坏性：`rm -rf /`、`git push --force`、`git reset --hard`、`git clean -f`
- 危险：`dd`、`mkfs`、`shutdown`、`reboot`、`chmod 777`
- 供应链：`npm publish`、`cargo publish`、`curl | bash`
- 密钥：`cat ~/.ssh/*`、`cat *id_rsa*`

拒绝列表是配置里投入产出比最高的一项。模型偶尔会产生幻觉想跑危险命令，这没关系 —— 只要真的跑不起来就行。这东西应该在出事之前就配好，而不是出事之后再补。

想更细的话，权限接受*参数形状*模式 —— 比如 `Bash(gh pr *)` 而不是整条 `Bash` 都放开给 `gh`。我在"想用某个 CLI 但不完全信任它任意子命令"的场景会用这种写法。

---

#### Hooks —— 自动护栏

Hooks 在 Claude 使用工具的前后执行 shell 命令。官方文档列了二十多个事件 —— `PreToolUse`、`PostToolUse`、`UserPromptSubmit`、`SessionStart`、`Stop`、`PreCompact`、`PostCompact`、`SubagentStart`、`FileChanged` 等等。每个事件会通过 stdin 收到一段 JSON，返回结构化输出可以拦截、修改或者只是观察这次调用。

我用了三个。

`PreToolUse` —— 任何 `Bash` 调用前提醒一下：

```json
{
  "hooks": {
    "PreToolUse": [{
      "matcher": "Bash",
      "hooks": [{
        "type": "command",
        "command": "echo '⚠️  Confirm branch and remote before pushing'"
      }]
    }]
  }
}
```

`matcher` 是工具名、`|` 分隔的列表，或者正则。`PreToolUse` 也可以用 `{"hookSpecificOutput": {"permissionDecision": "deny"}}` 直接拦下一次调用 —— 组织级策略超出静态拒绝列表的部分就靠这个。

`PostToolUse` —— 每次编辑文件后，按扩展名提醒跑对应的 linter。参数走 stdin，用 `jq` 取文件路径：

```bash
FILE=$(jq -r '.tool_input.file_path // empty')
case "$FILE" in
  *.rs) echo "Reminder: run cargo clippy" ;;
  *.cs) echo "Reminder: run dotnet build" ;;
  *.py) echo "Reminder: run ruff check && ruff format" ;;
esac
```

两个都不阻塞调用，就是拍一下肩膀。但它们能拦住不少问题，因为每次都会触发，不依赖 Claude 自己记得问。

`SessionStart` hook 给我一次机会，在会话第一条消息里塞进自定义上下文 —— git 状态、分支陈旧程度、某个在进行中的实验提醒。

---

#### 插件和 marketplace

插件把 skill、agent、hook、MCP server、slash 命令打包成一个可安装的整体。Claude Code 自带官方 marketplace（`claude-plugins-official`），第三方 marketplace 可以从 GitHub 仓库、Git URL 或本地路径加进来。

装一个插件就一行：

```shell
/plugin install github@claude-plugins-official
/plugin install supabase@claude-plugins-official
/plugin install vercel@claude-plugins-official
```

插件的 skill 会按插件名加命名空间（`/github:create-pr`），所以两个插件可以有同名 skill 而不冲突。

我装了十来个。官方的：

- **commit-commands** —— `/commit`、`/commit-push-pr`
- **github** —— issue、PR、release，基于 GitHub MCP
- **vercel** —— 部署、可观测性、Next.js 和 AI SDK 指导
- **supabase** —— 项目检视和 SQL
- **code-review** —— 按置信度过滤的 PR 审查
- **rust-analyzer-lsp**、**typescript-lsp** —— 基于 LSP 的实时诊断

第三方：

- **[context7](https://github.com/upstash/context7)** —— 拉实时库文档，填训练数据过时的坑
- **[interface-design](https://github.com/Dammyjay93/interface-design)** —— 设计系统审计
- **[ecc](https://github.com/affaan-m/everything-claude-code)** —— 一个很大的社区合集
- **playwright** —— 浏览器自动化

加第三方 marketplace 通常通过 `/plugin marketplace add`，也可以写到 `settings.json` 里 —— 这样队友信任这个仓库后会被提示安装：

```json
{
  "extraKnownMarketplaces": {
    "interface-design": {
      "source": {
        "source": "github",
        "repo": "Dammyjay93/interface-design"
      }
    }
  }
}
```

我在 `rules/agents.md` 里放了一张插件—任务映射表，让 Claude 不用问就能选对：

| 任务 | 插件 |
|------|------|
| 规划功能 | `/feature-dev` 或 Plan 模式 |
| 审查代码 / PR | `/code-review` |
| 简化代码 | `/simplify` |
| Git 提交 | `/commit-commands:commit` |
| 提交 + 推送 + PR | `/commit-commands:commit-push-pr` |
| Rust 诊断 | `rust-analyzer-lsp` |
| 浏览器测试 | `playwright` |

---

#### 平台 CLI

刚才插件列表里有几个（`github`、`vercel`、`supabase`、`context7`、`playwright`）本质上就是包了一层的 MCP server，去 API 拿结构化的数据回来，用来读状态很合适。但真要动手做事 —— 发部署、跑迁移、出 release —— 我更习惯让 Claude 直接通过 `Bash` 调平台自己的 CLI。

常用的几个：

- **`gh`** —— 建 PR、轮询 CI、合并。`/ship` 说白了就是一串 `gh` 命令串起来。
- **Supabase CLI** —— `db diff`、migration、edge function 部署。Supabase MCP 查状态，CLI 负责改动。
- **Vercel CLI** —— `vercel deploy`、`env pull`、日志 tail。插件告诉 Claude 现在什么情况，CLI 让它能动手。
- **AWS CLI** —— 列桶、tail 日志、看 EC2。IAM 继续兜底，能做什么由它管。
- **gcloud / kubectl / docker** —— 进各自的世界都一样。

不用再套一层，反正走的是 `Bash`，前面"权限"一节的拒绝列表已经拦住 `--force` 这类危险参数，`PostToolUse` hook 也会在改动前提醒一下。想更细一点，就用 `Bash(gh pr *)` 这种方式放开特定子命令，不把 `gh` 整个开出去。

慢慢磨出来的分工：MCP 读、CLI 写、插件负责两样都要用到的流程。这条写进规则之后，Claude 就不再纠结用哪个工具了。

---

#### 状态栏 —— 实时仪表盘

我最喜欢的自定义。终端底部一行，该看的都在：

```
[Opus 4.6] misoto22-site | main | 45k 12k | 32% (57k/200k) | $0.45 | 12m30s | 3f +42 -8
```

从左到右：模型、项目目录、Git 分支、输入 / 输出 token、上下文使用率（带颜色：50% 以下绿、50–75% 黄、75% 以上红）、会话费用、会话时长、Git diff 统计。

费用来自本地缓存的 Anthropic 定价，一个小脚本每天抓一次公开定价页，把每个模型的单价写到 `~/.claude/cache/`：

```bash
if [ ! -f "$PRICING_CACHE" ] || \
   [ "$(( $(date +%s) - $(stat -f %m "$PRICING_CACHE") ))" -gt 86400 ]; then
    bash "$PRICING_SCRIPT" &>/dev/null &
fi
```

不需要 API key。我盯得最多的其实是那根变颜色的上下文条 —— 一旦变黄就该在合适的地方 `/compact` 了，不等它爆。

---

#### 自动记忆 —— 跨会话学习

规则和 `CLAUDE.md` 是静态的 —— 你写，Claude 读。自动记忆是动态的，而且从 Claude Code v2.1.59 起它就是**内建功能**了。Claude 会在会话过程中自己记笔记 —— 它搞清楚的构建命令、你做过的纠正、它学到的架构模式 —— 存在 `~/.claude/projects/<project>/memory/` 下，全是纯 markdown 文件。

目录里有一份 `MEMORY.md` 做入口，外加若干 Claude 按需要创建的主题文件：

```
~/.claude/projects/<project>/memory/
├── MEMORY.md           # 索引，每次会话加载
├── user-profile.md     # 角色、偏好、技术水平
├── feedback.md         # 纠正和确认有效的做法
├── architecture.md     # 值得记住的决策
└── references.md       # 外部系统指引
```

会话开始时，`MEMORY.md` 的前 200 行（或 25 KB，先到者为准）自动加载进上下文。其它文件按需读，Claude 觉得需要再去取。整套东西都是纯 markdown —— 可以直接读、改、删，或者在会话里用 `/memory` 打开这个目录。

我在内建行为之上加了几样：

1. **约定格式。** 每个记忆文件带 YAML frontmatter（`name`、`description`、`type`），`MEMORY.md` 这张索引就能一眼扫，以后的 Claude 也知道每个文件是干嘛的。
2. **四种分类。** `user`（身份、偏好）、`feedback`（纠正）、`project`（架构决策）、`reference`（外部系统指引）。纯粹是写在全局 `CLAUDE.md` 里的一个约定，但能避免目录变成一堆散文件。
3. **明确授权它写。** `CLAUDE.md` 里写一句"遇到值得保留的信息就写进自动记忆" —— 默认行为偏保守。

实际效果是：Claude 已经知道我是中文母语、喜欢简短回复、Efision 用 5 层 Clean Architecture 和 `Service<R>` 泛型、在 `pnpm` 项目里跑 `npm` 是我纠正过的错。这些细节会积累，几个月下来慢慢改变它处理任务的方式。

让记忆真正有用的那点自律：别想着一次性全写好。让它从真实的纠正中长出来。因为模型实际犯过错而写的记忆是有分量的；提前脑补写进去的基本是噪音。

---

#### 自定义技能 —— 可复用的工作流

技能是 Claude Code 里"可复用工作流"的基本单位。一个技能就是 `~/.claude/skills/`（或项目里 `.claude/skills/`）下的一个目录，里面有 `SKILL.md` 和任意辅助文件。frontmatter 告诉 Claude 什么时候用它，markdown 正文告诉它怎么做。

一个最小例子：

```markdown
---
name: deploy
description: Deploy the application to production
disable-model-invocation: true
allowed-tools: Bash(gh *) Bash(vercel *)
---

Deploy $ARGUMENTS to production:

1. Run the test suite
2. Build the application
3. Push to the deployment target
4. Verify the deployment succeeded
```

我常用的几个 frontmatter 字段：

- **`disable-model-invocation: true`** —— "只有用户能调"。给有副作用的工作流用，防止 Claude 自己觉得代码写好了就触发一次部署。
- **`allowed-tools`** —— 技能激活时无需再逐次问权限就能用的工具。
- **`context: fork`** 加上 `agent: Explore` —— 在子代理的独立上下文里跑，适合那种不想污染主会话的研究型任务。

用得最多的是 `/ship`。一条命令，从工作区到 PR 合并：

1. **测试** —— 跑测试，失败自动修，最多 2 次。
2. **提交** —— 暂存、从 diff 生成 conventional commit 消息。
3. **PR** —— 建分支、推送、开 PR（带摘要和测试计划）。
4. **CI** —— 每 30 秒轮询 `gh pr checks`，10 分钟超时；失败就读日志、修、推，最多重试 2 次。
5. **合并** —— squash merge，删分支，报告 PR URL。

没有代码，全是 markdown。Claude 按步骤走，出问题就停下来问。一条 `/ship` 顶过去 5–10 条手动命令。

还有一个 `deep-research` 技能 —— 八步把模糊问题变成结构化报告，信源按权威度分级，中间产物写到 `~/Downloads/research/<topic>/`。技能版的实验室笔记本。

插件里的技能用带命名空间的形式调：`/interface-design:audit`、`/commit-commands:commit-push-pr`。`~/.claude/skills/` 下你自己的技能就用裸名：`/ship`、`/deep-research`。

---

#### 日常怎么用

上面这些配好之后，一天大致是这样：

1. 在项目里打开 Claude Code。状态栏马上显示模型、分支、费用、上下文。
2. 全局 `CLAUDE.md`、项目 `CLAUDE.md`、规则、自动记忆、插件技能全部自动加载。
3. 我说要做什么。Claude 先读相关代码（规则里写了的），不清楚就问，然后动手。
4. 每次编辑 Hooks 都会提醒跑 linter，push 前再确认一次分支。
5. 上下文条变黄，在合适的地方 `/compact`。根部的规则和 `CLAUDE.md` 会从磁盘重新注入。
6. 大功能走 Plan 模式，或者开子代理并行跑。
7. 值得带进下一次会话的，写进自动记忆。

整个过程不太像在"操控一个 AI"，更像是在和一个已经读过代码、了解我偏好、对用什么工具有自己主见的同事结对。

---

#### 从零开始，先配这几样

从一张白纸起步，按投入产出比排序：

1. **先写拒绝列表。** 把 `rm -rf`、`--force`、`publish`、`curl | bash` 封掉。在你需要封掉它们之前就封。
2. **写全局 `CLAUDE.md`。** 保持短 —— 200 行以内。身份、工作方式、几条行为规则。
3. **把约定外置到 `.claude/rules/`。** 任何你已经在会话里打了第二遍的东西，都值得写成规则文件。用 `paths` frontmatter 把只适用于某部分目录的规则限定好。
4. **加一个 `PostToolUse` linter 提醒。** 一行配置，能拦住不少事。
5. **自定义状态栏。** 一旦能实时看到费用和上下文，你的工作方式会变 —— 会更早 `/compact`，会更早发现跑偏的会话。
6. **装上你基础设施实际在用的那几个官方插件。** 一条 `/plugin install github@claude-plugins-official` 一周就把投入赚回来了。
7. **让自动记忆自己长出来。** 别提前填满。Claude 犯错你纠正，纠正就成了记忆。
8. **分层配置。** 全局管通用标准，项目管本地约定，local 管机器差异。

这套配置大半是从真实的坑里长出来的 —— 一次不该发生的 force-push、在 `pnpm` 项目里跑了 `npm`、一场写到一半上下文就爆的会话。规则文件里的每一条，都是因为我曾经需要它。

---

#### 参考资料

下面每个链接都是写这篇时实际翻过的页面。一句话注释说的是"点进去你真正能拿到什么"，不是页面标题的复述。

1. **[Settings](https://code.claude.com/docs/en/settings)** —— 四层作用域和 `settings.json` 每一个字段的权威参考。从零搭配置时从这里开始。
2. **[Memory](https://code.claude.com/docs/en/memory)** —— Claude Code 记忆体系的两半都在这里：`CLAUDE.md` 的加载顺序、`.claude/rules/` 的 `paths` frontmatter、内建自动记忆目录怎么存和怎么在压缩后重新注入。
3. **[Hooks](https://code.claude.com/docs/en/hooks)** —— 完整事件目录（二十多个）、matcher 正则语法、以及 `command` / `http` / `prompt` / `agent` 几种 handler 的 JSON I/O 约定。想让 hook 真的"做事"而不仅仅是打印提醒的话，必读。
4. **[Skills](https://code.claude.com/docs/en/skills)** —— frontmatter 字段、`$ARGUMENTS` 替换、内联 shell 执行块、以及用 `context: fork` 把技能放到子代理里跑。写任何超出玩具级 `/command` 的东西之前应该先看。
5. **[Plugins](https://code.claude.com/docs/en/plugins)** 和 **[Plugin marketplaces](https://code.claude.com/docs/en/discover-plugins)** —— 插件结构、manifest schema，以及 `/plugin install <name>@<marketplace>` 的完整流程。第二篇才真正讲清楚 `extraKnownMarketplaces` 怎么用。
6. **[Subagents](https://code.claude.com/docs/en/sub-agents)** 和 **[MCP](https://code.claude.com/docs/en/mcp)** —— 当你开始把 Claude Code 当编排器而不是单个 agent 来用时要读的两篇：子代理委派、预加载技能、以及通过 Model Context Protocol 接外部工具。

### Next.js 渲染方式详解：CSR、SSR、SSG、ISR

https://misoto22.com/zh/blog/nextjs-rendering-methods · 2025 · 约 8 分钟读完 · 开发

> 用代码示例和数据流讲清楚 Next.js App Router 下四种渲染策略的区别和实际用法。

用 Next.js 开发，核心问题之一就是：页面在哪里渲染、什么时候渲染。不同的策略在性能、SEO、数据实时性和服务器成本之间做取舍——为每个页面选对渲染方式，效果差距很大。

在开始之前，先说一个关键前提：**Next.js App Router 默认使用 React Server Components（RSC）**。所有组件默认在服务端渲染，不会向客户端发送任何 JavaScript，除非你显式加上 `'use client'`。这和旧的 Pages Router 有本质区别——也直接影响了下面四种策略的实际表现。

---

#### 1. 客户端渲染（CSR）

在 App Router 里，纯客户端渲染需要你主动选择。给组件加上 `'use client'`，它会在服务端预渲染 HTML 后，在客户端进行注水（hydration）。

```tsx
'use client'

import { useState, useEffect } from 'react'

export default function Dashboard() {
  const [stats, setStats] = useState(null)

  useEffect(() => {
    fetch('/api/stats').then(res => res.json()).then(setStats)
  }, [])

  if (!stats) return <div>加载中...</div>
  return <div>共 {stats.totalUsers} 位用户</div>
}
```

**数据流：**

客户端 → 服务器（返回预渲染 HTML + JS 包）→ 客户端（注水）→ API → 客户端（用数据重新渲染）

注意，即使是 `'use client'` 组件，首次加载时也会有服务端渲染的 HTML——浏览器不会看到白屏。但数据获取仍然发生在客户端注水之后。

**适用场景：** 交互式组件、数据看板、需要登录的页面——需要浏览器 API 或实时状态的地方。

---

#### 2. 服务端渲染（SSR）

在 App Router 里，SSR 发生在 Server Component 动态获取数据时。默认情况下 `fetch()` 请求会被缓存——要让页面每次请求都重新渲染，需要用 `cache: 'no-store'` 或者标记页面为动态。

```tsx
// app/news/page.tsx — 每次请求都重新渲染
export const dynamic = 'force-dynamic'

export default async function NewsPage() {
  const res = await fetch('https://api.example.com/news', {
    cache: 'no-store',
  })
  const articles = await res.json()

  return (
    <ul>
      {articles.map(a => <li key={a.id}>{a.title}</li>)}
    </ul>
  )
}
```

**数据流：**

客户端 → 服务器 → 数据库/API → 服务器（构建 HTML）→ 客户端（直接展示）

用户马上就能看到完整页面，搜索引擎也能抓取到渲染好的内容。代价是每个请求都要经过服务器。

**适用场景：** 内容每次请求都可能不同的页面——新闻流、用户主页、搜索结果。

---

#### 3. 静态生成（SSG）

SSG 在构建时预渲染页面。在 App Router 里，这是默认行为——如果页面没有获取动态数据，它自动就是静态的。对于动态路由，用 `generateStaticParams()` 告诉 Next.js 需要预生成哪些路径。

```tsx
// app/blog/[slug]/page.tsx — 构建时预渲染
export async function generateStaticParams() {
  const posts = await getAllPostSlugs()
  return posts.map(slug => ({ slug }))
}

export default async function BlogPost({ params }: { params: { slug: string } }) {
  const post = await getPost(params.slug)
  return <article>{post.content}</article>
}
```

**数据流：**

构建时：服务器 → API/数据库 → 生成静态 HTML
请求时：客户端 → CDN（瞬间返回预生成页面）

这是速度最快的方案。内容在 build 时就固定了——数据更新后，用户看到的还是旧内容，直到下次部署。这个站的博客页面就是用 SSG 生成的——内容存在 MDX 文件里，只有我推送新 commit 才会更新。

**适用场景：** 博客文章、文档、营销页面——更新不频繁的内容。

---

#### 4. 增量静态再生（ISR）

ISR 兼具 SSG 的速度和无需全量重新部署就能更新内容的能力。导出一个 `revalidate` 间隔，Next.js 在过期前一直返回缓存页面。过期后，下一个请求触发后台重新生成。

```tsx
// app/products/[id]/page.tsx — 静态页面，每 60 秒刷新
export const revalidate = 60

export default async function ProductPage({ params }: { params: { id: string } }) {
  const product = await fetch(`https://api.example.com/products/${params.id}`, {
    next: { revalidate: 60 },
  }).then(res => res.json())

  return <div>{product.name} — ${product.price}</div>
}
```

**数据流：**

首次请求：客户端 → CDN（瞬间返回缓存 HTML）
后台：服务器 → API/数据库 → 生成并缓存新 HTML
下次请求：客户端 → CDN（返回更新后的页面）

小小的代价是：总有一个用户拿到的是触发重建的旧版本。但对绝大多数场景来说，几秒的延迟换来近乎瞬时的加载速度，完全值得。

**适用场景：** 商品详情页、列表页、博客首页——定期更新但不需要实时的内容。

---

#### 额外：部分预渲染（PPR）

从 Next.js 15 开始，还有第五种方案模糊了静态和动态的界限。**部分预渲染（Partial Prerendering）** 让一个页面可以同时包含静态和动态部分——静态外壳从 CDN 瞬间返回，动态内容在准备好后流式传入。

```tsx
// app/page.tsx — 静态外壳 + 动态内容
import { Suspense } from 'react'

export default function HomePage() {
  return (
    <div>
      <h1>欢迎</h1>                    {/* 静态 — 从 CDN 返回 */}
      <Suspense fallback={<p>加载中...</p>}>
        <RecommendedItems />            {/* 动态 — 流式传入 */}
      </Suspense>
    </div>
  )
}
```

PPR 是这四种策略的自然演进——不再需要为整个页面选一种模式，而是在组件级别做精细控制。它还比较新，但代表了 Next.js 渲染的发展方向。

---

#### 对比总览

| 方式 | 首屏速度 | SEO  | 数据实时性       | Next.js 配置                          |
| ---- | -------- | ---- | ---------------- | ------------------------------------- |
| CSR  | 中等     | 尚可* | 实时（客户端）   | `'use client'` + `useEffect`          |
| SSR  | 中等     | 好   | 实时（每次请求） | `dynamic = 'force-dynamic'`           |
| SSG  | 快       | 好   | 构建时固定       | 默认 / `generateStaticParams()`       |
| ISR  | 快       | 好   | 定期刷新         | `export const revalidate = N`         |
| PPR  | 快       | 好   | 混合             | `Suspense` 边界 + 动态数据            |

*\*App Router 中 CSR 组件首次加载仍有服务端渲染的 HTML，所以 SEO 比传统 SPA 好。*

---

#### 我的选择原则

- **默认用 SSG 或 ISR** ——快、便宜，能覆盖大部分页面。
- **需要 SSR 的场景**：内容确实因请求或用户不同而变化。
- **CSR 留给**：交互式组件，或者需要登录才能看的页面。
- **考虑 PPR**：当页面既有静态又有动态部分时——不要因为一小块需要实时数据，就让整个页面变成动态的。

> 能静态就静态，该动态才动态。把渲染的复杂度交给框架，你只管专注于真正重要的事——给用户创造价值。

## 摄影

91 张照片，题材为澳大利亚的自然风光、城市建筑与街头场景，每张都附 EXIF 元数据（机身、镜头、光圈、快门、ISO），有记录的还标注了拍摄地点。

- [摄影](https://misoto22.com/zh/photography)

## Agent Skills

- [email](https://misoto22.com/zh/skills/email): Draft, reply to, forward, format, send, or verify outbound email under a policy — drafting is the default, and sending stays blocked until a narrow local scope authorizes that exact message. Use when recipients, external disclosure, attachments, authorization, HTML bodies, or Sent-folder confirmation matter, and on requests such as write an email to, reply to this thread, draft a note to the client, forward this with a cover note, send it and confirm it arrived, 写封邮件给, 回一下这个, 帮我发出去, 这封邮件再改改. Not for triaging or summarising a mailbox you are not answering, chat and internal notes, or rewriting how a message sounds without sending it.
- [tempering](https://misoto22.com/zh/skills/tempering): Rewrites blunt, sarcastic, or impatient workplace messages into professional ones that retain the underlying request, offering three registers from collegial to formally documented. Use when a draft is addressed to a colleague, manager, client, vendor, or cross-team counterpart and carries visible frustration — sarcasm, blame, exasperation, ultimatums, or lines such as "are you serious", "脑子有问题", "到底做不做", "有没有一个准信". Trigger on requests to make a message professional, soften it, tone it down, check whether it is too harsh, or work out how to say something without damaging the relationship, including 润色一下, 帮我改得客气点, 这样发出去会不会太冲, 怎么说才不得罪人. Also handles the reverse direction — plain-language interpretation of corporate phrasing when asked what a message actually means, 说人话, or decode this. Not for marketing copy, resumes, blog posts, or grammar cleanup without interpersonal stakes.
- [personal-blog](https://misoto22.com/zh/skills/personal-blog): Use when researching, outlining, drafting, revising, or polishing a personal blog article—an explainer, idea essay, personal essay, cultural review, or technical post—or when asked to preserve voice in a personal blog draft, turn notes into a personal blog post, 写博客, or 写一篇博客. Not for newsletters, magazine profiles, manuscript editing, email, chat, repository documentation, marketing copy, fiction, academic papers, or generic grammar cleanup.
- [repo-polish](https://misoto22.com/zh/skills/repo-polish): Build or restore a repository's public face — README, hero banner, LICENSE, SECURITY.md, CONTRIBUTING.md, the forge's one-line About text, and its GitHub or GitLab topics — written from what the repository's own files say. Every pass runs unless flags narrow it. Use when asked to 装修一下仓库, 仓库装修, 美化仓库, 把开源文件补齐, 写个 readme, 加个 license, 补个安全策略, 设置仓库 topics, 仓库描述写一下, polish this repo, set the repo up properly, add a security policy, fill in the repository description, or tag a repository on GitHub. Not for API reference docs, changelogs, release notes, marketing landing pages, or the source code itself.
- [sync](https://misoto22.com/zh/skills/sync): Fetch, prune, and fast-forward the base branch to match its remote. It only fast-forwards; anything diverged is reported for the user to decide. Use when asked to sync, pull latest, get up to date with the remote, 同步一下, 拉一下最新的, 跟 main 对齐. Not for shipping changes, deleting merged branches, or resolving a merge conflict.
- [ship](https://misoto22.com/zh/skills/ship): Ship the current changes as a merged pull request — branch off, run the project's tests, commit, open the PR, wait for CI, merge, and clean up the worktree. Use when asked to ship it, land it, get this merged, open a PR and merge it, push this up and merge, 发出去, 合掉, 开个 PR 合了, 把这些改动提上去, 推上去合并. Not for tagging a release, publishing a package, deploying, or writing a commit message without pushing it.
- [cleanup](https://misoto22.com/zh/skills/cleanup): Remove what shipping left behind — local and remote branches whose pull request merged, worktrees for those branches, and ignored residue a move stranded, such as a __pycache__ that git mv could not see. Use when asked to clean up, tidy the repo, delete merged or stale branches, prune the remote branches, clear out old worktrees, 清理一下, 清掉合并过的分支, 删掉没用的分支, 清理远程分支, 收拾一下仓库. Not for discarding uncommitted work, resetting a branch, or removing untracked files you have not been shown.
- [retitle](https://misoto22.com/zh/skills/retitle): Normalize agent conversation titles onto a dated `MMDD｜TYPE｜subject` scheme — English by default, Chinese with `--lang=zh` — across Codex, Claude Code, and any client that exposes its session list. Use when asked to 规范对话名称, 整理会话标题, 统一对话命名, 批量重命名会话, 会话名太乱了, clean up my conversation titles, rename my chat sessions, or make my session names consistent. Not for renaming projects, folders, git branches, worktrees, or files; not for editing, archiving, pinning, or deleting the conversations themselves.
- [steward](https://misoto22.com/zh/skills/steward): Sweep every repository your agent sessions have touched, found from the agent's own session records and any roots you name, and run the dev loop's housekeeping across all of them at once — fast-forward each base branch, remove what already merged, keep conversation titles in scheme, and report which branches are ready to merge, which pull requests are blocked, and which worktrees a live session still occupies. Built to run unattended on a schedule, so a question a pass would have stopped to ask lands in the report instead. Use when asked to 巡一遍所有项目, 大内总管, 看看哪些分支该合并了, 定时清理本地环境, 扫一下所有 worktree, sweep all my repos, check on every project, what needs merging across my projects, run the housekeeping, keep my local environment clean. Not for syncing or cleaning the one repository you are standing in, for shipping or merging a branch, or for renaming a single conversation.
- [reunite](https://misoto22.com/zh/skills/reunite): Make every signed-in account see every conversation in the desktop app's sidebar. The app keeps one conversation index per account, so signing in as a second account hides the first account's history — this unions the indexes and brings a shared conversation's diverged titles back onto one name, adding entries and never removing one. Use when asked why sessions disappeared after switching accounts, where my old conversations went, share sessions between two accounts, merge the session lists, 换账号以后 session 都不见了, 会话历史没了, 两个账号共享会话, 把 session 列表合起来, 找回以前的对话. Not for deleting conversations, renaming them (that is retitle), or moving history between machines.
- [handoff](https://misoto22.com/zh/skills/handoff): Mirror a live conversation into the other agent's history as it happens, so Claude Code sessions show up in Codex and Codex threads show up in Claude Code. Hooks on both sides watch their own transcript and write each exchange into the other side's format. Use when asked to share sessions between Claude and Codex, see my Codex conversations in Claude, continue this in Codex without re-explaining, keep the two agents' history in sync, 让 Codex 看到 Claude 的会话, 两个 agent 的历史同步, 把这段对话搬到 Codex, 在 Codex 里接着聊. Not for one agent's own per-account sidebar, which reunite covers; not for naming conversations; not for carrying history to a different computer.
- [synastry](https://misoto22.com/zh/skills/synastry): Use when two people's birth details need an uncertainty-aware JSON v2 synastry calculation, including 合盘, 星盘配对, exact times, bounded time windows, or date-only records. Not for interpreting an existing v2 artifact, legacy TXT, one-person natal charts, transits, forecasts, predictions, or compatibility scores.
- [synastry-reading](https://misoto22.com/zh/skills/synastry-reading): Use when $synastry hands off a JSON v2 artifact, or when someone supplies a synastry-chart schema 2.0 JSON path or object and asks for 合盘解读, interpretation, relationship dynamics, or an evidence-linked Markdown report. Not for legacy TXT, raw birth details, one-person natal charts, recalculation, transits, forecasts, predictions, or compatibility scores.
- [natal-chart](https://misoto22.com/zh/skills/natal-chart): Compute one person's natal chart from an exact birth date, minute, and place — planet positions with sign, house, dignity and retrograde state, the four angles, intra-chart aspects, sect, and the classical lots, written as canonical JSON plus data-only Markdown. Use for 本命盘, 星盘, 出生星图, natal chart, birth chart, my rising sign, my chart's aspects, or where a planet sits. Not for two-person 合盘, interpreting a chart that already exists, transits, progressions, returns, or any dated prediction.
- [natal-reading](https://misoto22.com/zh/skills/natal-reading): Explain a natal chart artifact someone already has — what the rising sign shapes, where the sect light sits, which aspects are tight enough to lean on, and what the angles and lots add. Writes a reader report plus an auditable evidence file, and a shareable ink-wash page on request. Use for 解读星盘, 看本命盘, 我的上升是什么意思, or asking what a placement or aspect pattern means. Not for computing a chart from raw birth details, 合盘 between two people, transits, progressions, returns, or predicting a dated event.
- [bazi-chart](https://misoto22.com/zh/skills/bazi-chart): Calculate one reusable BaZi chart from a named person's exact birth date, minute, and birthplace, write canonical JSON plus data-only Markdown, then start the natal reading automatically. Use for 八字排盘, 生辰八字, four pillars, or informal single-person birth details. Not for two-person compatibility, existing-chart interpretation, Da Yun, annual luck, or event forecasts.
- [bazi-reading](https://misoto22.com/zh/skills/bazi-reading): Interpret a completed single-person BaZi JSON chart or equivalent verified four-pillar data as an evidence-linked static natal report. Use when a calculator hands off its artifact or someone asks to 解读八字, 看命局, explain day-master strength, structure, or favorable elements from an existing chart. Not for raw birth details, relationship matching, luck cycles, dated predictions, or incomplete source data.
- [bazi-compatibility](https://misoto22.com/zh/skills/bazi-compatibility): Compare two people from reusable BaZi chart JSON files, two complete birth records, or one of each; write auditable interaction data and transparent general or relationship-specific scores before automatic interpretation. Use for 八字合婚, 合八字, two-person compatibility, 配不配, or whether two charts work together. Not for one-person natal work, reading an existing comparison, forecasting, or missing birth minutes.
- [bazi-compatibility-reading](https://misoto22.com/zh/skills/bazi-compatibility-reading): Interpret a completed BaZi compatibility JSON artifact or equivalent verified comparison as a balanced, directional, evidence-linked relationship report. Use after the compatibility calculator or when asked to explain general, romance, marriage, friendship, family, or work scores already computed for two charts. Not for raw birth records, one-person readings, recalculation, predictions, or a binary destiny verdict.
- [ziwei-chart](https://misoto22.com/zh/skills/ziwei-chart): Place one twelve-palace Zi Wei Dou Shu 命盘 from someone's stated birth moment, birthplace, and gender, recording palaces, stars, 生年四化, and 大限 windows as reusable placement data. Use for 紫微斗数, 紫微排盘, 排紫微, 紫微命盘, 十二宫, or purple star astrology. Not for 八字 four pillars, matching two people, comparing two systems against each other, 流年 or monthly transformations, or a 命盘 that has already been placed.
- [ziwei-reading](https://misoto22.com/zh/skills/ziwei-reading): Interpret an already-placed Zi Wei 命盘 artifact, or equivalent verified twelve-palace data, writing a reader report plus a separate audit artifact and, when asked, an ink-wash HTML poster. Use for 解读紫微, 看命盘, 看十二宫, or explaining the stars sitting in 命宫, the 生年四化, and what each 大限 window emphasizes. Not for unplaced birth records, 八字 four pillars, comparing two systems against each other, matching two people, 流年, or dated predictions.
- [bazi-ziwei-cross](https://misoto22.com/zh/skills/bazi-ziwei-cross): Read one person's finished 八字 artifact and finished 紫微 artifact against each other, recording every place the two systems agree, complement, or flatly contradict, without merging them into a single number. Use for 八字紫微综合, 双系统印证, 两盘合参, or asking whether the two systems say the same thing about one person. Not for placing either 命盘, reading one system by itself, matching two people, or forecasting.
- [logo-banner](https://misoto22.com/zh/skills/logo-banner): Create cohesive raster logo, app-icon, favicon, and repository-hero systems through ChatGPT Image when a user asks to design or refresh visual identity, a logo, a banner, branding assets, or light and dark brand variants; stop clearly when the host lacks that generator rather than substituting another tool; not for code-drawn SVG icons, general website layout, or ordinary photo retouching.
- [photo-abstract-editorial-native](https://misoto22.com/zh/skills/photo-abstract-editorial-native): Assemble a source-faithful photograph with a supplied editorial abstraction panel as a sharp comparison board, preserving orientation, aspect ratio, source provenance, and lower-panel scale. Use for original-versus-abstract photography diptychs, photo comparison cards, or repairing blurred and stretched editorial boards; not for generating lower artwork or replacing a separately licensed art-direction skill.

## 联系方式

- 邮箱: henrycxw@gmail.com
- GitHub: https://github.com/Misoto22
- LinkedIn: https://linkedin.com/in/henry-misoto22
- X: https://x.com/misoto222
- Instagram: https://instagram.com/hry.photography
- Unsplash: https://unsplash.com/@misoto22

## 机器可读资源

- [供 LLM 使用的全站内容](https://misoto22.com/zh/llms-full.txt): 本文档的展开版，含文章全文
- [Agent skills 目录](https://misoto22.com/skills.md): 每个技能及其安装命令，Markdown 版
- [Atom 订阅源](https://misoto22.com/feed.xml): 英文博客文章
- [Sitemap](https://misoto22.com/sitemap.xml): 两种语言的全部规范 URL
- [OpenAPI 描述](https://misoto22.com/openapi.yaml): 公开的只读 API
- [API catalog](https://misoto22.com/.well-known/api-catalog): RFC 9727 linkset
