Implement sidebar tree view state persistence

This commit is contained in:
lix-2026
2026-05-27 16:32:05 +08:00
parent bd7470d03e
commit e43efacbce
15 changed files with 1875 additions and 17 deletions
@@ -2610,6 +2610,128 @@ mod tests {
assert_eq!(payload["result"]["sources"]["layoutDensity"], "workspace");
}
#[tokio::test]
async fn tree_view_state_api_is_user_scoped_and_tree_scope_isolated() {
let app = app();
let put_response = app
.clone()
.oneshot(
Request::builder()
.method("PUT")
.uri("/api/tree/view-state")
.header("content-type", "application/json")
.header("x-mnote-actor-id", "alice")
.header("x-mnote-actor-type", "user")
.body(Body::from(
serde_json::json!({
"userId": "mallory",
"workspaceId": "default",
"sourceKind": "local_folder",
"treeKind": "filetree",
"rootUri": "file:///mnt/Data1T/mnote",
"scope": "design",
"state": {
"schemaVersion": 1,
"treeKind": "filetree",
"rootUri": "file:///mnt/Data1T/mnote",
"scope": "design",
"expandedRelativePaths": ["design/05-editor-mainline"],
"expandedIds": [],
"selectedId": "local:folder:design/05-editor-mainline",
"focusedId": "",
"activeId": "",
"scrollTop": 12
}
})
.to_string(),
))
.expect("request"),
)
.await
.expect("response");
assert_eq!(put_response.status(), StatusCode::OK);
let put_body = to_bytes(put_response.into_body(), usize::MAX)
.await
.expect("body");
let put_payload: Value = serde_json::from_slice(&put_body).expect("json");
assert_eq!(put_payload["result"]["userId"], "alice");
assert_ne!(put_payload["result"]["workspaceId"], "default");
assert!(put_payload["result"]["scopeId"]
.as_str()
.expect("scope id")
.starts_with("filetree:"));
let get_response = app
.clone()
.oneshot(
Request::builder()
.method("GET")
.uri("/api/tree/view-state?workspaceId=local:_mnt_Data1T_mnote&sourceKind=local_folder&treeKind=filetree&rootUri=file:///mnt/Data1T/mnote&scope=design")
.header("x-mnote-actor-id", "alice")
.header("x-mnote-actor-type", "user")
.body(Body::empty())
.expect("request"),
)
.await
.expect("response");
assert_eq!(get_response.status(), StatusCode::OK);
let get_body = to_bytes(get_response.into_body(), usize::MAX)
.await
.expect("body");
let get_payload: Value = serde_json::from_slice(&get_body).expect("json");
assert_eq!(get_payload["result"]["source"], "sqlite");
assert_eq!(
get_payload["result"]["state"]["expandedRelativePaths"][0],
"design/05-editor-mainline"
);
let page_tree_response = app
.clone()
.oneshot(
Request::builder()
.method("GET")
.uri("/api/tree/view-state?workspaceId=local:_mnt_Data1T_mnote&sourceKind=local_folder&treeKind=pagetree&rootUri=file:///mnt/Data1T/mnote&scope=design")
.header("x-mnote-actor-id", "alice")
.header("x-mnote-actor-type", "user")
.body(Body::empty())
.expect("request"),
)
.await
.expect("response");
assert_eq!(page_tree_response.status(), StatusCode::OK);
let page_tree_body = to_bytes(page_tree_response.into_body(), usize::MAX)
.await
.expect("body");
let page_tree_payload: Value = serde_json::from_slice(&page_tree_body).expect("json");
assert_eq!(page_tree_payload["result"]["source"], "default");
assert_eq!(
page_tree_payload["result"]["state"]["expandedRelativePaths"]
.as_array()
.expect("array")
.len(),
0
);
let bob_response = app
.oneshot(
Request::builder()
.method("GET")
.uri("/api/tree/view-state?workspaceId=local:_mnt_Data1T_mnote&sourceKind=local_folder&treeKind=filetree&rootUri=file:///mnt/Data1T/mnote&scope=design")
.header("x-mnote-actor-id", "bob")
.header("x-mnote-actor-type", "user")
.body(Body::empty())
.expect("request"),
)
.await
.expect("response");
assert_eq!(bob_response.status(), StatusCode::OK);
let bob_body = to_bytes(bob_response.into_body(), usize::MAX)
.await
.expect("body");
let bob_payload: Value = serde_json::from_slice(&bob_body).expect("json");
assert_eq!(bob_payload["result"]["source"], "default");
}
#[tokio::test]
async fn document_shell_renders_local_markdown_with_same_sidebar_surfaces() {
let root =