# 5-37 [process] MNote UI Foundation — 统一设计系统与组件基础设施 v1 > 创建时间:2026-06-15 > > 状态:process > > Owner:05-editor-mainline / 03-rust-web / 11-wolai > > 目标:为 MNote 建立统一设计系统与组件基础设施,解决当前 UI 风格分散、组件缺失、交互 primitive 手写重复、CSS 单文件膨胀、SSR/CSR 两套运行时不统一的问题。 > 本轮完成 goal:`5-37-ui-foundation-p0-icon-qa`。范围是补齐 P0 中可独立落地的 token/portal/toast 基础设施,收紧 Material Symbols 图标验收,并交给 AgentBoard 做风险审查与浏览器 QA。P0 中编辑器全量 Button/Radix focus trap、CSS 全量拆分仍作为后续目标,不在本轮强行扩散。 ## 1. 问题诊断 ### 1.1 当前 UI 资产分布 | 层次 | 位置 | 规模 | 问题 | |------|------|------|------| | CSS | `styles.rs` | 6718 行单文件 Rust 字符串常量 | 单文件膨胀、无模块拆分、修改风险高 | | SSR 模板 | `layout.rs`, `document.rs`, `home.rs` 等 | ~10 个 Leptos view 组件 | 交互逻辑混入 JS runtime,非 reactive | | 浏览器 runtime | `browser/` | 46 个 JS 文件 | 覆盖全但 ad-hoc,无统一 primitive | | 编辑器 CSR | `editor_runtime/` | 27 个 Rust 文件 | 菜单/overlay 手写,缺 radix 级基础设施 | ### 1.2 核心缺口 **组件抽象层缺失** - 没有 `Button/Dialog/Popover/Menu/Tabs/Input/Select/Toast/Skeleton` 等可复用 primitive。 - root `package.json` 只有 Playwright + 预览库,无 UI 框架依赖。 - recycle 旧前端有 Radix UI + shadcn 的完整组件体系(19 个组件文件),但依赖 React,不能直接用于当前 Rust SSR + CSR 架构。 **交互 primitive 重复手写** - Escape 关闭:`tree-shell-filetree-menu-runtime.js`、`block_handle_menu_view.rs`、`mindmap_node_view.rs`、`sidebar-page-ai-runtime.js` 各写一份。 - outside click 关闭:同上。 - focus trap:仅 filetree menu 有基础实现,dialog 没有。 - body scroll lock:不存在。 - z-index 管理:无统一层叠规范,`1200`、`1000`、`999` 散落在 `styles.rs` 各处。 **反馈系统缺失** - toast/notification 不存在。错误反馈多数用 `alert()` fallback 或局部 `set_command_feedback`。 - 无全局反馈管道。 **图标系统不统一** - 主壳用 Material Symbols (Google Fonts CDN)。 - 编辑器菜单用 Unicode 文本符号(`"✦"` `"↻"` `"⌫"`)。 - mindmap 用 emoji + Material Symbols 混合。 **设计 token 无管线** - CSS 变量已在 `:root` 定义(`--wolai-bg`、`--wolai-text-primary` 等),但没有 token 文件、组件 variant 规范、尺寸等级、状态色。 - 6718 行 CSS 直接塞在 Rust 常量里,长期维护不可持续。 **组件演示与可视化验收缺失** - 无 Storybook 或内部 `/ui-debug` 组件画廊。 - 现有 Wolai 视觉 smoke 偏页面级,不是组件级基线。 **前端工程守门缺失** - root 没有 `lint`、`test`、`format` 脚本。 - JS runtime 靠 smoke 和局部 `node --check`,无统一质量门禁。 **SSR / CSR 双运行时鸿沟** - 主壳是 Leptos SSR → HTML 字符串 + 46 JS runtime。 - 编辑器是 Leptos CSR/WASM → reactive signal。 - 两个运行时之间没有共享的组件层、token 层或交互契约。 ### 1.3 为什么不能直接搬旧 recycle 前端组件 - 旧前端依赖 React 19 + Radix UI React + shadcn + Tailwind + Next.js。 - 当前主壳是 Rust Leptos SSR + 浏览器 JS runtime,编辑器是 Leptos CSR/WASM。 - 不能把 React 组件直接嵌入 Rust 栈,需要适配。 ## 2. 设计原则 1. **单一真相源**:token 只有一份定义,SSR 和 CSR 共同引用。 2. **渐进迁移**:不在主壳大规模重写 SSR 架构;先在编辑器 island 落地 primitive,再逐步上溯到主壳。 3. **headless 优先**:组件 primitive 不绑定视觉风格,外观由 Wolai 设计 token + CSS 变量控制。 4. **事件驱动,取消轮询**:所有 UI 状态同步优先走 realtime event stream / MutationObserver / command result,不新增 `setInterval` 轮询。 5. **可测试**:每个 primitive 必须有组件级 smoke,每个 variant/state 有截图或 DOM 断言覆盖。 6. **不新增依赖债务**:只用成熟、有维护者的 Leptos 生态库;不自行从零实现已有成熟方案。 ## 3. 技术选型 ### 3.1 编辑器 island (CSR/WASM) 组件 primitive **选 radix-leptos v0.9.x** | 对比维度 | radix-leptos | thaw-ui | rust-ui/ui | leptonic | 自研 | |---------|-------------|---------|------------|----------|------| | 风格 | headless,不绑定视觉 | Fluent Design | shadcn/Tailwind | Material | 任意 | | 与 Wolai CSS 兼容 | ✅ headless | ❌ Fluent 冲突 | ⚠️ 需 Tailwind | ❌ Material | ✅ | | Leptos 0.8 兼容 | ✅ v0.9.0 | ✅ v0.5-beta | ✅ | ✅ | ✅ | | 组件数量 | 57+ | ~40 | ~30 | ~20 | 0 | | 测试覆盖 | 1792+ tests | 未知 | 未知 | 未知 | 0 | | 维护状态 | 活跃 (Q2 2026) | 活跃 | 活跃 | 稳定 | N/A | | WASM 体积 | 538KB 优化后 | 未知 | 未知 | 未知 | 0 | | FocusTrap/Escape/Portal | ✅ 全套 | 未知 | 无 | 无 | 需自建 | 结论:radix-leptos 是唯一提供完整 headless primitive + a11y infrastructure 的 Leptos 生态库,与 MNote Wolai CSS 变量体系零冲突。 ### 3.2 主壳 (SSR + JS runtime) 过渡策略 短期不迁移 SSR 架构,但做三件事: 1. **CSS 拆分**:`styles.rs` → `styles/tokens.css` + `styles/components/` + `styles/pages/`。 2. **交互契约统一**:参考 radix-leptos 的 hook 行为语义,统一 JS runtime 中的 escape/focus trap/outside click 模式。 3. **Portal 容器标准化**:`layout.rs` 中预留统一 portal root (`#mnote-portal-root`),所有 popover/dialog/menu 渲染到此处。 ### 3.3 图标系统 统一使用 Material Symbols。 - 编辑器菜单 Unicode 文本符号逐步替换为 Material Symbols。 - 维护 `ICON_MAP` 常量将语义名映射到 ligature。 - 不引入额外图标库。 ### 3.4 构建工具 - **编辑器 island**:现有 `trunk` 构建不变,`Cargo.toml` 加 `radix-leptos-primitives` 依赖。 - **主壳 CSS**:保持 Rust `include_str!` 编译期嵌入,但源文件拆分为多个 `.css` 文件。 - **token 源**:一个 `tokens.css` 文件,被主壳和编辑器共同引用。 ## 4. 设计 token 体系 ### 4.1 设计变量分层 ``` Layer 0: Raw Colors (--color-basic-50 .. --color-basic-900) Layer 1: Semantic Colors (--wolai-bg, --wolai-text-primary, --wolai-border, --wolai-brand ...) Layer 2: Component Tokens (--mnote-btn-bg, --mnote-dialog-shadow, --mnote-popover-radius ...) Layer 3: State Tokens (--mnote-state-hover, --mnote-state-active, --mnote-state-disabled) ``` Layer 0-1 已存在于 `styles.rs` 的 `:root` 块。Layer 2-3 当前缺失。 ### 4.2 尺寸与间距等级 ```css --wolai-space-xs: 4px; --wolai-space-sm: 8px; --wolai-space-md: 12px; --wolai-space-lg: 16px; --wolai-space-xl: 24px; --wolai-space-2xl: 32px; --wolai-radius-sm: 4px; --wolai-radius-md: 6px; --wolai-radius-lg: 8px; --wolai-radius-xl: 12px; --wolai-font-xs: 12px; --wolai-font-sm: 13px; --wolai-font-md: 14px; --wolai-font-lg: 16px; --wolai-font-xl: 20px; --wolai-font-2xl: 24px; ``` ### 4.3 阴影等级 ```css --wolai-shadow-sm: 0 1px 3px rgba(15, 23, 42, 0.08); --wolai-shadow-md: 0 4px 12px rgba(15, 23, 42, 0.12); --wolai-shadow-lg: 0 18px 54px rgba(15, 23, 42, 0.18); --wolai-shadow-overlay: 0 18px 48px rgba(15, 23, 42, 0.22), 0 2px 8px rgba(15, 23, 42, 0.08); ``` ### 4.4 z-index 层级 ```css --wolai-z-sidebar: 100; --wolai-z-topbar: 200; --wolai-z-floating: 500; --wolai-z-popover: 1000; --wolai-z-dialog-backdrop: 1100; --wolai-z-dialog: 1200; --wolai-z-toast: 1300; --wolai-z-tooltip: 1400; ``` ## 5. 组件清单与优先级 ### 5.1 P0 — 编辑器 island 落地(blocking 风格统一) | 组件 | radix-leptos 对应 | 当前手写位置 | 迁移收益 | |------|------------------|-------------|---------| | Button | `Button` (6 variants) | 各处零散 `