feat: configurable track forward, track to end, skip-and-mark (REQ-189)

Track 5 Frames becomes Track Forward with a 1-100 count input, remembered in
localStorage (default 5). A separate -> End action runs to the last frame
behind a confirm and can be cancelled mid-flight.

Frames that already hold a shape of the tracked class are skipped instead of
re-annotated: hidden and dimmed shapes count as present. Skips are reported in
the result banner and marked with a dot plus a class name on the filmstrip
thumbnail. frames() now returns the class ids each frame holds, the same query
row, no migration.

Chaining and seed growth stay out: every frame is still seeded from the drawn
box only.
This commit is contained in:
asus committed 2026-10-05 13:46:24 +07:00
1 parent f6b0335c93
commit 4f15335092
9 files changed
+226 -52

No files matched your search

+41
View File
@@ -1405,6 +1405,47 @@ read it.
(accent border + glow) — no CSS added; **browser eyeball still owed by the user**
(375 px 2-col wrap, long class names).
## Task — Configurable Track Forward + track-to-end (REQ-189) `[DONE]`
> Status caveat: code, build and docs are verified below; the **end-to-end GPU run in the
> user's browser is still owed**, so the feature is not proven on real shapes yet.
1. **Backend** `backend/batches.py:136` `frames()` — the skip check needs *which* classes a
frame holds, and the frontend only ever received `annotation_count`. Added
`GROUP_CONCAT(DISTINCT a.class_id)` to the existing subquery (same row, no extra round trip,
no migration) and parsed it to `[int]`, `[]` when NULL.
→ verify: **[DONE]** `uv run` against the real DB: batch 19 returns 507 frames, the five
frames of the earlier manual track run (`22829`–`22833`) come back `class_ids: [2]`, frames
with none return `[]`. `EXPLAIN QUERY PLAN` shows both subqueries seek
`idx_annotations_frame` (no per-frame scan). Other consumers (`autolabel.py:95`,
`dataset.py:480`, `preview.py:35`) only read dict keys — unaffected.
2. **Cancel plumbing** `frontend/src/api.js` — `request` takes `signal`, `assist(frameId, body,
{ signal })` forwards it. No other caller touched.
3. **UI** `frontend/src/pages/ReviewPage.jsx` — `trackForward(toEnd)` now takes an explicit
count: N input (1–100, clamped and **persisted on blur** — an effect would save the
half-typed value, so typing "15" survives), *Track Forward [T]* with a dynamic tooltip,
*→ End* with a confirm showing the real frame count, `AbortController` per run so the
"asking SAM3…" indicator becomes a **Cancel** button, skip-on-existing-class, and a banner
reading `Tracked N of M — skipped K already had <class> — <failures>`. The banner shows when
something was skipped *or* failed — a fully clean run stays silent, as before. A successful
assist also unions its `class_id` into the local frame's `class_ids`, so a second run in the
same session skips its own output instead of writing duplicates.
4. **Filmstrip** `frontend/src/components/Filmstrip.jsx` — optional `markedIds` + `markLabel`
props; `frontend/src/app.css` `.thumb .skip-dot` (`--warn` + 2 px dark ring for contrast on
light thumbs, top-left so it never collides with the count badge), `.track-count` for the
input, and `flex-wrap` on `.frame-bar` so the bar still fits 375 px. Skipped frames also name
the class in the thumb tooltip.
→ verify: **[DONE]** `npm run build` green; clamp covers `"abc"`, `0`, `999`, `-3`, `""`,
`Infinity` and non-numeric `localStorage` garbage (`Number.isFinite` guard → 5), and the
slice bound is computed from `Number(trackFrames)` so a string can never concatenate into the
slice end; `slice(index+1, index+1+N)` and `slice(index+1)` both bounded; cancel lands as
`cancelled — frame n may still be saved by the server` and keeps the shapes already written;
skip check reads the `class_ids` array, not `annotation_count`; marks cleared at the start of
every run; `Shift+T` deliberately **not** bound (a fat-fingered 500-frame GPU run is not worth
a keystroke). Reviewer pass done — 4 findings fixed. **Still owed, user browser + GPU**: set
N=3 → 3 shapes, reload → N remembered; to-end over the already-reviewed part of batch 19 →
banner reports the skips and dots appear on those thumbs; Cancel mid-run stops it.
## Task — Track 5 Frames works at all (REQ-189) `[DONE]`
1. `ReviewPage.jsx:trackForward` read `geometry.coordinates` — a key the backend never emits