references/maidang-personal-os-case-study.md
maidang-personal-os-case-study.md
Maidang Personal OS 案例复盘
1. 改造前是什么
项目最初是一个数据库驱动的个人网站和知识库,包含:
- 首页自我介绍;
- Posts、Projects、Tools、Cat、Career;
- Demo 页面;
- Contact API;
- Dashboard 内容后台;
- CLI 中维护的年度歌曲数据。
这些内容已经有真实 URL、Prisma 数据、API、SEO、后台编辑和发布流程。改造目标不是推倒重做,而是在不改变后端语义的前提下,将它组织成一个可浏览、可交互的 Personal OS。
2. 最重要的产品判断
OS 不是首页皮肤
如果只把首页换成桌面,文章页、Career、Demo、Contact 和后台仍然是另一套产品,OS 只是主题。因此产品被定义为:
桌面是入口,页面是应用,真实内容与工作流组成系统。
文件与应用必须分开
- Posts、Projects、Tools、Cat 是可浏览资源集合。
- 文章、图片、项目记录是文件或资源。
- Career 是结构化内容应用。
- Contact 是 Mail 应用,因为它有表单、验证、发送和回执状态。
- Music 是应用和持久运行时,不是普通文件夹。
- Demo 使用 Browser 应用打开,但保留 Demo 自己的主体验。
后端保持原样
Prisma、公开 API、Contact 发送、Dashboard 写操作、文章 URL、canonical、sitemap 和 revalidation 继续工作。OS 只在领域适配器与表现层/运行时增加能力。
3. 为什么先做静态原型
第一阶段没有立即接数据库,而是先用假文件树验证:
- 复古苹果方向是否成立;
- 菜单栏、桌面图标、Finder 和窗口是否形成一致语言;
- 单击选择、双击打开、拖动和聚焦是否自然;
- 目标桌面尺寸是否能容纳布局。
视觉方向属于高成本产品决策。先接真实数据会让错误方向携带更多代码和迁移成本。
4. 实施顺序
001-007:从原型到首页切换
- 静态桌面视觉原型。
- 窗口 reducer 和 Finder 历史。
- Prisma、本地注册表到只读虚拟文件系统。
- Document、Project、Tool、Image、Contact 查看器。
- Demo Browser 与 iframe 白名单。
- 键盘、焦点、错误态、尺寸、性能和回归。
- 根首页切换、SEO、revalidation、部署与回退。
008-016:从桌面变成系统
- Tailwind OS Design System。
- 应用注册表与统一应用生命周期。
- 移动应用工作区。
- 可执行的命令/菜单注册表。
- Dock 启动、聚焦、运行状态。
- Mail 真实联系表单。
- Career 应用与直接路由复用。
- CLI 歌曲数据与 YouTube 播放映射。
- 持久媒体运行时、Music 应用和 Dock 迷你播放器。
每个规格只解决一个边界,并在结束时把测试、构建和浏览器证据写回文档。
5. 核心架构
Prisma / 本地注册表 / CLI 数据 / 外部映射
|
领域适配器
|
虚拟文件系统 + 应用注册表 + 命令注册表
|
OS 运行时 + 媒体运行时
/ | \
桌面工作区 应用窗口 应用模式
虚拟文件系统
真实数据被映射为扁平、可序列化的树:
- 稳定 ID 不依赖展示名称;
- 父子关系双向一致;
- Posts 按年份形成目录;
- 单类数据失败只让对应目录变为不可用;
- UI 不接触 Prisma 对象和 Date 实例。
应用注册表
应用名称、图标、默认窗口、Dock 策略和移动端能力只有一个真源。桌面、Finder、Dock、菜单和窗口不再分别维护应用定义。
运行时 Reducer
窗口打开、聚焦、关闭、移动、去重,活跃应用和 Finder 历史使用纯 reducer。这样最容易验证长交互序列和不变量。
持久运行时
Music 窗口关闭后,YouTube 播放器和播放状态继续存在。窗口只是播放器的一个视图,媒体运行时才负责服务生命周期。
6. 测试方法
纯逻辑优先
优先测试:
- 文件树映射、排序、稳定 ID 和不变量;
- window reducer 的 open/focus/close/reopen;
- Finder back/forward/home;
- app registry 和 command state;
- media reducer 的 repeat、shuffle、ended 和错误跳过;
- YouTube mapping 的漂移、重复和非法 ID。
这些测试不依赖 DOM,能快速覆盖复杂状态组合。
组件测试状态闭环
验证用户可见状态,而不是只测函数被调用:
- 表单 pending/sent/failed/retry;
- Viewer dispatch 与 unknown fallback;
- Dock running/active;
- Music window 关闭后播放状态仍在;
- 外部 player 不完整时控制命令不让页面崩溃。
浏览器验证真实系统行为
浏览器检查包括:
- 键盘完整路径和关闭后的焦点恢复;
390x844、768x1024、最小桌面和宽屏;- body 是否真实横向溢出;
- 图片
object-fit和实际像素加载; - iframe 白名单、loading、timeout 和 retry;
- YouTube script 是否只在用户操作后加载;
- 真实播放进度、关窗续播和 Dock 控制;
- route、canonical、sitemap 和 deployed smoke。
层级不能只看 z-index 数字
Music 播放器最初通过全局固定 host 渲染。虽然数字看起来合理,但它位于桌面 stacking context 外部,因此会盖住所有窗口。
修复方式:
- 将播放器 portal 到桌面层根节点;
- 使用 Music 所属窗口的 z-index;
- 观察所属窗口样式变化;
- 将桌面图标层降到窗口之下;
- 在窗口真实重叠坐标用
document.elementFromPoint()验证谁位于最上层。
这说明 CSS stacking context 是结构问题,不是简单增减 z-index 数字。
7. 典型问题与经验
不要在运行时搜索媒体
YouTube 搜索和播放分层。歌曲元数据仍来自 CLI,视频映射是离线研究产物,记录来源、验证时间和可嵌入状态。运行时只消费稳定 video ID。
不要让第三方 SDK 控制应用稳定性
YouTube IFrame API 在初始化、热更新或 stub 环境下可能提供不完整对象。适配器需要检查方法存在、保护时间值,并把错误转为稳定媒体事件。
热更新结果不是最终证据
曾出现 server 输出新 Tailwind class、client 保留旧 class 的 hydration mismatch。重启 dev server 后问题消失。涉及 SSR/hydration、样式和外部 iframe 时,应在干净服务器和新页面复测。
文档完成不等于产品完成
OpenSpec 的 done 必须有:
- 验收标准对应证据;
- targeted 和 regression tests;
- TypeScript;
- 生产构建;
- 浏览器尺寸和交互;
- 必要时真实域名冒烟测试;
- 明确回退方案。
8. 可迁移结论
把知识库变成 OS 的关键不是画窗口,而是建立四个转换:
- 从内容表和页面,转换为稳定的资源模型。
- 从零散入口,转换为应用、命令和生命周期。
- 从单页展示,转换为 Desktop、Mobile、Application 和 Admin 多种 surface。
- 从一次性改版,转换为有依赖、有门禁、有证据的 OpenSpec 工程过程。
当这四层成立后,复古 Mac、Windows 98、终端、掌机或其他视觉语言都只是可替换的 presentation。系统本身仍然成立。