Subtic 提炼审查
记录什么值得提炼、如何分类,以及哪些职责应留在应用。
本次审查于 2026 年 10 月 23 日 对 Subtic Web 进行只读检查,基线提交为 6512eb109445b879fe8f82fb9d08885bd1172570。审查范围是 Web 前端,当前交付在 Kitseed 中建设组件体系,Subtic 没有迁移或替换。
2026-10-31 追加:Image Lightbox
0.3.0 将内容图片预览与反馈附件的全屏视图提取为 ImageLightbox,并同步接入 Subtic。通用层只消费 id/src/alt,提供缩放、平移、捏合、动态集合、焦点恢复与失败重试接口。ClientContentMedia、desktopMediaUrl、userViewResourceUrl、翻译、快捷键偏好和反馈已读逻辑保留在产品适配层。
此前“源项目只读”和 24 个家族为首期历史范围;随着 ImageLightbox 与跨项目统一的 Sidebar 应用侧边栏入库,当前为 26 个组件家族。附件缩略图网格仍为产品实现,不随预览视图入库。历史版本 Registry 保留,新增功能使用 v0.3.0 本地候选。
审查覆盖
| 区域 | 扫描范围 | 判断重点 |
|---|---|---|
components/ui | 19 个文件:16 TSX、2 TS、1 CSS | 样式基线、交互语义、基础依赖 |
components 顶层 | 101 个 TSX | 可复用结构与业务耦合 |
components/route-states | 6 个文件 | 状态展示与路由恢复职责 |
features | 165 个 TSX | 是否存在可提取的通用呈现层 |
| 路由 | 118 个文件 | 布局使用方式与应用边界 |
这些数字说明扫描覆盖,不代表所有文件都应成为组件。一个源文件可以拆成多个家族;同一家族也可能依赖多个源文件。
提炼的三个判断
直接复用视觉与语义。 Badge、Panel、Kbd、Segmented、Skeleton、Spinner、Tooltip 主要依赖 React、Base UI、tokens 和少量本地工具,适合作为设计基础。保留原有内容密度,补齐清晰文档。
保留成熟交互,移除应用环境。 Button、Menu、Field、Form、Copy Field、Confirm Dialog、Toast、Choice Picker、Time Picker 已有实际产品交互。提炼时移除应用翻译单例、权限、API 错误映射、桌面桥接与全局平台状态,改由公开 props、通用配置、文案和回调传入。
保留结构,重新定义输入。 Settings、Page、Navigation、States、Identity 与 Collection 有跨产品价值,但不应携带 Subtic 路由、模型与操作。提炼后的组件接收展示数据与插槽,由调用方提供导航、计数、菜单和行为。
组件归类
| 分类 | 家族 | 决策依据 |
|---|---|---|
| 操作与输入 | Button、Copy Field、Kbd、Field、Form、Choice Picker、Time Picker、Segmented、Selection | 完成动作、编辑与选择;以标签、值、验证和键盘为共同约定 |
| 浮层与反馈 | Menu、Tooltip、Confirm Dialog、Toast、States | 传递补充信息、风险和操作结果;以焦点与状态恢复为共同约定 |
| 布局与导航 | Panel、Settings、Page、Navigation、Sidebar | 建立空间、信息层级和位置感;内容与路由由调用方拥有 |
| 内容与展示 | Badge、Skeleton、Spinner、Identity、Data Display、Collection | 呈现身份、数据与集合;与具体领域模型分离 |
Selection 将搜索字段、开关、筛选条件和选项内容组织为通用呈现;原生复选与单选保留在 Field 的 ChoiceRow 中。Data Display 将定义项等内容展示能力独立记录。这些分类按用途组织,不要求与源文件一一对应。
需要留下的 Subtic 职责
| 领域 | 典型来源 | 留在应用的原因 |
|---|---|---|
| 订阅与投递 | delivery-fields、内容保存面板、集成授权 | 依赖提供商、权限和后端契约 |
| 内容读取与编辑 | collection-reader、具体 collection card 内容 | 依赖 Entry 类型、阅读状态与领域操作 |
| 管理与计费 | features/admin*、权益和销售面板 | 权限、资金与运营规则属于应用 |
| 运行时恢复 | 路由错误页、集合滚动恢复、未保存保护 | 需要路由、持久化和平台桥接 |
| 品牌与资源 | Subtic 专属头像、Logo、插画 | 不应成为其他产品默认身份 |
这类功能可以使用 Kitseed 构造界面,但不会以完整业务模块进入 Registry。
提炼后的依赖边界
| 从源项目移除 | Kitseed 中的承接方式 |
|---|---|
isUserViewWindow、写权限与平台判断 | 应用决定 disabled、只读状态与原因 |
translate、useUiLocale | 应用传入标签、提示与错误文案 |
presentError、API 响应 | 通用错误节点、字段错误 map 与回调 |
| 桌面 dirty state 与桥接 | 应用管理保存周期和未保存离开保护 |
| 固定路由、查询和业务模型 | 链接、active 状态、展示数据与操作插槽 |
逐项依赖与交付映射
重新核对源项目 16 个 TSX 的实际 imports 后,可以按 7 个主要结构可迁移家族、9 个需要去除产品上下文的家族 分组。标准是是否导入 Subtic 的翻译单例、权限、API 错误处理、桌面或工作区状态;不以视觉是否简单判断。Choice Picker、Copy Field、Time Picker 即使主要是通用交互,也仍依赖应用 i18n,不能标成零去耦复制。
下表的源文件均位于 Subtic 的 apps/web/components/ui/;目标为 Kitseed 仓库路径。所有阶段均指 Kitseed 首期交付,不表示已迁移 Subtic。通用调整包括改为相对 imports、只提取 cn 所需的 clsx/tailwind-merge 工具、接入独立 tokens;Base UI imports 从源包路径 @base-ui-components/react 调整为本项目安装的 @base-ui/react。
主要结构可迁移:7 个家族
| 家族与源定位 | 原依赖 | 去耦原因与处理 | Kitseed 路径 | 交付阶段 |
|---|---|---|---|---|
Badge · badge.tsx:1–3 | React 类型、CVA、@/lib/utils 的 cn | 没有产品上下文;保留 tone/variant,调用者负责业务状态到 tone 的映射 | registry/kitseed/ui/badge.tsx | 首期·结构迁移,已实现 |
Kbd · kbd.tsx:1–2 | React、cn;组件内读取浏览器 userAgent | 保留键帽和通用修饰键显示;userAgent 只决定 ⌘/Ctrl,不依赖桌面桥或产品快捷键注册 | registry/kitseed/ui/kbd.tsx | 首期·结构迁移,已实现 |
Panel · panel.tsx:1–2 | React 类型、cn | 只有表面和排布;迁移 tokens,不引入应用数据或页面状态 | registry/kitseed/ui/panel.tsx | 首期·结构迁移,已实现 |
Segmented · segmented.tsx:1 | React useId、原生 radio | 结构已受控;保留 radio 与键盘语义,补通用 disabled/density,不与链接导航合并 | registry/kitseed/ui/segmented.tsx | 首期·结构迁移,已实现 |
Skeleton · skeleton.tsx:1–2 | React 类型、cn | 仅占位视觉;具体内容的行数、比例和加载策略交给模式层 | registry/kitseed/ui/skeleton.tsx | 首期·结构迁移,已实现 |
Spinner · spinner.tsx:1 | cn、内联 SVG | 没有产品依赖;保留装饰性 SVG,并补 reduced-motion 处理 | registry/kitseed/ui/spinner.tsx | 首期·结构迁移,已实现 |
Tooltip · tooltip.tsx:1–16 | Base UI Tooltip、React、同层 Kbd、tooltip.module.css | 本体没有 i18n/API/权限 imports;保留焦点、截断检测和样式,将产品命令解析留在调用侧 | registry/kitseed/ui/tooltip.tsx、registry/kitseed/ui/tooltip.module.css | 首期·结构迁移,已实现 |
需要去除产品上下文:9 个家族
| 家族与源定位 | 原依赖 | 去耦原因与处理 | Kitseed 路径 | 交付阶段 |
|---|---|---|---|---|
Button · button.tsx:1–8 | Base UI useRender、CVA、React、cn、Tooltip;useMobileWorkspace、isUserViewWindow、translate | 不能让其他产品读取 Subtic 窗口或权限;去 requiresWrite 策略,改为调用者传 disabled/disabledReason,通用 provider 提供 density | registry/kitseed/ui/button.tsx | 首期·去耦实现,已实现 |
Choice Picker · choice-picker.tsx:1–19 | Base UI Combobox/Select、Phosphor、React、cn、FieldContext/Field/picker-styles;translate/useUiLocale/uiLocale | 选项、过滤与浮层可复用,但文案刷新绑应用语言单例;改用 props/messages,保留字段关联和焦点,options 由调用者提供 | registry/kitseed/ui/choice-picker.tsx、registry/kitseed/ui/picker-styles.ts | 首期·去耦实现,已实现 |
Copy Field · copy-field.tsx:1–6 | Phosphor、React、cn、Button、Toast;translate/useUiLocale | 保留复制与显示/隐藏反馈;去应用翻译,改通用 messages;复制拒绝时保留可选文本和明确失败提示 | registry/kitseed/ui/copy-field.tsx | 首期·去耦实现,已实现 |
Dialog / Confirm Dialog · dialog.tsx:1–8 | Base UI AlertDialog、React、Button/Input;translate/useUiLocale、presentError、产品 ErrorFeedback | 原错误组件会解释 API 与权益;改用 renderError/通用消息,保留 onPrepare/onConfirm、pending 和焦点,应用自行解释业务异常 | registry/kitseed/ui/dialog.tsx | 首期·去耦实现,已实现 |
Field · field.tsx:1–12 | React、cn、FieldContext/FormErrorContext;useMobileWorkspace、translate/useUiLocale,支持文件 validationMessage 也调用 translate | 去工作区和语言单例;通用 messages 生成原生约束提示,provider 提供 density,保留 label/hint/error 关联,不接服务端协议 | registry/kitseed/ui/field.tsx、registry/kitseed/ui/form-context.ts | 首期·去耦实现,已实现 |
Form · form.tsx:1–20 | React/ReactDOM、cn、form-context;i18n、presentError、ErrorFeedback、isUserViewWindow、canMutate/subscribePlatform、DesktopFormChanges/desktop dirty state | 去账户写权限、平台订阅和原生未保存状态;公开 fieldErrors/error/pending 与回调,保留原生校验/摘要/错误聚焦;离开保护和保存周期留应用 | registry/kitseed/ui/form.tsx、registry/kitseed/ui/form-context.ts | 首期·去耦实现,已实现 |
Menu · menu.tsx:1–12 | Base UI Menu、React、Tooltip、cn、picker-styles;isUserViewWindow、translate | 去产品只读权限判断;由调用者传 disabled/disabledReason/tooltip,保留菜单层级、选择项和键盘操作 | registry/kitseed/ui/menu.tsx | 首期·去耦实现,已实现 |
Time Picker · time-picker.tsx:1–6 | React、Input、SearchPicker、cn;translate/useUiLocale/uiLocale、@/lib/time-picker 的 normalizeTimeInput/quickTimes | 去应用语言单例;将通用时间解析和快捷时段迁成本地 helper,提示来自 messages,不携带 DigestSchedule、时区投递或计划 API | registry/kitseed/ui/time-picker.tsx、registry/kitseed/lib/time-utils.ts | 首期·去耦实现,已实现 |
Toast · toast.tsx:1–5 | Base UI Toast、Phosphor、React;translate/useUiLocale、API 模块的 safeErrorText | 原通知会净化应用错误;去 API/翻译依赖,调用者传已整理内容,组件保留队列、去重、持续时间与关闭语义 | registry/kitseed/ui/toast.tsx | 首期·去耦实现,已实现 |
lib/provider.tsx 是独立、可选的 Kitseed 配置,提供 density 与默认中文 messages;不是 Subtic i18n 或权限 provider 的复制。上面 7/9 是来源依赖分类,不意味着只有 9 个组件需要工程调整,也不把子导出与支持文件重复计数。
发布与验证边界
每个家族应同时具备独立预览、安装条目、关键 Props、状态说明和来源。Registry 产物声明直接依赖,并以版本路径分发;文档不把原 Subtic 的历史验收记录算作 Kitseed 的当前测试结果。
本仓库的类型、构建、交互、可访问性与消费项目检查,以实际交付结果为准。文档完成不代表已经部署,也不代表 Subtic 已采用新组件。
来源与许可
源项目为 /Users/cbq926/Program/Project/subtic,组件页记录 repo-relative 来源路径。Subtic 为私有应用,其开源授权尚未确认;Kitseed 首期保持私有,不默认添加 MIT 等源码许可证。第三方依赖与 Geist 字体继续遵循各自许可。