No description
Find a file
mwiegand cb391ad456
feat(files): migrate uploads + drag-drop to uploads.js; legacy file is a stub
Step 4/12 of docs/superpowers/plans/2026-05-17-files-overlay-rewrite.md.

End of Phase A. files-overlay.js is now a 19-line tombstone comment;
all behavior lives in the 4 modules under files-overlay/. Step 10
deletes the stub and its <script> tag.

uploads.js owns:
  * The upload queue (concurrency 3, XHR-based progress, queued/active/
    done/cancelled/error/conflict states) and the progress panel
  * Drag-drop on treeRoot (5 events: dragstart, dragend, dragover,
    dragleave, drop). Internal drags (row → folder, via the custom
    application/x-files-overlay MIME) and external drags (OS files +
    folders via webkitGetAsEntry) flow through the same drop handler.
  * walkEntry for recursive folder walks during external drops
  * withCollisionSuffix (moves here from files-overlay.js — its biggest
    caller is the upload-conflict "keep both" path; exposed on
    __filesOverlay for editor.js's save-409 path too)
  * "zip" action handler (registered into __filesOverlay) — pure URL
    navigation; placed here as the closest thematic home

Pattern change: upload-row cancel buttons converted from direct-bound
per row (inside buildUploadRow, which captured `item` in a closure) to
a single document-level delegated click listener. Each row carries
data-upload-id; an uploads Map<uploadId, item> looks up the item at
click time. The Map entry is removed in the uploadsClearBtn handler
when a done/error/cancelled row is cleared, so the Map doesn't grow
unbounded.

Drag-drop stays direct-bound to treeRoot per the plan escape hatch —
the 5 events share coordinated highlight state (is-drag-source,
is-drop-target classes that are toggled across events), and treeRoot
is persistent (never swapped). Delegation would obscure the state
coordination logic without any real benefit.

Phase A end-state:
  * core.js (247 lines): helpers, manager guard, registry dispatch
  * editor.js (550 lines): editor flows (legacy + URL-addressable)
  * dialogs.js (212 lines): new-folder, delete-confirm, conflict
  * uploads.js (423 lines): upload queue + drag-drop + zip + collision
  * files-overlay.js (19 lines): tombstone comment, deleted in Step 10

Total: ~1432 lines across 4 modules + 19-line stub. The plan estimated
~780 lines across 4 modules; actual is ~1.8× larger, the difference
being module-header comments and the delegation-with-state scaffolding
(e.g., editor.js's dual-editor listener split, dialogs.js's per-dialog
state plus close-event resolvers).

Verified live on /overlays/2 in Chromium:
  * 5 script tags load in document order (core → editor → dialogs →
    uploads → legacy stub)
  * Registry has 10 keys; askConflict and withCollisionSuffix are both
    callable; withCollisionSuffix('foo.tar.gz') === 'foo.tar (1).gz'
    (legacy behavior preserved — lastIndexOf('.') splits before the
    final extension)
  * Uploads panel + list elements present in DOM, panel hidden by
    default
  * "zip" action button exists; registered handler would set
    window.location.href to /overlays/2/files/download_zip?path=...
  * No console errors
  * pytest still 573 passed, 1 skipped, 3 deselected

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 15:57:30 +02:00
deploy deploy/journalctl: anchor server log to current unit start 2026-05-15 23:04:53 +02:00
docs docs(files): errata — script tag lives in overlay_detail.html, not base.html 2026-05-17 15:02:21 +02:00
examples/script-overlays feat(l4d2-web): seed example script overlays from examples/script-overlays/ 2026-05-08 18:41:08 +02:00
l4d2host refactor(repo): uv workspace + hatchling + layout restructure 2026-05-15 22:04:29 +02:00
l4d2web feat(files): migrate uploads + drag-drop to uploads.js; legacy file is a stub 2026-05-17 15:57:30 +02:00
scripts feat(scripts): add scripts/dev-server.py for local UI smoke 2026-05-17 00:04:11 +02:00
.envrc chore(envrc): switch direnv from use uv to layout uv 2026-05-16 13:20:16 +02:00
.gitignore chore(gitignore): ignore .tmp/ scratch directory 2026-05-16 11:53:14 +02:00
.python-version refactor(repo): uv workspace + hatchling + layout restructure 2026-05-15 22:04:29 +02:00
AGENTS.md refactor(modals): consolidate modal.js + modal-router.js as inline/routed 2026-05-17 14:31:38 +02:00
cvar_list feat(editor-v2): vocab generator + cvar_list-derived JSON 2026-05-17 01:55:33 +02:00
pyproject.toml test(e2e): scaffold Playwright + live-server fixture 2026-05-16 21:00:45 +02:00
README.md refactor(repo): uv workspace + hatchling + layout restructure 2026-05-15 22:04:29 +02:00
uv.lock test(e2e): scaffold Playwright + live-server fixture 2026-05-16 21:00:45 +02:00

left4me

left4me is a local L4D2 server management platform with two planned components:

  1. l4d2host + l4d2ctl (host library + CLI)
  2. l4d2-web-app (Flask web app for users, blueprints, servers, jobs, and logs)

Status

Implementation plans remain the source of truth for architecture and task sequencing:

  • docs/superpowers/plans/2026-04-22-l4d2-host-lib-v1.md
  • docs/superpowers/plans/2026-04-23-l4d2-web-app-v1.md

Locked v1 Decisions

  • Naming is strictly l4d2 (not l4d).
  • Host library and web app are separate components.
  • Host CLI write commands are fixed to:
    • install
    • initialize <name> -f <spec.yaml>
    • start <name>
    • stop <name>
    • delete <name>
  • Host CLI read commands are available for the web/host boundary:
    • status <name> --json
    • logs <name> --lines <n> --follow/--no-follow
  • The web app calls host operations through l4d2ctl, not direct l4d2host imports.
  • Deployment uses /var/lib/left4me for runtime state, /opt/left4me for repository contents and the virtualenv, /etc/left4me for environment files, and global units under /usr/local/lib/systemd/system.
  • Overlay handling is directory-based; the web app populates each overlay (workshop downloads, managed-global refresh).
  • No lock manager, no rollback, no preflight checks in host library.
  • CLI propagates subprocess failures via stderr and return code.
  • delete on missing instance is no-op success.
  • Blueprint model (web app):
    • user-private in v1
    • servers are live-linked to blueprint
    • no per-server overrides
    • delete blueprint blocked when linked servers exist
    • blueprint changes apply on next action
    • server can reassign blueprint anytime

Planned Repository Layout

  • l4d2host/
  • l4d2web/
  • deploy/
  • docs/superpowers/plans/

Deployment

See deploy/README.md for the Linux test deployment contract, including the runtime user, target filesystem layout, systemd units, privileged helpers, sudoers rules, admin bootstrap, and overlay reference rules.

Local development

This repo is a uv workspace (l4d2host + l4d2web as members) with a committed uv.lock and a .python-version pinning Python 3.13 (matching the Debian Trixie production target).

One-time prereq: install uv (macOS: brew install uv; Linux: curl -LsSf https://astral.sh/uv/install.sh | shuv is not yet in Debian stable's apt).

  1. direnv allow once per fresh checkout (and after any .envrc change). .envrc uses use uv, which runs uv sync and activates .venv/ on cd.
  2. Without direnv: uv sync at the repo root creates .venv/, installs both workspace members editable, and pulls in dev deps (pytest) from the lockfile.
  3. Tests: uv run pytest (or just pytest once the venv is on PATH).

Tech Stack (planned)

  • Python 3.13+ (workspace uses uv + hatchling)
  • Typer, PyYAML, pytest
  • Flask, SQLAlchemy, Alembic
  • HTMX (vendored locally), custom CSS, SSE
  • systemd units, kernel overlayfs (mounted via the left4me-overlay privileged helper), steamcmd
  1. Implement l4d2host plan first.
  2. Implement l4d2web plan second.
  3. Keep tests green task-by-task (TDD flow from plans).
  4. Keep commits small and aligned with plan tasks.

Contributing Notes

  • Follow plan task order unless explicitly re-planned.
  • Keep contracts above unchanged unless the user asks to change them.
  • Update plan docs when scope or behavior changes.