# M9 主题边界到共享 UI：从可用到可继续设计

到了 `M9`，这个项目已经不缺“功能能跑起来”的证明了。

前面的里程碑已经把工程、鉴权、内容、导出、发布和持久化都逐步立起来，但体验层还有一个很明显的问题：

- `web / admin` 已经都能用，却还没有统一的体验基线
- 公开页开始像一份简历，但还不够像一份真正可展示的成品
- 后台已经具备内容维护入口，但页面结构仍偏“联调壳”
- `web / admin` 之间开始出现重复展示结构，却还没有清晰的共享收口点

换句话说，项目已经从“能跑 demo”进入了“该开始认真对待展示与体验”的阶段。

所以 `M9` 要解决的，不是新增业务能力，而是把项目从“功能可用”推进到“体验有基线、后续能继续设计”的状态。

这篇文章围绕 `M9` 讲清楚四件事：

1. 为什么这一步先收主题边界，而不是先做更大的视觉重构
2. 为什么后台首页要先整理信息架构，而不是直接套一个 admin 模板
3. 为什么公开页升级不能只停在 CSS，而要同步补信息层次
4. 为什么共享层当前只适合沉淀展示原语，而不该急着做完整组件库

## 一、为什么 `M9` 必须单独成立

`M8` 结束后，项目已经有了更稳定的数据和契约边界：

- 草稿 / 发布流持久化成立
- `web / admin` 开始共用最小契约
- 三端能做真实联调

如果这时继续直接往后推更多业务，短期看似乎也没问题。但从长期演进看，会很快遇到新的阻力：

- 每次改页面都要重新判断主题、颜色和深浅模式到底归谁管
- 后台首页结构不够稳定，稍微加一点能力就会继续向下堆叠
- 公开页虽然已经能展示真实内容，但还没有建立足够清晰的阅读节奏
- 两端重复结构越来越多，却还没有适合共享的抽象层次

这些问题如果不先收住，后面无论是做模板化、接设计稿，还是继续扩后台体验，都会越来越难讲清楚。

所以 `M9` 的真正价值，不是“把页面做得更漂亮”，而是先把体验层最容易发散的几个边界收口：

- 主题边界
- 页面壳边界
- 展示层级边界
- 共享展示边界

## 二、`M9` 这轮到底要交付什么

这一轮的交付目标可以概括成一句话：

让读者看到项目如何在不做大重构、不引入完整模板系统的前提下，先立统一主题基线，再把后台首页和公开页拉到更稳定的展示状态，最后沉淀出最小共享展示原语。

这里面有五个关键边界。

### 1. 先让主题能力真正归位

在 `M9` 之前，`web` 已经有局部主题切换，但它还停留在页面内部；`admin` 则还没有统一主题入口。

如果在这种前提下直接继续做体验升级，后面每个页面都会再次决定：

- 主题状态放哪里
- 深浅色变量放哪里
- 持久化逻辑归谁管

所以 `M9` 的第一步不是改页面，而是先让主题能力真正有统一归属。

### 2. 后台首页先收成“控制台入口”

教学型后台和成熟商业后台不一样。

当前阶段真正更重要的，不是把后台做成一个复杂管理系统，而是先让读者能快速看懂：

- 当前账号是谁
- 当前角色能做什么
- 内容维护入口在哪里
- 发布、导出和 AI 体验入口如何组织

所以 `M9` 选择先整理后台信息架构，而不是急着套模板。

### 3. 公开页先建立更明确的阅读节奏

`M7` 已经把真实简历内容接到了公开页，`M8` 又收住了持久化和已发布读取。

这意味着公开页下一步最需要解决的，不再是“有没有数据”，而是“这页是否已经像一个真正对外展示的作品”。

因此 `M9` 的公开页升级，重点不是多做功能，而是：

- 补更清楚的题头
- 补更快建立认知的信号区
- 补更稳定的 section 节奏

### 4. 共享层先收原语，不收成品业务组件

当 `web / admin` 都开始变得更完整时，重复一定会出现。

但 `M9` 很克制地没有把业务组件整体搬进 `packages/ui`，而是先抽：

- 表面容器
- 标题块
- 指标卡
- 标签胶囊

这是因为当前两端共享的是展示骨架，不是完整业务语义。

### 5. 这轮刻意不做模板系统

进入 `M9` 后，项目确实已经开始碰到主题和模板边界。

但这一轮没有顺势把“多模板、多主题、多布局”全部铺开，而是只做当前最值得收住的体验基线。这样后续要继续设计时，能站在更稳定的基础上，而不是半途拖进一个更大的系统。

