A drawer with two part types is one label, so the page, the preview, the
print loop and the PDF all work from plan_groups rather than iterating
the selection. The preview takes the parts that share a label, and the
page reports how much tape the run will use.
Drawer labels are now a set physical size rather than however long the
text happens to be:
one part type in the drawer 40 x 15 mm
two part types 40 x 20 mm
They lie across the tape, so the 40mm spans the width and each label only
costs its height in tape length - 1.5m per 100 standard labels instead of
4m. That needs 50mm tape, which is now the default; a narrower tape is
detected and warned about rather than silently clipping.
Two part types in one drawer are matched on the location field and
combined onto a single taller label. Nothing is ever dropped: in
"always 40 x 15" a shared drawer simply yields two labels, and a third
part type grows the label by 5mm rather than vanishing.
Part names are wrapped to two lines and the type auto-sized to fit, so
"Plate, Modified with Clip" prints in full instead of truncating.
"Fit the tape width" keeps the previous content-sized behaviour.
Printing redirected back to a bare tape page, so every option - rotation
most visibly - reverted to the default the moment you pressed Print.
Carry the chosen options through the redirect instead.
Also read tape_rotate_default so an untick can be made permanent in
Settings; it only applies to a fresh arrival, never over an explicit
choice on the page.
Same absent-checkbox bug as the tape page, caught alongside it. The
hidden form marker lets an empty field list be honoured as a real
choice instead of being treated as "nothing was submitted".
An unticked checkbox is absent from a form submission entirely, so
"rotate unticked" and "rotate never mentioned" arrived at the parser
looking identical - and rotate defaults to on, so it snapped straight
back every time. Add a hidden marker the options form always sends, so
absent-but-submitted can be read as unticked.
Same bug silently reset the field list when every field was unticked;
that is fixed by the same marker. rotate_default lets a saved site-wide
preference apply on a fresh arrival without overriding an explicit
untick on the page.
LIM configured no logging at all, so the instruction fetcher had no trail to follow. Adds a rotating file handler on the root logger plus two owner-only admin pages.
- Admin > Logs: filter by level, source and text; tracebacks kept with their error; auto-refresh, raw download, rotated-file browsing and rotate-now
- Admin > Logs > Brickset API calls: every outbound call recorded with method, params, caller, duration, result count and error, plus daily quota accounting (only getSets counts) and a comparison against getKeyUsageStats
- New owner concept (services/ownership.py): a single account, distinct from is_admin, since these pages expose other admins activity. Defaults to the first admin; flask owner show/set to change
- Services now log through module loggers; retention setting and pruning for call records
- New CLI: flask owner show/set, flask logs path/prune
- New table brickset_api_calls, created automatically at boot; LOG_* env vars, /data/logs in Docker
Downloads official instruction PDFs from LEGO, located via the Brickset API, one set per interval (10 minutes by default) so neither API gets hammered. Official sets only; MOCs are excluded throughout.
- New admin page at /admin/instruction-fetch: queue builder, pacing controls, live countdown and per-set job history
- Database-backed queue (instruction_fetch_jobs) and last-run timestamp, so pacing survives restarts
- Background worker thread, plus 'flask instructions queue/run/status' for Task Scheduler or cron
- SHA-256 dedupe (instructions.sha256), PDF header check, size cap and streaming downloads via temp files
- brickset_api: getInstructions2 by set number and getInstructions by setID (the old code sent a set number to the setID method); getSets params now proper JSON; getKeyUsageStats added
- Schema top-ups applied automatically at boot: new table plus instructions.sha256/source
- Docker: fetcher env vars passed through compose, no worker thread in the entrypoint bootstrap