From 99044ab28a5a191b3f0997e8a81f69e01cd1f3f0 Mon Sep 17 00:00:00 2001 From: Alexander Date: Sat, 4 Jul 2026 01:24:02 -0400 Subject: [PATCH] =?UTF-8?q?fix(ui):=20stack=20edit=20drawer=20=E2=80=94=20?= =?UTF-8?q?labeled=20multi-line=20slot=20cards=20+=20fix=20escaped=20toggl?= =?UTF-8?q?e=20knob?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The per-slot editor crammed 6 controls into one grid row (110px 1fr 96px 1fr auto auto), squeezing the model and profile selects to ~45px each (unreadable, just '—▾') with no labels. And .pf-switch lacked position:relative, so the absolutely-positioned .pf-switch-knob escaped to the drawer's left edge (the stray grey 'indicator dots' overlapping the panel). Redesign each slot as a labeled card: a header row (full-width slot name + remove) above a two-column labeled field grid — Model spans full width, with Device / Profile / Speculative-decode labeled beneath. Every field is now fully visible and labeled. Add position:relative to .pf-switch so the knob sits on its track (fixes it everywhere). data-testids preserved. Verified live against lxc105 data via the vite dev proxy + Playwright: model select 395px (was 45px), knobs inside their switches. Co-Authored-By: Claude Opus 4.8 (1M context) --- ui/src/dash/stacks.jsx | 61 +++++++++++++++++++++++++++--------------- ui/src/dashboard.css | 29 +++++++++++++++----- 2 files changed, 62 insertions(+), 28 deletions(-) diff --git a/ui/src/dash/stacks.jsx b/ui/src/dash/stacks.jsx index 1b22219d..de584867 100644 --- a/ui/src/dash/stacks.jsx +++ b/ui/src/dash/stacks.jsx @@ -488,28 +488,45 @@ function StackDrawer({ mode, source, existing = [], onClose, onSaved }) { {v.slots.map((s, i) => { const isNew = !!s.slot && !liveSlots.some(ls => ls.name === s.slot); return ( -
- setSlot(i, 'slot', e.target.value)} placeholder="pick or name…" maxLength={32} - title={isNew ? 'New slot — created on apply' : 'Existing slot'} data-testid={`st-slot-name-${i}`} /> - {isNew && new} - - - - - +
+
+ setSlot(i, 'slot', e.target.value)} placeholder="slot name — pick or type…" maxLength={32} + title={isNew ? 'New slot — created on apply' : 'Existing slot'} data-testid={`st-slot-name-${i}`} /> + {isNew && new} + + +
+
+ + + +
+ Speculative decode + +
+
); })} diff --git a/ui/src/dashboard.css b/ui/src/dashboard.css index 2371652b..33b03eb1 100644 --- a/ui/src/dashboard.css +++ b/ui/src/dashboard.css @@ -2924,7 +2924,7 @@ select.pf-input { cursor: pointer; } .pf-seg-btn.on { color: var(--bk); border-color: color-mix(in srgb, var(--bk) 50%, transparent); background: color-mix(in srgb, var(--bk) 12%, transparent); } /* MTP switch */ -.pf-switch { display: inline-flex; align-items: center; gap: 9px; padding: 0; border: none; background: transparent; cursor: pointer; } +.pf-switch { position: relative; display: inline-flex; align-items: center; gap: 9px; padding: 0; border: none; background: transparent; cursor: pointer; } .pf-switch::before { content: ""; width: 34px; height: 19px; border-radius: 999px; background: var(--bg-3); border: 1px solid var(--line); position: relative; transition: all 0.15s ease; flex-shrink: 0; } .pf-switch.on::before { background: var(--accent-soft); border-color: var(--accent-line); } .pf-switch-knob { position: absolute; left: 3px; width: 14px; height: 14px; border-radius: 50%; background: var(--fg-4); transition: all 0.15s ease; pointer-events: none; } @@ -3142,14 +3142,31 @@ select.pf-input { cursor: pointer; } .st-slugrow { display: flex; align-items: center; gap: 10px; margin-top: 10px; } .st-slugrow .pf-input { flex: 1; } -/* Editor: per-slot row. */ -.st-slots-edit { margin-top: 6px; display: flex; flex-direction: column; gap: 8px; } -.st-slot-edit { display: grid; grid-template-columns: 110px 1fr 96px 1fr auto auto; gap: 6px; align-items: center; } -.st-slot-edit .pf-input { min-width: 0; } +/* Editor: per-slot labeled card. Each slot is a card — a header row (slot name + + remove) above a two-column labeled field grid — so model/profile/device are + each fully visible and labeled, instead of crammed into one unreadable row. */ +.st-slots-edit { margin-top: 6px; display: flex; flex-direction: column; gap: 10px; } +.st-slot-card { + border: 1px solid var(--line-soft); border-radius: var(--rad-md, 9px); + background: var(--bg-1); padding: 10px 11px 11px; + display: flex; flex-direction: column; gap: 9px; +} +.st-slot-head { display: flex; align-items: center; gap: 8px; } +.st-slot-head .st-slot-name { flex: 1 1 auto; min-width: 0; } +.st-slot-rm { flex: 0 0 auto; padding: 5px 8px; } +.st-slot-fields { display: grid; grid-template-columns: 1fr 1fr; gap: 9px 10px; align-items: end; } +.st-fld { display: flex; flex-direction: column; gap: 4px; min-width: 0; } +.st-fld-model { grid-column: 1 / -1; } /* model spans full width — ids are long */ +.st-fld-lbl { + font-size: 9.5px; letter-spacing: 0.06em; text-transform: uppercase; + color: var(--fg-4); font-weight: 600; +} +.st-fld .pf-input, .st-fld select { width: 100%; min-width: 0; } +.st-fld-mtp .pf-switch.sm { margin-top: 2px; } .pf-switch.sm { gap: 5px; } .pf-switch.sm .mono { font-size: 10px; color: var(--fg-3); } @media (max-width: 720px) { - .st-slot-edit { grid-template-columns: 1fr 1fr; } + .st-slot-fields { grid-template-columns: 1fr; } .st-diff-row { grid-template-columns: 1fr; gap: 2px; } }