useLabelKeyboard now accepts labels as Label[] | (() => Label[]).
The keymap is rebuilt on every keypress from the getter result instead of
being captured once at construction time — so keys 1–9 now fire correctly
after the async /api/config/labels fetch completes.
LabelView passes () => labels.value so the reactive ref is read lazily.
New test: 'evaluates labels getter on each keypress' covers the async-load
scenario (empty list → no match; push a label → key fires).
Two bugs fixed:
1. Blank white page after vue SPA rebuild: browsers cached old index.html
referencing old asset hashes. Assets are deleted on rebuild, causing
404s for JS/CSS -> blank page. Fix: serve index.html with
Cache-Control: no-cache so browsers always fetch fresh HTML.
Hashed assets (/assets/chunk-abc123.js) remain cacheable forever.
2. Queue draining to empty on skip/discard: handleSkip and handleDiscard
never refilled the local queue buffer. After enough skips, store.current
went null and the empty state showed (blank-looking). Fix: both handlers
now call fetchBatch() when queue drops below 3, matching handleLabel.
Also: sync classifier_adapters LABELS to match current 10-label schema
(new_lead + hired, remove unrelated).
48 Python tests pass, 48 frontend tests pass.
- Add _item_id() (content hash) + _normalize() to map legacy JSONL fields
(from_addr/account/no-id) to Vue schema (from/source/id)
- All mutating endpoints now look up by _normalize(x)[id] — handles both
stored-id (test fixtures) and content-hash (real data) transparently
- Change uvicorn bind from 127.0.0.1 to 0.0.0.0 so LAN clients can connect
Implements Task 13: LabelView.vue wires together the label store, API
fetch, card stack, bucket grid, keyboard shortcuts, haptics, motion
preference, and three easter egg badges (on-a-roll, speed round, fifty
deep). App.vue updated to mount LabelView and restore hacker-mode theme
on load. 3 new LabelView tests; all 48 tests pass, build clean.
status.write() per email grows the log unboundedly on big pulls.
Now uses status.empty() to create one updatable slot; per-email
progress overwrites it, cleared after each account completes.
Per-account summaries still use status.write() (one line each).