swarm-ui: fix create-agent form field alignment and panel width

TextField and SelectField's shared FormField wrapper had no width of
its own, so inside the form's shrink-to-fit flex column each field's
input/select resolved its 'width: 100%' against its own shrunk
wrapper rather than a shared column width — two fields with
differently-long labels ended up with differently-wide controls.
FormField now caps its own width the same way the control already
does, so every field in a form lines up regardless of label length.

Also wrapped the page in a max-width container: Panel has no width
opinion of its own, so it filled the full page column, leaving a lot
of bare panel to the right of the ~16em-wide form.
This commit is contained in:
iris 2026-08-19 14:13:02 +02:00 committed by mara
commit f14fc154cc
3 changed files with 69 additions and 40 deletions

View file

@ -4,7 +4,19 @@
not row: with a second field (hive) added, a row layout put fields of
different natural widths on one baseline and looked misaligned
mara: "make it a col". Reuses the same base16 slots (../theme.css)
every other component draws from. */
every other component draws from.
`.create-agent-page` caps the panel's own width: `Panel` has no width
opinion of its own, so left unconstrained it filled `.shell-body`'s
full 60em column a lot of bare panel to the right of a ~16em-wide
form, the "weird empty space" mara flagged alongside the field
misalignment. 24em roughly matches the fields' own 16em cap
(../ui/form-field/FormField.css) plus the panel's 1em body padding on
each side and a little breathing room, so the panel reads as sized to
its content instead of to the page column. */
.create-agent-page {
max-width: 24em;
}
.create-agent-intro {
margin: 0 0 1.5em;
color: var(--muted);

View file

@ -125,44 +125,48 @@ export function CreateAgentPage() {
}
return (
<Panel title="create agent">
<p class="create-agent-intro">
Create a new agent's swarm-level identity. This only queues the job — check{' '}
<Link href="/jobs">jobs</Link> to watch it settle.
</p>
{hivesError && <ApiErrorPanel context="failed to load the hive list" problem={hivesError} />}
<form class="create-agent-form" onSubmit={submit}>
<TextField
id="agent-name"
label="agent name"
value={name}
pattern={NAME_PATTERN}
title="1-63 chars: lowercase letters, digits, hyphens"
required
onInput={setName}
/>
<SelectField
id="agent-hive"
label="hive"
value={hive}
onChange={setHive}
options={hiveOptions}
required
disabled={!hivesReady}
placeholder={!hives ? 'loading…' : hives.length === 0 ? 'no hives configured' : 'select a hive'}
/>
<Button variant="primary" type="submit" disabled={result.status === 'submitting' || !hivesReady}>
{result.status === 'submitting' ? 'creating…' : 'create'}
</Button>
</form>
{result.status === 'done' && (
<p class="create-agent-result create-agent-result-ok">
queued as job node #{result.nodeId} <Link href="/jobs">watch it in jobs</Link>
<div class="create-agent-page">
<Panel title="create agent">
<p class="create-agent-intro">
Create a new agent's swarm-level identity. This only queues the job — check{' '}
<Link href="/jobs">jobs</Link> to watch it settle.
</p>
)}
{result.status === 'error' && (
<ApiErrorPanel context="failed to queue" problem={result.problem} />
)}
</Panel>
{hivesError && <ApiErrorPanel context="failed to load the hive list" problem={hivesError} />}
<form class="create-agent-form" onSubmit={submit}>
<TextField
id="agent-name"
label="agent name"
value={name}
pattern={NAME_PATTERN}
title="1-63 chars: lowercase letters, digits, hyphens"
required
onInput={setName}
/>
<SelectField
id="agent-hive"
label="hive"
value={hive}
onChange={setHive}
options={hiveOptions}
required
disabled={!hivesReady}
placeholder={
!hives ? 'loading…' : hives.length === 0 ? 'no hives configured' : 'select a hive'
}
/>
<Button variant="primary" type="submit" disabled={result.status === 'submitting' || !hivesReady}>
{result.status === 'submitting' ? 'creating…' : 'create'}
</Button>
</form>
{result.status === 'done' && (
<p class="create-agent-result create-agent-result-ok">
queued as job node #{result.nodeId} <Link href="/jobs">watch it in jobs</Link>
</p>
)}
{result.status === 'error' && (
<ApiErrorPanel context="failed to queue" problem={result.problem} />
)}
</Panel>
</div>
);
}

View file

@ -8,11 +8,24 @@
every control in the kit whether or not it's ever used on a touch
device, since the alternative is a component that behaves differently
per input method. Colours are the shared base16-derived vars
(../../theme.css), never literal. */
(../../theme.css), never literal.
The field wrapper repeats `.ui-form-control`'s own `width: 100%;
max-width: 16em` rather than leaving the wrapper unconstrained: inside
a shrink-to-fit flex column (`CreateAgentPage`'s form is one), an
unconstrained wrapper sizes to its own content and a `width: 100%`
*control* inside an auto-width wrapper resolves against that shrunk
width, not the intended 16em cap, so two fields with differently-long
labels ("agent name" vs "hive") ended up with differently-wide inputs
the misalignment mara reported on the create-agent page. Matching the
two declarations here means every field's control width is driven by
the same fixed cap regardless of its label's length or its siblings'. */
.ui-form-field {
display: flex;
flex-direction: column;
gap: 0.3em;
width: 100%;
max-width: 16em;
}
.ui-form-field-label {
font-size: 0.85em;