Step 9/12 of docs/superpowers/plans/2026-05-17-files-overlay-rewrite.md.
The inline <dialog id="files-editor-modal"> block in overlay_detail.html
is gone. All editor flows (text edit, binary replace, create new) now
exclusively use the URL-addressable modal swapped into #modal-content.
editor.js is now single-purpose (URL-addressable only). Removed:
* editorDialog reference and the editorEls DOM-ref struct
* The legacy editor state object
* CM6 bridge wrappers + UI helpers (getEditorValue/setEditorValue/
setEditorTitle/updateByteCount/updateRenameHint/updateSaveEnabled/
setQueuedReplacement) — they only ever drove the legacy dialog
* withCollisionSuffix (uploads.js still has its copy for the
upload-conflict path; editor.js no longer needs it since the
URL-addressable conflict path is "alert + keep modal open" rather
than overwrite/keep-both)
* openEditorTextNew and openEditorForFile — both functions were
already unreachable from a user action after Steps 6/8
* inLegacyEditor predicate
* Direct-bound listeners on editorEls.filename / contentBox /
editorDialog (input, keydown for Ctrl+S, close)
* Legacy branches in every delegated handler (dragover, dragleave,
drop, change, click)
* legacySaveClicked and legacyDeleteClicked
What stays:
* Routed state (routedReplacement, isRoutedBinaryMode,
setRoutedReplacement, updateRoutedBinarySaveEnabled)
* Delegated dragover/dragleave/drop/change/click handlers — now
single-path each, no legacy/routed branching
* Filename input delegated listener for routed binary mode (so
rename-only Replace stays reachable)
* modal-container close listener that clears routedReplacement
* routedSaveClicked (text edit + is_new), routedReplaceClicked
(binary, with rename-or-replace fork), routedDeleteClicked
* "new-file" and "edit" registered handlers (the "edit" handler is
no longer split editable/binary — the server picks the template
branch)
routedDeleteClicked gained one capability that was missing in Step 8:
it now reads rel-path from either the textarea (text mode) OR the
.files-editor-binary panel, so deletion works for binary files in the
URL-addressable modal too (previously routed-mode binary delete fell
through to legacy, which is now gone).
Test added: test_overlay_detail_no_longer_renders_legacy_editor_dialog
asserts the legacy dialog markup is absent from /overlays/<id> while
the other inline dialogs are still present (Step 3 didn't move them).
Numbers:
editor.js: 660 → 309 lines (-351). Plan estimated ~200; actual is
~50% larger due to module-header comments + the rename-or-replace
fork in routedReplaceClicked. Pure-routing-only and single-mode
per click, which was the structural goal.
Total across files-overlay/: 1432 → 1191 lines.
pytest: 579 → 580 passed, 1 skipped, 3 deselected.
Verified live on /overlays/2 in Chromium:
* id="files-editor-modal" not in DOM; new-folder/delete/conflict
dialogs still present
* "+ new file" → routed modal, Create button
* Click other.cfg (editable) → routed modal, Save button, content
pre-filled
* Click test.png (binary) → routed modal, Replace button, initially
disabled
* No console errors
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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>
Step 5/12 of docs/superpowers/plans/2026-05-17-files-overlay-rewrite.md.
Adds a server-rendered editor page for creating a new file in a target
folder. Renders the same overlay_file_editor.html as the edit route,
but with is_new=True so the template:
* Hides the Delete button
* Hides the Download link (no file to download yet)
* Changes the Save button label to "Create"
* Emits data-at-folder on the textarea for the JS save handler to
compose path = at_folder + "/" + filename
* Updates the title block (browser tab + heading) to "New file" /
"<at_folder>/…new file"
The existing edit route now passes is_new=False and at_folder=""
explicitly so both call sites are explicit about the contract.
Path validation: ?at may be empty (overlay root) or a relative folder
path. safe_resolve_for_listing rejects traversal attempts (400). A
missing or non-directory at returns 404. Reused the helper to keep
the safety story identical to the listing / edit routes.
Phase B Step 5 is server-side only — no JS changes here. Step 6
migrates openEditorTextNew in editor.js to use this route via
window.modals.openRouted().
Tests added (tests/test_url_addressable_modals.py):
* test_new_route_renders_with_empty_content
* test_new_route_renders_with_target_folder_attribute
* test_new_route_renders_create_button_not_save
* test_new_route_400s_for_invalid_at_path
* test_new_route_404s_for_non_files_overlay
pytest: 573 → 578 passed, 1 skipped, 3 deselected.
Verified live: curl /overlays/2/files/new returns markup with
data-at-folder="", "UTF-8 · 0 bytes" byte count, save button label
"Create", and no Delete / Download buttons.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Server-renders the file editor as a real page. With HX-Modal:1 returns
a layoutless fragment for modal embedding; without it returns the full
standalone page. Mirrors overlay_file_content's path/editability checks.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Switches the Jinja base layout to _modal_partial.html (yield-only) when
the HX-Modal:1 request header is set, otherwise base.html. Foundation
for URL-addressable modals (spec 2026-05-17-url-addressable-modals).
Guards with has_request_context() so the processor is safe when
render_template_string is called from app_context() without a request
(e.g. test_timeago_filter_registered_on_app).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>