diff --git a/AGENTS.md b/AGENTS.md index 2491cec..572b90b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -46,11 +46,56 @@ Hooks: `` opens (full-page nav fallback if JS - **The outermost element of `{% block content %}` is a `
`, NOT a ``.** The persistent slot in `base.html` already provides top-layer + backdrop + focus-trap + Esc-to-close semantics. Nested `` collapses to 2 px in every browser. - **Close buttons use `data-routed-modal-dismiss`** (NOT the inline-modal attribute). `modals.js` delegates at document level. -- **Form-bearing content needs document-level event delegation** for submit/save/delete, gated on `event.target.closest("#modal-content")`. Direct binding to elements in the swapped-in fragment only works in standalone mode — HTMX-swapped content arrives as fresh DOM nodes with no listeners attached. See `files-overlay.js` lines ~599-664 for the canonical pattern (read `data-*` attributes from the swapped DOM, NOT from JS state set during open). +- **Form-bearing content needs document-level event delegation** for submit/save/delete, gated on `event.target.closest("#modal-content")`. Direct binding to elements in the swapped-in fragment only works in standalone mode — HTMX-swapped content arrives as fresh DOM nodes with no listeners attached. See `static/js/files-overlay/editor.js`'s document-level click listener + the `routedSaveClicked` / `routedReplaceClicked` / `routedDeleteClicked` functions for the canonical pattern (read `data-*` attributes from the swapped DOM, NOT from JS state set during open). - **CSS classes targeting modal chrome are scoped to the outer slot** — `dialog.modal, div.modal` in `components.css`. The inner content div should NOT carry `class="modal modal-wide"` (the outer dialog owns chrome; otherwise both paint card-in-a-card). **Reference:** `docs/superpowers/specs/2026-05-17-url-addressable-modals-design.md` (design + verification matrix) and the plan errata at the top of `docs/superpowers/plans/2026-05-17-url-addressable-modals.md`. +### Files overlay: module layout + +The file-manager JS for files-type overlays is split across four +modules under `l4d2web/l4d2web/static/js/files-overlay/`, all loaded +with `defer` from `templates/overlay_detail.html`. They cooperate via +the `window.__filesOverlay` action registry that `core.js` sets up: + +- **`core.js`** — manager-element detection (`.files-manager` guard), + derived state (`overlayId`, `baseUrl`, `treeRoot`, `csrfToken`), + shared helpers (`joinPath`, `parentOf`, `basename`, `humanSize`, + `fetchJson`, `postJson`, `postForm`, `refreshFolder`, + `findRowByPath`, `cssEscape`, `scheduleRefresh`), and the + document-level click listener that dispatches `[data-action]` + clicks through `__filesOverlay.handleAction(op, path, actionEl)` + into per-feature handlers. +- **`editor.js`** — URL-addressable editor only. Handles the new-file + route (`/files/new?at=...`), edit route for text + binary + (`/files/edit?path=...`), and the save / replace / delete delegated + click handlers scoped to `#modal-content`. Registers `"new-file"` + and `"edit"` into the registry. +- **`dialogs.js`** — the three inline `` modals (new-folder, + delete-confirm, conflict). Module-scope state per dialog (one + delegated listener each, no clone-and-rebind). Exposes + `askConflict(path) → Promise<"overwrite"|"keep-both"|"cancel">` + on `__filesOverlay` for use by editor.js + uploads.js. Registers + `"new-folder"` and `"delete"` into the registry. +- **`uploads.js`** — upload queue (concurrency 3, XHR-based progress, + `data-upload-id` delegated cancel), drag-drop on `treeRoot` + (direct-bound — 5 coordinated events share highlight state), and + the `"zip"` registry handler. Exposes + `withCollisionSuffix(path) → suffixedPath` for the upload + save + conflict paths. Drag-drop on `treeRoot` is the **only** direct-bound + listener block in the four modules; everything else is document-level + delegation (see escape-hatch comments in-source). + +When adding a new file-row action, the contract is: + +1. Render the `