ZDDC/browse/js/grid.js
ZDDC d90975662f 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>
2026-05-11 16:15:25 -05:00

69 lines
2.6 KiB
JavaScript

// grid.js — "Grid mode" plugin for browse. Loads the classifier tool
// as an iframe scoped to the current directory so users get classifier's
// full bulk-rename workflow without leaving browse.
//
// Availability: the cascade decides. Grid auto-activates wherever the
// .zddc cascade resolves default_tool=classifier (defaults.zddc.yaml
// declares this for archive/<party>/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: <currentDirURL>/classifier.html. Iframe
// embedding only works in server mode; file:// pages don't get the
// Grid toggle.
(function () {
'use strict';
var state = window.app.state;
var mounted = false;
function classifierAvailableHere() {
// 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() {
var host = document.getElementById('gridView');
if (!host) return;
if (mounted) return;
if (state.source !== 'server' || !classifierAvailableHere()) return;
// Compute the iframe src: current page's directory + classifier.html.
var pathname = window.location.pathname || '/';
if (!pathname.endsWith('/')) {
var lastSlash = pathname.lastIndexOf('/');
pathname = lastSlash >= 0 ? pathname.substring(0, lastSlash + 1) : '/';
}
var src = pathname + 'classifier.html';
host.innerHTML = '';
var frame = document.createElement('iframe');
frame.src = src;
frame.title = 'ZDDC Classifier (Grid mode)';
frame.style.cssText = 'width:100%;height:100%;border:0;display:block;'
+ 'background:var(--bg);';
host.appendChild(frame);
mounted = true;
}
// When the user navigates between scopes (client-side rescope on
// dblclick), the iframe needs to be reloaded for the new path.
// Callers reset before re-activating.
function reset() {
mounted = false;
var host = document.getElementById('gridView');
if (host) host.innerHTML = '';
}
window.app.modules.grid = {
activate: activate,
reset: reset,
// Hook for events.js to show/hide the Grid toggle button.
availableHere: function () {
return state.source === 'server' && classifierAvailableHere();
}
};
})();