fix(browse): dblclick rescopes client-side instead of hard nav
Hard navigation to /<path>/ lets zddc-server's auto-serve kick in for
canonical folders — /archive/ swaps in the archive tool, /staging/ the
transmittal tool, etc. — so double-clicking a canonical folder from
inside browse silently swapped the user out of browse, contrary to
what they expected.
Fix: client-side rescope. navigateIntoFolder() now fetches the new
directory listing via the loader, calls tree.setRoot() with virtual
canonicals re-applied, and pushes the new URL via history.pushState.
The page never reloads. A subsequent reload still works (browse loads
itself at the new URL since trailing-slash → ServeDirectory → embedded
browse SPA).
Side effects:
- augmentRoot (the canonical-folder injection helper) exposed via
window.app.modules so events.js can re-apply it on rescope.
- popstate handler: back/forward in the browser triggers the same
rescope path with the historic URL.
- Selection + preview reset on rescope; the previous file's preview
isn't carried into the new scope.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
cb2cf1ebe3
commit
dbb7f6c9b5
2 changed files with 73 additions and 1 deletions
|
|
@ -49,6 +49,9 @@
|
||||||
return augmented;
|
return augmented;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Expose for events.js's client-side rescope on dblclick.
|
||||||
|
window.app.modules.augmentRoot = withVirtualCanonicals;
|
||||||
|
|
||||||
async function bootstrap() {
|
async function bootstrap() {
|
||||||
events.init();
|
events.init();
|
||||||
|
|
||||||
|
|
@ -67,6 +70,27 @@
|
||||||
+ ' from ' + detected.path);
|
+ ' from ' + detected.path);
|
||||||
}
|
}
|
||||||
// Else: empty state stays visible; user can click Select Directory.
|
// Else: empty state stays visible; user can click Select Directory.
|
||||||
|
|
||||||
|
// Browser back / forward: client-side rescope when the URL
|
||||||
|
// changes via popstate. We can't tell server-vs-fs mode from
|
||||||
|
// popstate alone, so only honor it in server mode.
|
||||||
|
window.addEventListener('popstate', async function () {
|
||||||
|
if (window.app.state.source !== 'server') return;
|
||||||
|
var path = location.pathname;
|
||||||
|
if (!path.endsWith('/')) path += '/';
|
||||||
|
try {
|
||||||
|
var es = await loader.fetchServerChildren(path);
|
||||||
|
window.app.state.currentPath = path;
|
||||||
|
window.app.state.selectedId = null;
|
||||||
|
window.app.state.lastPreviewedNodeId = null;
|
||||||
|
tree.setRoot(withVirtualCanonicals(es, path));
|
||||||
|
tree.render();
|
||||||
|
var previewBody = document.getElementById('previewBody');
|
||||||
|
if (previewBody) previewBody.innerHTML = '';
|
||||||
|
var previewTitle = document.getElementById('previewTitle');
|
||||||
|
if (previewTitle) previewTitle.textContent = 'No file selected';
|
||||||
|
} catch (_e) { /* swallow — leave the tree as-is */ }
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (document.readyState === 'loading') {
|
if (document.readyState === 'loading') {
|
||||||
|
|
|
||||||
|
|
@ -311,9 +311,16 @@
|
||||||
|
|
||||||
async function navigateIntoFolder(node) {
|
async function navigateIntoFolder(node) {
|
||||||
if (state.source === 'server') {
|
if (state.source === 'server') {
|
||||||
|
// Rescope client-side rather than hard-navigating. A hard
|
||||||
|
// nav would let zddc-server's auto-serve kick in and swap
|
||||||
|
// us out of browse for canonical folders (e.g. /archive/
|
||||||
|
// → archive tool, /staging/ → transmittal). Staying in
|
||||||
|
// browse is what the user asked for; pushState keeps the
|
||||||
|
// URL bar accurate so a reload would re-load browse at the
|
||||||
|
// new scope.
|
||||||
var url = window.app.modules.tree.pathFor(node);
|
var url = window.app.modules.tree.pathFor(node);
|
||||||
if (!url.endsWith('/')) url += '/';
|
if (!url.endsWith('/')) url += '/';
|
||||||
window.location.assign(url);
|
await rescopeServer(url, node.name);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (state.source === 'fs') {
|
if (state.source === 'fs') {
|
||||||
|
|
@ -333,6 +340,47 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Client-side rescope for server mode. Updates the URL via
|
||||||
|
// history.pushState, fetches the new directory listing, and
|
||||||
|
// re-renders the tree from scratch. Page DOES NOT reload.
|
||||||
|
async function rescopeServer(url, displayName) {
|
||||||
|
var entries;
|
||||||
|
try {
|
||||||
|
entries = await loader.fetchServerChildren(url);
|
||||||
|
} catch (e) {
|
||||||
|
statusError('Failed to enter ' + displayName + ': ' + (e.message || e));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
state.currentPath = url;
|
||||||
|
// Selection / preview belong to the old scope; clear them so
|
||||||
|
// the new root doesn't carry stale highlight state.
|
||||||
|
state.selectedId = null;
|
||||||
|
state.lastPreviewedNodeId = null;
|
||||||
|
// Inject virtual canonical folders at the new scope if it's a
|
||||||
|
// project root. (app.js owns this helper; expose via window.app.)
|
||||||
|
var augment = window.app.modules.augmentRoot;
|
||||||
|
var rootEntries = (typeof augment === 'function')
|
||||||
|
? augment(entries, url)
|
||||||
|
: entries;
|
||||||
|
tree.setRoot(rootEntries);
|
||||||
|
tree.render();
|
||||||
|
// Reset the preview pane so the user sees an "empty selection"
|
||||||
|
// state at the new scope instead of the previous file.
|
||||||
|
var previewBody = document.getElementById('previewBody');
|
||||||
|
if (previewBody) previewBody.innerHTML = '';
|
||||||
|
var previewTitle = document.getElementById('previewTitle');
|
||||||
|
if (previewTitle) previewTitle.textContent = 'No file selected';
|
||||||
|
var previewMeta = document.getElementById('previewMeta');
|
||||||
|
if (previewMeta) previewMeta.textContent = '';
|
||||||
|
// pushState so the URL bar reflects the new scope. A real
|
||||||
|
// reload would re-load browse at this URL (trailing slash →
|
||||||
|
// ServeDirectory → embedded browse SPA).
|
||||||
|
try {
|
||||||
|
history.pushState({ zddcBrowse: true, path: url }, '', url);
|
||||||
|
} catch (_e) { /* private browsing edge cases */ }
|
||||||
|
statusInfo('Entered ' + displayName);
|
||||||
|
}
|
||||||
|
|
||||||
// Public API
|
// Public API
|
||||||
window.app.modules.events = {
|
window.app.modules.events = {
|
||||||
init: init,
|
init: init,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue