fix(maestro): flatten structured composer

The composer was a floating bordered card inset from the pane edge, and
its send button was bottom-aligned beside the textarea, so it sank as the
draft grew.

It is now a flush band at the foot of the pane: one hairline rule against
the transcript, which doubles as the focus indicator. The textarea spans
the full width and the controls sit in a fixed strip below it, so the send
button keeps its place however tall the draft grows.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Miguel Palhas
2026-09-07 10:16:56 +01:00
co-authored by Claude Opus 5
parent 0dc10ffe76
commit f78aa13b36
2 changed files with 64 additions and 48 deletions
+11 -9
View File
@@ -97,17 +97,19 @@ export function createStructuredConnection(
autocomplete="off" aria-autocomplete="list" aria-expanded="false"
aria-controls="structured-command-menu-${sessionId}"></textarea>
</div>
<div class="structured-config" hidden>
<button type="button" class="structured-config-trigger" aria-haspopup="true"
aria-expanded="false" aria-label="Session settings" title="Session settings">
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M4 7h10M18 7h2M4 17h2M10 17h10M14 4v6M6 14v6"/></svg>
<div class="structured-actions">
<div class="structured-config" hidden>
<button type="button" class="structured-config-trigger" aria-haspopup="true"
aria-expanded="false" aria-label="Session settings" title="Session settings">
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M4 7h10M18 7h2M4 17h2M10 17h10M14 4v6M6 14v6"/></svg>
</button>
<div class="structured-config-menu" role="group" aria-label="Session settings" hidden></div>
</div>
<button type="button" class="btn structured-cancel" hidden>Cancel turn</button>
<button type="submit" class="btn btn-primary structured-send"><span>Send</span>
<svg viewBox="0 0 20 20" aria-hidden="true"><path d="m7 5 5 5-5 5M12 10H3"/></svg>
</button>
<div class="structured-config-menu" role="group" aria-label="Session settings" hidden></div>
</div>
<button type="submit" class="btn btn-primary structured-send"><span>Send</span>
<svg viewBox="0 0 20 20" aria-hidden="true"><path d="m7 5 5 5-5 5M12 10H3"/></svg>
</button>
<button type="button" class="btn structured-cancel" hidden>Cancel turn</button>
</form>
</div>
</div>
+53 -39
View File
@@ -2176,12 +2176,14 @@ input.leaf-rename:focus {
.structured-config {
position: relative;
flex: 0 0 40px;
flex: 0 0 32px;
width: 32px;
height: 32px;
}
.structured-config-trigger {
width: 40px;
height: 40px;
width: 100%;
height: 100%;
display: inline-flex;
justify-content: center;
align-items: center;
@@ -2219,7 +2221,7 @@ input.leaf-rename:focus {
position: absolute;
z-index: 20;
bottom: calc(100% + var(--sp-2));
right: 0;
left: 0;
width: min(320px, 86vw);
padding: var(--sp-2);
border: 1px solid var(--border-strong);
@@ -3062,36 +3064,28 @@ input.leaf-rename:focus {
}
/* The composer is a sibling of the scrolling transcript, not part of it: it
stays reachable at the pane edge without changing transcript scroll math. */
stays reachable at the pane edge without changing transcript scroll math.
It is a flush band at the foot of the pane rather than a floating card —
one hairline separates it from the transcript, and that hairline is also
the focus indicator. Input and controls stack, so the send button keeps a
fixed place at the composer's foot however tall the draft grows. */
.structured-composer {
flex: 0 0 auto;
margin: var(--sp-2) var(--sp-3) max(var(--sp-2), env(safe-area-inset-bottom));
padding: var(--sp-1);
border: 1px solid var(--border-strong);
border-radius: var(--r-3);
background: var(--surface);
display: flex;
align-items: flex-end;
flex-direction: column;
gap: var(--sp-1);
box-shadow:
inset 0 1px 0 color-mix(in oklab, var(--ink) 6%, transparent),
var(--shadow-1);
transition:
border-color var(--dur-fast) var(--ease-out),
box-shadow var(--dur-fast) var(--ease-out);
padding: var(--sp-2) var(--sp-3) max(var(--sp-2), env(safe-area-inset-bottom));
border-top: 1px solid var(--border);
background: var(--bg);
transition: border-color var(--dur-fast) var(--ease-out);
}
.structured-composer:focus-within {
border-color: var(--accent);
box-shadow:
inset 0 1px 0 color-mix(in oklab, var(--ink) 8%, transparent),
0 0 0 3px var(--accent-tint),
var(--shadow-1);
border-top-color: var(--accent);
}
.structured-input-wrap {
position: relative;
flex: 1 1 auto;
min-width: 0;
}
@@ -3100,24 +3094,40 @@ input.leaf-rename:focus {
width: 100%;
min-width: 0;
max-height: 160px;
min-height: 40px;
min-height: 20px;
resize: none;
overflow-y: hidden;
padding: 10px var(--sp-2);
padding: 0;
border: 0;
border-radius: var(--r-2);
background: transparent;
color: var(--ink);
font-size: var(--fs-13);
line-height: 20px;
}
.structured-input:focus-visible {
outline: none;
}
.structured-actions {
display: flex;
align-items: center;
justify-content: flex-end;
gap: var(--sp-1);
min-height: 32px;
}
.structured-actions .structured-config {
margin-right: auto;
}
.structured-composer .structured-send,
.structured-composer .structured-cancel {
height: 40px;
min-height: 40px;
height: 32px;
min-height: 32px;
border-radius: var(--r-2);
font-family: var(--font-sans);
font-size: var(--fs-12);
}
.structured-composer .structured-send {
@@ -3138,8 +3148,8 @@ input.leaf-rename:focus {
}
.structured-composer .structured-send svg {
width: 16px;
height: 16px;
width: 15px;
height: 15px;
fill: none;
stroke: currentColor;
stroke-width: 1.7;
@@ -3169,17 +3179,21 @@ input.leaf-rename:focus {
}
@media (max-width: 520px) {
.structured-composer {
margin: var(--sp-1) 0 0;
padding-bottom: calc(var(--sp-1) + env(safe-area-inset-bottom, 0px));
border-right: 0;
border-bottom: 0;
border-left: 0;
border-radius: var(--r-2) var(--r-2) 0 0;
.structured-composer .structured-send,
.structured-composer .structured-cancel {
height: var(--tap);
min-height: var(--tap);
font-size: var(--fs-13);
}
.structured-config-menu {
right: calc(-1 * (var(--control-h) + var(--sp-2)));
.structured-actions {
min-height: var(--tap);
}
.structured-config,
.structured-config-trigger {
width: var(--tap);
height: var(--tap);
}
}