left4me/l4d2web
mwiegand f094eca074
feat(files): migrate editor handlers to files-overlay/editor.js
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>
2026-05-17 15:42:58 +02:00
..
alembic feat(l4d2-web): add command_history table for RCON console transcript 2026-05-14 21:26:56 +02:00
l4d2web feat(files): migrate editor handlers to files-overlay/editor.js 2026-05-17 15:42:58 +02:00
scripts fix(editor-v2): fix cm6 to rows-derived height, eliminate layout shift 2026-05-17 10:27:28 +02:00
tests feat(modals): GET /overlays/<id>/files/edit route 2026-05-17 11:43:18 +02:00
alembic.ini chore(l4d2): flatten component layout 2026-05-05 23:47:06 +02:00
pyproject.toml refactor(repo): uv workspace + hatchling + layout restructure 2026-05-15 22:04:29 +02:00
README.md refactor(repo): uv workspace + hatchling + layout restructure 2026-05-15 22:04:29 +02:00

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 l4d2ctl via a local host command runner, not direct l4d2host imports

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 example sqlite:////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