# Changelog
## Unreleased
- **The screen gate has candidates for twelve more states that were untested for want of data or a seam** (#268, decision 595). A new `edge` family of gate worlds (`brickwork/demo/gate_edge.py`: the `extras` world plus invented data) holds what the states need: an instruction booklet with no price and a box with only a listing, three catalogue sets nobody owns, a copy whose part list has spare lines with a check that said yes to the spare pass, and two pinned copies. `edge-read-only` is the same data on an instance switched to read-only and `edge-empty` has no copy at all. In the edge worlds only, BrickLink looks configured (invented keys) and Part Spotter's photo recognition is answered by a staged reply (no network). New candidates, none approved: Module states (loading, error, listings only), Item lists read-only, the Add import progress, Part Spotter's Has a sticker, the opened link controls of the Orders import cards, Check this copy (no copies, save error, JavaScript off), the spare pass and the pinned view of Complete sets. The capture script has a new `javascript: false` key for a capture with scripts switched off. The Add into a lot state stays unbuilt: the Add page has no lot switch. `tests/test_gate_edge_world.py` proves that the data of the `demo`, `showcase` and `extras` worlds is unchanged, so no approved baseline moves.
- **Two screen-gate candidates now show the state they are named for** (#268). The Part lot's `set-detection` state hides the parts list (a script in `states.json`) so its capture holds the Set detection section instead of the first 3,000 px of the list. The Sets `toolbar-options-menu` state gets an optional `sizes` key: the options menu does not exist on a phone, so it is captured at 1024 only (Ulf, 12.11.). `scripts/screen_gate.py` and `scripts/design_screens.py check` honour `sizes` (validated; an approved sized state needs one baseline per named width and theme, not six). Both states stay `approved: false`.
- **The local Chrome runner survives a slow start on macOS** (#293). The screen gate's capture script and the browser harnesses now count Chrome's printed `DevTools listening` line as started, wait up to 30 s, and launch the whole browser again (fresh profile, the failed process killed, up to 3 attempts) instead of failing with `Chrome did not start:`; `chrome.dump_dom()` does the same for a rendered test whose `--dump-dom` run stalls or ends in `Teardown watchdog expired`. The error after the last attempt shows Chrome's last stderr lines. Linux CI starts Chrome once, as before. On macOS `BW_CHROME_HEADLESS_SHELL=1` runs the rendered tests on the pinned `chrome-headless-shell` (opt-in; the screenshot gate keeps the full Chrome). See [Contributing](CONTRIBUTING.md#rendered-tests).
- **The screen gate has candidates for the approved states that were untested** (#268). `design/screens/states.json` now lists 39 more built states (candidates, not yet approved) for the Shell Jump, the edit sheet, Instruction and Box detail, the Part lot and its pull list and sticker chooser, module states, the copy chooser, item lists, the lot and instruction sheets, filters, grouping and the options menu, orders, the checker, Complete sets and the wish list with a dollar maximum, and records the rest: a page covered by another state names it in `covered_by` (checked by `design_screens.py check`), and a screen the app does not have (the price watch, the barcode scanner) says so. The data they need is in the gate's `showcase` world (orders, a lot, two-copy sets, staged checks) and a new `extras` world (`brickwork/demo/gate_extras.py`: an instruction, boxes, a dollar wish, a finished check and a running figure check); nothing was added to the `demo` dataset, so no approved baseline moves.
- **Build Scout has its own icon, and "No image" fits the small tile** (#284). Build Scout no longer borrows the Sets icon in the sidebar and the phone's top strip: it is a magnifier over a brick. A set or part without a picture, in a list row's 56 px thumbnail (Complete sets, Prices, Wishlist, the Workbench lists), now shows its number on one line and a smaller **No image** under it, inside the tile instead of wrapping and being cut off.
- **Fixed: a save line no longer says Undo twice, and a receipt shows only on its own screen** (#236). Prices and an item's save line read just **Saved** beside the one **Undo** button, as Set Inspector and the Wishlist do. A save's Undo is kept with the screen it was made on: pinning a set on Complete sets no longer shows **Saved** and an Undo for it on the Wishlist (or on Prices), and the same holds for the collection's saves and the wishlist's own receipt.
- **Docs for the new functions, and Zensical 0.1.1.** Two tutorials, [Find the sets in a bulk lot](docs/tutorials/find-the-sets-in-a-bulk-lot.md) and [Build from what you have, and order the rest](docs/tutorials/build-from-what-you-have.md), walk through set detection, stickers, the pull list, Build Scout, Order missing parts and Change variant with pictures; the how-to guides say which release brought each, and the README lists them. The docs site is built with Zensical 0.1.1.
- **Order missing parts** (#225). A new page under Planning, reached from **Complete sets** (and at `/planning/order-missing-parts`), lists every copy with a gap, with a tick, the gap, the repair gain and a pill for a check in progress. Search, Theme, **Leave out sets to sell or part out** (on by default: a copy whose status or tag says to sell or part it out stays visible, greyed, and can be ticked by hand), **Only sets that gain value** and Sort live in the address. The order list combines the ticked copies per part, colour and condition, with the quantity still to buy (loose stock taken off as the repair export does) and a chip per source set (`6080-1 ×2`); its header counts exactly the selection. Lines with no BrickLink number or colour are named and left out. **Export K sets · BrickLink XML** downloads the same `missing-parts.xml` as Complete sets' export. Read-only, and no script is needed for any of it.
- **Pull a set out of a lot** (#202). Each candidate set of a lot's Set detection has a **Pull list**: the set's parts (spares left out, whole minifigures as lines) grouped by category with pictures, a counter per line (found of needed), search, Show (To find, Found, All) and Sort (Category, Number). What the lot already holds counts as found; the pieces you tick are kept between visits (a setting row, no schema change, ADR 0029) and need no scripts. **Make it a copy** is one undoable step: the ticked pieces are filed in the lot, exactly the found pieces move out of this lot into a new copy, everything not found is recorded as missing on it, and the lot keeps the leftovers. The confirm shows the three numbers; Undo puts the pieces back. API v2: `GET /lots/{id}/pull-list/{set_id}`, `POST .../found` and `POST .../make-copy` (tools `get_lot_pull_list`, `make_copy_from_lot`); the contract is 199 operations. See [Pull a set out of a lot](docs/how-to/pull-a-set-out-of-a-lot.md).
- **Build Scout: which sets can I build from my loose stock** (#9). A new Workbench item beside Matchmaker ranks every catalogue set by how much of it your loose parts already make: the percentage of its parts you hold, the missing parts and lines, the whole figures you hold of those it needs, an approximate cost to complete (with the number of lines that have no price) and, listed apart and never in the percentage, the parts that would fit in another colour. Choose the stock (loose stock, lots, storages; damaged off by default) and narrow by minimum percentage, most missing, theme, years and size. **Build it** makes a copy of the set and moves the matching parts and figures into it as one change you can undo, **Add to wishlist** notes the wish *Buildable from stock*, and **Missing parts** downloads a BrickLink wanted list. The API gets `GET /planning/build-scout` (tool `get_planning_build_scout`) and `POST /items/{id}/build`; the contract is 193 operations. A how-to, the reference text and ADR 0027 describe it; saved filter sets, a maximum cost and Reserve parts are not part of this version.
- **Sets versus a pile of parts** (#199). One read-only engine ranks the catalogue's sets by how much of a pile of loose parts each explains, for the set detection and Buildable sets that follow. Every (part, colour) line is weighed by how rare it is (`ln(N/df)`: a part in thousands of sets weighs almost nothing, one in two sets a lot); a set's evidence is what it holds of the pile, weighed; figures count through their parts and as whole figures, sub-sets through their parent; the same part in another colour or an alternate mould is listed as a swap and never counted. Inverted lists make a query touch only the sets that share a part, cached per catalogue build. API: `GET /api/v2/planning/set-scores` over a lot, a storage, all loose stock or a selection of stock lines (and the MCP tool `get_planning_set_scores`); the Python service is `brickwork.core.set_scoring`; `scripts/bench_set_scoring.py` times it on a real catalogue. Reference: [Set scoring](docs/reference/set-scoring.md); decision: [ADR 0026](docs/adr/0026-set-scoring-weights.md).
- **Lot Detective, backend** (#200). `GET /api/v2/lots/{id}/candidate-sets` (and the MCP tool `get_lot_candidate_sets`) ranks the sets a lot may have come from, on the set-scoring engine of #199, read-only. Each set has a score from 0 to 100 (how strongly the found parts point to it, weighed by how rare they are, relative to the best candidate; not a probability), reasons such as "7 of 9 distinctive parts found" and "2 printed parts only in this set", and the distinctive parts found and not found yet with their pictures. Filters: minimum score, theme (id or name, sub-themes included), year range, part count range. Parts that more than N sets hold are ignored by default (`ignore_common`; N is the setting `candidate_sets_common_max`, default 300, shown by `GET /settings` and changed with `PATCH /settings`). A flag named Sticker on a lot's stock is passed to the engine as `sticker`; the screen follows. Reference: [Set scoring](docs/reference/set-scoring.md).
- **Lot Detective screen** (#200). The lot page has a Set detection section, "Which sets are in this lot?": the candidate sets with score bar, reasons, Details (distinctive parts found and not found with pictures, swaps listed apart, Open set), filters for minimum score, theme, years and size, and Ignore common parts, all in the page's address and working without scripts. Candidate sets now list their swaps. See [Find the sets in a bulk lot](docs/how-to/find-the-sets-in-a-bulk-lot.md).
- **Stickers** (#201). Part Spotter's review step has Has a sticker, which tags the filed stock line Sticker; the lot page shows the tag and a chooser, Which sticker is it?, of the sets that hold the part and a sticker sheet, with This one, Keep as a hint and Not a sticker, each undoable (no schema change, ADR 0028). A chosen sticker is strong evidence for its set in set detection and set scores, an open one a small lift. API v2: `GET`/`POST`/`DELETE` on `/lots/{id}/stock/{stock_id}/sticker(s)` and three tools. See [Mark a sticker](docs/how-to/mark-a-sticker.md).
- **The screen gate renders in parallel again** (#267). Every capture now starts from an empty browser, loads every font face and lays the page out again once the fonts are in, so a capture no longer depends on what a Chrome session rendered before it: one process and four give the same pixels on Linux (`python scripts/screen_gate.py agree`, the `gate-agree` workflow). The gate runs on up to four processes by default (`BW_GATE_WORKERS=1` for one). Six states' baselines were rendered again for a 1 px text shift in drop-downs. [ADR 0024](docs/adr/0024-parallel-check.md) has the cause.
- **Change an item's variant** (#151). A new action moves everything owned of one catalogue item (loose stock, wishes, the figure lines of copies with their whole part lists) to another item of the same kind in one undoable event, keeping each row's id and history, and makes BrickLink's number the new item's preferred link (a link to check is confirmed; the old item's link of it becomes to check). The old item stays a plain catalogue item. Refused like a merge: a merged item, another kind, a job in flight, a line for both items, and a stock row or wish that would meet a live one of the new item. API: `POST /api/v2/items/{id}/change-variant`, undone through `POST /api/v2/events/{id}/undo`; the item page has the screen: **Change variant…** in the Identity module opens a page that copies the merge page (suggestions with the same BrickLink number, search by number or name in the same kind, a preview of what moves, the same refusals in the red message, Undo after the change). How-to: [Change an item's variant](docs/how-to/change-variant.md).
- **The screen gate shows a fixed version in the footer** (#257). The gate's invented world now renders `Brick Work (1.0.0)` whatever the release, the text the approved baselines already show, so a release bump no longer changes a baseline and needs no re-approval. The app's own footer is unchanged.
## Brick Work 1.0.1 (schema 4.2) · 2026-11-10
A fix release: fresh containers set themselves up on first start and no longer log the gunicorn control-socket error. The schema stays 4.2, so a 1.0.0 collection needs no upgrade step, and going back means starting 1.0.0 on the same file. The published image is `ghcr.io/ulef1005/brick-work:1.0.1`; a new container no longer needs the console `brickwork setup` step. Back up `brickwork.db` and `secret.key` before you upgrade, as always.
- **Containers set themselves up on first start** (#266). A new container creates and seeds its missing collection automatically; existing installs are unchanged and the console `brickwork setup` step is no longer needed with this version. The new `brickwork setup --if-missing` flag skips any existing collection file.
- **No control server error at container start** (#265). Gunicorn runs with its unused control socket disabled, removing the "Control server error" permission-denied line.
- **Facts for AI assistants.** The Pages site now serves [`/llms.txt`](https://ulef1005.github.io/brick-work/llms.txt) (the llmstxt.org convention), built by `scripts/build_site.py` from `scripts/site/llms.md`: what Brick Work is and is not for, requirements, install, data, keys, imports, API and MCP, what it contacts, with the release's version filled in and links to the docs as raw Markdown. The README has a short **For AI assistants** section that points to it. `tests/test_llms_txt.py` checks that every linked file exists and every placeholder is filled.
- **What it contacts names the picture downloads.** The README's **What it contacts** now says that pictures of the items you add come from BrickLink and Rebrickable as plain downloads, with no key, as the note under its screenshots already did.
- **A video tour.** A 1:36 walkthrough on YouTube ([watch](https://www.youtube.com/watch?v=R5Q_6GUB1OQ)) shows the collection, Set Inspector, Part Runner and Matchmaker on the demo's invented collection, with a chapter card and captions for each. The README's link bar and its demo line link it, and the landing page has a **Watch the tour** button beside **Try the demo**. Its opening and closing scenes are generated with Google Veo, as the video and its description say.
- **Set cards show each copy's status again** (#258). On **Collection › Sets** every card said **Not checked**, also for a copy that is complete, incomplete or sealed; the filter chips above the list were right. The card read the status in a form the list stopped sending with the 4.0 data model (0.6.0). It now shows **Complete**, **Incomplete**, **Sealed** or **Not checked** for each copy, one pill per status when a set has several copies.
- **Install on your computer with Docker Desktop.** A new how-to, [Install on your computer with Docker Desktop](docs/how-to/install-docker-desktop.md), takes a Windows or Mac user without a server or Git from installing Docker Desktop to the first sign-in: a folder, two files (`compose.yaml` of the release and `data/.env` with the password), three commands, and what to do when a step fails. Tested on macOS with Docker Desktop 4.75; Windows is not tested yet. The README's install section and the Pages landing page link it.
- **The README says who it is for on its first screen.** The paragraph under the tagline now says who Brick Work is for, how it relates to Rebrickable and Brickset, and which sites and apps it brings a collection over from. The paragraph on the database files and ids it replaces moved, unchanged, to **Works with the sites you already use**.
- **Discussions with forms.** The repository's Discussions are on, and three categories have their own form in `.github/DISCUSSION_TEMPLATE/`: **Show and tell** (your collection, where it runs, what you use, where you came from, what got in the way), **Q&A** (question, version, where it runs, what you tried) and **Ideas** (the job, which kind of collector, how you do it today; a clear idea becomes an enhancement issue). Each asks you to leave out personal data.
- **A social preview picture.** `docs/img/social/social-preview-1280x640.png` is the picture GitHub shows when the repository link is shared: the lockup, the lead line and the README's Sets and Part Runner captures on the invented collection.
- **The install steps follow the release.** `tests/test_install_docs.py` fails when the README's or the landing page's `git clone --branch`, or the Docker Desktop guide's `compose.yaml` link, names another version than `brickwork/version.py`, so a release commit bumps them with the image tag.
- **The `check` job runs in about a third of the time** (#254). The tests run in parallel (`pytest -n auto --dist loadgroup`, pytest-xdist; the Chrome and screen-gate tests share one group), the screen gate runs on its own after them in one Chrome session, as its baselines were made, and a pull request that changes only documentation runs the docs check and the site build instead of the whole gate (`scripts/check_scope.py`). The standalone smoke test no longer runs twice. No test was removed or loosened; [ADR 0024](docs/adr/0024-parallel-check.md) has the numbers.
## Brick Work 1.0.0 (schema 4.2) · 2026-11-09
The first release after milestone B: its maintainer's real collection was set up from scratch through **Get started** on a test install, and every workflow ran on it. The schema stays 4.2, so a 0.9.0 collection needs no upgrade step, and going back to 0.9.0 means starting it on the same file. It is the first release whose `compose.yaml` pulls the published image, `ghcr.io/ulef1005/brick-work:1.0.0` (amd64 and arm64, with build provenance and an SBOM): install and upgrade with `docker compose pull`; 0.9.0 and older still build with `docker compose build` ([Upgrade Brick Work](docs/how-to/upgrade.md)). Back up `brickwork.db` and `secret.key` before you upgrade, as always.
- **The README describes 1.0.0 and introduces the crew.** A new section, **The crew**, above **Where we stand**, gives each named Workbench workflow a paragraph: Part Spotter, Set Inspector, Part Runner and Matchmaker, with links to their guides; it replaces the short list of three. **Where we stand** describes release 1.0.0 (milestone B passed, the first release that pulls the published image), and the shipped list names Part Spotter, Storage and the tutorials.
- **The upgrade guide shows how to check the published image** (#190). [Upgrade Brick Work](docs/how-to/upgrade.md#move-to-the-new-version) now gives the two `gh attestation verify` commands for a pull-first release: one for the build provenance, one for the SBOM attached to the image, and what a failure means. The README names the SBOM check too.
- **Tutorial: finish a half-built set** (#247). [Finish a half-built set](docs/tutorials/finish-a-half-built-set.md) is a second learning path in the docs, on the same invented collection as the demo: check a half-built copy in Set Inspector, put the missing parts on a wishlist, receive a parts order against them and watch the copy turn complete. It links [Check a set's parts](docs/how-to/check-set-parts.md), [Manage wishlists](docs/how-to/manage-wishlists.md) and [Receive a parts order](docs/how-to/receive-an-order.md). Its pictures come from `scripts/site_shots.py` (new `tut-` stems), light and dark; the docs site now ships `docs/img/tutorials/`.
- **Tutorial: your first set, start to finish** (#246). The tutorials page had no tutorial. It now has one: install from the README, the **Get started** first run (catalogue, keys optional), add one set, check its parts and see what is missing and where it shows up. See [Your first set, start to finish](docs/tutorials/first-set.md).
- **Tutorial: build from your bulk** (#248). [Build from your bulk](docs/tutorials/build-from-your-bulk.md) is a learning path on the user docs site: set up storage with drawers, collect the missing parts of several sets in one pass with Part Runner, then let Matchmaker show the sets your loose stock finishes. Phone and desktop pictures, light and dark, come from `scripts/site_shots.py` (new stems `bulk-*`).
- **Tutorial: script your collection** (#249). [Script your collection](docs/tutorials/script-your-collection.md) is a learning path on the user docs site: create an API token, read and change the collection with the API and the command line, then connect an assistant over MCP and ask it about the collection. Every command and output was run on the invented showcase collection.
- **The SBOM attestation uses `actions/attest`** (#190). The release workflow's SBOM attestation ran `actions/attest-sbom`, which GitHub has deprecated; it now runs `actions/attest` v4.2.2 (pinned by SHA) with the same subject, SBOM and registry push.
- **No empty band under the phone's bottom navigation in the browser** (#245). The bottom bar always reserved at least 34 px below its icons, so in Safari, where the browser's own toolbar already sits at the bottom, an empty strip showed under it. The bar now reserves only the space the phone reports: nothing in the browser, the home-indicator space in the installed app. Page content, sheets and save bars still sit just above the bar. The approved phone (390 px) screenshots change by that strip.
- **The demo's picture note fits its tile** (#197). On the demo, a tile with no picture said where the app gets pictures (*Loads from BrickLink when you add the set*), even in a list row's small thumbnail, where the sentence spilled out of the tile. The note now goes only into a picture box (`.bc-picture`, `.bw-picture`, `.check-picture`, `.scan-tile`) at least 120 px wide; any other tile keeps the app's short *No image*. The demo page check fails if a note leaves its tile.
- **The copy of the upstream changelog is gone** (#237). `docs/upstream-CHANGELOG.md`, the upstream project's release history kept from the fork, is removed; this changelog links the upstream repository for releases 1.4.2 and older. The credit in the README and THIRD_PARTY_NOTICES stays.
- **The Add search field sits right under the ways to find** (#221). On **Manage › Add**, step 1 put **What** and **Where** above the **Search**, **Paste**, **File** and **Photo** tabs and the search field, so on a phone the field you type into sat below the first screen. The field (or the file and photo choosers) now follows the tabs directly, then **What** and **Where** in one row (stacked on a phone), then the results and **Review**. Nothing else changed, and the Tab key follows the new order. See [Bring your collection in from another app](docs/how-to/migrate.md#by-hand).
- **The install and upgrade steps for 0.9.0 say `docker compose build`** (#238). The README and [Upgrade Brick Work](docs/how-to/upgrade.md) told you to check out `v0.9.0` and run `docker compose pull`, but that tag's `compose.yaml` builds the image from source and names no published one, so the pull fetched nothing useful. For 0.9.0 the steps now say `docker compose build`, and going back to 0.9.0 or older does too. The pull-first install, and the provenance check of the published image, start with the first release cut after 0.9.0. The tag itself is unchanged. [Back up and restore](docs/how-to/back-up-and-restore.md) and the Pages landing page say the same.
- **The Pages landing page no longer lists Collection sync as planned** (#239). Its roadmap row said sync was planned although the page's own section says it is available for Rebrickable and Brickset; the row now reads *Collection sync with BrickLink*, the part that is still planned. The page also said the catalogue uses your own keys; it now says what the README says: the catalogue comes from Rebrickable's public download and needs no key, and your keys add prices, pictures and sync.
- **An order e-mail line for a Technic figure in an unknown colour is no longer placed without a colour** (#239). A line such as "Red Technic Figure ..." for an old Technic figure that the catalogue holds as a part was placed as that part in no colour even when the e-mail's colour name was not known here, where an ordinary part says *The colour name is not known*. The figure now takes the no-colour path only when the line names no colour; a named colour that is not known leaves the line for you to match, with that message.
- **Lists say the right noun before the page's script runs** (#239). The Instructions list's subtitle and **Show** button said "instructions" until the script replaced it with "booklets", and the Wishlist said "1 wishlist" or "3 wishlist". Both now read "booklets", "1 wish" and "2 wishes" from the first paint, with the same nouns the script uses for every list.
- **The Wishlist chips have their gap, and Undo shows once** (#224). On **Planning › Wishlist** the list chips (**All lists**, **Wishlist** and your own lists) touched each other; they now have the 8 px gap of the other chip rows. After a save the line read *Saved · Undo* with an **Undo** button under it, so Undo showed twice. It now reads *Saved* with the **Undo** button beside it on one line, as in **Set Inspector**. See [Manage wishlists](docs/how-to/manage-wishlists.md#undo).
- **Get started and Statistics sit at the top of Tools & Admin** (#219). **Manage › Tools & Admin** now opens with two cards on the soft accent tint, **Get started** (with how many steps are done) and **Statistics**, side by side; on a phone they stack. The whole card is a link. The other groups follow in their usual order. See [Settings and maintenance](docs/how-to/settings-and-maintenance.md).
- **Statistics charts read at a glance** (#220). **Tools & Admin › Statistics** drops the cards: two charts sit side by side on a wide screen and one under the other on a phone, each row one line (label, bar, value). All four charts share one scale, so a bar means the same number of copies everywhere, with one note under them instead of one per chart. Each title states its point from your data ("Only 5 copies are complete", "Harbour is the biggest theme, 3 copies"), the headline reads "9 of 25 copies are incomplete", and completeness is in status order (Complete, Incomplete, Not checked, Sealed). After six rows a long tail folds into one outlined row such as "14 more themes". See [Settings and maintenance](docs/how-to/settings-and-maintenance.md#statistics). The approved Statistics screenshots change with it.
- **Jump search is calmer** (#216). In the search that opens from the top bar (**Jump to a set, copy, figure or part**, also **⌘K**), the highlighted result row is now a soft neutral grey-beige with normal text instead of a clay-tinted fill, and the search field has a thin clay outline instead of a heavy one. The colours follow the light and dark modes; nothing else about the search changed.
- **Found 1 on the set page, a missing-only check and a three-step end of a check** (#215, #222, #214). Every missing part card on a copy's page has **Found 1**, which lowers its missing count by one with Undo and leaves the loose stock alone; where the card has **Take N**, it is a text link under it. **Check missing only**, beside **Check parts**, starts a Set Inspector run over just the parts the last check left missing (**Missing part n of N**, each showing what was missing since the last check); the API's `start` takes `scope: missing`. The end of any check now shows three steps, **1 Skipped**, **2 Missing**, **3 Save**: you can check the skipped parts at once (`recheck` in the API) or leave them unchecked, then work through **Missing so far**. The progress bar in a check now spans the full width. See [Check a set's parts](docs/how-to/check-set-parts.md).
- **Found 1 no longer moves a part to the top of Missing so far** (#213). In **Set Inspector**, selecting **Found 1** on a part that was still missing afterwards, or answering a listed part again, moved it to the top of the **Missing so far** list, so the list reordered under your finger. A part already listed now keeps its place, and only a new gap goes on top. See [Check a set's parts](docs/how-to/check-set-parts.md).
- **Items in a storage link to their pages** (#223). On **Workbench › Storage**, the sets, minifigures, parts and other items in a storage were listed as plain text. Their names now open the item's page: a set opens on that copy, anything else on that loose stock. A lot's name stays text, since a lot has no page. In select mode a click on a name still selects the row. See [Storage](docs/how-to/storage.md).
- **Sort sets by date added** (#226). **Collection › Sets** has a new sort, **Date added**: when a set came into Brick Work, its newest copy for a set with several. Picking it turns the order to newest first; **Ascending** turns it round. A collection brought in by an import or a migration has one date for all of it, so those sets keep number order among themselves; sets added from then on sort by when you added them. The API's `GET /copies` takes `sort=added` too.
- **The Add review shows the set's picture** (#217, second half). Reviewing a set, minifigure or other item Brick Work had no picture of showed *No image* until after **Add**. The review now fetches and stores the picture when the row is shown (the same sources, order and budgets as the pictures step; a source without one is passed over), and the tile fills in place. The page's line now reads *Nothing is saved before the last step, except the pictures fetched to show you.* A part waits for its colour. See [Pictures and prices](docs/how-to/pictures-and-prices.md).
- **A new set's parts get their pictures** (#217, first half). Adding a set queued the picture of the set only; its parts and minifigures showed *No image* until **Refresh pictures**. Add, and a **Fetch from Rebrickable** follow-up, now queue the pictures the new copies' parts and figures still lack, as one bulk action that runs after your own work. See [Pictures and prices](docs/how-to/pictures-and-prices.md).
- **Jobs update live** (#218). On **Manage › Jobs**, only a job's progress bar was polled, and its last update set the bar back to the finished steps and read *Import queued* for a running job. The state, the step counts and the **Bulk actions** rows changed only on a reload. While anything is queued, running or waiting, the page now fetches its job area again every five seconds and swaps it in, so states, counts, bars and new jobs show without a reload. When a fetch changes nothing the wait doubles, up to a minute, so a job waiting hours for its budget does not re-render the page every five seconds; a settled page looks once a minute. The search, filters and an open **… failed · why** list stay as they are, nothing is swapped while you are on a button in the job list, and a hidden tab does not poll. A running import on **Add** now reads *Importing · pictures · 38 of 112* and its bar keeps the running step's share. See [Settings and maintenance](docs/how-to/settings-and-maintenance.md#jobs-and-bulk-refresh-prices).
- **Breaking for MCP clients: the wishlist tools have plain names** (#152). `post_wishlists` is `create_wishlist`, `patch_wishlists_id` is `edit_wishlist`, `delete_wishlists_id` is `archive_wishlist`, `post_wishlists_id_restore` is `restore_wishlist` and `post_wishlists_id_transfer` is `transfer_wishes`, with descriptions that say what they do. An assistant or script that calls an old name gets *unknown tool* and must use the new one; the arguments are unchanged. The REST API and every other tool are unchanged. This is the one rename before v1.0.0 ([ADR 0017](docs/adr/0017-api-v2.md), amendment of 8 Nov; [MCP](docs/brickwork/mcp.md)).
- **The Workbench workflows have new names** (#204). Part to Inventory is **Part Spotter**, Check Set Parts is **Set Inspector**, Part Picker is **Part Runner** and Matcher is **Matchmaker**, in the navigation, page titles, docs, the README and the site. The old names still work in the jump search, and the addresses, API operation ids, MCP tool names and CLI commands are unchanged.
- **The demo and every picture on the README and the Pages site are the built app, on an invented collection only.** The demo (`/demo/`) shows 22 invented sets (25 copies) over 1989-2023 and a dozen themes with part lists and 44 figures, 8 incomplete copies with missing and damaged parts and 3 missing figures, 90 kinds of loose parts and 33 loose figures in nested storage, gear, books and catalogs, four wishlists with maximum prices, three orders and prices, and it shows Gear, Books, Catalogs, the Matcher, Orders and the Manage pages. None of the design test data's real-named sets, figures, wishes or licensed parts appears: a catalogue item whose name matches the licence list in `brickwork/demo/showcase.py`, or a part that is a printed design, is left out. It lives in its own layer, so the screen gate's world and approved baselines are unchanged, and it is built with a fixed clock. Every item has an original black-and-white line drawing instead of an empty picture tile (`design/demo-art/`, drawn by `scripts/demo_art.py`, stored as ordinary pictures with the source *Demo illustration*). The screenshots (README, landing page, use cases, workflows) replace the design project's mock-ups with captures of the running app on that world (`scripts/site_shots.py`); the sync picture is the built Collection sync screen and is no longer labelled a preview.
- **Brickset metadata asks for many sets in one call** (#194). The **Fetching set metadata** step asked Brickset about one set per `getSets` call, and Brickset allows 100 of those a day, so a collection of N sets took N/100 days to import. The step now also takes the other queued or waiting jobs whose next step is Brickset metadata, up to 500 sets in all, asks for them in one call and finishes each job's step with its own result (a set Brickset does not return is *not found*, as before). The call counts once against the daily budget of 100, which is unchanged. If the call fails, the other jobs stay as they were.
- **The README describes 0.9.0.** Where we stand says 0.9.0 is the first stability release and v1.0.0 follows milestone B; the intro and **Catalogue, keys and jobs** say the catalogue is built from Rebrickable's public download (no key) with BrickLink item lists optional, instead of BrickLink's catalogue XML; the Shipped list names Get started, the other kinds, e-mail and XML orders and the item lists; the services table shows collection sync as available for Rebrickable and Brickset. The Pages landing page, the use-cases page and the tutorials index say the same (no more *Pre-1.0*, `brickwork migrate` or `docker compose build` as the install step), and the settings reference no longer names `bricklink-catalog.db`. [Settings and maintenance](docs/how-to/settings-and-maintenance.md#catalogue) has the details.
- **Install from the published image** (#78). `compose.yaml` now names `ghcr.io/ulef1005/brick-work` at the release's version and keeps `build: .` as the fallback, and the README install is pull-first: clone the release tag, make `data/` writable for uid 1000, then `docker compose pull`. Upgrade is checking out the new tag and `docker compose pull`; going back to 0.8.x or older, which have no published image, still needs `docker compose build`. [Upgrade Brick Work](docs/how-to/upgrade.md) and [Back up and restore](docs/how-to/back-up-and-restore.md) say the same. A test keeps the image tag equal to the app version.
- **A BrickLink figure that the catalogue knows as a part is that part** (#150). BrickLink files the old Technic figures (`tech028`, `tech039`) as minifigures, while the catalogue links the same numbers to Rebrickable parts. A BrickLink order line, a BrickLink subsets list or `GET /lookup?provider=bricklink&kind=minifigure` that names such a number found no minifigure and made an unidentified one. When no minifigure holds the number and exactly one part holds the very same BrickLink number, it now resolves to that part, as a part line in no colour. A catalogue minifigure still wins, several parts keep today's behaviour, and no other number is tried. Figures already made unidentified keep resolving to themselves.
- **The Wishlist on a phone has no empty gap under the list chips** (#141). At phone width, Planning › Wishlist left about half a screen of empty space between the list chips and the **Manage lists**, **Export**, **Print** and **Download CSV** buttons. The buttons and the search now follow the chips directly, as on a wider screen. Two approved phone screenshots (light and dark) change with it.
- **Counts of one use the singular** (#180). The Sets page said "1 copies", the Catalogs page "1 catalogs", and the filter panel's button "Show 1 parts" and the like. A list's subtitle and its **Show** button, and the Wishlist count, now use the singular for exactly one and the plural otherwise, for every kind.
## Brick Work 0.9.0 (schema 4.2) · 2026-11-05
The first stability release (milestone 1.0): a stable schema with tested upgrades from 0.5.x, a written upgrade and backup guide, and the first published container image, `ghcr.io/ulef1005/brick-work` (amd64 and arm64; see [Releases and the container image](CONTRIBUTING.md#releases-and-the-container-image)). A 0.8.x collection (schema 4.1) is upgraded in place when 0.9.0 first opens it read-write: three columns are added and nothing else changes. The upgrade is one-way: 0.8.x cannot open a 4.2 file and stops with *a 3.3 file must be migrated*; ignore that hint, which is meant for 3.3 files. So back up `brickwork.db` and `secret.key` before you upgrade, and to go back, restore that backup ([Upgrade Brick Work](docs/how-to/upgrade.md), [Back up and restore](docs/how-to/back-up-and-restore.md)). `catalogue.db` is not touched.
- **New items get their picture from every path that adds them** (#183). Only **Add** queued the picture download; an item placed by an order line into loose stock, by **Part to Inventory**, by **Import from BrickTracker**, by a **Collection sync** pull or by **Found** in the Workbench showed *No image* until you pressed **Refresh pictures**. These paths now queue the **Downloading images** step for an item that has no picture for a colour you hold, once per item and never for an unidentified one. See [Pictures and prices](docs/how-to/pictures-and-prices.md).
- **Gear, Books and Catalogs get their own icons, simpler filters and a visible phone strip** (#153). The sidebar entries for the three kinds no longer borrow the Parts icon: Gear is a shopping bag, Books an open book and Catalogs a catalog over a second page. Their lists drop the filters that only mean something for parts (**Problems**, **Stock: In sets only** and **Theme**), **Group by** no longer offers **Theme**, and the search box says *Name or number*. In the grid, a card without a picture shows its number once, in the picture tile, instead of again above the name; the list view keeps it on the name line, where a long ISBN stays readable. On a phone the top strip scrolls the current entry into view, also after the window is narrowed or the phone turned, so Catalogs no longer sits off the edge. Five approved phone screenshots change with it.
- **Brickset's old New and Used values are marked legacy** (#173). A collection moved from 3.3 keeps Brickset's set values from that time. Brick Work 3.3 read them from Brickset's website, and Brickset's API has none, so they are never refreshed. The item page's **Price records** now mark them *legacy · Brickset 3.3, not refreshed*, and so does the **Estimate** when it comes from one; the Prices screen labels them the same. Market prices stay BrickLink's. See [Pictures and prices](docs/how-to/pictures-and-prices.md).
- **An empty kind's page keeps its place in the menu** (#171). Opening **Gear**, **Books** or **Catalogs** when the collection holds none, for example from a link, showed no active entry in the sidebar, and on a phone the bottom bar marked **Manage** and showed its links. While such a page is open, its kind now shows under **Collection**, marked as the current page. On other pages an empty kind stays hidden, as before.
- **Upgrade, backup and restore are written down and tested** (#80). New guide [Upgrade Brick Work](docs/how-to/upgrade.md): what each version does to the schema, which step you run yourself (`brickwork migrate4` for a 0.5.x collection), how to check the result and how to go back. The README has **Upgrade** and **Backup and restore** sections. A new test takes a 0.5.0-shaped (3.3) collection through `migrate4` and the 4.0, 4.1 and 4.2 steps, counting rows and running `brickwork check`'s checks at the end. [Back up and restore](docs/how-to/back-up-and-restore.md) now names the API v2 paths of the export and the check (the v1 paths answer 410).
- **Schema 4.2.** One in-place step adds three columns: who holds a job (#167) and why an order line was not placed (#155). A 4.1 collection is upgraded the first time it is opened, as 4.1 was; Undo of changes made before the upgrade works as before. See [Schema notes](docs/brickwork/schema-notes.md#42-job-claim-identity-167-and-order-line-reasons-155).
- **Order e-mails: more lines get suggestions, and an undone import can be pasted again** (#155). A line no item matches exactly now offers **Could be** candidates from the BrickLink item lists: the longest list name that starts the line's name (a seller's note such as "micro scratches" written after it), then names that hold all of its words (an item BrickLink renamed). They are suggestions only; you still choose. The API's order line has `unplaced_reason`, in words, for a line that is not placed. Undoing an import and pasting the same e-mail or XML again brings the document back as a draft, where it was reported as already imported. **Export orders** with **One per order** lists only orders that have saved lines. See [Receive an order](docs/how-to/receive-an-order.md).
- **A job left running at a restart runs again** (#167). If the app stopped mid-job and, after the restart, the old process number belonged to another program, the job could stay **running** for good, and a catalogue job would refuse every new one as already running. A job claim now records the machine's boot and the start of the process that holds it, and a claim whose process is not that one is taken over. Jobs left running by 4.1 are taken over at the first start on 4.2. A call that was being sent when the process was killed is counted again when the step restarts: restart between jobs, or once the day's budget is used up, to lose none ([Sources](docs/brickwork/sources.md)).
- **Brickset's minifigure CSV** (#146). **Add** › **Minifigure** › **File** reads Brickset's minifigure export: with **Loose** the figures you own loose (Brickset's loose count, so the figures in your sets are not added twice), with **Wishlist** the figures marked wanted. Before, it was refused with *CSV needs part_num, set_num or minifig_num*. See [Bring your collection in from another app](docs/how-to/migrate.md).
- **Clearer CLI, MCP and order messages** (#152). `brickwork mcp` calls `http://127.0.0.1:3333/api/v2` by default, the install's own port (was 5000), and a URL that answers with the web app says so instead of returning the page. `brickwork settings set incomplete_discount_pct` with a word gives the range message. A second `brickwork wishlist undo` of the same change says **Already undone**. `python -m brickwork.store4 jobs` names an item job's item (was *item None*). Importing a placed BrickLink order whose items BrickLink has purged says so and suggests pasting the e-mail (was *No order lines were found*).
- **What is this? and the import banner** (#142, #145). **Workbench › What is this?** now lights **Part to Inventory** in the sidebar, the Workbench tool that also identifies a part by photo (was **Tools & Admin**). After **Import from BrickTracker**, the banner counts loose items by line, as the preview does, with the pieces beside them: *351 loose items (435 pieces)*, or just the lines when they agree. Before, it showed the pieces as items.
- **Workbench lists show their count** (#174). **Recent saves** on every Workbench tool, and the Part Picker's **Pick for which sets?** list, showed `` as text where the number of shown rows belongs. They now show the count.
- **Focus stays in a sheet you have just opened** (#174). Closing a sheet with Escape and at once opening another could move the focus back to the sidebar, out of the new sheet. Focus now goes back to where it came from only when no sheet is open.
## Brick Work 0.8.1 (schema 4.1) · 2026-11-04
Two fixes found while setting up and testing milestone B. No schema change: a 0.8.0 collection opens as it is, and going back to 0.8.0 needs no restore. Keep the new `secret.key` with `brickwork.db` in your backups.
- **Orders with items only BrickLink has** (#154). An order line naming an instruction booklet or a BrickLink-only part (some sticker and printed parts) could not be matched, so the order could not be reviewed or saved. When the BrickLink item lists are loaded (**Tools & Admin → BrickLink item lists → Load lists**), such a line now gets a new item of Brick Work's own, named as BrickLink names it, when the order is read and when you match the line by its number. An e-mail's "(Instructions) for Set 7946" names the set's one booklet (`7946-1`). A number another item already holds is never made a second time. Undoing the import leaves the new item, unused. See [Receive an order](docs/how-to/receive-an-order.md).
- **Key entry works on a fresh install** (#144). Without `BW_SECRET_KEY`, Brick Work now makes its own encryption key on first use and keeps it in `secret.key` beside `brickwork.db` (mode 600), so **Get started** step 2, **Catalogues & keys** and `PUT /sources/{source}/key` no longer need a setting. `BW_SECRET_KEY`, when set, still wins and no file is made. If the data folder cannot be written, key entry stays disabled and the message says to set `BW_SECRET_KEY` (with the command that makes a key) or make the folder writable. A damaged `secret.key` fails closed and is never shown or replaced. Keep `secret.key` with `brickwork.db` in your backups: without it the stored source keys cannot be decrypted and must be entered again (the database export leaves them out anyway). See [Back up and restore](docs/how-to/back-up-and-restore.md) and [Settings](docs/reference/settings.md).
## Brick Work 0.8.0 (schema 4.1) · 2026-11-03
Milestone A, onboarding: a fresh install reaches its own collection through one guided flow, **Get started**. No schema change: a 0.7.0 collection (schema 4.1) opens as it is, and going back to 0.7.0 needs no restore. See [Bring your collection in from another app](docs/how-to/migrate.md) and [Settings and maintenance](docs/how-to/settings-and-maintenance.md).
- **No key yet is not a failure** (#139). A step that only lacks a key (a BrickLink price, Brickset's set data) now ends as done and waiting for the key, so a job no longer shows **failed** on an install where you skipped the keys. Jobs says **Waiting for a key · runs once it is stored**. Storing the key puts those steps back in the queue, and so does a start with a key set in the environment. A key the site refuses still fails.
- **BrickLink part numbers that name several parts** (#140). BrickLink files some Rebrickable parts under one number: 3001 also names the old mould 3556. An XML line for such a part now takes the part with the same Rebrickable number and says so in the review. Without one, it names the candidates. Before, it was refused as *Colour needs manual matching* although the colour was known.
- **Bulk actions are one job** (#127): **Refresh pictures**, **Bulk refresh prices** and fetching missing booklets show as one row in **Jobs**, with its progress (done, failed, cancelled, to go), the failed items with their reasons, and **Cancel** for what is left. Your own work runs first: an Add's import and part list, or a single **Update price**, go before the batch's next item, so a new set no longer waits up to 45 minutes behind a picture refresh. The per-source budgets hold as before. See [Settings and maintenance](docs/how-to/settings-and-maintenance.md#jobs-and-bulk-refresh-prices).
- **Get started** (#83, milestone A): the first run is one flow of four steps, in this order: **Load the catalogue**, **Set up your keys** (optional), **Bring your collection in** and **See how the inventory works**. Every step can be skipped for now and taken back. The flow resumes at the first open step, and after the first run it opens again from **Tools & Admin › Get started**, with how many steps are done. Before, the keys came first and the BrickTracker import was a fourth step. The catalogue step explains what the catalogue is (what it holds, Brick Work ids, what comes later), and once it is done shows how many items and colours it holds and the date of its files (#85). The keys step carries the key cards of Catalogues & keys: what each key gives, a link to where it is made, Test and save, and the site's reason when it refuses one; it says what is missing without keys (#84). Step 3, **Bring your collection in**, asks where your collection lives today: a Rebrickable or Brickset account (Collection sync), a BrickLink XML file, Import from BrickTracker, another app's BrickLink XML or Rebrickable CSV, or by hand. Each opens its existing path, which offers **‹ Back to Get started**. There, a summary counts what came in, how many copies still lack condition and completeness, and links each **imported from …** tag. **Brickset's My Sets CSV** is read by Add (File): owned sets with their count, split by new and used, or wanted sets to a wishlist; a file without the columns it needs is refused by name (#12). **Add** opens on a mode given in its link (`?mode=file`). New guide: [Bring your collection in from another app](docs/how-to/migrate.md) (#86). Step 4, **See how the inventory works**, is five short cards (sets and copies, completeness, loose stock and lots, the workflows at the table, an assistant), each linking to the screen it explains, with the important words in bold; **Got it** marks it read (#87). See [Settings and maintenance](docs/how-to/settings-and-maintenance.md).
## Brick Work 0.7.0 (schema 4.1) · 2026-11-03
A wish gets a maximum price with box, which brings schema 4.1. A 0.6.0 collection (schema 4.0) is upgraded in place when 0.7.0 first opens it read-write. The upgrade is one-way: 0.6.0 cannot open a 4.1 file, so back up `brickwork.db` before you upgrade. To go back, restore that backup ([Back up and restore](docs/how-to/back-up-and-restore.md)).
- **Schema 4.1.** A wish has a maximum price with box (#131). On a wish's page, **Edit** has **Max price with box** and its currency beside **Your max price**. The price card's footer shows **Your max price** and **Max with box**, each a button to its field, or *not set*. Only a set wish has one. In the API, a set's price with box is compared with the max with box, or with your max price when that is empty: the Wish has `max_price_boxed`, `max_price_boxed_currency` and `under_max_boxed`. A 4.0 collection is upgraded in place when the app starts (or at the first write); a read-only instance (`BW_READ_ONLY`) or a read-only open refuses a 4.0 file until it has been opened read-write once. Undo of a change made before the upgrade still works. Every wish's ETag changes once at the upgrade, so an API client re-reads a wish before its next edit. The wish card keeps the set's estimate beside the two maxima, and the wishlist CSV export has two new columns, **Max with box** and its currency, before **Note**. A stale 4.0 file (made before #124) is refused before the upgrade writes anything, and an upgrade that cannot finish says to open the file again, never to migrate it. The BrickLink wanted list has no such field, so the XML export and import leave it out. See [Collection model](docs/concepts/collection-model.md#wishes-and-wishlists).
- **First run** says how long loading the catalogue takes: about a minute. Measured on a Raspberry Pi 5 from an empty data folder: 51 s, most of it building the catalogue (#126).
- **Faster** (#133, #134), measured on a benchmark collection: the jump search by a number 204–262 ms → 29 ms, the item page 185 → 79 ms, the wish page 149 → 58 ms, the Matcher 164 → 71 ms, and app start about 1,000 → 660 ms. A test fixes the outputs of these paths, so the speed-ups change no result.
- **Set estimate** no longer looks like a link when it does nothing: without edit rights the footer label is plain text (#131).
## Brick Work 0.6.0 (schema 4.0, API v2) · 2026-10-28
The data-model release: the collection moves to schema 4.0 with a separate local catalogue, and the API to v2. **Breaking:** API v1 answers 410 except `/api/v1/health`; clients move to `/api/v2` ([API v2](docs/brickwork/api-v2.md)). A 0.5 collection is moved with `brickwork migrate4` on a copy first ([CLI](docs/reference/cli.md#migrate4)); the 3.3 file is never changed.
- **Add** says what each step does: numbered steps (Find and select, Review and add, Added), **Select** instead of **Add to list** on a search result (tap again to unselect), a bar that keeps **Review** in view, and an Add button that names where the items go. An item you already own or wish waits for **Add another copy** or **Skip** before Add is offered; before, Add was offered and the save failed. The wishlist options show only for the wishlist, and the item types wrap on a phone.
- The site's API reference shows API v2 (#125).
- An order keeps its currency when its document has no total (M.12, #114). An order e-mail has only line prices, so its order now stores EUR and the line prices show it instead of **Currency not recorded**; a total still needs its currency. Orders stored before get the currency from their raw document when the app starts. See [Receive an order](docs/how-to/receive-an-order.md).
- `brickwork check` and the Database page check that the two files belong together (M.12, #114): the collection is bound to the catalogue, every catalogue item and colour in the collection has the same born key there, every part list it names exists, and the digests agree. Each is a named check; a failure names the fix. API v2 `/database/check` is unchanged. See [Back up and restore](docs/how-to/back-up-and-restore.md#check-the-database).
- First run needs no key (M.12, #114). **Sets** on an empty install offers **Download the catalogue** (or **Build from today's files** when today's download is on disk, or **Activate the catalogue** for a file that is there but not switched on): one background job fetches Rebrickable's files, builds the catalogue, creates and seeds the collection file if it is missing, and activates and binds the catalogue. Add by number and Check Set Parts then work with no key. Keys became an optional step. See [Settings and maintenance](docs/how-to/settings-and-maintenance.md#catalogue).
- Part lists on the copy page and in Add (M.10 A). The **Part list** card names the list a copy uses and offers **Switch** with what changes and which marks will not carry over; a copy can ask for BrickLink's list and waits on the fetch (**Cancel** stops it); a copy built before a re-download says what changed; retired lines are kept in a collapsed note; a figure line names where its parts came from. The Add review has a **Part list** picker (with **Use this for new copies of this set**), warns about a part with no known picture, and offers **Fetch from Rebrickable** or **Add as unidentified** for a number not in the catalogue. A missing picture is a quiet dashed tile in light and dark. See [Choose and switch a part list](docs/how-to/part-lists.md).
- `main` is the 4.0 line: the app runs on the 4.0 store; API v1 answers 410 (except `/api/v1/health`) until API v2 (M.11).
- The collection lists, the item page, the wish, lot and purchase-place pages and the jump search run on the 4.0 store. An item page is `/collection/item/`; the old number URLs redirect to it. Screens show the Rebrickable number first with the BrickLink number beside it where it differs, a BrickLink-only item marked, and an unidentified item by its name; search takes both numbers. Conditions and completeness are words on every screen.
- Check Set Parts and the collection's writes run on the 4.0 store: a check keeps its answers by part line, so switching a copy to another part list keeps a check in progress; checks in progress before 4.0 start again. Editing, archiving, restoring and Undo (one receipt per save), own photos and captions, new part lots and the loose-stock merge on edit work as before. A figure's own parts are checked at `/workbench/check-parts//figure/`. Own photos are served at `/manage/images/`.
- The Workbench runs on the 4.0 store: Part Picker, Matcher (Give and Take), Found it, instruction booklets, Part to Inventory (the page, its save and Undo), Storage with its history, and the printable lists with their CSV. A move writes one receipt and a `stock_transfer` row naming it. Matching is by item: a part, a figure, or a set's booklet or box meets the loose stock of the same item and colour, and an item still marked to check is matched like any other (the **Needs a check** list is gone). Instruction booklets are added by the numbers of booklets you already hold or the BrickLink item list names. Photo recognition in Part to Inventory comes back with Identify.
- Pictures, prices and keys run on the 4.0 store. The import job fetches a picture (from the Rebrickable catalogue, BrickLink and the Rebrickable API, in the order of the setting; parts try the catalogue first), Brickset metadata and retail price, and BrickLink prices for every colour a part is held in; calls are counted per source and day. An item with no BrickLink number ends the price step as **unidentified** instead of failing. The Prices screen works by item id and its Update buttons queue the same step. **Fetch from Rebrickable** now makes the copies (or the wish) the Add row asked for when the job is done, with its own Undo, and says so on the Add page. Updating from 3.3 turns the default picture order into the new default and splits a changed one into the catalogue and the API. See [Pictures, prices and where they come from](docs/how-to/pictures-and-prices.md).
- Planning, wishlists and money run on the 4.0 store. **Complete sets** ranks the copies with a gap by what a repair gains per unit spent, from the cached BrickLink quotes and the repair settings; a pin and the settings are saved and undone as events. Its **Export** names every line by BrickLink number and colour; an item BrickLink has no number for is left out of the file and named in the message instead of stopping the export. The **Wishlist** screen, the named lists (create, move, copy, archive, restore, Undo) and the BrickLink XML and Rebrickable CSV exports work by item id; a wish that cannot be named in the chosen format is left out and listed in the export summary. A wish id is now a uuid. Part-out value, retail on the wishlist, wish progress, exchange rates (a rate or the ECB's, converted figures marked ≈) and manual prices (Undo voids the observation) work as before.
- Orders and Identify run on the 4.0 store. An order document (BrickLink XML, the store's e-mail, the BrickLink API) is placed line by line on items through the BrickLink type, number and colour number, looking in your collection's links and the catalogue together; a key that names several items is never guessed: the line stays unmatched, offers its candidates under **Could be**, and you choose. Review, counting, allocation to a copy's gap or to loose stock, hold, reconcile, the short list and the CSV export work as before; every save is one event with Undo, and an undone import keeps its document and lines as provenance. A total comes with its currency, and a currency can stand without a total (the e-mail's EUR; M.12). A set line cannot be allocated to loose stock (add the set as a copy). **What is this?** and Part to Inventory recognise a photo through Brickognize and list a number that names several items apart instead of choosing one. **Manage → Add** takes a photo again: it offers the matches, adds nothing until you choose one, and keeps the photo only with **Keep photo**.
- Tools & Admin runs on the 4.0 store: **Settings** (currencies, retail country, the source order, freshness rules; a save is one event with **Undo**), **Catalogues & keys** (sources with their budgets, write-only key cards, and a Catalogue card that says what is loaded and names the command when the catalogue is missing or not activated), **Values**, **Jobs**, **Bulk refresh prices**, **Statistics**, **Database** (the check keeps its result as `maintenance:database_check`; the export is the collection file), **Instruction files** (a PDF belongs to a set; a booklet shows the files of the set with its number), **Identify history** and **API tokens**. A new collection starts with the freshness rules of 0.5. The catalogue is built on the server with `python -m brickwork.store4`, not from the browser; the app's `catalogue` command is the same operation. `brickwork migrate4` is `python -m brickwork.store4 migrate`; the `brickwork wishlist` commands take wish ids as text. The BrickLink catalogue XML loader and the colour load are gone with the catalogue builds.
- **Unresolved items** (identity admin) runs on the 4.0 store: the list holds the local items that may be a catalogue item, the links and statuses to check and the items with no number, and each row offers its decisions (merge, confirm or reject a link, link a BrickLink number, keep as unmatched, move to another kind), each with Undo. A merge moves every reference of a local item onto the catalogue item in one step and unmerge puts all of it back exactly. See [Resolve unresolved items and merge items](docs/how-to/merge-items.md).
- Collection sync and Import from BrickTracker run on the 4.0 store (M.8j). A pull places each item by its kind (a set as copies through the part-list preselection, other kinds as loose stock) with one receipt and Undo; a send names an item by its Rebrickable or BrickLink number and lists an item with no number as not on the site; the link is carried by the migration and the first sync compares everything. The BrickTracker import is rewritten on Rebrickable numbers: copies through Add's path with BrickTracker's marks on the part lines, loose parts and figures as stock, lots, wishes and flags, one receipt, one Undo. **`brickwork migrate` and the "Build a new database instead" form are removed** (a 3.3 collection moves with `brickwork migrate4`). The demo is rebuilt on a 4.0 collection. Two guards join the suite: no code uses `(item_type, item_no)` as an identity, and every query on part, figure and loose-figure lines leaves retired rows out.
- Tools & Admin looks after the catalogue lifecycle (M.10 C, #112): **Catalogue** (the current build, **Re-download** and **Rebuild** as background jobs with progress, what changed and the copies whose part list changed), **BrickLink item lists** (load from a folder, what the last load did), **Mappings and coverage** and the **Migration report** with downloads. Settings gets **Prefer BrickLink's part list for new copies** and a reorderable **Picture source order**. A catalogue swapped in but not yet reconciled is reconciled when the app (or `brickwork worker`) starts, as a job the drainer runs; `BW_MIGRATION_REPORT` names the report folder.
- `GET /api/v1/health` also returns the app version (`app`), so monitors and update checks need one call.
- Error responses never carry database or operating-system error text (#76).
- **Security:** `certifi` 2026.10.19, outside the range of the emSign root CA advisory (#81).
- An API write that queues a catalogue import (adding a set, part or wish, or moving a wish to owned) now starts the import worker; before, the import waited until something in the web app started it (#121).
## Brick Work 0.5.0 (first public release) · 2026-10-18
The first public release: a self-hosted web app for a brick collection that helps at the table, not only in the inventory. It is pre-1.0; the schema and the API can still change between minor versions.
- **Collection:** sets, minifigures, parts, instructions and boxes, with copies, storage, purchase details, statuses, tags and notes, in one SQLite file. Every list is searchable, filterable and sortable.
- **Check Set Parts:** count a copy's parts at the table, in inventory, colour or largest-first order, with a missing pass, Undo and resume.
- **Part Picker:** choose unfinished sets and get the parts you can pull from your sorted loose stock; **Complete sets** shows which sets that stock can finish.
- **Matcher, Part to Inventory and What is this?:** match loose stock to the sets that need it, and identify parts and minifigures from a photo (Brickognize) with what you already own of them.
- **Orders:** import a BrickLink order, an email or an XML file, count what arrived and allocate it to sets or loose stock.
- **Planning:** wishlists with a maximum price per wish, named lists and BrickLink XML or Rebrickable CSV export; prices from BrickLink, Rebrickable and Brickset with your own keys, including a part-out value.
- **Storage, printable lists, statistics** and an installable web app with light and dark themes.
- **For scripts and assistants:** a versioned REST API with scoped tokens and an Undo link on every change, an admin CLI and an MCP server.
- **Import** an existing collection from an older tracker database, read-only.
- **Self-hosted with Docker Compose.** No telemetry and no account with this project; no catalogue data or images are bundled, each install uses its owner's keys.
Source and compose files only; there is no container image for this release. Install: see the README.
### All changes in this release
- Kit controls: no control falls back to the grey bordered look any more. The off-design secondaries (Print missing parts, Pin, Take, the Matcher's help links, the ledger Filters) are the kit's outline clay, the Manage pages use the kit's neutral secondary, the value chips are filter chips, and every `.primary` is the kit's clay primary. The wish page follows frame 7b: Move or copy to another list is a quiet action in the Wanted module that opens a sheet with the lists as segments, and Undo is a quiet action.
- Error responses no longer carry database, file-system or other internal error text (#76): messages written for people still show, anything else is logged on the server and replaced by the handler's plain message.
- Tools & Admin → About: version, licence, source link, data credits, the LEGO disclaimer and BrickLink's required notice (also in THIRD_PARTY_NOTICES).
- What is this?: Enable camera opens a live preview with a guide, as in Part to Inventory; the next press (or Space) takes the picture and identifies it. A saved photo stays the quiet alternative, and the Part/Minifigure switch is a kit segment.
- README: how to get BrickLink, Rebrickable and Brickset keys and the limits each service sets; the install starts with the clone and asks for the password before the first start (found by a fresh-machine test).
- Demo: four copies hold a running set check made with the app's own checker at build time (first part, about 40 % answered, finished with six missing, finished clean), so Start, Resume and the finish screens show real states.
- Demo: the missing-picture tiles keep the item number and say where pictures come from: a set's picture from BrickLink when the set is added; part and figure pictures from Rebrickable or BrickLink with Refresh pictures.
- Publication review: the design test data uses invented owner names, and the private-material scans keep only generic patterns in the repository; a maintainer's own patterns come from `BW_PRIVATE_PATTERNS_FILE`.
- Static demo: `scripts/build_demo.py OUT --base /brick-work/demo/` renders the Sets, Minifigures and Parts lists, set and part pages, Check Set Parts, Part Picker, Part to Inventory, Wishlist, Prices, Complete sets and Storage with the real templates on an invented collection (no pictures, no network). Writes say "Demo with sample data: changes are not saved".
- The public snapshot keeps executable files executable (`scripts/check.sh`, `bin/brickwork`, `entrypoint.sh` had lost the bit in the first snapshot).
- Migration proof: the reviewed collection counts are re-pinned for the cutover (two set price lookups since the review added two items with their price, source and search rows).
- Item pages: Update price queues a market price refresh for that item (every colour it is used in) and says so. It opened the bulk refresh page, which lists stale prices only and so showed 0 for an item never priced.
- Storage: the selected storage is an inset, rounded band rather than a flush strip, and the list count no longer counts the Not stored row, so it matches the header (2 storages, not 3).
- Part Picker: the set list shows 15 sets a page with the Collection pager (#52). Ticked sets on other pages stay chosen.
- Tools & Admin: every option carries one short sentence on what you can do there.
- The app footer no longer carries the upstream credit; the README credits the upstream project in its closing Acknowledgements section.
- Wishlist management restyled after BrickLink's Wanted Lists: the lists as chips with counts, one row of outline actions (Manage lists, Export, Print, Download CSV), Export and Manage lists as sheets, and a selection bar with Move and Copy that shows while wishes are ticked.
- Part-out value: shown only when at least 90% of a set's lines have a price. With fewer, the price card says "Not enough prices yet" and offers Fetch part prices, which queues a market price refresh for the parts without one. It used to sum whatever few lines were priced (4 of 54 lines gave €0.18).
- Check Set Parts, running check: the header follows frame 3e (set line, name, progress). The bordered Choose another copy button is gone (it belongs to the Check this copy step, as a link) and the status pill shows at the finish only.
- Documentation site for GitHub Pages (`scripts/build_site.py`, `.github/workflows/pages.yml`): the README, the API reference from the OpenAPI contract, and a new MCP guide whose tool lists are generated from the server. A settings reference in the README lists all 38 settings. `tests/test_docs_drift.py` fails when the docs name a setting, a command or a link the code does not have, or the code reads a setting the reference does not list.
- Check Set Parts, *Largest first*: orders by the part's physical size (from its name and category: a baseplate, then a 2 x 4 brick, then a 2 x 4 plate, then a 1 x 1 plate), then by count and colour. It ordered by count alone, so twenty 1 x 1 plates came before the baseplate.
- Review follow-ups for #62 to #68: the add page's photo upload keeps its 8 MB; money such as -2.00 stays a number in CSV exports; the plan receipt search runs only for a plan file marked applied without a marker, and keeps the event id; the CLI says how to fix an unwritable data folder (and the README chowns it before setup); the session key file appears whole or not at all and a damaged one is replaced; the token form id is kept in the database.
- Small web defects (#68): query arguments named like url_for's own options (`_external`, `_anchor`, `kind`) no longer give a 500 or a link to another host; purchase places and collection sync show the usual notice before setup; reloading the page that showed a new token's secret does not create a second token; a read-only instance exports without writing an event; the embedded job drainer cannot miss a job queued while it stops.
- Sessions (#66): changing the password ends every open session; a cross-site link cannot log you out (logout also takes POST); `BW_SECURE_COOKIES=1` sends the session cookie only over HTTPS; every response carries `X-Content-Type-Options`, `X-Frame-Options` and `Referrer-Policy`; the navigation counts no longer show before login.
- Container (#65): runs as uid 1000 with the code read-only, writes only to `/app/data`, has a health check on `/api/v1/health`, and stops with the fix when `data/` is not writable (**after an upgrade from a root image: `sudo chown -R 1000:1000 data`**). Gunicorn's timeout is 120 s. `compose.local.yaml` publishes on `127.0.0.1`.
- CSV exports (lists, orders, wishlists) open as text in a spreadsheet (#67): a cell starting with `=`, `+`, `-`, `@`, a tab or a carriage return gets a leading apostrophe.
- Review follow-ups for #59 to #64: a warning when forwarded headers arrive with `BW_TRUSTED_PROXIES=0`; the check API returns the checker's own event and its Undo link again, and a repeated operation from another token is answered, not a 500; `brickwork mcp --transport http` also refuses an open install; browser uploads get their own caps (8 MB identification, 2 MB add list, 1 MB order files, 15 MB photos, 20 MB PDFs); the session key file is created once even with two workers; plans applied before this release are recognised by their receipt, markers are pruned after three days, and a repeated apply says so instead of repeating the counts.
- A sync pull or an import plan applies once (#64): two submits, or a crash between the commit and the plan file, applied it twice. The applied state is now a database row written in the same transaction.
- `POST /api/v1/copies/{id}/check` works after its first call (#63): it failed with 500 on any store that has the "Set is checked" flag. Its events now record `via='api'` and the token name, and the response's `event_id` names the API operation.
- Request bodies are capped before they are read (#62): 4 MB for JSON and forms, 8 MB for identification photos, the import limit (`BW_IMPORT_MAX_MB`, default 200) for other uploads; larger bodies answer 413.
- The legacy import's "Build a new database instead" form (#61): the browser form only reads and writes inside the data folder; `brickwork migrate` keeps taking any path.
- First run (#60): an empty `BW_AUTHENTICATION_KEY` no longer breaks every page; a session key is generated into `data/session.key`. **The server refuses to start without `BW_AUTHENTICATION_PASSWORD` unless `BW_ALLOW_NO_LOGIN=1`**, and `compose.yaml` publishes the port on `127.0.0.1` only.
- Forwarded headers are trusted only with `BW_TRUSTED_PROXIES` set (#59): a client reaching the app directly can no longer pick its own address to escape the login and token back-off. **Behind a reverse proxy, set `BW_TRUSTED_PROXIES=1`.** MCP over HTTP counts each client on its own.
- Release files: README status, privacy, repository and trademark sections; `CONTRIBUTING.md`, `SECURITY.md`, `CODE_OF_CONDUCT.md` (Contributor Covenant 3.0), `THIRD_PARTY_NOTICES.md` and the Patrick Hand licence; issue templates; a CI workflow running `scripts/check.sh` with read-only permissions and pinned actions; Dependabot; `scripts/public_snapshot.py`, which exports the public tree without internal build notes and checks it for private material and secrets.
- Remaining upstream code replaced: one settings module (13 settings) and one `.env` loader, login in `web/auth.py`, unused packages and Socket.IO hooks removed, upstream user docs and screenshots removed, upstream history moved to `docs/upstream-CHANGELOG.md`.
- Settings no longer read (setting them does nothing): `BW_UI`, `BW_TIMEZONE`, `BW_HIDE_MISSING_PARTS` (use `BW_HIDE_ALL_PROBLEMS_PARTS`), `BW_HIDE_TABLE_CHECKED_PARTS`, `BW_BRICKLINK_LINKS` and the other upstream `BW_*` display, pagination, folder and socket settings.
## 4.6d-1 · Collection design comparison (partial)
- Capture the Collection design frames and deployed app; record differences, approved deviations and blocked visual checks.
- Align open cards, compact detail heroes, identity order, mobile module order, Info facts, Prices typography, status dots and edit segments with the kit.
- Keep existing database reads, transactions, events and Undo; post-change screenshots and remaining component differences are still outstanding.
## 4.5v · list cost and quiet name refresh
- Select list pictures by source priority and recency without filesystem checks; keep containment and missing-file checks on the image route.
- Refresh only changed names and source properties, and report changed identities rather than every catalogue match.
- Label wishlist quantities “wanted”; reserve “missing” for collection defects.
## 4.5u · names, colours and identity
- Map catalogue names into migrated items with Rebrickable fallback and property provenance; refresh held names on later catalogue loads.
- Load validated BrickLink colour RGB in Catalogues & keys and show colour coverage.
- Show identity numbers once, omit absent names and leading separators, and share unsigned defect labels between templates and browser rows.
- Restore versioned PWA assets offline, focused-button keyboard activation, missing-asset URL fallback and the Part to Inventory shortcut.
- Fix review findings: start Planning refresh after commit, require login for all Planning actions, classify migrated/reference pictures consistently, preserve merged stock defects and tolerate later receipt quantities.
- Preserve Planning export selection and Collection row/image nodes across filtering and sorting, restore keyboard actions after button focus, show batch expiry and bulk picture failure reasons, and version Brick Work assets by deployed file mtime.
- Make Settings SQL tracing and navigation-count tests measure the actual paths; document the manifest shortcut blocked by the task's path allowlist.
- Suppress cooperative yields during SQLite transactions; materialize inventory per copy and yield after commit.
- Recover same-pid import owners that this process did not claim, without a schema change.
- Poll future budget waits at their earliest deadline, capped at 60 seconds; require editable mode and CSRF for selective and all-failed-step Retry.
- Yield between picture identities and other import work units so gevent can serve requests during long jobs.
- Start checkpoint recovery at app startup and retain the existing atomic process-owner claims.
- Show interrupted import jobs with Resume; preserve completed work and budget deadlines.
- Batch Import jobs step reads and test fixture yield timing, restart, concurrent claims and CLI operation without gevent.
- Replace the Settings picture identity payload with a small SQL count matrix. Skip picture file checks on page loads; retain exact confirmation selection and report queued counts that differ from the dialog estimate.
- Keep the rail theme picker below an independently scrolling navigation list at iPad landscape and desktop sizes.
- Share picture-kind classification between existing-file imports and pipeline downloads: only Rebrickable parts are renders, so refreshing imported set and minifigure photos replaces the same kind.
- Add Settings picture refresh: source choice, missing/replace scope, colour-specific counts, budgeted import progress and searchable findings. Verify and copy existing BrickTracker cache files using migrated identity evidence; preserve originals and picture priority.
- Fix Planning phone rows: put wish edits and copy actions in separate flowing blocks, hide closed/hidden controls and empty feedback, and show row prices with at most two decimals while preserving stored precision.
- Add Planning Wishlist, Prices and Complete sets on brickwork.db, with shared cached prices, audited pins/settings, loose-stock repair estimates and BrickLink XML export. Queue market-price refreshes through the budgeted pipeline. Share missing/damaged gaps with Workbench and move mobile footer clearance into the shell. Report the missing wish-currency and alert-storage contracts before enabling wanted-watch comparisons.
- Speed up Collection lists with SQL reads scoped to the listed kind and identities; clear the bottom tab bar in every shared list. Merge colliding loose-stock edits by item, colour, condition, lot and storage, retaining minifigures separately, per-row events and exact receipt Undo. Show the remaining stock row after a merge.
- Rebuild Collection and the home Sets list on brickwork.db: bulk-decorated item lists, shared Info/Prices/Matching/Consists of details, canonical jump links, colour-specific wishes, copy editing and archive Undo, local photos/captions/lightbox, lots, grouping and purchase-location totals. Share bounded matching and reversible transfers into loose-minifigure part gaps. Preserve the previous home at `/house/collection-legacy-home`.
- Schema 3.2 adds storage archival. Delete shows copy/entry/piece counts and either requires an empty location or explicitly moves live contents to Not stored atomically. Hide archived locations in pickers and lists; guarded Undo restores contents, and creation Undo archives the new location. Include the NULL archive column in migration mapping and proof.
- Merge loose-stock additions by live item, colour, condition, lot and storage across Add, Part to Inventory and Orders; keep figures separate and guard quantity Undo with audited before/after receipts. Identification IDs stay independent; photos follow the stock row.
- Rebuild Part to Inventory on brickwork.db: camera/saved-photo capture, audited Brickognize parts and figures, colour-confirmed queue, atomic loose-stock/lot batches, own photos and receipt Undo. Local model remains unavailable.
- Add v3 Orders review/import, receipt counting, explicit copy/stock allocation, holds, reconciliation and audited Undo; add Storage counts, location detail/edit and atomic moves. Rebuild navigation. Location archive and creation Undo are available with schema 3.2.
- Fix checker phone layout: consolidate Saved/Undo above the copy and part headings, remove the floating toast and sticky answer overlap, show colour/name/number once, and replace the wrapped “to go” label with “remaining”.
- Brick Work 4.2e: schema 3.1 wishlist row ids, colour and quantity, live-key uniqueness and row-id flags. Preserve XML colour/quantity and user notes; explicitly add duplicate quantities with exact Undo. Migrate deterministic wish ids/flags and checksum colour/quantity; include wishes in price colour discovery and live navigation counts.
- Share derived completeness between checker and workbench: damaged non-spare parts block Complete. Validate booklet batches against catalogue instructions with explicit unknown/ambiguous line errors, index exact stock matches and picker gap counts, and validate transfers only against their requested source and gap.
- Clear process-global metadata caches when shell test fixtures finish, preventing picker/matcher tests from leaking wish-owner columns into fresh capture databases.
- Add Part Picker and Matcher on `brickwork.db`: copy selection, completion ranking, colour grids and found receipts, exact loose-stock Take/Give for parts, whole figures, physical booklets and boxes, bulk booklet entry, atomic history/events and guarded Undo.
- Add Manage → Add on `brickwork.db`: all eight BrickLink types, local catalogue search, paste/XML/CSV review, budgeted Brickognize candidates, explicit confirmation, duplicate choices, audited collection receipts and Undo, durable per-item imports and batch progress. Skip hidden AppleDouble files during catalogue loading.
- Fix Check Set Parts to use the migrated “Set is checked” status flag for Finish, Undo, derived completeness and chooser defaults. Return save deltas without inventory-wide picture lookups.
- Rebuild Check Set Parts on `brickwork.db`: copy chooser, immediate audited counts, figure and spare passes, full miss history, Found 1 recovery, derived-only finish, Undo and an ordered offline queue.
- Add Manage → Tools & Admin on `brickwork.db`: source keys and catalogues, durable refresh jobs, unresolved identity review, statistics, values, database export/checks, settings, theme and identification history. Keep the retired tools routes reachable.
- Add the Toni v2 Brick Work shell: four navigation groups, responsive rails and bottom tabs, read-only counts, local brand assets, system pages and Home Screen installation with a shell-only offline fallback.
- Pin production dependencies and Python 3.14.8; build the local Brick Work image reproducibly.
Fork work since upstream `8b0b85f`:
### House data store and API
- Add an independent Rebrickable side catalogue and house SQLite store with idempotent migrations, collection tracking and price history.
- Add authenticated house API and CLI workflows for capture, copy/wishlist editing, missing parts, loose inventory, identification history, minifigure resolution and BrickLink XML import/export.
- Add physical box/booklet tracking, manual completeness, reviewed database maintenance and receipt-protected loose-stock transfers with reconciliation after split commits.
### Toni UI
- Add selectable toni/upstream UI modes, local light/dark/system themes, responsive navigation and global item jump.
- Rebuild collection lists, item and copy details, inventory tabs, parts checking, editing, statistics, jobs and administration; improve sorting, filters, repeated-figure grouping and mobile controls.
- Add enlarged pictures, booklet reference images, direct found-count saving and a full-width checker Found action.
- Brand the fork surfaces as Brick Work while retaining upstream credits and technical names.
### Prices and orders
- Add paced Brickset fetching, BrickLink price slots, condition-aware valuations, settings, colour maps, offline catalogue indexing and part-price observations with due keys.
- Add completion ranking, repair pricing, email/XML/Store API order import, receipt-ledger review, reconciliation and received/reviewed allocation.
- Normalize price formatting and numeric sorting, and provide bounded authenticated BrickLink image caching and fallbacks.
### Scanner and matcher
- Add Workbench parts scanning and matching, conservative colour/name matching, mould/alternate resolution and manual figure mappings.
- Resolve scanner-added pictures with authoritative part mappings and exact-colour sources; show owned sets needing loose stock and support reviewed inventory transfers.
### Tests
- Add offline Python coverage for stores, API, migrations, imports, prices, matching and UI contracts, plus Node runtime harnesses for toni interactions.
- Add the repository check gate and synthetic order fixtures with invented order, set and catalogue identities.
## Upstream history
Releases 1.4.2 and older come from the upstream BrickTracker project: see [its repository](https://gitea.baerentsen.space/FrederikBaerentsen/BrickTracker).