## 三、`M9` 为什么要按这些 issue 来拆

这一轮的拆法，其实是在回答一个很典型的体验层问题：

当数据和业务主线已经基本成立后，应该先收哪些展示边界，才能让项目从“能工作”进入“可继续设计”阶段？

`M9` 的答案是：

先收主题边界，再收后台壳，再收公开页视觉基线，最后收最小共享展示原语。

### issue-42：先收主题边界

第一步不是改页面，而是先把 `light / dark` 的状态、持久化和根节点同步统一上收到 `packages/ui`。

这一层真正固定下来的，是：

- 主题状态如何被读取和写入
- 主题变量如何进入 `web / admin`
- 刷新后主题如何保持

这一步让后续所有体验升级，第一次站在同一个主题基线上继续长。

### issue-43：再整理后台首页信息架构

主题基线成立后，后台首页才有条件开始真正变成“控制台入口”。

这一步重点不是做一个复杂后台，而是先把页面组织收清楚：

- 顶部说明区
- 内容工作区
- 会话与角色边界区
- 动作中心与流程提示

这样读者第一次能从页面层就看懂“后台为什么这样组织”。

### issue-44：再升级公开页视觉基线

后台壳站稳后，下一步才是公开页的升级。

这一步没有新增内容字段，而是围绕既有真实简历内容，补了三层最关键的公开展示表达：

- 更像成品的题头
- 更快建立认知的速览区
- 更清楚的 section 主次和节奏

也就是说，这一轮不是“做模板”，而是先把公开页从验证态拉成可展示态。

### issue-45：最后沉淀最小共享展示原语

当 `admin / web` 都已经各自长出更稳定的页面结构后，才比较容易判断哪些结构值得共享。

这一步抽出来的不是具体业务组件，而是：

- `DisplaySurfaceCard`
- `DisplaySectionIntro`
- `DisplayStatCard`
- `DisplayPill`

它们共同解决的是“重复展示骨架”问题，而不是取代两端的页面语义。

### issue-46：及时把这一轮收成可继续写的文档骨架

`M9` 结束后并没有立刻进入模板系统或设计系统。

所以这一轮必须及时收文档，把：

- 已完成的边界
- 有意没做的内容
- 后续可以怎么继续写

明确记录下来。这样下个阶段再继续升级时，不会把 `M9` 的边界写散。

## 四、这一轮最重要的设计取舍

### 1. 为什么先收主题边界，再做页面升级

如果主题 tokens、状态和持久化逻辑仍然散在各应用里，后续每次改页面都要重复判断“这段逻辑到底归谁管”。

所以 `M9` 的第一个判断不是“先改哪个页面更有成就感”，而是“先让体验层有统一底座”。

### 2. 为什么后台首页先做结构，不先换模板

很多项目一到后台整理阶段，就会自然引入现成模板。

但对于教程型仓库来说，这样做有一个问题：读者会立刻失去对当前真正重点的注意力。因为最值得讲清楚的，不是这个后台长得像不像一个成品 SaaS，而是：

- 单管理员 + viewer 的教学型角色语义
- 草稿 / 发布 / 导出的组织方式
- AI 工作台和后续动作入口准备放在哪里

所以先做信息架构，比先套视觉模板更重要。

### 3. 为什么公开页升级不能只停在 CSS

如果只是改背景、改圆角、改阴影，页面当然会变得更“好看”，但读者很难明白这轮到底提升了什么。

`M9` 的判断是：公开页升级必须同时补信息层次。

也就是让页面不只是“样式不同”，而是让访问者能更快看到：

- 这是谁的简历
- 这份公开版是什么版本
- 当前有哪些关键履历信号
- 页面怎么往下读会更顺

### 4. 为什么共享层现在只抽原语

进入 `M9` 之后，共享确实已经值得开始做了。

但当前最合适的层次，是展示原语，而不是完整组件库。因为两端的业务语义仍然差别很大：

- `web` 偏公开展示
- `admin` 偏工作台与动作组织

如果现在把更高层组件也一起共享，很快就会把路由、角色和简历业务语义带进 `packages/ui`，这反而会破坏边界。

### 5. 为什么这轮刻意不碰模板系统

模板系统会自然诱惑人往前走，因为它听起来很像“下一步就能让页面更高级”。

但 `M9` 克制住了这件事。因为当前更重要的是：

- 主题有没有统一入口
- 页面壳有没有清楚结构
- 公开页有没有稳定基线
- 共享层有没有最小收口点

这些都成立之后，再讨论模板系统，才不会回头补地基。

## 五、关键文件应该怎么读

如果你想顺着代码和文档复盘 `M9`，建议优先看这几组材料。

