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
+58 -44

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 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 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 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 `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 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 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` 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`; with expected drops; node eval proves `buildClassParams` emits `max_box_frac`;
`git diff --stat` = 5 backend + 5 frontend files + docs. `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 ## Known open points
- *Not closed by any task, by choice:* **any rebuild kills the running job.** Task 14's resume - *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. - Confidence threshold — 0.05…0.95 step 0.05, default 0.35.
- NMS IoU threshold — 0…0.9 step 0.05, default 0.0. - 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. - 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 - **Per-class overrides** (REQ-181) — one **stacked block** per currently selected class
selected class: `Conf` / `IoU` / `MinBox` / `MaxBox` number inputs and a **Container** checkbox below the sliders: the class name on the first line with its **Container** checkbox
(REQ-184). Each input's placeholder shows the current global value and an empty input (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 inherits it; only filled cells are sent, as
`class_params` on **both** the preview and the job request, so preview and run cannot `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 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 unmarked classes are governed by IoU alone. Toggling is optimistic
`PATCH /projects/{id} { containers: { [classId]: bool } }` into `project_classes.container` `PATCH /projects/{id} { containers: { [classId]: bool } }` into `project_classes.container`
with revert on rejection — preview and batch job read the same stored flag. 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 `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. 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 `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 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)`. 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 The per-class overrides block (REQ-181) appears here too, below the sliders — the same
including the **Container** checkbox (REQ-184) — and its stacked rows, **Container** checkbox (REQ-184) included — and its
`class_params` ride along on every preview and start request — one set of overrides applies `class_params` ride along on every preview and start request — one set of overrides applies
to all selected batches. to all selected batches.
+28 -36
View File
@@ -92,26 +92,33 @@ export default function ClassParamsTable({ classNames, globals, value, onChange,
<span aria-live="polite">{copied ? 'Copied' : 'Copy'}</span> <span aria-live="polite">{copied ? 'Copied' : 'Copy'}</span>
</button> </button>
</div> </div>
<table style={{ width: '100%', borderCollapse: 'collapse' }}> {/* Stacked rows (REQ-181): the name line carries the container flag, the
<thead> grid below it the four overrides, so a wide class list never squeezes
<tr> the numbers or strands the checkbox away from its class. */}
<th className="hint" style={{ fontWeight: 400, fontSize: '0.74rem', textAlign: 'left', padding: '2px 4px' }}>Class</th> <div>
{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>
{classNames.map(name => { {classNames.map(name => {
const cls = project?.classes.find(c => c.name === name) const cls = project?.classes.find(c => c.name === name)
return ( return (
<tr key={name}> <div key={name} style={{ padding: '6px 0', borderBottom: '1px solid #27272a' }}>
<td className="mono" style={{ fontSize: '0.76rem', padding: '2px 4px' }}>{name}</td> <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 }) => ( {KEYS.map(({ key, label, step, color }) => (
<td key={key} style={{ padding: '2px 4px' }}> <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 <input
type="number" type="number"
value={value[name]?.[key] ?? ''} value={value[name]?.[key] ?? ''}
@@ -123,33 +130,18 @@ export default function ClassParamsTable({ classNames, globals, value, onChange,
aria-label={`${name} ${label} override, empty uses global ${globals[key]}`} aria-label={`${name} ${label} override, empty uses global ${globals[key]}`}
onChange={(e) => set(name, key, e.target.value)} onChange={(e) => set(name, key, e.target.value)}
style={{ style={{
width: 64, fontSize: '0.76rem', background: '#09090b', color, width: '100%', minWidth: 0, fontSize: '0.76rem', background: '#09090b', color,
border: '1px solid #3f3f46', borderRadius: 4, padding: '2px 4px', border: '1px solid #3f3f46', borderRadius: 4, padding: '2px 4px',
cursor: 'text', 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> </label>
)} ))}
</td> </div>
)} </div>
</tr>
) )
})} })}
</tbody> </div>
</table>
</> </>
) )
} }