chore: 收口 review 执行清单与 runtime 验证

- 补齐 design/10-review 执行清单、验收标准与相关设计治理记录

- 迁移已完成的 tree、mindmap、runtime fallback、AI kernel 等设计和缺陷条目

- 推进 Rust Web runtime、tree/sidebar、page aggregate、mindmap 与 OnlyOffice 路由侧验证支撑

- 增加 task177-task180 smoke/audit 脚本及前端相关测试覆盖
This commit is contained in:
lix-2026
2026-05-14 05:52:08 +08:00
parent b4a452a8b7
commit 96e03645f7
69 changed files with 4780 additions and 979 deletions
+96 -18
View File
@@ -2437,10 +2437,9 @@ pub(crate) async fn load_workspace_shell_projection(
query: None,
max_results: None,
};
let dataset = load_projection_snapshot(config, context, &spec)
.await
.map(|snapshot| snapshot.dataset)
.unwrap_or_else(|_| {
let dataset = match load_projection_snapshot(config, context, &spec).await {
Ok(snapshot) => snapshot.dataset,
Err(_) if config.allow_dev_fixtures => {
let documents = active_document_id
.map(|document_id| {
json!([{
@@ -2457,9 +2456,19 @@ pub(crate) async fn load_workspace_shell_projection(
"active_workspace_id": workspace_id,
"active_page_id": active_document_id,
"workspaces": [{ "id": workspace_id, "name": default_workspace_name }],
"documents": documents
"documents": documents,
"dev_fixture": true
})
});
}
Err(_) => json!({
"active_workspace_id": workspace_id,
"active_page_id": active_document_id,
"workspaces": [{ "id": workspace_id, "name": default_workspace_name }],
"documents": [],
"degraded": true,
"degraded_reason": "projection_unavailable"
}),
};
build_workspace_shell_projection(
&dataset,
@@ -2489,7 +2498,7 @@ pub(crate) async fn load_sidebar_tree_html(
max_results: None,
};
let result = match load_projection_snapshot(config, context, &spec).await {
Ok(snapshot) => Some(snapshot.projection),
Ok(snapshot) => Some((snapshot.projection, false)),
Err(_) if config.allow_dev_fixtures => {
// Dev 模式降级:如果调用方已经有 active 页面,优先保留这条真实选择链。
let documents = active_document_id
@@ -2518,17 +2527,20 @@ pub(crate) async fn load_sidebar_tree_html(
"mindmap_docs": [],
"mindmap_asset_children": {}
});
execute_kernel_query(context, workspace_id, projection_query(&spec), dev_dataset).ok()
execute_kernel_query(context, workspace_id, projection_query(&spec), dev_dataset)
.ok()
.map(|projection| (projection, true))
}
Err(_) => None,
};
result.map(|projection| {
result.map(|(projection, dev_fixture)| {
let rows = collect_page_tree_render_rows(&projection);
render_initial_page_tree_html(&PageTreeInitialRenderInput {
let html = render_initial_page_tree_html(&PageTreeInitialRenderInput {
rows,
active_node_id: active_document_id.map(ToOwned::to_owned),
focused_node_id: None,
})
});
mark_dev_fixture_html(html, dev_fixture, "sidebar-tree")
})
}
@@ -2550,7 +2562,7 @@ pub(crate) async fn load_file_tree_html(
max_results: None,
};
let result = match load_projection_snapshot(config, context, &spec).await {
Ok(snapshot) => Some(snapshot.projection),
Ok(snapshot) => Some((snapshot.projection, false)),
Err(_) if config.allow_dev_fixtures => {
let documents = active_document_id
.map(|document_id| {
@@ -2577,16 +2589,28 @@ pub(crate) async fn load_file_tree_html(
"mindmap_docs": [],
"mindmap_asset_children": {}
});
execute_kernel_query(context, workspace_id, projection_query(&spec), dev_dataset).ok()
execute_kernel_query(context, workspace_id, projection_query(&spec), dev_dataset)
.ok()
.map(|projection| (projection, true))
}
Err(_) => None,
};
result.map(|projection| {
result.map(|(projection, dev_fixture)| {
let rows = collect_filetree_render_rows(&projection, active_document_id);
render_initial_filetree_html(&FileTreeInitialRenderInput { rows })
let html = render_initial_filetree_html(&FileTreeInitialRenderInput { rows });
mark_dev_fixture_html(html, dev_fixture, "file-tree")
})
}
fn mark_dev_fixture_html(html: String, dev_fixture: bool, kind: &'static str) -> String {
if !dev_fixture {
return html;
}
format!(
r#"<span hidden data-mnote-dev-fixture="true" data-mnote-dev-fixture-kind="{kind}"></span>{html}"#
)
}
pub(crate) fn render_local_sidebar_tree_html(
root_uri: &str,
active_document_id: Option<&str>,
@@ -2614,8 +2638,9 @@ pub(crate) fn render_local_file_tree_html(
#[cfg(test)]
mod tests {
use crate::app::{build_app, AppConfig, AppState};
use crate::context::RequestContext;
use axum::body::{to_bytes, Body};
use axum::http::{header, Request, StatusCode};
use axum::http::{header, HeaderMap, Method, Request, StatusCode, Uri};
use serde_json::Value;
use tower::util::ServiceExt;
@@ -2701,6 +2726,9 @@ mod tests {
.expect("body");
let html = String::from_utf8(body.to_vec()).expect("html");
assert!(html.contains("data-testid=\"wolai-sidebar\""));
assert!(html.contains("data-mnote-dev-fixture-kind=\"workspace-shell\""));
assert!(html.contains("data-mnote-dev-fixture-kind=\"sidebar-tree\""));
assert!(html.contains("data-mnote-dev-fixture-kind=\"file-tree\""));
assert!(html.contains("data-testid=\"wolai-topbar\""));
assert!(html.contains("data-testid=\"wolai-floating-ai\""));
assert!(html.contains("星标置顶"));
@@ -2755,7 +2783,7 @@ mod tests {
.headers()
.get("x-mnote-page-aggregate-owner")
.and_then(|value| value.to_str().ok()),
Some("rust-kernel")
Some("compat-join")
);
assert_eq!(
response
@@ -2770,7 +2798,7 @@ mod tests {
let payload: Value = serde_json::from_slice(&body).expect("json");
assert_eq!(payload["owner"], "mnote-web");
assert_eq!(payload["schema"], "mnote.page_aggregate.v1");
assert_eq!(payload["result"]["source"], "KernelProjection");
assert_eq!(payload["result"]["source"], "CompatMetaContentJoin");
assert_eq!(payload["result"]["projectionVersion"], 1);
assert_eq!(payload["result"]["identity"]["documentId"], "doc_1");
assert_eq!(payload["result"]["body"]["revision"], 7);
@@ -2893,6 +2921,56 @@ mod tests {
));
}
#[tokio::test]
async fn sidebar_and_filetree_do_not_return_dev_fixtures_by_default() {
let config = AppConfig {
service_name: "mnote-web".into(),
service_version: "0.1.0".into(),
bind_addr: "127.0.0.1:0".into(),
public_bind_addr: "127.0.0.1:3000".into(),
legacy_next_base_url: Some("http://127.0.0.1:3100".into()),
enable_legacy_next_compat: true,
enable_debug_shell_routes: false,
hermes_base_path: "/api/hermes".into(),
compat_next_base_path: "/api/compat/next".into(),
convex_url: None,
convex_admin_key: None,
allow_dev_fixtures: false,
query_fixtures_json: None,
mutation_fixtures_json: None,
dev_user_id: "dev-user".into(),
dev_user_name: "开发用户".into(),
dev_user_email: "dev@mnote.local".into(),
};
let headers = HeaderMap::new();
let context = RequestContext::from_http_parts(
&Method::GET,
&"/documents/doc_1?workspaceId=ws_demo"
.parse::<Uri>()
.expect("uri"),
&headers,
);
let sidebar_html =
super::load_sidebar_tree_html(&config, &context, "ws_demo", Some("doc_1")).await;
let filetree_html =
super::load_file_tree_html(&config, &context, "ws_demo", Some("doc_1")).await;
let workspace_projection = super::load_workspace_shell_projection(
&config,
&context,
"ws_demo",
Some("doc_1"),
"个人空间",
)
.await;
assert!(sidebar_html.is_none());
assert!(filetree_html.is_none());
assert!(workspace_projection.degraded);
assert!(workspace_projection.my_page_items.is_empty());
assert!(!workspace_projection.dev_fixture);
}
#[tokio::test]
async fn document_shell_renders_local_markdown_attachment_name_in_html() {
let root = std::env::temp_dir().join(format!(