refactor(files): collapse files_overlay binary mode into per-capability gates

Read-only file explorers (server detail page, read-only overlay) used
to omit the hover-action panel entirely and make the filename itself
the download link. Editable overlays did the opposite: hover-action
panel with download + delete, filename-click opens the editor.

Unify both surfaces around the editable pattern: always emit the
hover-action span when any action applies. Gate the download button
on download_supported only (now visible on all surfaces). Keep
delete + folder actions (+file, +folder, zip) gated on files_overlay,
since read-only surfaces never offer those. In read-only mode, the
filename becomes a plain <span> — the hover ⬇ is the single download
affordance, matching editable mode's filename-click ≠ hover-download
split.

Prefactor for the files-overlay.js rewrite (docs/superpowers/plans/
2026-05-17-files-overlay-rewrite.md). No JS changes; files-overlay.js
doesn't run on read-only surfaces (manager-element guard at line 23-24).

Verified: pytest stayed at 573/1/3 (URL substrings still appear in the
rendered HTML even though they moved out of the filename anchor into
the hover-action span). Direct Jinja render confirmed all four
branches: read-only downloadable file (new hover ⬇, plain <span>
filename), broken read-only symlink (no hover panel — correct, can't
download dangling links), read-only download_supported=False (no
hover panel, plain <span>), editable mode (byte-identical to before).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
mwiegand 2026-05-17 15:19:00 +02:00
parent d76ee05956
commit 4fa39642b0
No known key found for this signature in database

View file

@ -18,7 +18,9 @@
<div class="file-tree-children" hidden></div>
</li>
{% else %}
<li class="file-tree-row file-tree-row-file{% if files_overlay %} files-row{% endif %}"
{% set show_download = download_supported and not entry.broken %}
{% set has_actions = (files_overlay or show_download) and not entry.broken %}
<li class="file-tree-row file-tree-row-file{% if has_actions %} files-row{% endif %}"
{% if files_overlay %}draggable="true" data-target-path="{{ entry.rel }}" data-row-kind="file" data-editable="{{ '1' if entry.editable else '0' }}"{% endif %}>
{% if entry.broken %}
<span>{{ entry.name }}</span>
@ -26,18 +28,20 @@
{% else %}
{% if files_overlay %}
<button type="button" class="file-tree-name-button" data-action="edit" data-target-path="{{ entry.rel }}" data-editable="{{ '1' if entry.editable else '0' }}" title="Open in editor">{{ entry.name }}</button>
{% elif download_supported %}
<a href="{{ files_base_url }}/files/download?path={{ entry.rel|urlencode }}">{{ entry.name }}</a>
{% else %}
<span>{{ entry.name }}</span>
{% endif %}
{% if entry.is_symlink %}<span class="file-tree-badge">link</span>{% endif %}
<span class="muted">{{ entry.size_human }}</span>
{% endif %}
{% if files_overlay and not entry.broken %}
{% if has_actions %}
<span class="files-row-actions" aria-label="File actions">
{% if show_download %}
<a class="files-row-action" href="{{ files_base_url }}/files/download?path={{ entry.rel|urlencode }}" title="Download"></a>
{% endif %}
{% if files_overlay %}
<button type="button" class="files-row-action files-row-danger" data-action="delete" data-target-path="{{ entry.rel }}" data-row-kind="file" data-row-name="{{ entry.name }}" title="Delete"></button>
{% endif %}
</span>
{% endif %}
</li>