chore: land tree view-state, vault, Pi module split, and repo hygiene

Persist PageTree expand state via control-plane view-state and align
chevron/DOM with restored expansion; keep Sidex-style shallow page-tree
scan and drop the unused recursive scanner that only added cargo noise.

Add password vault workbench routes/runtime/skill/CLI, split page_ai_pi
into a module package, and retire Hermes/ACP/OpenHub recycle + root
harness evidence from the index while gitignoring recycle and local
diag dumps.

Archive superseded design/bugs docs under old/, point architecture at
ARCHITECTURE.md, and refresh smokes for Pi S1–S7, vault, and editor
regressions so the working tree can stay clean.
This commit is contained in:
Agent Board
2026-07-21 05:13:05 +08:00
parent 6f9c7d3b58
commit b798f628ee
264 changed files with 17480 additions and 17314 deletions
+57 -10
View File
@@ -11,8 +11,9 @@ use crate::routes::gateway::default_workspace_name_for_context;
use crate::routes::local_folder_source::{
ensure_local_workspace_read_access_with_state, is_local_access_policy_admin_context,
load_local_folder_file_tree_children_snapshot_with_reveal,
load_local_folder_file_tree_snapshot_with_reveal, load_local_folder_page_tree_scope_snapshot,
load_local_folder_page_tree_snapshot, resolve_local_markdown_page_aggregate,
load_local_folder_file_tree_snapshot_with_reveal,
load_local_folder_page_tree_scope_snapshot_with_reveal,
load_local_folder_page_tree_snapshot_with_reveal, resolve_local_markdown_page_aggregate,
};
use crate::routes::query_support::execute_runtime_query_against_data;
use crate::routes::snapshot_support::{
@@ -21,12 +22,12 @@ use crate::routes::snapshot_support::{
use crate::routes::tree::{collect_filetree_render_rows, collect_page_tree_render_rows};
use crate::ssr::pages::document::DocumentPage;
use crate::tree_shell::filetree_renderer::{
render_initial_filetree_html, FileTreeInitialRenderInput,
render_filetree_pending_shell_html, render_initial_filetree_html, FileTreeInitialRenderInput,
};
use crate::tree_shell::page_renderer::{render_initial_page_tree_html, PageTreeInitialRenderInput};
use crate::workspace_shell::{
apply_active_page, build_workspace_shell_projection, render_workspace_shell_sidebar_html,
WorkspaceShellProjection,
apply_active_page, build_workspace_shell_projection, render_page_breadcrumb_html,
render_workspace_shell_sidebar_html, WorkspaceShellProjection,
};
use axum::body::Body;
use axum::extract::{Extension, Path, Query, State};
@@ -261,6 +262,7 @@ pub async fn document_page_shell(
file_tree_scope,
);
let workspace_name = workspace_projection.workspace_name.clone();
let breadcrumb_html = render_page_breadcrumb_html(&workspace_projection, Some(&document_id));
let page_subtree_json =
serde_json::to_string(&aggregate.tree.page_subtree).unwrap_or_else(|_| "null".to_string());
let page_options_json = serde_json::to_string(&aggregate.layout.page_options)
@@ -300,6 +302,10 @@ pub async fn document_page_shell(
);
let pi_lab_loader_script =
render_page_ai_pi_lab_loader_script(state.config().enable_page_ai_pi_lab);
let vault_nav_href = crate::routes::gateway::vault_nav_href_for_context(
primary_source_kind,
primary_root_uri,
);
let body_content = crate::ssr::render_view(leptos::view! {
<DocumentPage
title={title.to_string()}
@@ -308,6 +314,7 @@ pub async fn document_page_shell(
sidebar_tree_html={sidebar_tree_html}
workspace_name={workspace_name}
workspace_sidebar_html={workspace_sidebar_html}
breadcrumb_html={breadcrumb_html}
page_subtree_json={page_subtree_json}
page_options_json={page_options_json}
secondary_title={secondary_aggregate.as_ref().map(|aggregate| aggregate.head.title.clone()).unwrap_or_default()}
@@ -318,6 +325,7 @@ pub async fn document_page_shell(
primary_hide_title_header={aggregate.layout.page_options.hide_title_header}
secondary_hide_title_header={secondary_aggregate.as_ref().map(|aggregate| aggregate.layout.page_options.hide_title_header).unwrap_or(secondary_source_kind == Some("local_folder"))}
show_admin_access_policy={is_local_access_policy_admin_context(&context)}
vault_nav_href={vault_nav_href}
/>
});
let hermes_settings_config_script = render_hermes_settings_config_script();
@@ -766,6 +774,12 @@ pub(crate) fn render_document_title_controller_script() -> &'static str {
let saving = false;
const saveTitle = async () => {
if (input.readOnly || input.getAttribute('data-document-user-readonly-mode') === 'true') {
input.value = readLastSavedTitle();
autosize(input);
setStatus(input, 'saved');
return;
}
const title = input.value.trim() || '无标题';
const currentTarget = resolveTitleTarget(input);
autosize(input);
@@ -820,6 +834,12 @@ pub(crate) fn render_document_title_controller_script() -> &'static str {
};
input.addEventListener('input', () => {
if (input.readOnly || input.getAttribute('data-document-user-readonly-mode') === 'true') {
input.value = readLastSavedTitle();
autosize(input);
setStatus(input, 'saved');
return;
}
autosize(input);
setStatus(input, (input.value.trim() || '无标题') === readLastSavedTitle() ? 'saved' : 'dirty');
});
@@ -3103,6 +3123,20 @@ pub async fn document_editor_adapter_runtime_asset() -> Response {
.unwrap_or_else(|_| Response::new(Body::empty()))
}
pub async fn vault_workbench_runtime_asset() -> Response {
const JS: &str = include_str!("../../browser/vault-workbench-runtime.js");
Response::builder()
.status(StatusCode::OK)
.header(
header::CONTENT_TYPE,
"application/javascript; charset=utf-8",
)
.header(header::CACHE_CONTROL, runtime_asset_cache_control())
.header(HEADER_MNOTE_WEB_OWNER, "mnote-web")
.body(browser_runtime_js_body(JS))
.unwrap_or_else(|_| Response::new(Body::empty()))
}
pub async fn leptos_tiptap_manifest() -> Response {
let manifest = json!({
"entryAssetPath": "mnote-leptos-tiptap-spike-island.js",
@@ -3603,9 +3637,13 @@ pub(crate) fn render_local_sidebar_tree_html_scoped(
.map(str::trim)
.filter(|value| !value.is_empty())
{
load_local_folder_page_tree_scope_snapshot(root_uri, scope)?
load_local_folder_page_tree_scope_snapshot_with_reveal(
root_uri,
scope,
active_document_id,
)?
} else {
load_local_folder_page_tree_snapshot(root_uri)?
load_local_folder_page_tree_snapshot_with_reveal(root_uri, active_document_id)?
};
Ok(render_local_sidebar_tree_html_from_snapshot(
&snapshot,
@@ -3633,6 +3671,12 @@ pub(crate) fn render_local_file_tree_html(
render_local_file_tree_html_scoped(root_uri, active_document_id, active_row_id, None)
}
/// Fast path for home SSR: keep FileTree shell structure without scanning the folder.
/// Browser hydrates rows after first paint when `data-filetree-ssr="pending"`.
pub(crate) fn render_local_file_tree_pending_shell_html() -> String {
render_filetree_pending_shell_html()
}
pub(crate) fn render_local_file_tree_html_scoped(
root_uri: &str,
active_document_id: Option<&str>,
@@ -5320,8 +5364,10 @@ mod tests {
));
assert!(session_runtime.contains("new EventSource(url.toString())"));
assert!(session_runtime.contains("localFolderEventRegistry"));
assert!(session_runtime
.contains("if (!response.ok) {\n if (session.sourceKind === 'local_folder')"));
assert!(session_runtime.contains("if (!response.ok) {"));
assert!(session_runtime.contains(
"if (session.sourceKind === 'local_folder' && (response.status === 404 || errorCode === 'local_markdown_not_found'))"
));
assert!(session_runtime.contains("shouldSuppressLocalFolderSelfChange"));
assert!(session_runtime.contains("kind.includes('Modify(Name')"));
assert!(session_runtime.contains("targetSession.views.size === 0"));
@@ -5707,7 +5753,8 @@ mod tests {
assert!(runtime.contains("legacyInlineContentToTiptap"));
assert!(runtime.contains("legacyStylesToTiptapMarks"));
assert!(runtime.contains("legacyMarkArrayToTiptapMarks"));
assert!(runtime.contains("firstNonEmptyText(block?.props?.sourcePath"));
assert!(runtime.contains("const sourcePath = firstNonEmptyText("));
assert!(runtime.contains("block?.props?.sourcePath"));
assert!(runtime.contains("marks.push({ type: 'bold' })"));
assert!(runtime.contains("marks.push({ type: 'italic' })"));
assert!(runtime.contains("marks.push({ type: 'underline' })"));