feat: add evidence search and stabilize pdf previews

- add document evidence parsing/search/open routes, Hermes tool wiring, local index settings/status, and the document-evidence skill plus design notes
- fix PDF resource tabs by rendering PDFs inline with pdf.js canvases instead of iframe preview pages, release PDF documents on close, and document the fourth-PDF stall bug
- keep PDF preview at 2x rendering while removing the previous lazy-load/placeholder direction, and make dev:hot bind loopback defaults externally reachable

Verification:
- node --check rust/crates/mnote-web/browser/document-resource-tab-runtime.js
- node scripts/task-dev-hot-plan-test.js
- cargo test -p mnote-web --manifest-path rust/Cargo.toml pdf_preview_page_does_not_render_visible_toolbar
- cargo test -p mnote-web --manifest-path rust/Cargo.toml document_shell_returns_page_aggregate_snapshot
- cargo build -p mnote-web --manifest-path rust/Cargo.toml
- browser smoke: sequentially opened the four tea_seed_oil_cosmetic PDFs; fourth PDF rendered 15/15 canvases, iframeCount=0, browser errors=0
This commit is contained in:
lix-2026
2026-06-04 18:51:16 +08:00
parent 9f4b5c4d48
commit 627213dc01
51 changed files with 15960 additions and 1844 deletions
@@ -43,7 +43,11 @@ export const createSidebarTreeLiveApplyRuntime = (dependencies = {}) => {
var fileTreeLazyCacheRootUri = '';
var fileTreeExpansionStorageRootUri = '';
var fileTreeExpansionRestoreTimer = 0;
var fileTreeVisibleHydrateTimer = 0;
var fileTreeVisibleHydrateQueued = false;
var fileTreeCommandBatchRefreshParents = new Map();
var FILETREE_VISIBLE_EXPANDED_HYDRATE_MAX = 20;
var FILETREE_VISIBLE_EXPANDED_HYDRATE_BATCH = 2;
var FILETREE_EXPANSION_STORAGE_KEY = 'mnote.localFileTree.expandedRelativePaths.v1';
var SIDEBAR_TREE_VIEW_STATE_KEY = 'mnote.sidebarTreeViewState.v1:{userId}:{workspaceId}:{sourceKind}:{treeKind}:{rootUriHash}:{scopeHash}';
var SIDEBAR_TREE_VIEW_STATE_API_KEY = 'sidebarTreeViewState.v1';
@@ -337,6 +341,24 @@ export const createSidebarTreeLiveApplyRuntime = (dependencies = {}) => {
parents.add(normalized);
}
function watchBatchPathIsMarkdown(item) {
var relativePath = String(item && (item.relativePath || item.relative_path) || '').trim().toLowerCase();
return relativePath.endsWith('.md') || relativePath.endsWith('.markdown');
}
function watchBatchEventKind(item) {
return String(item && (item.kind || item.eventKind || item.event_kind) || '').trim();
}
function watchBatchNeedsPageTreeRefresh(item) {
if (!watchBatchPathIsMarkdown(item)) return false;
var kind = watchBatchEventKind(item);
return kind.indexOf('Create') >= 0
|| kind.indexOf('Remove') >= 0
|| kind.indexOf('Modify(Name') >= 0
|| kind.indexOf('Rename') >= 0;
}
function addAffectedParentsFromCommandResult(parents, result) {
var affectedParents = Array.isArray(result && result.affectedParents)
? result.affectedParents
@@ -452,10 +474,7 @@ export const createSidebarTreeLiveApplyRuntime = (dependencies = {}) => {
affectedParents.forEach(function(parent) {
addCommandRefreshParent(parents, parent && (parent.relativePath || parent.relative_path));
});
var needsSidebarRefresh = changedPaths.some(function(item) {
var relativePath = String(item && (item.relativePath || item.relative_path) || '').trim().toLowerCase();
return relativePath.endsWith('.md') || relativePath.endsWith('.markdown');
});
var needsSidebarRefresh = changedPaths.some(watchBatchNeedsPageTreeRefresh);
document.documentElement.setAttribute('data-mnote-local-folder-watch-batch-applied', String(batch.revision || 'true'));
void Promise.all(Array.from(parents).map(function(parentRelativePath) {
return refreshFileTreeParent(parentRelativePath).catch(function(error) {
@@ -465,6 +484,8 @@ export const createSidebarTreeLiveApplyRuntime = (dependencies = {}) => {
})).then(function() {
if (needsSidebarRefresh) {
void refreshLocalFolderSidebarSnapshot();
} else {
document.documentElement.setAttribute('data-mnote-local-folder-watch-sidebar-refresh-skipped', 'content-only');
}
markLocalFolderWatchApplied('watch_batch');
});
@@ -587,6 +608,11 @@ export const createSidebarTreeLiveApplyRuntime = (dependencies = {}) => {
return Boolean(resolved && Array.isArray(resolved.items));
}
function hasProjectionItemArray(projection) {
var resolved = readProjection(projection);
return Boolean(resolved && Array.isArray(resolved.items));
}
function nodeIdOf(item) {
var runtimeFn = fileTreeRuntimeFunction('nodeIdOf');
if (runtimeFn) return runtimeFn(item);
@@ -1238,6 +1264,81 @@ export const createSidebarTreeLiveApplyRuntime = (dependencies = {}) => {
}, 0);
}
function scheduleFileTreeIdleTask(callback, timeout) {
if (typeof window.requestIdleCallback === 'function') {
return window.requestIdleCallback(callback, { timeout: timeout || 800 });
}
if (typeof window.requestAnimationFrame === 'function') {
return window.requestAnimationFrame(function() {
window.setTimeout(callback, 0);
});
}
return window.setTimeout(callback, 0);
}
function isFileTreeRowVisibleForHydrate(row) {
if (!(row instanceof HTMLElement)) return false;
if (typeof row.getBoundingClientRect !== 'function') return true;
var rect = row.getBoundingClientRect();
var viewportHeight = window.innerHeight || document.documentElement.clientHeight || 0;
if (!viewportHeight) return true;
return rect.bottom >= -64 && rect.top <= viewportHeight + 256;
}
function fileTreeRowNeedsVisibleHydrate(row) {
if (!(row instanceof HTMLElement)) return false;
if (currentSourceKind() !== 'local_folder') return false;
if (!currentRootUri()) return false;
if (row.getAttribute('data-shell-mode') !== 'filetree') return false;
if (String(row.getAttribute('data-row-kind') || '').trim() !== 'folder') return false;
if (row.getAttribute('aria-expanded') === 'true') {
var relativePath = localFileTreeRelativePathFromRow(row);
if (!relativePath || !fileTreeExpandedRelativePaths.has(relativePath)) return false;
if (row.getAttribute('data-filetree-children-loaded') !== 'true' && row.getAttribute('data-filetree-children-loading') !== 'true') {
return isFileTreeRowVisibleForHydrate(row);
}
}
return false;
}
function collectVisibleExpandedFileTreeRowsForHydrate() {
var rows = [];
document.querySelectorAll('#sidebar-file-tree-root .tree-row[data-shell-mode="filetree"]').forEach(function(row) {
if (rows.length >= FILETREE_VISIBLE_EXPANDED_HYDRATE_MAX) return;
if (fileTreeRowNeedsVisibleHydrate(row)) rows.push(row);
});
return rows;
}
function scheduleHydrateVisibleExpandedFileTreeRows(reason) {
if (fileTreeVisibleHydrateQueued) return;
if (currentSourceKind() !== 'local_folder') return;
fileTreeVisibleHydrateQueued = true;
fileTreeVisibleHydrateTimer = scheduleFileTreeIdleTask(function() {
fileTreeVisibleHydrateTimer = 0;
fileTreeVisibleHydrateQueued = false;
hydrateVisibleExpandedFileTreeRows(reason || 'idle');
}, 800);
}
function hydrateVisibleExpandedFileTreeRows(reason) {
if (currentSourceKind() !== 'local_folder') return false;
var rows = collectVisibleExpandedFileTreeRowsForHydrate();
if (!rows.length) return false;
var batch = rows.slice(0, FILETREE_VISIBLE_EXPANDED_HYDRATE_BATCH);
batch.forEach(function(row) {
var button = row.querySelector('[data-rust-action="toggle"]');
void loadFileTreeChildren(row, button, { persist: false, idleHydrate: true }).then(function(loaded) {
if (loaded) {
document.documentElement.setAttribute('data-mnote-filetree-idle-hydrate-applied', String(reason || 'idle'));
scheduleHydrateVisibleExpandedFileTreeRows('cascade');
}
});
});
if (rows.length > batch.length) scheduleHydrateVisibleExpandedFileTreeRows('batch');
return true;
}
function rememberFileTreeExpansionState() {
var changed = false;
document.querySelectorAll('#sidebar-file-tree-root .tree-row[data-shell-mode="filetree"]').forEach(function(row) {
@@ -1300,6 +1401,7 @@ export const createSidebarTreeLiveApplyRuntime = (dependencies = {}) => {
tree.replaceChildren(template.content.cloneNode(true));
reprojectFileTreeSelectionState();
scheduleRestorePersistedFileTreeExpansionState();
scheduleHydrateVisibleExpandedFileTreeRows('render');
return true;
}
@@ -1329,28 +1431,29 @@ export const createSidebarTreeLiveApplyRuntime = (dependencies = {}) => {
setTreeRowExpanded(row, button, wasExpanded);
syncSidebarFileTreeSelection();
reprojectFileTreeSelectionState();
scheduleHydrateVisibleExpandedFileTreeRows('patch');
return true;
}
function renderSidebarSnapshot(payload) {
var renderedPage = false;
if (hasProjectionItems(payload)) {
if (hasProjectionItemArray(payload)) {
renderedPage = true;
void renderPageProjection(payload);
}
var fileProjection = readDatasetProjection(payload, 'kernel_file_tree_projection', 'kernelFileTreeProjection');
var renderedFile = fileProjection && hasProjectionItems(fileProjection) ? renderFileProjection(fileProjection) : false;
var renderedFile = fileProjection && hasProjectionItemArray(fileProjection) ? renderFileProjection(fileProjection) : false;
return renderedPage || renderedFile;
}
function renderLiveSidebarSnapshot(payload) {
var renderedPage = false;
if (hasProjectionItems(payload)) {
if (hasProjectionItemArray(payload)) {
renderedPage = true;
void renderPageProjection(payload);
}
var fileProjection = readDatasetProjection(payload, 'kernel_file_tree_projection', 'kernelFileTreeProjection');
var hasFileProjection = fileProjection && hasProjectionItems(fileProjection);
var hasFileProjection = fileProjection && hasProjectionItemArray(fileProjection);
if (currentFileTreeScope()) {
if (!hasFileProjection) return renderedPage;
var projectionParent = projectionParentRelativePath(fileProjection);
@@ -1473,9 +1576,8 @@ export const createSidebarTreeLiveApplyRuntime = (dependencies = {}) => {
var sidebarPayload = sidebarResponse && sidebarResponse.ok ? await sidebarResponse.json().catch(function() { return null; }) : null;
var resolvedSidebarPayload = sidebarPayload ? (sidebarPayload.result || sidebarPayload) : null;
var renderedPage = false;
if (resolvedSidebarPayload && hasProjectionItems(resolvedSidebarPayload)) {
renderedPage = true;
void renderPageProjection(resolvedSidebarPayload);
if (resolvedSidebarPayload && hasProjectionItemArray(resolvedSidebarPayload)) {
renderedPage = await renderPageProjection(resolvedSidebarPayload);
}
if (renderedFile && fileTreeScope) {
var fileRoot = document.getElementById('sidebar-file-tree-root');
@@ -1745,6 +1847,7 @@ export const createSidebarTreeLiveApplyRuntime = (dependencies = {}) => {
ensureFileTreeLazyCacheScope();
if (!fileTreeExpandedRelativePaths.size) return false;
var restored = false;
var needsVisibleHydrate = false;
document.querySelectorAll('#sidebar-file-tree-root .tree-row[data-shell-mode="filetree"]').forEach(function(row) {
if (!(row instanceof HTMLElement)) return;
if (String(row.getAttribute('data-row-kind') || '').trim() !== 'folder') return;
@@ -1763,12 +1866,12 @@ export const createSidebarTreeLiveApplyRuntime = (dependencies = {}) => {
restored = true;
return;
}
void loadFileTreeChildren(row, button, { persist: false }).then(function(loaded) {
if (loaded) restorePersistedFileTreeExpansionState();
});
setTreeRowExpanded(row, button, true, { persist: false });
needsVisibleHydrate = true;
restored = true;
});
if (restored) document.documentElement.setAttribute('data-mnote-filetree-expansion-restored', 'true');
if (needsVisibleHydrate) scheduleHydrateVisibleExpandedFileTreeRows('restore');
return restored;
}
@@ -1784,6 +1887,9 @@ export const createSidebarTreeLiveApplyRuntime = (dependencies = {}) => {
flushPendingSidebarTreeViewState('filetree');
flushPendingSidebarTreeViewState('pagetree');
});
document.addEventListener('scroll', function() {
scheduleHydrateVisibleExpandedFileTreeRows('scroll');
}, true);
window.addEventListener('tree:title-updated', function(event) {
var detail = event.detail || {};
@@ -1927,6 +2033,7 @@ export const createSidebarTreeLiveApplyRuntime = (dependencies = {}) => {
refreshLocalFolderSidebarSnapshot,
refreshLocalFolderAfterCommand,
removeDocumentRowForMode,
renderPageProjection,
renderSidebarSnapshot,
revealFileTreeResource,
restorePersistedFileTreeExpansionState,