docs(agents): update modal-pattern reference + add files-overlay layout

Two updates to AGENTS.md after the files-overlay rewrite:

1. The "canonical pattern" reference at the bottom of the inline-vs-
   routed modals section pointed at files-overlay.js lines ~599-664.
   That file is gone. Updated the reference to point at the new
   single-purpose location: editor.js's document-level click listener
   and the routedSaveClicked / routedReplaceClicked / routedDeleteClicked
   functions.

2. Added a "Files overlay: module layout" subsection right after the
   modals one. Names the four modules under static/js/files-overlay/,
   what each owns, the window.__filesOverlay action-registry contract,
   and the recipe for adding a new file-row action. Future agents
   touching the file manager hit a one-paragraph orientation instead
   of re-deriving the layout from git log.

No behavior change. The "Modals: inline vs routed" decision tree
itself stays accurate post-rewrite: editor is routed; new-folder,
delete-confirm, conflict stay inline.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
mwiegand 2026-05-17 17:20:23 +02:00
parent 8dc14f0cca
commit e1723f751c
No known key found for this signature in database

View file

@ -46,11 +46,56 @@ Hooks: `<a data-routed-modal href="<path>">` opens (full-page nav fallback if JS
- **The outermost element of `{% block content %}` is a `<div>`, NOT a `<dialog>`.** The persistent slot in `base.html` already provides top-layer + backdrop + focus-trap + Esc-to-close semantics. Nested `<dialog>` collapses to 2 px in every browser. - **The outermost element of `{% block content %}` is a `<div>`, NOT a `<dialog>`.** The persistent slot in `base.html` already provides top-layer + backdrop + focus-trap + Esc-to-close semantics. Nested `<dialog>` 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. - **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). - **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`. **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 `<dialog>` 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 `<button data-action="my-op" data-target-path="...">` in
`templates/_overlay_file_node.html` (gated on the right capability
flag).
2. Pick the module that owns the action and register a handler:
`fo.registerHandler("my-op", (path, actionEl) => { ... })`.
3. The dispatch wiring in `core.js` takes care of catching the click
and calling the handler. No new listeners needed.
### Dev server and filesystem paths ### Dev server and filesystem paths
- **Production paths (`/var/lib/left4me`, `/usr/local/lib/systemd/system`, `/usr/local/libexec/left4me`, `/etc/left4me`) exist only on Linux deploy hosts.** Never create or write to these on a developer machine. They are referenced in `l4d2host/l4d2host/paths.py` and the spec only as the production layout. - **Production paths (`/var/lib/left4me`, `/usr/local/lib/systemd/system`, `/usr/local/libexec/left4me`, `/etc/left4me`) exist only on Linux deploy hosts.** Never create or write to these on a developer machine. They are referenced in `l4d2host/l4d2host/paths.py` and the spec only as the production layout.