diff --git a/browse/js/app.js b/browse/js/app.js index 59cac74..0ceb90c 100644 --- a/browse/js/app.js +++ b/browse/js/app.js @@ -34,6 +34,12 @@ 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. diff --git a/browse/js/grid.js b/browse/js/grid.js index b5c9763..ce53feb 100644 --- a/browse/js/grid.js +++ b/browse/js/grid.js @@ -2,11 +2,11 @@ // as an iframe scoped to the current directory so users get classifier's // full bulk-rename workflow without leaving browse. // -// Availability: only inside an `incoming/` subtree (case-insensitive). -// Working/staging support the classifier tool at the URL level, but -// they're file-staging contexts in normal use, not rename surfaces — -// the Grid toggle is for the inbound side. Outside an incoming/ path, -// the Grid button is hidden entirely (no explanatory empty state). +// Availability: the cascade decides. Grid auto-activates wherever the +// .zddc cascade resolves default_tool=classifier (defaults.zddc.yaml +// declares this for archive//incoming/). Operators can extend +// — e.g. setting default_tool=classifier on a custom dir activates +// grid mode there too — without touching this code. // // Iframe src resolution: /classifier.html. Iframe // embedding only works in server mode; file:// pages don't get the @@ -18,10 +18,11 @@ var mounted = false; function classifierAvailableHere() { - // Grid is the classifier-embedded view. Only meaningful in - // incoming/ — that's where bulk-rename actually happens. - var path = (window.location && window.location.pathname) || ''; - return /\/incoming(\/|$)/i.test(path); + // state.scopeDefaultTool is set by the loader from the + // X-ZDDC-Default-Tool response header on every listing fetch. + // Grid mode is meaningful exactly where the cascade picks + // classifier as the default — no client-side path matching. + return state.scopeDefaultTool === 'classifier'; } function activate() { diff --git a/browse/js/init.js b/browse/js/init.js index 0d76df5..2876e65 100644 --- a/browse/js/init.js +++ b/browse/js/init.js @@ -55,6 +55,9 @@ // Cascade-resolved scope flags, refreshed on each listing // fetch from response headers. // scopeDropTarget: cascade's drop_target at currentPath - scopeDropTarget: false + // scopeDefaultTool: cascade's default_tool at currentPath + // (empty when no default declared) + scopeDropTarget: false, + scopeDefaultTool: '' }; })(); diff --git a/browse/js/loader.js b/browse/js/loader.js index 61d24ef..a9c0fb7 100644 --- a/browse/js/loader.js +++ b/browse/js/loader.js @@ -93,6 +93,12 @@ // — so a rescope or popstate re-reads it from the new listing. var dropTargetHdr = (resp.headers.get('X-ZDDC-Drop-Target') || '').toLowerCase(); 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) { return []; } diff --git a/zddc/internal/handler/directory.go b/zddc/internal/handler/directory.go index 7680282..460f1c7 100644 --- a/zddc/internal/handler/directory.go +++ b/zddc/internal/handler/directory.go @@ -130,12 +130,16 @@ func ServeDirectory(cfg config.Config, appsSrv *apps.Server, w http.ResponseWrit // Surface cascade-resolved scope flags via response headers so // the browse SPA can render scope-aware UI (drop-zone overlay, - // future affordances) without re-implementing the cascade - // client-side. Keep the header surface tight — only routing- - // shape booleans go here; ACL details stay server-side. + // grid-mode auto-activation, future affordances) without + // re-implementing the cascade client-side. Keep the header + // surface tight — only routing-shape signals go here; ACL + // details stay server-side. if zddc.DropTargetAt(cfg.Root, absDir) { 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") { // Content-hash ETag on the listing payload. Re-fetched on every diff --git a/zddc/internal/handler/tables.html b/zddc/internal/handler/tables.html index acb2549..a0e41e3 100644 --- a/zddc/internal/handler/tables.html +++ b/zddc/internal/handler/tables.html @@ -1300,7 +1300,7 @@ body.help-open .app-header {
ZDDC Table - v0.0.17-alpha · 2026-05-11 21:11:16 · 6310afa-dirty + v0.0.17-alpha · 2026-05-11 21:14:28 · 4b04f61-dirty