Step 8/12 of docs/superpowers/plans/2026-05-17-files-overlay-rewrite.md.
The "edit" action handler in editor.js now ALWAYS opens the URL-
addressable modal — both editable text files and binary files. The
server's /files/edit route picks the template branch (text editor vs.
binary-replace panel) based on is_editable(target); data-editable on
the action element becomes informational only.
Binary-replace handlers extended in editor.js to cover the routed
modal (the panel that .files-editor-binary now lives inside):
* routedReplacement (module-scope nullable) — the queued File for
routed binary mode. Cleared on modal-container close so reopening
starts fresh.
* setRoutedReplacement(mc, file) — updates idle/queued markup,
populates name/size labels, calls updateRoutedBinarySaveEnabled.
* isRoutedBinaryMode(mc) — true when #modal-content holds a binary
panel; used by the click delegation to route Save → replace.
* updateRoutedBinarySaveEnabled(mc) — enables Save when a file is
queued OR the filename input has been edited. Mirrors the legacy
binary-mode updateSaveEnabled logic.
The existing dragover / dragleave / drop / change / click delegated
listeners gained routed-mode branches alongside the legacy branches,
gated by inLegacyEditor / inRoutedEditor (mutually exclusive — the
selectors only match inside one editor at a time).
routedReplaceClicked added, mirroring legacy binary save:
* Queued file present → POST /files/replace (multipart, with optional
new_path for rename-on-replace)
* Queued file absent but filename edited → POST /files/move (rename
only)
* Neither → no-op (Save was disabled, shouldn't be reachable)
Filename-input delegated listener re-evaluates Save enablement when
the user types in routed binary mode (so rename-only is reachable
without queueing a file).
The legacy openEditorForFile function in editor.js is now unreachable
from a user action (the "edit" handler no longer calls it). It stays
in this file until Step 9 deletes the legacy dialog block wholesale.
Verified live on /overlays/2 in Chromium:
* Click test.png (binary, data-editable="0") → URL becomes
?modal=%2Foverlays%2F2%2Ffiles%2Fedit%3Fpath%3Dtest.png
* Routed modal opens with binary panel; Save labeled "Replace" and
disabled
* Synthetic drop event with a File → Save enables, idle label hides,
queued label shows "new.png · 18 B"
* Clear button → idle restored, Save disables
* Type new filename without queueing → Save enables (rename-only)
* Revert filename → Save disables
* No console errors
* pytest still 579 passed, 1 skipped, 3 deselected (no Python changes
in Step 8)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|---|---|---|
| .. | ||
| alembic | ||
| l4d2web | ||
| scripts | ||
| tests | ||
| alembic.ini | ||
| pyproject.toml | ||
| README.md | ||
l4d2-web-app
Flask web app for managing L4D2 servers through user-private blueprints.
Key v1 behaviors
- Local username/password login; no public signup
- Admin-managed overlay catalog
- Private blueprints per user
- Server creation from blueprints (live-linked; no per-server blueprint overrides)
- Async job model with persisted command logs in
job_logs - Desired vs actual state model
- Live logs for jobs and servers via SSE endpoints
- Host operations go through
l4d2ctlvia a local host command runner, not directl4d2hostimports
Frontend constraints
- Server-rendered templates (Jinja)
- Vendored HTMX (
static/vendor/htmx.min.js) - Custom CSS only
- Tokenized, consistent link and accent colors
Development
From the workspace root (../):
uv sync # creates .venv, installs l4d2host + l4d2web editable, plus dev deps
uv run pytest l4d2web/tests -q
Configuration
The web app reads these settings from the environment:
DATABASE_URL: SQLAlchemy database URL, for examplesqlite:////var/lib/left4me/left4me.db.SECRET_KEY: Flask secret key used for sessions and CSRF-sensitive state.JOB_WORKER_THREADS: number of background job worker threads.
In the systemd deployment, environment is loaded from /etc/left4me/host.env and /etc/left4me/web.env.
Admin Bootstrap
Create the first admin account with the Flask CLI. Provide the password through LEFT4ME_ADMIN_PASSWORD:
LEFT4ME_ADMIN_PASSWORD='change-me' flask create-user <username> --admin