feat(editor-v2): wire data-editor-language attrs into three textareas

Templates:
- blueprint_detail.html:52 — config textarea gets
  data-editor-language="srccfg".
- overlay_detail.html:25 — script textarea gets
  data-editor-language="bash".
- overlay_detail.html files-modal — content textarea gets
  data-editor-language="auto"; new <select data-editor-language-select>
  (auto / srccfg / bash / plain); filename input gets
  data-editor-filename.
- Both templates {% include "_editor_assets.html" %} before
  {% endblock %}.

Tests (TDD red-green):
- test_blueprint_get_includes_editor_markup pins srccfg + bundle + glue
  in blueprint detail GET.
- test_script_overlay_detail_carries_editor_markup pins bash + bundle
  + glue in script overlay GET.
- Files-modal markup verified end-to-end in Task 14 Playwright (its
  pytest fixtures are heavyweight; not worth duplicating for a static
  markup assertion).

Fast suite stays at 564 passed (no regressions).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
mwiegand 2026-05-17 02:06:58 +02:00
parent 9ca0e789f4
commit 59446bc105
No known key found for this signature in database
4 changed files with 44 additions and 4 deletions

View file

@ -49,7 +49,7 @@
<pre class="config-preview" aria-label="Auto-loaded overlay configs">{% for o in exposed %}exec {{ o.name }}.cfg
{% endfor %}</pre>
{% endif %}
<textarea name="config" rows="8" spellcheck="false">{{ config_lines | join('\n') }}</textarea>
<textarea name="config" rows="8" spellcheck="false" data-editor-language="srccfg">{{ config_lines | join('\n') }}</textarea>
</div>
</label>
<button type="submit">Save blueprint</button>
@ -92,4 +92,5 @@
</div>
</dialog>
<script src="{{ url_for('static', filename='js/blueprint-overlay-picker.js') }}" defer></script>
{% include "_editor_assets.html" %}
{% endblock %}

View file

@ -22,7 +22,7 @@
<form method="post" action="/overlays/{{ overlay.id }}/script" class="stack">
<input type="hidden" name="csrf_token" value="{{ session.get('csrf_token', '') }}">
<label>Bash script
<textarea name="script" rows="20" spellcheck="false">{{ overlay.script or "" }}</textarea>
<textarea name="script" rows="20" spellcheck="false" data-editor-language="bash">{{ overlay.script or "" }}</textarea>
</label>
<p class="muted">Runs sandboxed against the overlay directory mounted at <code>/overlay</code>.</p>
{% if not latest_build_is_running %}
@ -168,14 +168,23 @@
<div class="modal-body">
<label class="files-editor-field">
<span class="files-field-label">Filename</span>
<input type="text" class="files-editor-filename" autocomplete="off" spellcheck="false">
<input type="text" class="files-editor-filename" data-editor-filename autocomplete="off" spellcheck="false">
</label>
<p class="files-editor-rename-hint" hidden>↻ Save will rename <code class="files-rename-from"></code><code class="files-rename-to"></code>.</p>
<div class="files-editor-text">
<label class="files-editor-field files-editor-language-field">
<span class="files-field-label">Language</span>
<select data-editor-language-select aria-label="Editor language">
<option value="auto">auto (from filename)</option>
<option value="srccfg">srccfg (.cfg)</option>
<option value="bash">bash (.sh)</option>
<option value="plain">plain</option>
</select>
</label>
<label class="files-editor-field">
<span class="files-field-label">Content</span>
<textarea class="files-editor-content" rows="14" spellcheck="false"></textarea>
<textarea class="files-editor-content" rows="14" spellcheck="false" data-editor-language="auto"></textarea>
</label>
<div class="files-editor-meta muted">
<span class="files-editor-byte-count">UTF-8 · 0 bytes</span>
@ -273,4 +282,5 @@
<script src="{{ url_for('static', filename='js/files-overlay.js') }}" defer></script>
{% endif %}
{% include "_editor_assets.html" %}
{% endblock %}

View file

@ -453,3 +453,19 @@ def test_blueprint_config_form_post_round_trip(user_client) -> None:
# line round-tripped into the rendered textarea.
for line in ("// pinned by form-contract test", "sv_cheats 0", "exec server.cfg"):
assert line in body, f"line not found in rendered page: {line!r}"
def test_blueprint_get_includes_editor_markup(user_client) -> None:
"""Blueprint detail page must carry the editor opt-in attribute and
the editor asset partial the v2 CodeMirror 6 wiring contract."""
create = user_client.post(
"/blueprints",
data={"name": "editor-attrs", "arguments": "", "config": "", "overlay_ids": []},
headers={"X-CSRF-Token": "test-token"},
)
assert create.status_code == 302
page = user_client.get(create.headers["Location"])
body = page.get_data(as_text=True)
assert 'data-editor-language="srccfg"' in body
assert "vendor/editor.bundle.js" in body
assert "js/editor.js" in body

View file

@ -277,3 +277,16 @@ def test_permissions_admin_can_edit_any(app, alice_id, admin_id) -> None:
with session_scope() as s:
overlay = s.query(Overlay).filter_by(id=overlay_id).one()
assert overlay.script == "echo admin"
def test_script_overlay_detail_carries_editor_markup(app, alice_id) -> None:
"""Script overlay detail page must carry the editor opt-in attribute
and the editor asset partial the v2 CodeMirror 6 wiring contract."""
overlay_id = _create_script_overlay(app, alice_id)
client = _client_for(app, alice_id)
response = client.get(f"/overlays/{overlay_id}")
assert response.status_code == 200
body = response.get_data(as_text=True)
assert 'data-editor-language="bash"' in body
assert "vendor/editor.bundle.js" in body
assert "js/editor.js" in body