### 共享主题入口

- `packages/ui/src/theme.tsx`
- `packages/ui/src/display.css`
- `apps/web/app/layout.tsx`
- `apps/admin/app/layout.tsx`

这里解决的是“主题到底由谁统一提供”。

### 后台首页信息架构

- `apps/admin/components/admin-dashboard-shell.tsx`
- `apps/admin/components/theme-mode-toggle.tsx`
- `apps/admin/app/globals.css`

这里解决的是“后台为什么先做控制台壳，而不是先追求复杂模板”。

### 公开页视觉基线

- `apps/web/components/published-resume-shell.tsx`
- `apps/web/components/published-resume/published-resume-hero.tsx`
- `apps/web/app/globals.css`

这里解决的是“公开页如何从验证态走到可展示态”。

### 共享展示原语

- `packages/ui/src/display.tsx`
- `packages/ui/src/display.spec.tsx`

这里解决的是“什么时候应该开始共享，以及当前到底共享哪一层”。

## 六、`M9` 的测试重点是什么

`M9` 的测试重点不是新增更多业务场景，而是确认主题、展示结构和共享入口是否已经站稳。

### 1. 先锁共享主题行为

这里至少要证明：

- 非法主题值会被规范化
- 主题值能在刷新后保持
- 主题切换会同步到根节点
- `web / admin` 都在消费同一个共享入口

### 2. 再锁后台壳结构

这一层要证明：

- 后台首页的核心区块已经清楚可感知
- `admin / viewer` 的页面级差异已经可见
- 壳层整理没有破坏既有编辑、发布、导出和主题切换

### 3. 再锁公开页的展示层次

这一层要证明：

- 题头、速览区和 section 节奏都已经进入页面结构
- 语言切换、主题切换和导出入口没有漂移
- 空状态和已发布展示都仍然成立

### 4. 最后锁共享展示原语接入

这一层要证明：

- 原语层行为本身可测试
- 使用侧迁移后 `web / admin` 仍能正常构建
- 共享层没有把业务语义错误带进公共包

## 七、这一轮最容易踩的坑

### 1. 一上来就把页面升级做成模板系统

这样看似一步到位，实际上会把当前最值得讲的主题边界、页面结构和共享层时机全部打散。

### 2. 只做视觉变化，不补信息层次

如果没有新的题头、信号区和页面主次，读者很难理解为什么这轮不是单纯换皮。

### 3. 共享层抽过头

当前两端共享的是展示骨架，不是完整业务语义。过早抽成更高层组件，会让 `packages/ui` 很快变成一个难以解释的混合层。

### 4. 后台一整理就急着引入完整模板

这样会遮住最重要的教学点：这个后台为什么这样组织，它在当前阶段究竟服务什么目标。

## 八、`M9` 完成后，项目到了什么状态

`M9` 结束后，这个项目第一次真正拥有了体验层基线：

- 主题边界已经统一
- 后台首页已经有清晰的信息架构
- 公开页已经更像一份可展示的公开简历
- `packages/ui` 已经开始承载最小共享展示原语

也就是说，项目这时已经不再只是“功能闭环”意义上的全栈简历仓库，而是开始进入“可以继续设计、继续沉淀展示系统”的阶段。

## 九、这篇文章对应的原始材料

如果你想继续往下看每个 issue 的实现细节，可以配合这些开发日志一起阅读：

- `docs/30-开发日志/M9-issue-42-共享主题-tokens-与-light-dark-最小闭环.md`
- `docs/30-开发日志/M9-issue-43-admin-后台信息架构与仪表盘壳整理.md`
- `docs/30-开发日志/M9-issue-44-web-公开简历页视觉基线升级.md`
- `docs/30-开发日志/M9-issue-45-packages-ui-最小共享展示组件沉淀.md`
- `docs/30-开发日志/M9-issue-46-教程大纲与里程碑收束.md`
- `docs/40-教程与博客/M9-主题边界到共享-UI-从可用到可继续设计-教程大纲.md`

这些记录更适合看“每一步具体做了什么”，而本文更希望回答“为什么 `M9` 要先收体验基线与共享边界，再继续进入更复杂的设计升级”。

## 小结

`M9` 做的不是一轮大规模视觉重构，而是先把体验层最容易发散的几个边界收住：

- 主题统一入口成立
- 后台壳结构成立
- 公开页展示基线成立
- 最小共享展示原语成立

这些边界一旦成立，后面无论是继续做模板化、接设计稿，还是扩更成熟的后台体验，都会更像是在已有基线上继续增强，而不是重新回头补一遍体验层地基。
