The /incoming/ path regex in browse/js/grid.js was the second-most
visible client-side hardcode of the canonical convention. Migrating
it to the cascade:
Header surface:
X-ZDDC-Default-Tool: <name> The cascade-resolved default tool
for the listing's directory. Empty
header = no default declared.
Client wiring:
loader.fetchServerChildren reads the header into
state.scopeDefaultTool on every listing fetch (initial mount,
rescope on dblclick, popstate). grid.classifierAvailableHere
now returns scopeDefaultTool === 'classifier' instead of
regex-matching the URL.
Effect:
Grid mode auto-activates wherever the cascade picks classifier
as the default — currently archive/<party>/incoming per
defaults.zddc.yaml. An operator who sets default_tool: classifier
on a custom directory gets grid mode there too, no code change.
An operator who removes the default at incoming sees grid mode
stop auto-activating there.
Bootstrap timing fix:
The initial events.init() runs applyResolvedViewMode before the
detection fetch completes, so state.scopeDefaultTool is empty
at that point and grid never auto-activates on first paint.
app.js bootstrap now re-applies the resolved view mode after
autoDetectServerMode returns, so a fresh /incoming URL lands
on grid mode immediately.
The /incoming/ regex is gone. Two client hardcodes remaining
(archive source heuristics, shared/nav stage strip) — Phase 4c/d.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
75 lines
3.4 KiB
JavaScript
75 lines
3.4 KiB
JavaScript
// app.js — bootstrap. Runs after every other module's IIFE has
|
|
// registered its functions on window.app.modules.
|
|
(function () {
|
|
'use strict';
|
|
|
|
var state = window.app.state;
|
|
var loader = window.app.modules.loader;
|
|
var tree = window.app.modules.tree;
|
|
var events = window.app.modules.events;
|
|
|
|
// Virtual canonical folder injection used to live here (browse
|
|
// appended archive/working/staging/reviewing entries at a project
|
|
// root when missing). zddc-server now emits them in the listing
|
|
// directly so the .zddc `display:` map can override their labels
|
|
// the same as real entries. This pass-through stub keeps the
|
|
// events.js rescope contract intact without doing any merging.
|
|
function passThroughEntries(entries) { return entries; }
|
|
|
|
// Expose for events.js's client-side rescope on dblclick.
|
|
window.app.modules.augmentRoot = passThroughEntries;
|
|
|
|
async function bootstrap() {
|
|
events.init();
|
|
|
|
// Try server auto-detect. If this page is served by zddc-server
|
|
// (or any server with a Caddy-shaped JSON listing), load the
|
|
// current directory automatically. Otherwise show the empty
|
|
// state with the "Select Directory" button.
|
|
var detected = await loader.autoDetectServerMode();
|
|
if (detected) {
|
|
tree.setRoot(detected.entries);
|
|
events.showBrowseRoot();
|
|
tree.render();
|
|
events.statusInfo('Loaded ' + detected.entries.length + ' item'
|
|
+ (detected.entries.length === 1 ? '' : 's')
|
|
+ ' from ' + detected.path);
|
|
// The initial events.init() applied view mode before the
|
|
// cascade headers were available (no fetch yet). Now that
|
|
// state.scopeDefaultTool is set from the detection
|
|
// response, re-resolve so an /incoming URL auto-activates
|
|
// grid mode.
|
|
if (events.applyResolvedViewMode) events.applyResolvedViewMode();
|
|
}
|
|
// 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(es);
|
|
tree.render();
|
|
var previewBody = document.getElementById('previewBody');
|
|
if (previewBody) previewBody.innerHTML = '';
|
|
var previewTitle = document.getElementById('previewTitle');
|
|
if (previewTitle) previewTitle.textContent = 'No file selected';
|
|
// Reapply view mode for the new URL (incoming/ → grid, etc).
|
|
if (events.applyResolvedViewMode) events.applyResolvedViewMode();
|
|
} catch (_e) { /* swallow — leave the tree as-is */ }
|
|
});
|
|
}
|
|
|
|
if (document.readyState === 'loading') {
|
|
document.addEventListener('DOMContentLoaded', bootstrap);
|
|
} else {
|
|
bootstrap();
|
|
}
|
|
})();
|