453 lines
20 KiB
Markdown
453 lines
20 KiB
Markdown
# 3-7 [done] Rust Web 3000 Wolai UI Parity Implementation Plan v1
|
||
|
||
> **For agentic workers:** REQUIRED SUB-SKILL: Use `superpowers:subagent-driven-development` (recommended) or `superpowers:executing-plans` to implement this plan task-by-task. Steps use checkbox (`- [x]`) syntax for tracking.
|
||
|
||
**Goal:** 让 `mnote-web` 持有的 `3000` 公共入口恢复到可用的 Wolai 工作区产品壳,并继续向 `/mnt/Data1T/mnote/design/design/html/image.png` 的目标 UI 收敛。
|
||
|
||
**Architecture:** 保持 `3000` 的 owner 是 `mnote-web`,不把 Next App Router 恢复为主入口。Rust Web 输出 workspace/document server-first shell、Page Aggregate snapshot、Sidebar projection 与 topbar/floating action 的稳定 HTML 合同;React/Next 仅保留交互 island、legacy 对照链和显式 debug 边界。
|
||
|
||
**Tech Stack:** Rust workspace、`mnote-web`、`axum`、Leptos SSR、Page Aggregate、Convex substrate、Rust tree projection、Playwright/Node smoke、Cargo tests、Vitest。
|
||
|
||
---
|
||
|
||
## 1. 当前事实基线
|
||
|
||
本计划基于 2026-04-29 本机检查结果建立:
|
||
|
||
- `127.0.0.1:3000` 当前由 `target/debug/mnote-web` 监听,`GET /` 返回 `x-mnote-web-owner: mnote-web`。
|
||
- `127.0.0.1:3100` 当前由 `node scripts/dev-server.js -p 3100` 监听,根路径返回 `307 /auth`,仍可作为 legacy Next 对照链。
|
||
- 当前 `3000` 截图 `/mnt/Data1T/mnote/tmp/image copy 30.png` 是极简 MNOTE 首页,只有“首页 / 搜索 / 文档”和欢迎文案。
|
||
- 当前 legacy/Next 对照截图 `/mnt/Data1T/mnote/tmp/image copy 31.png` 已有 workspace/document 左栏、页面树和文档内容,但仍不是目标 UI。
|
||
- 目标 UI 参考是 `/mnt/Data1T/mnote/design/design/html/image.png` 与 `/mnt/Data1T/mnote/design/design/html/个人.html`,它包含完整 Wolai 桌面壳:账号区、顶栏、快捷图标、星标置顶、我的页面、页面树、底部入口、右下悬浮按钮和居中页面内容。
|
||
- `harness-tasks.json` 目前只有 `task-001` 到 `task-026`,全部是 `completed`,没有待办任务。
|
||
- `task-019` 到 `task-026` 已证明主 Web owner cutover,但没有证明产品 UI parity。
|
||
- `MNOTE_UI_BASE_URL=http://127.0.0.1:3000 node scripts/task114-rust-web-gateway-entry-smoke.js` 当前失败,原因是脚本仍断言根页包含 `Rust Web gateway` 文案,而当前根页已经是极简 `MNOTE` 首页。
|
||
- `MNOTE_UI_BASE_URL=http://127.0.0.1:3000 node scripts/task115-rust-web-document-shell-smoke.js` 当前失败,原因是 `/documents/doc_1?workspaceId=ws_demo` 经过 Convex 查询返回 `未登录`,HTTP 状态为 `502`。
|
||
- `rust/crates/mnote-web/src/routes/gateway.rs::root_entry` 当前直接渲染 `ssr/pages/home.rs::HomePage`,不会把 `/` 代理到 3100 的 workspace/document 壳。
|
||
- `rust/crates/mnote-web/src/ssr/pages/layout.rs::PageLayout` 当前只实现极简侧栏,不具备目标 UI 的 workspace shell 合同。
|
||
|
||
结论:当前问题不是“3000 owner 还没切到 Rust Web”,而是“owner 已切到 Rust Web 后,产品工作区 UI、真实会话、真实数据壳和视觉/交互验收没有接上”。
|
||
|
||
## 2. 边界原则
|
||
|
||
- `3000` 保持唯一公开入口,默认 owner 继续是 `mnote-web`。
|
||
- 不恢复 `3104` 作为公开入口。
|
||
- 不把 `3100` 恢复成主入口;`3100` 只作为 legacy/reference/debug upstream。
|
||
- 不在前端重新创造树真相;Sidebar / 页面树继续消费 Rust kernel projection 或 Page Aggregate 中的稳定 projection。
|
||
- 不把“截图样式补丁”当成完成;必须同时修复当前 3000 的真实会话、真实路由和 smoke 验收。
|
||
- 不修改或回滚当前未提交改动;执行本计划时按任务最小写集落地。
|
||
|
||
## 3. 文件结构
|
||
|
||
### Rust Web SSR shell
|
||
|
||
- `rust/crates/mnote-web/src/routes/gateway.rs`
|
||
- 把 `/` 从极简欢迎页改成 workspace entry,负责选择 active workspace / active page / fallback route。
|
||
- `rust/crates/mnote-web/src/routes/web_shell.rs`
|
||
- 继续承接 document shell 与 Page Aggregate;新增 workspace shell 所需的共享加载器。
|
||
- `rust/crates/mnote-web/src/ssr/pages/layout.rs`
|
||
- 从极简 `PageLayout` 演进为 `WolaiWorkspaceLayout` 或等价组件,输出目标 UI 的稳定 DOM 合同。
|
||
- `rust/crates/mnote-web/src/ssr/pages/home.rs`
|
||
- 从营销/欢迎页改为 workspace home/page shell,不再作为默认产品首屏。
|
||
- `rust/crates/mnote-web/src/ssr/pages/document.rs`
|
||
- 对齐目标页面内容区:breadcrumb、顶部操作区、页面 icon/title、子页面列表、编辑 island mount。
|
||
- `rust/crates/mnote-web/src/ssr/styles.rs`
|
||
- 收口目标 UI 样式变量、sidebar/topbar/content/floating action 样式。
|
||
|
||
### Rust Web 数据合同
|
||
|
||
- `rust/crates/mnote-web/src/workspace_shell.rs`
|
||
- 新建 workspace shell projection 聚合器,组合 workspace summary、sidebar dataset、active page、starred/pinned 区域和 bottom entries。
|
||
- `rust/crates/mnote-web/src/routes/session.rs`
|
||
- 修复 3000 当前真实请求的 session/auth handoff,避免文档页在登录态/开发态下返回 `未登录` 502。
|
||
- `rust/crates/mnote-web/src/transport/convex.rs`
|
||
- 确认 Rust Web 到 Convex 的 cookie/header/token 透传合同;必要时补只读 helper,不绕开用户鉴权。
|
||
|
||
### 前端 legacy/island
|
||
|
||
- `wolai-frontend/src/lib/rust-web-main-execution-boundary.ts`
|
||
- 补充 UI parity guard,明确 Next 只能是 legacy/reference/island source。
|
||
- `wolai-frontend/src/components/sidebar/**`
|
||
- 只在需要复用现有 projection/DOM 合同时修改,不重新把 Next Sidebar 设为主产品壳。
|
||
- `wolai-frontend/src/components/editor/**`
|
||
- 只保留编辑器 island runtime 和必要 mount contract。
|
||
|
||
### Smoke / 验收
|
||
|
||
- `scripts/task114-rust-web-gateway-entry-smoke.js`
|
||
- 从“根页包含调试文案”升级为“根页是 Rust-owned Wolai workspace shell”。
|
||
- `scripts/task115-rust-web-document-shell-smoke.js`
|
||
- 从 fixture-only 文档壳验证升级为“当前 3000 可打开真实/开发态文档壳且不返回未登录 502”。
|
||
- `scripts/task118-rust-web-wolai-ui-parity-smoke.js`
|
||
- 新建目标 UI parity smoke,覆盖 sidebar、topbar、content、floating actions、legacy 禁用组合。
|
||
- `scripts/task119-rust-web-wolai-visual-regression-smoke.js`
|
||
- 新建可选 Playwright 截图 smoke,对比 3000、3100 legacy 和 `design/design/html/image.png` 的关键布局指标。
|
||
|
||
## 4. 实施任务
|
||
|
||
### Task U-0:冻结 UI parity 失败用例
|
||
|
||
**Files:**
|
||
|
||
- Create: `scripts/task118-rust-web-wolai-ui-parity-smoke.js`
|
||
- Modify: `scripts/task114-rust-web-gateway-entry-smoke.js`
|
||
- Modify: `scripts/task115-rust-web-document-shell-smoke.js`
|
||
|
||
- [x] **Step 1: 写 3000 UI parity smoke**
|
||
|
||
创建 `scripts/task118-rust-web-wolai-ui-parity-smoke.js`,固定当前必须失败的产品壳断言:
|
||
|
||
```js
|
||
#!/usr/bin/env node
|
||
"use strict";
|
||
|
||
const assert = require("node:assert");
|
||
|
||
const BASE_URL = (process.env.MNOTE_UI_BASE_URL || "http://127.0.0.1:3000").replace(/\/+$/, "");
|
||
const REQUEST_TIMEOUT_MS = Number(process.env.MNOTE_SMOKE_TIMEOUT_MS || 20_000);
|
||
|
||
async function fetchWithTimeout(path) {
|
||
const controller = new AbortController();
|
||
const timer = setTimeout(() => controller.abort(new Error(`请求超时: ${path}`)), REQUEST_TIMEOUT_MS);
|
||
try {
|
||
return await fetch(`${BASE_URL}${path}`, {
|
||
redirect: "manual",
|
||
cache: "no-store",
|
||
signal: controller.signal,
|
||
});
|
||
} finally {
|
||
clearTimeout(timer);
|
||
}
|
||
}
|
||
|
||
async function readHtml(path) {
|
||
const response = await fetchWithTimeout(path);
|
||
const html = await response.text();
|
||
assert.equal(response.status, 200, `${path} 状态码应为 200`);
|
||
assert.equal(response.headers.get("x-mnote-web-owner"), "mnote-web", `${path} 必须由 mnote-web 拥有`);
|
||
assert.match(response.headers.get("content-type") || "", /text\/html/, `${path} 必须返回 HTML`);
|
||
return html;
|
||
}
|
||
|
||
async function main() {
|
||
const rootHtml = await readHtml("/");
|
||
|
||
assert.match(rootHtml, /data-mnote-shell="workspace"/, "根页必须是 workspace shell,而不是欢迎页");
|
||
assert.match(rootHtml, /liaibo的个人空间|个人空间|开发用户 的空间/, "侧栏必须显示工作区身份");
|
||
assert.match(rootHtml, /星标置顶/, "侧栏必须包含星标置顶区域");
|
||
assert.match(rootHtml, /我的页面/, "侧栏必须包含我的页面区域");
|
||
assert.match(rootHtml, /垃圾箱/, "侧栏底部必须包含垃圾箱入口");
|
||
assert.match(rootHtml, /模板中心/, "侧栏底部必须包含模板中心入口");
|
||
assert.match(rootHtml, /data-testid="wolai-topbar"/, "主内容区必须包含 Wolai 顶栏");
|
||
assert.match(rootHtml, /data-testid="wolai-floating-ai"/, "页面必须包含右下 AI 悬浮入口");
|
||
assert.doesNotMatch(rootHtml, /欢迎使用 MNOTE 知识管理平台/, "根页不能停留在极简欢迎页");
|
||
assert.doesNotMatch(rootHtml, /<a href="\/documents">文档<\/a>/, "根页不能停留在三项导航壳");
|
||
|
||
console.log(JSON.stringify({ ok: true, baseUrl: BASE_URL, shell: "workspace", owner: "mnote-web" }, null, 2));
|
||
}
|
||
|
||
main().catch((error) => {
|
||
console.error(error instanceof Error ? error.stack || error.message : String(error));
|
||
process.exit(1);
|
||
});
|
||
```
|
||
|
||
- [x] **Step 2: 运行 smoke,确认当前失败**
|
||
|
||
Run:
|
||
|
||
```bash
|
||
cd /mnt/Data1T/mnote
|
||
MNOTE_UI_BASE_URL=http://127.0.0.1:3000 node scripts/task118-rust-web-wolai-ui-parity-smoke.js
|
||
```
|
||
|
||
Expected before implementation: FAIL,第一处失败应指向 `data-mnote-shell="workspace"` 缺失或极简欢迎页仍存在。
|
||
|
||
- [x] **Step 3: 更新 task114 的根页断言**
|
||
|
||
把 `scripts/task114-rust-web-gateway-entry-smoke.js` 中对 `/Rust Web gateway/` 的断言改成:
|
||
|
||
```js
|
||
assert.match(rootText, /data-mnote-shell="workspace"/);
|
||
assert.doesNotMatch(rootText, /Rust Web gateway|欢迎使用 MNOTE 知识管理平台/);
|
||
```
|
||
|
||
- [x] **Step 4: 更新 task115 的失败信息**
|
||
|
||
在 `scripts/task115-rust-web-document-shell-smoke.js` 中保留 `x-mnote-web-owner` / `x-mnote-web-shell` 断言,并在 502 时输出 body,确保“未登录”不被隐藏成普通 shell 失败。
|
||
|
||
Run:
|
||
|
||
```bash
|
||
cd /mnt/Data1T/mnote
|
||
MNOTE_UI_BASE_URL=http://127.0.0.1:3000 node scripts/task115-rust-web-document-shell-smoke.js
|
||
```
|
||
|
||
Expected before implementation: FAIL,错误信息包含 `未登录` 或 `convex_upstream_error`。
|
||
|
||
### Task U-1:修复 3000 当前真实 session / document shell 基线
|
||
|
||
**Files:**
|
||
|
||
- Modify: `rust/crates/mnote-web/src/routes/session.rs`
|
||
- Modify: `rust/crates/mnote-web/src/transport/convex.rs`
|
||
- Modify: `rust/crates/mnote-web/src/routes/web_shell.rs`
|
||
- Test: `rust/crates/mnote-web/src/routes/web_shell.rs`
|
||
- Test: `scripts/task115-rust-web-document-shell-smoke.js`
|
||
|
||
- [x] **Step 1: 写 Rust 测试覆盖 session handoff**
|
||
|
||
在 `web_shell.rs` 的测试模块中增加测试,构造带 `mnote_web_convex_token` cookie 的请求,断言 Rust Web 会把 token 传给 Convex transport plan,不会落入未登录错误路径。
|
||
|
||
Run:
|
||
|
||
```bash
|
||
cd /mnt/Data1T/mnote/rust
|
||
cargo test -p mnote-web document_shell session
|
||
```
|
||
|
||
Expected before implementation: FAIL,失败点是缺少 cookie/header 透传或测试 helper 不存在。
|
||
|
||
- [x] **Step 2: 修复 Rust Web 到 Convex 的认证透传**
|
||
|
||
保持 `DEV_USER_ID` 只作为开发辅助,不绕过真实用户鉴权。实际请求优先级固定为:浏览器 cookie / session handoff token > `mnote_web_convex_token` > 开发态显式 DEV_USER fallback。
|
||
|
||
- [x] **Step 3: 验证当前 3000 文档页不再 502**
|
||
|
||
Run:
|
||
|
||
```bash
|
||
cd /mnt/Data1T/mnote
|
||
MNOTE_UI_BASE_URL=http://127.0.0.1:3000 node scripts/task115-rust-web-document-shell-smoke.js
|
||
```
|
||
|
||
Expected after implementation: PASS,输出 JSON 中 `owner` 为 `mnote-web`,`shell` 为 `document`。
|
||
|
||
### Task U-2:建立 workspace shell projection 聚合器
|
||
|
||
**Files:**
|
||
|
||
- Create: `rust/crates/mnote-web/src/workspace_shell.rs`
|
||
- Modify: `rust/crates/mnote-web/src/lib.rs`
|
||
- Modify: `rust/crates/mnote-web/src/routes/gateway.rs`
|
||
- Modify: `rust/crates/mnote-web/src/routes/web_shell.rs`
|
||
- Test: `rust/crates/mnote-web/src/workspace_shell.rs`
|
||
|
||
- [x] **Step 1: 定义 workspace shell 最小数据结构**
|
||
|
||
新增结构:
|
||
|
||
```rust
|
||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub struct WorkspaceShellProjection {
|
||
pub schema: String,
|
||
pub workspace_id: String,
|
||
pub workspace_name: String,
|
||
pub active_page_id: Option<String>,
|
||
pub active_page_title: Option<String>,
|
||
pub starred_items: Vec<WorkspaceShellItem>,
|
||
pub my_page_items: Vec<WorkspaceShellItem>,
|
||
pub bottom_entries: Vec<WorkspaceShellEntry>,
|
||
}
|
||
|
||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub struct WorkspaceShellItem {
|
||
pub id: String,
|
||
pub title: String,
|
||
pub icon: Option<String>,
|
||
pub href: String,
|
||
pub depth: u32,
|
||
pub active: bool,
|
||
}
|
||
|
||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub struct WorkspaceShellEntry {
|
||
pub id: String,
|
||
pub label: String,
|
||
pub href: String,
|
||
pub icon: String,
|
||
}
|
||
```
|
||
|
||
- [x] **Step 2: 写聚合器测试**
|
||
|
||
测试输入使用 `sidebar.dataset.list` 形状,断言输出包含:`星标置顶` 数据、`我的页面` 数据、`垃圾箱`、`模板中心`,且 active page 能从请求参数或 projection 中确定。
|
||
|
||
Run:
|
||
|
||
```bash
|
||
cd /mnt/Data1T/mnote/rust
|
||
cargo test -p mnote-web workspace_shell
|
||
```
|
||
|
||
Expected before implementation: FAIL,模块或类型不存在。
|
||
|
||
- [x] **Step 3: 接入 gateway root**
|
||
|
||
`GET /` 不再直接输出 `HomePage` 欢迎页,而是加载 `WorkspaceShellProjection` 后输出 workspace shell。无 active page 时,选择第一个可见 my page;没有页面时输出空 workspace shell,不输出营销欢迎页。
|
||
|
||
### Task U-3:把 `PageLayout` 替换为 Wolai workspace shell 合同
|
||
|
||
**Files:**
|
||
|
||
- Modify: `rust/crates/mnote-web/src/ssr/pages/layout.rs`
|
||
- Modify: `rust/crates/mnote-web/src/ssr/pages/home.rs`
|
||
- Modify: `rust/crates/mnote-web/src/ssr/pages/document.rs`
|
||
- Modify: `rust/crates/mnote-web/src/ssr/styles.rs`
|
||
- Test: `rust/crates/mnote-web/src/ssr/pages/layout.rs`
|
||
|
||
- [x] **Step 1: 写 SSR DOM 合同测试**
|
||
|
||
测试 `WolaiWorkspaceLayout` 渲染结果必须包含:
|
||
|
||
```text
|
||
data-mnote-shell="workspace"
|
||
data-testid="wolai-sidebar"
|
||
data-testid="wolai-topbar"
|
||
星标置顶
|
||
我的页面
|
||
垃圾箱
|
||
模板中心
|
||
data-testid="wolai-floating-ai"
|
||
data-testid="wolai-floating-help"
|
||
```
|
||
|
||
Run:
|
||
|
||
```bash
|
||
cd /mnt/Data1T/mnote/rust
|
||
cargo test -p mnote-web wolai_workspace_layout
|
||
```
|
||
|
||
Expected before implementation: FAIL,当前只有 `mnote-shell` / `mnote-sidebar-nav`。
|
||
|
||
- [x] **Step 2: 实现 workspace shell DOM**
|
||
|
||
把极简三项导航替换为目标 UI 合同:顶部账号区、快捷操作区、星标置顶、我的页面、底部入口、主内容 topbar 和右下浮动入口。
|
||
|
||
- [x] **Step 3: 样式对齐目标 UI 的关键布局指标**
|
||
|
||
关键指标:左栏宽度约 `280px` 到 `360px` 区间;侧栏背景为浅灰;选中行浅红背景;主内容首屏留白大;页面中心内容宽度约 `720px`;顶栏高度约 `44px`;右下两个圆形浮动按钮不遮挡正文。
|
||
|
||
### Task U-4:恢复 3000 根页到 workspace/document 产品首屏
|
||
|
||
**Files:**
|
||
|
||
- Modify: `rust/crates/mnote-web/src/routes/gateway.rs`
|
||
- Modify: `rust/crates/mnote-web/src/ssr/pages/home.rs`
|
||
- Modify: `rust/crates/mnote-web/src/routes/web_shell.rs`
|
||
- Test: `scripts/task118-rust-web-wolai-ui-parity-smoke.js`
|
||
|
||
- [x] **Step 1: 根路径选择 active page**
|
||
|
||
选择顺序固定为:URL `pageId` 参数 > session 最近页面 > projection active page > 第一个 `my_page_items` > 空 workspace。
|
||
|
||
- [x] **Step 2: 根路径输出 workspace shell**
|
||
|
||
`GET /` 的 body 必须包含 `data-mnote-shell="workspace"`,不再包含 `欢迎使用 MNOTE 知识管理平台`。
|
||
|
||
- [x] **Step 3: 验证 3000 smoke**
|
||
|
||
Run:
|
||
|
||
```bash
|
||
cd /mnt/Data1T/mnote
|
||
MNOTE_UI_BASE_URL=http://127.0.0.1:3000 node scripts/task118-rust-web-wolai-ui-parity-smoke.js
|
||
```
|
||
|
||
Expected after implementation: PASS。
|
||
|
||
### Task U-5:文档页内容区对齐 legacy 3100 与目标 UI
|
||
|
||
**Files:**
|
||
|
||
- Modify: `rust/crates/mnote-web/src/ssr/pages/document.rs`
|
||
- Modify: `rust/crates/mnote-web/src/routes/web_shell.rs`
|
||
- Modify: `rust/crates/mnote-web/src/ssr/styles.rs`
|
||
- Test: `scripts/task115-rust-web-document-shell-smoke.js`
|
||
|
||
- [x] **Step 1: 文档 shell 输出 topbar / breadcrumb / action slots**
|
||
|
||
文档页必须输出 `data-testid="wolai-topbar"`、breadcrumb 当前页标题、收藏/更多/AI action slot。
|
||
|
||
- [x] **Step 2: 文档 shell 继续嵌入 Page Aggregate snapshot**
|
||
|
||
保留现有:`id="__MNOTE_PAGE_AGGREGATE__"`、`data-page-aggregate-snapshot="mnote.page_aggregate.v1"`、`data-editor-host="leptos_tiptap_island"`。
|
||
|
||
- [x] **Step 3: 文档内容区不退回 BlockNote-first**
|
||
|
||
默认编辑 host 仍是 `leptos_tiptap_island`;BlockNote 只作为 fallback/debug。
|
||
|
||
### Task U-6:补视觉回归 smoke
|
||
|
||
**Files:**
|
||
|
||
- Create: `scripts/task119-rust-web-wolai-visual-regression-smoke.js`
|
||
- Modify: `package.json` 或既有 smoke 调度脚本,仅在仓库已有集中 smoke 入口时接入。
|
||
|
||
- [x] **Step 1: 写 Playwright 截图与布局指标检查**
|
||
|
||
检查项固定为:左栏存在且宽度稳定、topbar 存在、主内容不被侧栏遮挡、右下浮动按钮不遮挡正文、根页没有极简欢迎页。
|
||
|
||
- [x] **Step 2: 运行可视 smoke**
|
||
|
||
Run:
|
||
|
||
```bash
|
||
cd /mnt/Data1T/mnote
|
||
MNOTE_UI_BASE_URL=http://127.0.0.1:3000 node scripts/task119-rust-web-wolai-visual-regression-smoke.js
|
||
```
|
||
|
||
Expected after implementation: PASS,并输出截图路径到 `/mnt/Data1T/mnote/test-results/`。
|
||
|
||
### Task U-7:文档和 harness 收口
|
||
|
||
**Files:**
|
||
|
||
- Modify: `design/03-rust-web/done/3-8-rust-web-3000-wolai-ui-parity-checklist-v1.md`
|
||
- Modify: `harness-tasks.json` only when explicitly scheduling execution.
|
||
- Modify: `harness-progress.txt` only when execution starts.
|
||
|
||
- [x] **Step 1: 更新 checklist 状态**
|
||
|
||
每完成一个 U 阶段,只更新对应 checklist 项,不把 owner cutover 结果重复写成 UI parity 完成。
|
||
|
||
- [x] **Step 2: 如需进入 harness,追加 task-027 起的新任务**
|
||
|
||
建议映射:
|
||
|
||
```text
|
||
task-027 -> U-0 UI parity smoke
|
||
task-028 -> U-1 session/document shell baseline
|
||
task-029 -> U-2 workspace shell projection
|
||
task-030 -> U-3 Wolai workspace layout
|
||
task-031 -> U-4 root workspace entry
|
||
task-032 -> U-5 document UI parity
|
||
task-033 -> U-6 visual regression smoke
|
||
```
|
||
|
||
只有在明确开始执行时修改 `harness-tasks.json`,避免把分析计划误写成已排队任务。
|
||
|
||
## 5. 完成判定
|
||
|
||
全部完成时必须同时成立:
|
||
|
||
- `GET http://127.0.0.1:3000/` 返回 `x-mnote-web-owner: mnote-web`。
|
||
- 根页 body 标记为 `data-mnote-shell="workspace"`。
|
||
- 根页不再出现 `欢迎使用 MNOTE 知识管理平台` 这类极简欢迎页文案。
|
||
- 根页包含目标 UI 的结构区:账号区、快捷入口、星标置顶、我的页面、底部入口、topbar、右下浮动按钮。
|
||
- `/documents/:id` 不因当前登录态或开发态缺失而返回 `未登录` 502。
|
||
- 文档页仍消费 Page Aggregate snapshot,并保留 `leptos_tiptap_island` 默认编辑 host。
|
||
- `MNOTE_WEB_ENABLE_LEGACY_NEXT_COMPAT=0` 或等价 skip legacy 场景下,核心 workspace/document shell 仍可打开。
|
||
- `3100` 仅作为 legacy/reference/debug,不是主入口 owner。
|
||
- 新增 smoke 能在当前 3000 上证明产品壳,而不只是证明 owner header。
|
||
|
||
## 6. 自检
|
||
|
||
- Spec coverage:覆盖当前 3000 截图、3100 对照截图、目标 UI 参考、git/harness 现状、session 502、root shell 极简化、owner cutover 与 UI parity 的口径拆分。
|
||
- Placeholder scan:本计划不使用 `TBD`、`TODO`、`implement later` 作为执行步骤。
|
||
- Type consistency:统一使用 `WorkspaceShellProjection`、`WolaiWorkspaceLayout`、`data-mnote-shell="workspace"`、`mnote-web`、`next-app-router legacy compat`。
|