fix(editor-v2): reserve editor slot to stop layout shift on mount

The previous flicker fix hid the textarea via CSS but display: none
removes it from layout entirely — so the page rendered with zero
height where the editor would go, then cm6 mounted and pushed the
surrounding form down by its full height (CLS).

Wrap each editor textarea in <div class="editor-mount" style="min-height: …rem">
so the slot is reserved before cm6 mounts. The wrapper is a flex
column with cm6 as flex: 1 so cm6 fills the reserved space rather
than collapsing to content-height with a gap below (the seeded
blueprint has 2 chars of content; without flex the editor would
shrink to one line).

Min-heights calibrated to rows × ~1.25rem + ~1.5rem chrome:
- config (rows=8)  → 12rem
- files (rows=14)  → 19rem
- script (rows=20) → 27rem

.cm-editor's own min-height: 8em rule removed — the wrapper is the
floor now, and the inner cm6 stretches to fill via flex.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
mwiegand 2026-05-17 02:29:43 +02:00
parent fd0d96b349
commit b915f2e766
No known key found for this signature in database
3 changed files with 15 additions and 4 deletions

View file

@ -9,10 +9,21 @@
* in _editor_assets.html un-hides for JS-disabled users. */ * in _editor_assets.html un-hides for JS-disabled users. */
textarea[data-editor-language] { display: none; } textarea[data-editor-language] { display: none; }
/* Wrapper that reserves layout space before cm6 mounts, sized by the
* inline `min-height` style each call site sets to match the original
* textarea's `rows`. cm6 is a flex child that fills the reserved space
* (otherwise short content would render cm6 small with a gap below). */
.editor-mount {
display: flex;
flex-direction: column;
}
.editor-mount > .cm-editor {
flex: 1;
}
.cm-editor { .cm-editor {
border: var(--line); border: var(--line);
border-radius: var(--radius-s); border-radius: var(--radius-s);
min-height: 8em;
} }
.cm-editor.cm-focused { .cm-editor.cm-focused {

View file

@ -51,7 +51,7 @@
<pre class="config-preview" aria-label="Auto-loaded overlay configs">{% for o in exposed %}exec {{ o.name }}.cfg <pre class="config-preview" aria-label="Auto-loaded overlay configs">{% for o in exposed %}exec {{ o.name }}.cfg
{% endfor %}</pre> {% endfor %}</pre>
{% endif %} {% endif %}
<textarea name="config" rows="8" spellcheck="false" data-editor-language="srccfg">{{ config_lines | join('\n') }}</textarea> <div class="editor-mount" style="min-height: 12rem"><textarea name="config" rows="8" spellcheck="false" data-editor-language="srccfg">{{ config_lines | join('\n') }}</textarea></div>
</div> </div>
</label> </label>
<button type="submit">Save blueprint</button> <button type="submit">Save blueprint</button>

View file

@ -24,7 +24,7 @@
<form method="post" action="/overlays/{{ overlay.id }}/script" class="stack"> <form method="post" action="/overlays/{{ overlay.id }}/script" class="stack">
<input type="hidden" name="csrf_token" value="{{ session.get('csrf_token', '') }}"> <input type="hidden" name="csrf_token" value="{{ session.get('csrf_token', '') }}">
<label>Bash script <label>Bash script
<textarea name="script" rows="20" spellcheck="false" data-editor-language="bash">{{ overlay.script or "" }}</textarea> <div class="editor-mount" style="min-height: 27rem"><textarea name="script" rows="20" spellcheck="false" data-editor-language="bash">{{ overlay.script or "" }}</textarea></div>
</label> </label>
<p class="muted">Runs sandboxed against the overlay directory mounted at <code>/overlay</code>.</p> <p class="muted">Runs sandboxed against the overlay directory mounted at <code>/overlay</code>.</p>
{% if not latest_build_is_running %} {% if not latest_build_is_running %}
@ -186,7 +186,7 @@
</label> </label>
<label class="files-editor-field"> <label class="files-editor-field">
<span class="files-field-label">Content</span> <span class="files-field-label">Content</span>
<textarea class="files-editor-content" rows="14" spellcheck="false" data-editor-language="auto"></textarea> <div class="editor-mount" style="min-height: 19rem"><textarea class="files-editor-content" rows="14" spellcheck="false" data-editor-language="auto"></textarea></div>
</label> </label>
<div class="files-editor-meta muted"> <div class="files-editor-meta muted">
<span class="files-editor-byte-count">UTF-8 · 0 bytes</span> <span class="files-editor-byte-count">UTF-8 · 0 bytes</span>