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
@@ -28,7 +28,10 @@ import {
(() => {
const PANES_BOOTSTRAP_ID = '__MNOTE_DOCUMENT_PANES_BOOTSTRAP__';
const ROOT_SELECTOR = '[data-testid="mnote-leptos-tiptap-island-editor-root"]';
// 历史 spike 前缀仍是 island runtime 的默认派发;稳定前缀 `mnote:tiptap-island`
// 由 island 双发(架构收口 21 Phase 3),host 优先监听 spike 事件保持兼容。
const EVENT_PREFIX = 'mnote:leptos-tiptap-spike';
const STABLE_EVENT_PREFIX = 'mnote:tiptap-island';
const CHANGE_EVENT = `${EVENT_PREFIX}:change`;
const SAVE_EVENT = `${EVENT_PREFIX}:save-request`;
const READY_EVENT = `${EVENT_PREFIX}:ready`;
@@ -50,6 +53,72 @@ import {
return url.toString();
};
const refreshBreadcrumb = (documentId, title, workspaceId) => {
const host = document.querySelector('[data-breadcrumb-pages="true"]');
if (!(host instanceof HTMLElement)) return;
const rows = Array.from(document.querySelectorAll(
'.wolai-page-row[data-node-id], .tree-row[data-shell-mode="page"][data-node-id]',
));
const byId = new Map();
rows.forEach((row) => {
const id = String(row.getAttribute('data-node-id') || row.getAttribute('data-document-id') || '').trim();
if (!id || byId.has(id)) return;
const titleNode = row.querySelector('.wolai-row-title, .tree-link-title');
byId.set(id, {
id,
title: String(titleNode?.textContent || '').trim() || '无标题',
parentId: String(row.getAttribute('data-parent-id') || '').trim(),
href: row instanceof HTMLAnchorElement ? row.href : '',
});
});
const chain = [];
const seen = new Set();
let cursor = String(documentId || '').trim();
while (cursor && !seen.has(cursor) && chain.length < 64) {
const item = byId.get(cursor);
if (!item) break;
seen.add(cursor);
chain.push(item);
cursor = item.parentId;
}
chain.reverse();
if (!chain.length) return;
const query = new URLSearchParams(window.location.search);
if (workspaceId) query.set('workspaceId', workspaceId);
const children = [];
chain.forEach((item, index) => {
if (index > 0) {
const separator = document.createElement('span');
separator.className = 'wolai-breadcrumb-separator';
separator.setAttribute('aria-hidden', 'true');
separator.textContent = '';
children.push(separator);
}
if (index === chain.length - 1) {
const current = document.createElement('span');
current.className = 'wolai-breadcrumb-current';
current.dataset.breadcrumbDocumentId = item.id;
const icon = document.createElement('span');
icon.className = 'material-symbols-outlined wolai-home-icon';
icon.dataset.icon = 'home';
icon.setAttribute('aria-hidden', 'true');
const text = document.createElement('span');
text.dataset.pageTitleCurrent = 'true';
text.textContent = title || item.title;
current.append(icon, text);
children.push(current);
} else {
const link = document.createElement('a');
link.className = 'wolai-breadcrumb-link';
link.dataset.breadcrumbDocumentId = item.id;
link.href = item.href || `/documents/${encodeURIComponent(item.id)}?${query.toString()}`;
link.textContent = item.title;
children.push(link);
}
});
host.replaceChildren(...children);
};
const parseJsonScript = (id) => {
const node = document.getElementById(id);
if (!node) return null;
@@ -406,6 +475,33 @@ import {
const ensureLocalFolderEventChannel = (...args) => documentSessions.ensureLocalFolderEventChannel(...args);
const sessionMatchesDocumentWorkspace = (...args) => documentSessions.sessionMatchesDocumentWorkspace(...args);
const getOrCreateDocumentSession = (...args) => documentSessions.getOrCreateDocumentSession(...args);
const setDocumentSessionUserReadOnlyMode = (...args) => documentSessions.setDocumentSessionUserReadOnlyMode(...args);
const syncDocumentEditModeChrome = (session) => {
if (!session) return;
sessionViews(session).forEach((view) => {
const pane = view.runtimeDescriptor.root.closest('[data-document-pane="true"]');
const shell = view.runtimeDescriptor.root.closest('.document-shell');
if (shell instanceof HTMLElement) {
shell.setAttribute('data-document-user-readonly-mode', String(session.userReadOnlyMode !== false));
shell.setAttribute('data-document-effective-readonly', String(session.readOnly === true));
}
const titleInput = pane?.querySelector('[data-page-title-input="true"]');
if (titleInput instanceof HTMLTextAreaElement) {
titleInput.readOnly = session.readOnly === true;
titleInput.setAttribute('data-document-user-readonly-mode', String(session.userReadOnlyMode !== false));
}
const toggle = pane?.querySelector('[data-document-edit-mode-toggle]');
if (toggle instanceof HTMLButtonElement) {
const editing = session.readOnly !== true;
toggle.textContent = editing ? '退出编辑' : '开始编辑';
toggle.title = editing ? '切回只读浏览' : '进入编辑模式';
toggle.setAttribute('aria-pressed', String(editing));
toggle.setAttribute('data-document-editing', String(editing));
toggle.disabled = session.permissionReadOnly === true && session.userReadOnlyMode === false;
}
});
};
const updatePaneChrome = (runtimeDescriptor) => {
const pane = runtimeDescriptor.root.closest('[data-document-pane="true"]');
@@ -467,8 +563,7 @@ import {
document.body.dataset.mnoteShell = 'document';
delete document.body.dataset.mindmapId;
document.title = title;
const topbarCurrent = document.querySelector('.wolai-breadcrumb-current [data-page-title-current]');
if (topbarCurrent instanceof HTMLElement) topbarCurrent.textContent = title;
refreshBreadcrumb(documentId, title, workspaceId);
window.dispatchEvent(new CustomEvent('mnote:primary-document-activated', {
detail: {
documentId,
@@ -566,13 +661,20 @@ import {
titleInput.setAttribute('data-title-endpoint', '/api/documents/title');
titleInput.rows = 1;
titleHeading.appendChild(titleInput);
const modeToggle = document.createElement('button');
modeToggle.type = 'button';
modeToggle.className = 'document-edit-mode-toggle';
modeToggle.setAttribute('data-document-edit-mode-toggle', 'true');
modeToggle.setAttribute('aria-pressed', 'false');
modeToggle.setAttribute('data-document-editing', 'false');
modeToggle.textContent = '开始编辑';
const meta = document.createElement('div');
meta.className = 'document-shell-meta';
meta.setAttribute('aria-label', '页面元信息');
const currentTitle = document.createElement('span');
currentTitle.setAttribute('data-page-title-current', 'true');
meta.appendChild(currentTitle);
header.append(titleHeading, meta);
header.append(titleHeading, modeToggle, meta);
const aggregateMarker = document.createElement('section');
aggregateMarker.setAttribute('data-page-aggregate-snapshot', 'mnote.page_aggregate.v1');
@@ -636,6 +738,7 @@ import {
}
const session = getOrCreateDocumentSession(runtimeDescriptor);
const view = createEditorViewBinding(session, runtime, runtimeDescriptor);
syncDocumentEditModeChrome(session);
const mountOptions = {
documentId: session.documentId,
workspaceId: session.workspaceId,
@@ -657,6 +760,7 @@ import {
setStatus(runtimeDescriptor, 'mounting-editor');
enhanceEditorAttachmentLinksSoon();
paneViewRegistry.set(paneRole, view);
syncDocumentEditModeChrome(session);
return view;
} catch (error) {
unmountEditorViewBinding(view);
@@ -739,6 +843,24 @@ import {
anchor.removeAttribute('href');
}, true);
document.addEventListener('click', (event) => {
const editToggle = event.target instanceof Element
? event.target.closest('[data-document-edit-mode-toggle]')
: null;
if (editToggle instanceof HTMLButtonElement) {
event.preventDefault();
const pane = editToggle.closest('[data-document-pane="true"]');
const documentId = pane?.getAttribute('data-pane-document-id') || document.body?.dataset.documentId || '';
const session = Array.from(documentSessionRegistry.values())
.find((item) => item && item.documentId === documentId);
if (!session) return;
setDocumentSessionUserReadOnlyMode(
session,
!(session.userReadOnlyMode !== false),
'mnote-web-document-edit-mode-toggle',
);
syncDocumentEditModeChrome(session);
return;
}
const target = event.target;
const anchor = target instanceof Element ? target.closest('a.mnote-page-block-link') : null;
if (!(anchor instanceof HTMLAnchorElement)) return;
@@ -1022,6 +1144,7 @@ import {
const runtime = await loadRuntime();
const session = getOrCreateDocumentSession(runtimeDescriptor);
const view = createEditorViewBinding(session, runtime, runtimeDescriptor);
syncDocumentEditModeChrome(session);
const mountOptions = {
documentId: session.documentId,
@@ -1045,6 +1168,7 @@ import {
setStatus(runtimeDescriptor, 'mounting-editor');
enhanceEditorAttachmentLinksSoon();
paneViewRegistry.set(runtimeDescriptor.paneRole, view);
syncDocumentEditModeChrome(session);
} catch (error) {
unmountEditorViewBinding(view);
throw error;