From c51089df1b734bec9ce2870e1ef147a5a659eb3b Mon Sep 17 00:00:00 2001 From: mwiegand Date: Sun, 17 May 2026 14:31:38 +0200 Subject: [PATCH] refactor(modals): consolidate modal.js + modal-router.js as inline/routed MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two modal pipelines coexisted after the URL-addressable pilot — modal.js (inline, ~30 lines) and modal-router.js (routed, ~150 lines) — operating on different attribute namespaces and exposing different APIs. Future modal authors had two systems to learn with no naming convention to help them pick the right one for a given use case. Consolidates both into static/js/modals.js with two clearly-named pipelines and a single window.modals.* API: Inline modal — content pre-rendered in the page. Hooks: data-inline-modal-open="" data-inline-modal-close API: window.modals.openInline(idOrEl) window.modals.closeInline(idOrEl) Use: confirmations, transient prompts, in-page forms without URL value. Routed modal — content fetched from a URL, ?modal= in URL, with history + share-link + refresh-survival. Hooks: data-routed-modal-dismiss API: window.modals.openRouted(path) window.modals.closeRouted() Use: content with standalone-page meaning. Single document-level click delegation handles all four attribute hooks; one DOMContentLoaded handler binds dialog 'close' / 'cancel' / backdrop on the routed slot; shared popstate and htmx:responseError listeners. Behaviour unchanged — pure rename + colocation. Renamed across 11 templates and files-overlay.js. Old data-modal-* attributes and window.openModal/closeModal globals are gone — clean break (no back-compat shims). AGENTS.md "Modals: inline vs routed" section documents the decision guide for new modals. Verified: 573 backend tests pass. 5/5 Chromium smoke checks pass (inline open/close, Esc, backdrop, routed open+save, routed Esc). Console clean. Co-Authored-By: Claude Opus 4.7 (1M context) --- AGENTS.md | 32 ++- l4d2web/l4d2web/static/js/files-overlay.js | 10 +- l4d2web/l4d2web/static/js/modal-router.js | 143 ------------ l4d2web/l4d2web/static/js/modal.js | 34 --- l4d2web/l4d2web/static/js/modals.js | 215 ++++++++++++++++++ l4d2web/l4d2web/templates/_modal_partial.html | 6 +- .../l4d2web/templates/_server_actions.html | 2 +- l4d2web/l4d2web/templates/admin_users.html | 6 +- l4d2web/l4d2web/templates/base.html | 3 +- .../l4d2web/templates/blueprint_detail.html | 10 +- l4d2web/l4d2web/templates/blueprints.html | 6 +- l4d2web/l4d2web/templates/overlay_detail.html | 26 +-- .../templates/overlay_file_editor.html | 4 +- l4d2web/l4d2web/templates/overlays.html | 6 +- l4d2web/l4d2web/templates/server_detail.html | 14 +- l4d2web/l4d2web/templates/servers.html | 6 +- 16 files changed, 289 insertions(+), 234 deletions(-) delete mode 100644 l4d2web/l4d2web/static/js/modal-router.js delete mode 100644 l4d2web/l4d2web/static/js/modal.js create mode 100644 l4d2web/l4d2web/static/js/modals.js diff --git a/AGENTS.md b/AGENTS.md index a7cf3b0..2491cec 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -23,15 +23,33 @@ Do not invent architecture outside these plans unless explicitly requested. - Do not use git worktrees. - Repo is a uv workspace; Python is pinned to 3.13 via `.python-version`. After fresh checkout: install `uv` (`brew install uv` / `curl -LsSf https://astral.sh/uv/install.sh | sh`), then `direnv allow` (or `uv sync` directly). See README **Local development** for details. -### URL-addressable modal templates +### Modals: inline vs routed -A template that renders **both** as a full standalone page AND as a modal fragment (i.e. `{% extends base_layout %}`, where `base_layout` resolves to `_modal_partial.html` for modal-mode requests and `base.html` otherwise — driven by the `HX-Modal: 1` header in `app.py:inject_base_layout`) MUST follow these conventions: +Two coexisting modal mechanisms, one module (`l4d2web/l4d2web/static/js/modals.js`). When adding a new modal, decide which pipeline it belongs to: -- **The outermost element of `{% block content %}` is a `
`, NOT a ``.** The persistent `` slot in `base.html` provides top-layer + backdrop + focus-trap + Esc-to-close semantics. Nested `` elements collapse to 2 px in every browser — Task 8.5 of the modals pilot fixed this the hard way; do not re-introduce it. In standalone mode, the content renders flat under `
`, which is the intended "this URL is a real page, not a modal-over-nothing" UX. -- **Close buttons use `data-modal-dismiss`** (NOT `data-modal-close` — that's the legacy inline-dialog system). `modal-router.js` listens at document level for this attribute and calls `dialog.close()` on the outer slot. -- **Form-bearing content needs document-level event delegation** for submit/save/delete actions, 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-641 for the canonical pattern (read `data-*` attributes from the textarea, 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"` (that's what painted card-in-a-card during Task 8.5b; the outer dialog owns chrome). -- **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`. +**Inline modal** — the dialog markup is pre-rendered into the page HTML. Content is whatever's already there; the JS just calls `showModal()` / `close()` on a specific `` by id. Use when: +- It's a confirmation (delete, overwrite, reset) +- It's a transient prompt mid-flow (conflict resolution during upload) +- It's a form whose URL state would be noise (rename, new-folder, new-server) +- The content has no standalone-page equivalent + +Hooks: ` + {% endif %}
{% if drift %} diff --git a/l4d2web/l4d2web/templates/admin_users.html b/l4d2web/l4d2web/templates/admin_users.html index 3a66a0a..ed346c4 100644 --- a/l4d2web/l4d2web/templates/admin_users.html +++ b/l4d2web/l4d2web/templates/admin_users.html @@ -39,7 +39,7 @@ {% endif %} - + {% endif %} @@ -55,7 +55,7 @@