From 14911aae21ec4975f7a805b91e3c4867cb41974b Mon Sep 17 00:00:00 2001 From: Christophe Besson Date: Mon, 12 Oct 2026 00:17:32 +0200 Subject: feat: let the operator pause and resume a root's indexing (MNP 6.2) A whole-root walk now checks its root before every file: removed or ejected, it stops instead of reading the drive to the end; paused, it finishes from the hash cache alone. A paused root is not watched, not swept by reconcile and survives a restart (roster), its indexed files stay served. Resume runs the reconcile sweep over the root, so entries and their enrichment are kept. Signed root_pause / root_resume over MNP, loopback routes, CLI `root pause|resume`, a pause button on the indexing dock and pause/resume in the roots table of Group settings. Co-Authored-By: Claude Opus 5.5 --- .../src/meshbay_hub/static/index-dock.js | 62 ++++++++++++++++++---- 1 file changed, 53 insertions(+), 9 deletions(-) (limited to 'packages/meshbay-hub/src/meshbay_hub/static/index-dock.js') diff --git a/packages/meshbay-hub/src/meshbay_hub/static/index-dock.js b/packages/meshbay-hub/src/meshbay_hub/static/index-dock.js index 6c89eb6..47a5818 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/index-dock.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/index-dock.js @@ -6,7 +6,7 @@ import { navigate, session } from './hub-client.js'; import { useStickyBand } from './sticky.js'; import { Icon } from './icon.js'; import { - fromLoopback, fromPush, mergeActivity, dockRows, hideRow, percent, queuedCount, + fromLoopback, fromPush, mergeActivity, dockRows, hideRow, pauseRow, percent, queuedCount, } from './index-dock-model.js'; // ── Indexing dock ─────────────────────────────────────────────────────────── @@ -29,6 +29,9 @@ const ABSENT_POLL_MS = 30000; // rather than a prop through App: the page and the dock are far apart, and a // push every two seconds must not re-render the whole application. const pushed = new Map(); +// How to pause a root of that group over the page's connection, signed by its +// operator — the dock holds no transport of its own. +const pushedPause = new Map(); const listeners = new Set(); /** @@ -36,11 +39,17 @@ const listeners = new Set(); * member keeps the sidebar dot. `status` is the push (or the handshake ack's * `indexing`); `roots` the roots table the page opened, which names `root_pos`. * `null` when the page lets go of the group: nothing will update the entry any - * more, and a stale "scanning" would stay on screen for ever. + * more, and a stale "scanning" would stay on screen for ever. `pause(rootName)` + * is what the row's pause button calls when no node answers on this machine. */ -export function reportIndexPush(groupId, status, roots) { - if (status) pushed.set(groupId, fromPush(groupId, status, roots)); - else pushed.delete(groupId); +export function reportIndexPush(groupId, status, roots, pause) { + if (status) { + pushed.set(groupId, fromPush(groupId, status, roots)); + if (pause) pushedPause.set(groupId, pause); + } else { + pushed.delete(groupId); + pushedPause.delete(groupId); + } for (const fn of listeners) fn(); } @@ -96,7 +105,7 @@ function usePushedActivity() { return snapshot; } -function DockRow({ row, onOpen, onHide }) { +function DockRow({ row, error, onOpen, onPause, onHide }) { const done = row.state === 'done'; const waiting = !done && !row.scanning; const listing = !done && row.scanning && !row.totalBytes; @@ -109,7 +118,9 @@ function DockRow({ row, onOpen, onHide }) { const where = row.root ? `${row.groupName} › ${row.root}` : row.groupName; let meta = ''; - if (listing) { + if (error) { + meta = error; + } else if (listing) { meta = t('indexdock.listing'); } else if (!done && !waiting) { meta = [ @@ -133,7 +144,8 @@ function DockRow({ row, onOpen, onHide }) { onClick=${onOpen}> ${status} ${where} - ${meta && html`${meta}`} + ${meta && html`${meta}`} ${next && html`${next}`} + ${!done && onPause && html` + + `} ${!done && html`