No description
Find a file
mwiegand 294b5b8489
feat(files): add binary-file support to edit route + template
Step 7/12 of docs/superpowers/plans/2026-05-17-files-overlay-rewrite.md.

overlay_file_edit_page now renders the binary-replace UI instead of
returning 415 for non-editable files. Two cases bridge to the same
binary template: the up-front is_editable() check and the late-binding
UnicodeDecodeError on read (8-KiB sniff said yes but the tail had
non-UTF-8 bytes). Both paths route through a small _render_binary_editor
helper that fills in:
  * is_binary=True
  * content="" (no inline editor)
  * byte_count=target.stat().st_size
  * mime_type from mimetypes.guess_type, falling back to
    application/octet-stream

Template (overlay_file_editor.html) gains an is_binary branch in the
modal body: hides .files-editor-text (CM6 textarea + language
dropdown), renders .files-editor-binary instead with file-info note,
replace-zone (drop area + browse button + hidden file input), and
the per-state idle/queued labels. The Save button reads "Replace"
in binary mode and starts disabled — Step 8 enables it via JS when
a replacement file is queued.

Delete and Download stay visible in binary mode (binary files can be
deleted and downloaded the same way text files can).

The /files/new route gets is_binary=False + mime_type="" passed
explicitly so the template's binary branch never fires there.

Server-side only — no JS changes in Step 7. Step 8 wires up the
binary-replace handlers (replace-zone drag, browse/clear clicks,
Replace button → POST /files/replace).

Tests:
  * Removed test_edit_route_415s_for_non_editable_file — the route
    no longer returns 415 for non-editable files; the binary template
    is the new contract.
  * Added test_edit_route_renders_binary_template_for_non_editable
    (asserts 200 + is_binary markup + Save button label + disabled
    state + .files-editor-text absent + byte count rendered).
  * Added test_binary_template_has_replace_zone (asserts the replace-
    zone markup: drop zone, idle/queued labels, browse button, hidden
    file input, and that Download stays visible).

pytest: 578 → 579 passed, 1 skipped, 3 deselected. The pre-existing
"still 404 / still 400" cases the plan asks for are covered by the
existing test_edit_route_404s_for_missing_file and
test_edit_route_400s_for_path_traversal tests — left alone rather
than duplicated.

Verified live: GET /overlays/2/files/edit?path=test.png (an actual
binary file in the demo overlay) returns the binary template; DOM
inspection confirms .files-editor-binary present with data-rel-path,
"Replace" save button starts disabled, Download href points at the
file's download endpoint, MIME type and byte count match, .files-
editor-text is absent.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 16:11:17 +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): add binary-file support to edit route + template 2026-05-17 16:11:17 +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.