style: stack per-class override rows instead of a wide table (REQ-181 layout)

The width:100% table plus the new MaxBox column let the Class column absorb
the slack, stranding the Container checkbox away from its class. One block
per class now: name + Container checkbox on the first line, Conf/IoU/MinBox/
MaxBox inputs in a wrapping grid below. Behavior identical - KEYS,
buildClassParams, compose() copy line, toggleContainer revert, aria labels.
This commit is contained in:
asus committed 2026-10-05 11:01:26 +07:00
1 parent d3a6aa49b6
commit 2971f9192c
4 files changed
+75 -61

No files matched your search

+4 -2
View File
@@ -359,9 +359,11 @@ that class to its full-set detections (blank if no full-set run covered it); cle
examples is then a plain full-set view again. Run Preview always refreshes the full-set for
every selected class first (with empty exemplars), then re-runs the exemplared classes
sequentially; drawing an example never triggers a full-set re-run. The
per-class overrides table also carries a **Container** checkbox (REQ-184) that toggles
per-class overrides block (REQ-181) stacks one row per class — name plus **Container**
checkbox on the first line, the four override inputs in a wrapping grid below — and that
checkbox toggles
`project_classes.container` through `PATCH /api/projects/{id} { containers: … }`. The same
table carries a **Copy** button (REQ-186): one click puts every selected class's effective
block carries a **Copy** button (REQ-186): one click puts every selected class's effective
settings (per-class override where set, else the global slider; container = the checkbox
state) on the clipboard as short lines `class <name> conf … iou … minbox … maxbox … container
true|false`, through `clipboard.js`'s `copyText` (Clipboard API with an `execCommand`
+16
View File
@@ -1389,6 +1389,22 @@ read it.
with expected drops; node eval proves `buildClassParams` emits `max_box_frac`;
`git diff --stat` = 5 backend + 5 frontend files + docs.
## Task — stacked per-class override rows (REQ-181 layout) `[DONE]`
1. The overrides surface drops the wide table for one block per class: name line with the
**Container** checkbox (REQ-184) at the right, then a wrapping
`repeat(auto-fit, minmax(118px, 1fr))` grid of `Conf`/`IoU`/`MinBox`/`MaxBox` inputs, each
labelled in its own colour. Cause of the old cramping: `width: 100%` + the new MaxBox
column let the Class column swallow the slack, stranding the checkbox far right.
Behavior untouched — `KEYS`, `buildClassParams`, `compose()` copy line, `toggleContainer`
revert path, `title`/`aria-label` all identical → verify: **[DONE]** reviewer diff-proved
the functional hunk is render-only (SPEC ✅, 4 Minor all fixed: two stale ui-spec
"table/columns" lines, `label` margin-bottom, dead class hook; plus long-name ellipsis +
`flexShrink: 0` guard); `npm run build` green; grid math checked at 375/768/920/1040 px;
contrast ≥6.6:1 on all label colors; focus ring left to the global `input:focus`
(accent border + glow) — no CSS added; **browser eyeball still owed by the user**
(375 px 2-col wrap, long class names).
## Known open points
- *Not closed by any task, by choice:* **any rebuild kills the running job.** Task 14's resume
+10 -6
View File
@@ -449,9 +449,13 @@ Two columns inside one dialog (920 px wide, max 96 vw / 90 vh, scrollable).
- Confidence threshold — 0.05…0.95 step 0.05, default 0.35.
- NMS IoU threshold — 0…0.9 step 0.05, default 0.0.
- Min box size (fraction of frame) — 0…0.5 step 0.005, default 0, shown as a percentage.
- **Per-class overrides** (REQ-181) — a compact table below the sliders, one row per currently
selected class: `Conf` / `IoU` / `MinBox` / `MaxBox` number inputs and a **Container** checkbox
(REQ-184). Each input's placeholder shows the current global value and an empty input
- **Per-class overrides** (REQ-181) — one **stacked block** per currently selected class
below the sliders: the class name on the first line with its **Container** checkbox
(REQ-184) at the right, then a wrapping grid of `Conf` / `IoU` / `MinBox` / `MaxBox`
number inputs, each labelled in its own colour. No column headers, no wide table — a
narrow modal wraps the grid instead of squeezing the numbers, and the checkbox never
drifts away from its class. Each input's placeholder shows the current global value and
an empty input
inherits it; only filled cells are sent, as
`class_params` on **both** the preview and the job request, so preview and run cannot
disagree. Classes without an override behave exactly as before. The checkbox marks the
@@ -462,7 +466,7 @@ Two columns inside one dialog (920 px wide, max 96 vw / 90 vh, scrollable).
unmarked classes are governed by IoU alone. Toggling is optimistic
`PATCH /projects/{id} { containers: { [classId]: bool } }` into `project_classes.container`
with revert on rejection — preview and batch job read the same stored flag.
- **Copy (REQ-186)** — small right-aligned ghost button above the overrides table; label
- **Copy (REQ-186)** — small right-aligned ghost button above the overrides block; label
`Copy`, flips to `Copied` for 1.5 s inside an `aria-live` span, and on failure silently
stays `Copy`. One line per selected class, e.g.
`class sack conf 0.4 iou 0.6 minbox 0.005 maxbox 1 container false` — an empty input copies the
@@ -537,8 +541,8 @@ Same anatomy, wider (1040 px), with three differences:
must not abort the rest; the backend GPU lock serialises the real work anyway. Progress is
reported as `done/total (failed)` and the result message is `Queued N auto-annotation job(s)`.
The per-class overrides table (REQ-181) appears here too, below the sliders — same columns
including the **Container** checkbox (REQ-184) — and its
The per-class overrides block (REQ-181) appears here too, below the sliders — the same
stacked rows, **Container** checkbox (REQ-184) included — and its
`class_params` ride along on every preview and start request — one set of overrides applies
to all selected batches.
+45 -53
View File
@@ -92,64 +92,56 @@ export default function ClassParamsTable({ classNames, globals, value, onChange,
<span aria-live="polite">{copied ? 'Copied' : 'Copy'}</span>
</button>
</div>
<table style={{ width: '100%', borderCollapse: 'collapse' }}>
<thead>
<tr>
<th className="hint" style={{ fontWeight: 400, fontSize: '0.74rem', textAlign: 'left', padding: '2px 4px' }}>Class</th>
{KEYS.map(k => (
<th key={k.key} className="hint" style={{ fontWeight: 400, fontSize: '0.74rem', color: k.color, padding: '2px 4px' }}>{k.label}</th>
))}
{project && (
<th className="hint" style={{ fontWeight: 400, fontSize: '0.74rem', textAlign: 'left', padding: '2px 4px' }}>Container</th>
)}
</tr>
</thead>
<tbody>
{/* Stacked rows (REQ-181): the name line carries the container flag, the
grid below it the four overrides, so a wide class list never squeezes
the numbers or strands the checkbox away from its class. */}
<div>
{classNames.map(name => {
const cls = project?.classes.find(c => c.name === name)
return (
<tr key={name}>
<td className="mono" style={{ fontSize: '0.76rem', padding: '2px 4px' }}>{name}</td>
{KEYS.map(({ key, label, step, color }) => (
<td key={key} style={{ padding: '2px 4px' }}>
<input
type="number"
value={value[name]?.[key] ?? ''}
min={0}
max={1}
step={step}
placeholder={String(globals[key])}
title={`Empty = use the global value (${globals[key]})`}
aria-label={`${name} ${label} override, empty uses global ${globals[key]}`}
onChange={(e) => set(name, key, e.target.value)}
style={{
width: 64, fontSize: '0.76rem', background: '#09090b', color,
border: '1px solid #3f3f46', borderRadius: 4, padding: '2px 4px',
cursor: 'text',
}}
/>
</td>
))}
{project && (
<td style={{ padding: '2px 4px', textAlign: 'center' }}>
{cls && (
<label title={`Keep boxes inside ${name} when auto-annotating (containment is not overlap)`} style={{ cursor: 'pointer', display: 'inline-flex' }}>
<input
type="checkbox"
checked={containers.has(name)}
onChange={() => toggleContainer(cls)}
aria-label={`Mark ${name} as container`}
style={{ cursor: 'pointer' }}
/>
</label>
)}
</td>
)}
</tr>
<div key={name} style={{ padding: '6px 0', borderBottom: '1px solid #27272a' }}>
<div className="row" style={{ justifyContent: 'space-between', alignItems: 'center', gap: 8 }}>
<span className="mono" style={{ fontSize: '0.76rem', minWidth: 0, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{name}</span>
{project && cls && (
<label title={`Keep boxes inside ${name} when auto-annotating (containment is not overlap)`} style={{ cursor: 'pointer', display: 'inline-flex', alignItems: 'center', gap: 4, flexShrink: 0, fontSize: '0.74rem' }}>
<input
type="checkbox"
checked={containers.has(name)}
onChange={() => toggleContainer(cls)}
aria-label={`Mark ${name} as container`}
style={{ cursor: 'pointer' }}
/>
Container
</label>
)}
</div>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(118px, 1fr))', gap: 6, marginTop: 4 }}>
{KEYS.map(({ key, label, step, color }) => (
<label key={key} style={{ display: 'flex', alignItems: 'center', gap: 4, minWidth: 0, marginBottom: 0 }}>
<span className="hint" style={{ fontSize: '0.7rem', color, whiteSpace: 'nowrap' }}>{label}</span>
<input
type="number"
value={value[name]?.[key] ?? ''}
min={0}
max={1}
step={step}
placeholder={String(globals[key])}
title={`Empty = use the global value (${globals[key]})`}
aria-label={`${name} ${label} override, empty uses global ${globals[key]}`}
onChange={(e) => set(name, key, e.target.value)}
style={{
width: '100%', minWidth: 0, fontSize: '0.76rem', background: '#09090b', color,
border: '1px solid #3f3f46', borderRadius: 4, padding: '2px 4px',
cursor: 'text',
}}
/>
</label>
))}
</div>
</div>
)
})}
</tbody>
</table>
</div>
</>
)
}