Drags server.cfg from the overlay root onto the cfg/ folder row,
asserts both that the file actually moved on disk AND that the tree
reflects the move after the debounced HTMX refresh of both parents.
Exercises the internal-drag path in uploads.js:332-366: the
custom-MIME setData/getData contract, the POST /files/move call,
and the dual scheduleRefresh(parentOf(src)) + scheduleRefresh(dst)
on success. Playwright's locator.drag_to() synthesizes setData
correctly — it relies on dataTransfer.getData(), NOT
webkitGetAsEntry which Playwright cannot fake.
Pitfalls handled inline: scoped the source/target locators to
li.file-tree-row-{file,dir} because action buttons inside the row
duplicate data-target-path + data-row-kind and would trip strict
mode. Used to_have_count instead of to_be_visible because cfg's
children div is collapsed (hidden) by default — the moved row is
in the DOM but not visually rendered until cfg is expanded.
Per docs/superpowers/plans/2026-05-17-files-overlay-e2e-handoff.md
(Tier 2 case C).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Opens server.cfg, drives the CM6 controller to a dirty buffer,
presses Escape to close without saving, then reopens the same file
and asserts the editor shows the original disk content — not the
discarded buffer.
Pins two invariants: native <dialog>'s cancel→close path stays
intact (no JS shortcut around Escape), and the reopened editor
fetches a FRESH fragment via htmx.ajax with CM6 re-mounting on
the new textarea. A regression that cached buffer state to "feel
snappy" would fail this test loudly.
Per docs/superpowers/plans/2026-05-17-files-overlay-e2e-handoff.md
(Tier 2 case B).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Navigates directly to /overlays/<id>?modal=<urlencoded edit path>
and asserts the routed editor auto-opens on the right file.
This is the central guarantee of the URL-addressable modals spec —
copy the URL, share it, and the recipient lands on the same modal
state. A regression in modals.js's DOMContentLoaded bootstrap (the
URLSearchParams.get("modal") → fetchAndShowRouted chain) would
silently dead-link every shared URL; this test fails loudly instead.
Per docs/superpowers/plans/2026-05-17-files-overlay-e2e-handoff.md
(Tier 2, highest-value case).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Opens server.cfg, changes the filename input to server-renamed.cfg,
clicks Save, and asserts: the routed modal closes, the old name is
gone from disk, the new name carries the original content, and the
tree row swaps over. Pins the rename-on-save branch in
routedSaveClicked (the non-is_new path that diffs originalLeaf vs
editedFilename and emits `new_path`).
Deliberately omits __filesEditor.setContent — rename should preserve
the textarea-seeded content. A regression that wrote an empty body
fails on the content-equality assertion.
Tier 1 complete: 7 tests + fixture extension. Full suite runs in
<10s on warm Chromium.
Per docs/superpowers/plans/2026-05-17-files-overlay-e2e-handoff.md.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Creates a folder via the inline new-folder dialog's Enter-keydown
submit path, then deletes it via the inline delete-confirm dialog.
Each step asserts disk + tree state. The Enter path is the
direct-bound listener (not delegated), so it's the most likely to
break under future refactors — pinning it here surfaces such
regressions immediately.
Row-action buttons (`✕`) live inside `<li draggable="true">` — the
draggable ancestor confuses Playwright's hit-test even though real
browsers click through fine. The click uses force=True to skip the
hit-test (documented inline).
Per docs/superpowers/plans/2026-05-17-files-overlay-e2e-handoff.md.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Opens icon.png, attaches new bytes via Playwright's file chooser
(intercepting the click → hidden-input.click() → OS picker chain),
clicks Replace, and asserts the new bytes land on disk under the
unchanged filename. Covers the multipart /files/replace endpoint and
the click → setRoutedReplacement → save-enabled UI wiring.
Per docs/superpowers/plans/2026-05-17-files-overlay-e2e-handoff.md.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Opens icon.png and asserts the routed editor renders the binary
branch of overlay_file_editor.html: replace-zone present, save
button labelled "Replace" and disabled on open, download link
pointing back at /files/download.
The same /files/edit route serves both text and binary modes — the
server picks the template branch from is_editable() + a magic-byte
check. Without this test, a regression that flipped a binary file
into the text branch would mean rendering raw PNG bytes inside an
editable textarea (and a misleadingly working save button).
Also asserts no textarea[data-rel-path] is in the DOM, so a future
regression that left both branches enabled fails loudly.
Per docs/superpowers/plans/2026-05-17-files-overlay-e2e-handoff.md.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Tries to create `cfg`, which collides with the seeded directory of the
same name. /files/save returns 409 "destination is not a file";
routedSaveClicked routes that through fo.askConflict, which opens the
inline #files-conflict-modal on top of the still-open routed editor.
Clicking keep-both triggers a second POST with the suffixed path
(`cfg (1)`), the routed modal closes, and the new row materialises in
the tree.
This is the F4 path from 8dc14f0 ("wire askConflict into the routed
new-file 409 path"). Before that commit, the routed code branch fell
through to a generic alert(). With this test in place, a missing
call site fails loudly instead of silently.
Pins three invariants:
* The conflict dialog is INLINE, not routed — it appears without a
URL change (the decision tree in AGENTS.md "Modals: inline vs
routed" hinges on this).
* .files-conflict-path echoes the original colliding path, not the
computed suffix — the suffix is internal, the user sees the
collision.
* withCollisionSuffix("cfg") → "cfg (1)" (no dot after the last
slash → trailing-suffix branch in uploads.js).
Per docs/superpowers/plans/2026-05-17-files-overlay-e2e-handoff.md.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Clicks `+ new file` at the overlay root, fills the routed editor's
filename + CM6 content, and clicks Create. Asserts the modal closes,
the file lands on disk, AND the new row appears in the live tree
after the debounced HTMX refresh — the last assertion catches the
class of bug where /files/save persists but
scheduleRefresh(parentOf(fullPath)) never lands a fresh listing.
The new-file modal reuses overlay_file_editor.html with is_new=True;
this test exercises the branch in routedSaveClicked that composes
fullPath from filename + data-at-folder, distinct from the
rename-on-save path the edit-mode test covers.
Per docs/superpowers/plans/2026-05-17-files-overlay-e2e-handoff.md.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Opens server.cfg through the file-row name button, drives the CM6
controller via window.__filesEditor.setContent, clicks save, and
asserts both that the routed modal closes and that the new bytes
landed on disk under overlay_root. Guards against four classes of
regression at once: the /files/edit fragment delivering the wrong
data-rel-path, editor.js's htmx:afterSwap re-init failing to wire
__filesEditor, routedSaveClicked stopping short of closeRouted(),
and the /files/save endpoint failing to persist.
Adds a `_wait_for_routed_editor` helper centered on `.cm-content`
inside `#files-editor-fragment` — the textarea itself is
display:none after CM6 mounts, so to_be_visible on the textarea
would always fail; the cm-content surface is the real "editor is
ready" signal.
Per docs/superpowers/plans/2026-05-17-files-overlay-e2e-handoff.md.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Extracts the shared boot/serve plumbing out of live_server into
_boot_app + _serve helpers, then adds files_overlay_server: a
function-scoped fixture that monkey-patches LEFT4ME_ROOT to tmp_path
BEFORE create_app(), seeds a files-type Overlay owned by alice, and
populates the overlay root with one editable text file, one binary
file, and one nested folder. Sets up the surface area the Tier-1
files-overlay e2e tests need without duplicating the live_server
boilerplate.
Also exposes a top-level `login(page, base_url, ...)` helper so
future test modules can share it instead of re-pasting the form-POST
flow.
Per docs/superpowers/plans/2026-05-17-files-overlay-e2e-handoff.md.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two e2e tests:
- test_blueprint_autocomplete_accept_writes_into_hidden_textarea:
loads /blueprints/1, types 'sv_che', asserts the cm6 autocomplete
popup shows 'sv_cheats', presses Tab to accept, fires a synthetic
submit on the form, and reads the hidden textarea value back.
Exercises both the autocomplete extension and the submit-time copy
bridge in editor.js end-to-end.
- test_copy_preserves_newlines_across_lines: regression gate for
bug class 1 from v1 (Prism+contenteditable collapsed multi-line
selections). cm6 preserves linebreaks in its doc by construction;
we verify via the per-textarea controller's getValue().
editor-entry.js: discovered during the e2e debug that cm6's default
completionKeymap does NOT bind Tab. Added an explicit
`{ key: "Tab", run: acceptCompletion }` ahead of the rest of the
keymap stack so Tab accepts when the popup is open and falls through
to indentWithTab otherwise. Bundle rebuilt + SHA refreshed.
Tests also surfaced a 200ms popup-settle timing race: the popup is
*visible* on the same tick acceptCompletion runs against null
selectedCompletion. A page.wait_for_timeout(200) before pressing
the accept key bridges the gap reliably in CI.
Chromium runs fine in Claude Code's default sandbox — the stale note
in the handoff doc about Mach-port IPC sandbox-blocking is no longer
accurate.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The contenteditable + CodeJar + Prism approach (Tasks 1-12 + 4 smoke
fixes shipped this session) hit too many contenteditable edge cases to
ship:
- Copy collapses multi-line selections to one line (Selection.toString()
doesn't reliably reconstruct newlines across Prism's tokenized <span>
topology).
- Enter sometimes requires two presses + cursor color shifts (caret
lands "between" sibling tokenized spans; first Enter shifts it into
a real text node, second actually inserts).
- Cascade of earlier bugs already fixed (cursor jumped to start, then
end; popup-accepted-quote duplicated; popup didn't accept at
end-of-line) were all symptoms of the same root cause: manual Range
API manipulation against tokenized contenteditable DOM is unreliable.
Exiting the sunk-cost path before more fixes accrue. The next attempt
will be a fresh brainstorming session weighing CodeMirror 6 (battle-
tested, accepts a one-time bundler step) vs textarea-overlay (real
<textarea> for editing, passive <pre> highlight, no contenteditable).
Kept (informs the next attempt):
- spec + plan documents in docs/superpowers/
- Playwright scaffolding (conftest + smoke test) + dev deps + e2e marker
- scripts/dev-server.py (independent of editor approach)
- AGENTS.md sandbox + Chromium Mach-port notes
Removed:
- editor JS (editor.js, srccfg-grammar.js)
- editor CSS (editor.css)
- vendored CodeJar + Prism + README
- srccfg vocab data
- editor partial (_editor_assets.html)
- template wiring (data-editor-language attributes, asset partial includes,
files-editor language <select>)
- files-overlay.js editor bridge (setEditorContent helper, dropdown
listener, filename-handler auto-redetect, dropdown reset)
- tokens.css syntax-color additions (dead without the editor)
- form-contract tests in test_blueprints.py + test_script_overlay_routes.py
- the editor-specific Playwright test (test_editor.py)
- create-blueprint modal trim that was tied to editor UX (Arguments +
Config textareas restored)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Logs in as the seed user, navigates to the blueprint detail page,
types sv_che into the editor, asserts the autocomplete popup appears
with sv_cheats, accepts via Tab, and asserts the hidden textarea
(form field) now contains the inserted cvar.
This exercises the full chain end-to-end: editor mount on
DOMContentLoaded, srccfg-vocab.json fetch, popup positioning,
capture-phase keydown handling (Task 9 fix), Range-API completion
insertion, and textarea-mirroring on every input.
Two follow-ups from the Task 11 code review.
Important — without SESSION_COOKIE_SECURE=0, Task 12's Playwright
login would silently fail. app.py:57 sets SESSION_COOKIE_SECURE = not
TESTING, so with our TESTING=False conftest the cookie is marked
Secure; the browser drops it over http://127.0.0.1 and the
session never establishes. The env-var override (app.py:53-55) is the
least invasive fix and preserves the SECRET_KEY guard.
Minor — the second init_db() looked redundant but is actually load-
bearing: create_app's init_db runs inside the app context (binds to
the in-app engine), while the seed work uses session_scope() outside
the app context (binds to an env-derived engine). The second
init_db() creates tables on THAT engine. Added a clarifying comment
so a future reader doesn't drop the line and silently break the seed.
Addresses Important #1 + Minor #1 from the Task 11 code review.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds playwright + pytest-playwright to workspace dev deps, an e2e
pytest marker, and a live_server fixture that boots the Flask app on
an ephemeral port with a temp SQLite DB. addopts default to -m 'not
e2e' so the regular fast suite excludes them; explicit
`pytest -m e2e` runs them. Smoke test confirms the live server is
reachable.
Workspace root pyproject.toml is the right place for the dev deps and
pytest config — l4d2web/pyproject.toml is minimal and has neither.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>