feat(zddc): Phase 4b — grid mode driven by cascade default_tool
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>
This commit is contained in:
parent
4b04f61e4b
commit
d90975662f
6 changed files with 34 additions and 14 deletions
|
|
@ -34,6 +34,12 @@
|
||||||
events.statusInfo('Loaded ' + detected.entries.length + ' item'
|
events.statusInfo('Loaded ' + detected.entries.length + ' item'
|
||||||
+ (detected.entries.length === 1 ? '' : 's')
|
+ (detected.entries.length === 1 ? '' : 's')
|
||||||
+ ' from ' + detected.path);
|
+ ' 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.
|
// Else: empty state stays visible; user can click Select Directory.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,11 @@
|
||||||
// as an iframe scoped to the current directory so users get classifier's
|
// as an iframe scoped to the current directory so users get classifier's
|
||||||
// full bulk-rename workflow without leaving browse.
|
// full bulk-rename workflow without leaving browse.
|
||||||
//
|
//
|
||||||
// Availability: only inside an `incoming/` subtree (case-insensitive).
|
// Availability: the cascade decides. Grid auto-activates wherever the
|
||||||
// Working/staging support the classifier tool at the URL level, but
|
// .zddc cascade resolves default_tool=classifier (defaults.zddc.yaml
|
||||||
// they're file-staging contexts in normal use, not rename surfaces —
|
// declares this for archive/<party>/incoming/). Operators can extend
|
||||||
// the Grid toggle is for the inbound side. Outside an incoming/ path,
|
// — e.g. setting default_tool=classifier on a custom dir activates
|
||||||
// the Grid button is hidden entirely (no explanatory empty state).
|
// grid mode there too — without touching this code.
|
||||||
//
|
//
|
||||||
// Iframe src resolution: <currentDirURL>/classifier.html. Iframe
|
// Iframe src resolution: <currentDirURL>/classifier.html. Iframe
|
||||||
// embedding only works in server mode; file:// pages don't get the
|
// embedding only works in server mode; file:// pages don't get the
|
||||||
|
|
@ -18,10 +18,11 @@
|
||||||
var mounted = false;
|
var mounted = false;
|
||||||
|
|
||||||
function classifierAvailableHere() {
|
function classifierAvailableHere() {
|
||||||
// Grid is the classifier-embedded view. Only meaningful in
|
// state.scopeDefaultTool is set by the loader from the
|
||||||
// incoming/ — that's where bulk-rename actually happens.
|
// X-ZDDC-Default-Tool response header on every listing fetch.
|
||||||
var path = (window.location && window.location.pathname) || '';
|
// Grid mode is meaningful exactly where the cascade picks
|
||||||
return /\/incoming(\/|$)/i.test(path);
|
// classifier as the default — no client-side path matching.
|
||||||
|
return state.scopeDefaultTool === 'classifier';
|
||||||
}
|
}
|
||||||
|
|
||||||
function activate() {
|
function activate() {
|
||||||
|
|
|
||||||
|
|
@ -55,6 +55,9 @@
|
||||||
// Cascade-resolved scope flags, refreshed on each listing
|
// Cascade-resolved scope flags, refreshed on each listing
|
||||||
// fetch from response headers.
|
// fetch from response headers.
|
||||||
// scopeDropTarget: cascade's drop_target at currentPath
|
// scopeDropTarget: cascade's drop_target at currentPath
|
||||||
scopeDropTarget: false
|
// scopeDefaultTool: cascade's default_tool at currentPath
|
||||||
|
// (empty when no default declared)
|
||||||
|
scopeDropTarget: false,
|
||||||
|
scopeDefaultTool: ''
|
||||||
};
|
};
|
||||||
})();
|
})();
|
||||||
|
|
|
||||||
|
|
@ -93,6 +93,12 @@
|
||||||
// — so a rescope or popstate re-reads it from the new listing.
|
// — so a rescope or popstate re-reads it from the new listing.
|
||||||
var dropTargetHdr = (resp.headers.get('X-ZDDC-Drop-Target') || '').toLowerCase();
|
var dropTargetHdr = (resp.headers.get('X-ZDDC-Drop-Target') || '').toLowerCase();
|
||||||
window.app.state.scopeDropTarget = dropTargetHdr === 'true';
|
window.app.state.scopeDropTarget = dropTargetHdr === 'true';
|
||||||
|
// X-ZDDC-Default-Tool surfaces the cascade-resolved default
|
||||||
|
// tool name for the current path. Browse uses it to decide
|
||||||
|
// grid-mode auto-activation (when default_tool==classifier)
|
||||||
|
// without re-implementing the cascade client-side.
|
||||||
|
window.app.state.scopeDefaultTool =
|
||||||
|
(resp.headers.get('X-ZDDC-Default-Tool') || '').toLowerCase();
|
||||||
if (resp.status === 404) {
|
if (resp.status === 404) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -130,12 +130,16 @@ func ServeDirectory(cfg config.Config, appsSrv *apps.Server, w http.ResponseWrit
|
||||||
|
|
||||||
// Surface cascade-resolved scope flags via response headers so
|
// Surface cascade-resolved scope flags via response headers so
|
||||||
// the browse SPA can render scope-aware UI (drop-zone overlay,
|
// the browse SPA can render scope-aware UI (drop-zone overlay,
|
||||||
// future affordances) without re-implementing the cascade
|
// grid-mode auto-activation, future affordances) without
|
||||||
// client-side. Keep the header surface tight — only routing-
|
// re-implementing the cascade client-side. Keep the header
|
||||||
// shape booleans go here; ACL details stay server-side.
|
// surface tight — only routing-shape signals go here; ACL
|
||||||
|
// details stay server-side.
|
||||||
if zddc.DropTargetAt(cfg.Root, absDir) {
|
if zddc.DropTargetAt(cfg.Root, absDir) {
|
||||||
w.Header().Set("X-ZDDC-Drop-Target", "true")
|
w.Header().Set("X-ZDDC-Drop-Target", "true")
|
||||||
}
|
}
|
||||||
|
if dt := zddc.DefaultToolAt(cfg.Root, absDir); dt != "" {
|
||||||
|
w.Header().Set("X-ZDDC-Default-Tool", dt)
|
||||||
|
}
|
||||||
|
|
||||||
if strings.Contains(accept, "application/json") {
|
if strings.Contains(accept, "application/json") {
|
||||||
// Content-hash ETag on the listing payload. Re-fetched on every
|
// Content-hash ETag on the listing payload. Re-fetched on every
|
||||||
|
|
|
||||||
|
|
@ -1300,7 +1300,7 @@ body.help-open .app-header {
|
||||||
</svg>
|
</svg>
|
||||||
<div class="header-title-group">
|
<div class="header-title-group">
|
||||||
<span class="app-header__title" id="table-title">ZDDC Table</span>
|
<span class="app-header__title" id="table-title">ZDDC Table</span>
|
||||||
<span class="build-timestamp"><span style="color:red;font-weight:bold">v0.0.17-alpha · 2026-05-11 21:11:16 · 6310afa-dirty</span></span>
|
<span class="build-timestamp"><span style="color:red;font-weight:bold">v0.0.17-alpha · 2026-05-11 21:14:28 · 4b04f61-dirty</span></span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="header-right">
|
<div class="header-right">
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue