Step 2/12 of docs/superpowers/plans/2026-05-17-files-overlay-rewrite.md.
editor.js is dual-purpose during Phase A: drives both the legacy
inline #files-editor-modal <dialog> (binary-replace + create-new flows)
and the URL-addressable modal swapped into #modal-content (editable
text files). Phase B migrates the legacy flows to URL-addressable too
and removes the legacy branches.
What moved:
* Editor state object, editorEls DOM refs, CM6 bridge (getEditorValue,
setEditorValue), UI helpers (setEditorTitle, updateByteCount,
updateRenameHint, updateSaveEnabled, setQueuedReplacement)
* openEditorTextNew (create-new file flow)
* openEditorForFile (legacy binary + editable-as-fallback flow)
* All save/delete/replace handlers — converted from direct-bound on
editorEls.{saveBtn,deleteBtn} to a single document-level click
listener that discriminates by ancestor (legacy editorDialog vs.
URL-addressable #modal-content)
* Replace-zone dragover/dragleave/drop — direct-bound on
editorEls.replaceZone → document-level delegation gated on the zone
being inside the legacy dialog
* Replace-input change, replace-clear / replace-browse clicks — also
delegated
* The previously-separate URL-addressable save/delete delegation
block (lines 593-664 of the legacy file) collapses into the same
delegated listeners
What stays direct-bound (per plan escape hatch):
* input on .files-editor-filename
* input + keydown on .files-editor-content (Ctrl+S handling)
* close on the persistent legacy <dialog>
These are high-frequency events on persistent inputs inside the
persistent legacy dialog; delegation would add per-keystroke
selector-matching overhead with no benefit.
Action dispatch: editor.js registers "new-file" and "edit" handlers
into __filesOverlay (set up by core.js). The legacy switch-case in
files-overlay.js's click delegation loses both cases — they're now
dispatched via the registry. The legacy switch still owns new-folder,
zip, and delete (those migrate in Step 3).
Cross-module exposure: askConflict and withCollisionSuffix stay in
files-overlay.js (the upload queue and drag-drop code at lines 857
and 974 still use them) and are exposed on __filesOverlay so editor.js
can call them. They migrate to dialogs.js (askConflict, Step 3) and
uploads.js (withCollisionSuffix, Step 4); the call sites in editor.js
don't change.
Numbers:
files-overlay.js: 1091 → 669 lines (-422)
files-overlay/editor.js: 550 lines (new)
Net: +128 lines; the growth is from the dual-editor delegation
scaffolding (separate handler functions for legacy vs. routed) and
module-header comments. The legacy file is now a stub editor section
comment plus the unmigrated dialogs/uploads/drag-drop blocks.
Verified live on /overlays/2 in Chromium:
* 3 script tags load in document order (core → editor → legacy)
* window.__filesOverlay registry now has 10 keys (added askConflict +
withCollisionSuffix); withCollisionSuffix('foo.txt') = 'foo (1).txt'
* No console errors on page load or after synthetic actions
* E2E dispatch check: clicking a "+ new file" action button opens the
legacy dialog with empty filename + Create save-button label
(proves core → handleAction → editor.js handler → openEditorTextNew
chain works)
* E2E dispatch check: clicking the filename button on an editable
file sets ?modal=%2Foverlays%2F2%2Ffiles%2Fedit%3Fpath%3D... in the
URL (proves editor.js's "edit" handler correctly routes editable
files through window.modals.openRouted)
* pytest still 573 passed, 1 skipped, 3 deselected
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