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 @@