You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit acbebb9
Browse filesBrowse the repository at this point in the historyBrowse files
-**One card per tier, one deck per product — for all four tiers.** The two keys were pulled apart and given one job each. `_spoolProductKey` is the PRODUCT: `id_product` when there is one, the attribute signature otherwise (brand + material + the full colour signature + both aspects + `id_type`). `_spoolGroupKey` is the CARD: the product key plus the tier. So the grid and the table show a TigerTag card beside a TigerData one, and a TigerTag+ beside a TigerData+ — a card means "these spools are interchangeable", and a chipless spool cannot be written, weighed on a TigerScale or proven — while the deck, keyed on the product, gathers every one of them behind those cards. A bucket now holds exactly ONE tier, so every card carries a single unambiguous badge with no special case.
13
+
-**The product state follows the product, not the tier.** Buy link, price, minimum stock, ★/❤ and the note hang off `_productKeyHash`, which now hashes the product key — so a price set on a chipped spool applies to the chipless one of the same filament, and "In stock" counts them together. The two decks of one product previously showed a different "In stock: 1 / 3" and a different ★ depending on the card opened.
14
+
-**Nothing already stored moved.**`_spoolProductKey` deliberately emits the same `tt:<id_product>` string the old TigerTag+ branch produced and the same attribute string everything else produced, so every existing record keeps its hash. Only a TigerData+ changes casing, and `_legacyProduct` reads through to its original hash — a read-through, not a migration: no Firestore write, and no window where a buy link is missing.
15
+
-**`_syncGroupPanelToSpool` gained `keepSingletons`** and counts the widened deck: a lone chipped spool is a one-member bucket, and counting the strict bucket closed the deck on exactly the pairing it exists to show.
16
+
-**The live refresh widened too.**`_refreshGroupPanelIfOpen`, fired by the Firestore snapshot, re-resolved the bucket and rendered THAT — so adding a spool from the deck made the chipped one vanish until the panel was closed and reopened. The slider's live total follows the same rule: the card shows its bucket's sum, the deck the whole product's. A structural assertion fails the harness if a future render path skips the widening.
17
+
- The unset-id guard stays where it belongs — on the WRITE side. Grouping productless spools together is what the attribute key has always done. 34 assertions across two harnesses — `renderer/inventory.js`.
18
+
- **Catalogue search reaches the whole record, and a scanned barcode lands on one product.** The index carried brand, title, material, SKU and measure; it now also carries the series and colour name on their own, both aspects ("translucent", "bicolor", "silk" are how people actually look), the product type, and the barcode. Codes are kept in a SEPARATE field from the words: folding a 13-digit EAN into the text haystack would make "1000" match every barcode containing 1000, so `_catCodeish` only consults them for a token of 6+ characters carrying a digit. A query that is one code and matches a `barcode`/`sku`/`id` exactly returns only those — a barcode gun fires the digits and an Enter, and a scan that lands on the right product plus nine coincidental cousins is a scan you have to read; it falls through to the ordinary search when nothing matches, so a typo still finds something. Weight is indexed in grams as whole tokens (`_catMeasureAliases`), which replaces an accident: "1000" used to return 284 products, every one matched through its SKU because some brands encode the weight there (`G00-W00-1.75-1000-spl`) — it now returns the spools that actually weigh 1 kg, and unifies the catalogue's own "0.5 kg" with its "500 g". Tokens, not substrings, because "2500g" ends in "500g". 18 assertions — `renderer/inventory.js`, i18n `catalogSearchPh`.
19
+
- **The series and the colour name are read from the catalogue, never derived from the title.** The list endpoint returns `series` and `name` as their own fields on every product, so `_catSplitTitle` — which cut `title` on its LAST " - " — is gone, renamed `_catSeriesName` because it no longer splits anything. The split guessed wrong whenever the colour name itself contains a dash: measured against the live 4 922-product catalogue it disagreed with the API on 6 products ("PLA Basic - CMYK - Magenta" is series `PLA Basic` + name `CMYK - Magenta`, not series `PLA Basic - CMYK`; same for Panchroma™ Galaxy PLA and the two PolyLite™ Version A/B). It survived only as a fallback for caches written before the API returned the fields, so the **local cache schema moved to v2**, which rejects those payloads outright: a rejected cache falls through to the bundled catalogue and a real sync follows, leaving the fallback nothing to protect. 0 empty series or names across the 4 922 products — `renderer/inventory.js`.
20
+
-**Bundled catalogue refreshed: 3 218 → 4 922 products** (32 brands, 2.6 MB, 4 919 filaments + 3 resins). `db_update.py` pulled 1 704 new entries; 1 966 records had changed upstream. The reference tables were already current — only the catalogue and its `last_update` stamp moved — `assets/db/tigertag/id_catalog.json`, `assets/db/tigertag/last_update.json`.
21
+
-**The README opened on the chip's logo and pointed at the wrong site.** The header showed `logo_tigertag_contouring.svg` — the ecosystem's chip mark rather than this app's identity — and it vanished into GitHub's dark theme. It now uses the app's own icon (`assets/img/icon.png`), which is opaque white inside with only the rounded corners transparent, so it reads on `#ffffff` and `#0d1117` alike. The site links were sorted out at the same time: the ecosystem, the catalogue and the account point to **tigersystem.io** (which serves `/catalog`, `/account`, `/download`), while `tigertag.io` is named for what it is — the shop — and keeps the TD1S product link — `README.md`.
22
+
23
+
### Fixed
24
+
25
+
- **Adding a spool made the TigerTag+ card flash a second, wrong badge.** `duplicateSpoolAsCloud` copied the source's `raw` wholesale and `_sanitizeCloudSeed` had no opinion on it either, so a spool minted from a TigerTag+ inherited its `id_tigertag`. That field names the CHIP's version in `id_version.json` and only four ids are legal; a chipless spool has no chip and therefore no version, which v2.16.0 already established — but these two mint paths kept writing one. The new document was read as `isPlus` until the mirror deleted the stray field, and for that moment it shared the TigerTag+ grouping bucket, so the card showed "TigerTag+ ×1 TigerData+ ×1" before settling. Both paths now strip a shared `CHIP_ONLY_FIELDS` list before writing — the tier is DERIVED from that field, so copying it makes the spool lie about what it is, and the flash was only the visible half of writing an out-of-referential value onto a fresh document. 10 assertions, including a replay of the old behaviour — `renderer/inventory.js`.
26
+
-**A product with no photo was drawn as a generic sketch instead of its own colour.**`product/get` never answers "no image": for a product without a photo it returns a shared placeholder — a line drawing of an empty spool hosted on Shopify (`DefaultFilament.jpg`) — and because that is a real image serving a 200, the `onerror` fallback every surface relies on never fired. The LIST endpoint is honest (`img_src` null or ""), only the DETAIL endpoint substitutes, so the same spool showed its colour in the grid and a stock sketch in the product card. `_isRealProductImg` recognises it in `normalizeRow`, the single funnel every surface reads `imgUrl` through, so the grid card, table thumbnail, detail panel and product card fall back together — including on documents that already stored the URL. The three ingest paths stop persisting it as well, so a TigerData+ created from an imageless product no longer carries a dead placeholder for good. 52 catalogue products are affected — `renderer/inventory.js`.
Copy file name to clipboardExpand all lines: FEATURES.md
+4-2Lines changed: 4 additions & 2 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -1,6 +1,6 @@
1
1
# Tiger Studio Manager — Feature Catalogue
2
2
3
-
Tiger Studio Manager is the Electron desktop companion to the TigerTag ecosystem: a filament-inventory manager built around RFID/NFC-tagged spools ("TigerTag" chips), a fully-digital "TigerData" tier (and its catalogue-identified "TigerData+" rung), live 3D-printer integration across six brands, physical storage/rack management, a Firebase-backed social layer (friends, shareable wishlists, public profiles), and companion hardware (TD1S color sensor, TigerScale, TigerPOD dual-reader stand). This document catalogues every **shipped** feature, grouped by domain, current as of **v2.16.0**. Per-version release detail lives in `CHANGELOG.md`; forward-looking / in-progress work lives in `ROADMAP.md`.
3
+
Tiger Studio Manager is the Electron desktop companion to the TigerTag ecosystem: a filament-inventory manager built around RFID/NFC-tagged spools ("TigerTag" chips), a fully-digital "TigerData" tier (and its catalogue-identified "TigerData+" rung), live 3D-printer integration across six brands, physical storage/rack management, a Firebase-backed social layer (friends, shareable wishlists, public profiles), and companion hardware (TD1S color sensor, TigerScale, TigerPOD dual-reader stand). This document catalogues every **shipped** feature, grouped by domain, current as of **v2.17.0**. Per-version release detail lives in `CHANGELOG.md`; forward-looking / in-progress work lives in `ROADMAP.md`.
4
4
5
5
---
6
6
@@ -25,7 +25,8 @@ Tiger Studio Manager is the Electron desktop companion to the TigerTag ecosystem
25
25
- Per-account **container-weight calibration** — correct a container's empty weight against your own scale, with a guided "how to measure" step (a Masterspool keeps its cardboard core, other types do not), a catalogue-vs-yours comparison, and a choice of whether the spools already using that container adopt the new value. Stored in `users/{uid}/containerOverrides/{containerId}` and applied over the bundled catalogue at read time — the shipped JSON is never modified (v2.13.0).
26
26
-**TigerData** — fully-digital spools with no physical chip (`CLOUD_<id>`), atomically promoted to a real hex UID (with all fields, twin pointers, rack assignment and friend ACLs carried over) the moment a chip is programmed; dedicated purple tier badge and stat tile (v1.4.12; stat tile v1.4.13).
27
27
-**TigerData+** — the catalogue-identified rung of the digital tier: a chipless spool carrying a real `id_product`, so it knows the exact brand, colour, material, temperatures, diameter, SKU and EAN of a product in the official catalogue instead of only what was typed into it. Derived from the data, never stored; explicitly not a TigerTag+ (no chip, no UID) and given its own badge and stat tile so the two are never confused (v2.15.0).
28
-
-**Catalogue search** — a dedicated **Search** view (grid + table) over the whole official TigerTag+ product catalogue, with Type · Brand · Series · Material filters and sorting at parity with the public catalogue page. The catalogue is imported once into a local cache and searched entirely in memory (self-refreshing after 24 h, plus a *Re-sync the catalogue* button in Settings → Tools); selecting a product previews it in the standard product card, and one `+` turns it into a TigerData+. Every product type is browsable, filament is creatable (v2.15.0). The catalogue also **ships inside the app** (`assets/db/tigertag/id_catalog.json`, refreshed by `db_update.py`), so Search works on first launch, offline, before any sync has run (v2.16.0).
28
+
- **Catalogue search** — a dedicated **Search** view (grid + table) over the whole official TigerTag+ product catalogue, with Type · Brand · Series · Material filters and sorting at parity with the public catalogue page. The catalogue is imported once into a local cache and searched entirely in memory (self-refreshing after 24 h, plus a *Re-sync the catalogue* button in Settings → Tools); selecting a product previews it in the standard product card, and one `+` turns it into a TigerData+. Every product type is browsable, filament is creatable (v2.15.0). The catalogue also **ships inside the app** (`assets/db/tigertag/id_catalog.json`, refreshed by `db_update.py`), so Search works on first launch, offline, before any sync has run (v2.16.0). The index reaches the whole record — series and colour name on their own, both aspects, product type, SKU and EAN — and weight is matched as whole gram tokens, so "1 kg", "1000" and "500 g" find the actual weight rather than codes containing those digits (v2.17.0).
29
+
-**Barcode / SKU lookup** — a query that is a single code (6+ characters carrying a digit) matching a product's `barcode`, `sku` or `id` exactly returns only that product, so a barcode gun firing digits + Enter lands on one result instead of a list of coincidental cousins; codes live in an index field kept separate from the words, and an unmatched code falls through to the ordinary search so a typo still finds something (v2.17.0).
29
30
-**Self-maintaining catalogue data** — spools tied to a catalogue product reconcile themselves as the catalogue evolves: a `catalogSyncedAt` stamp is compared against the product's own `updated_at` after each sync and only the ones that actually moved are refreshed, in the background and never in a friend's inventory. A TigerData+ updates in complete silence; a TigerTag+ is asked for a chip re-write **only** when the change touched a field the chip physically carries (`TTAG_ON_CHIP_FIELDS`, kept in lockstep with `docs/TTAG-FIELDS.md` by `npm run docs:check`) (v2.16.0).
30
31
-**Chip history & backup** — private list of every physical chip ever programmed, with the repairable TigerTag+ signature backed up on first read; a green "Backed up" shield badge marks protected spools (v1.10.28, badge v1.10.29). Product ID shown as a clickable catalogue link (v1.10.29).
31
32
- Grouping of identical spools — table view first (v1.10.15), then grid view + a dedicated group side panel with a live speedometer gauge of combined remaining weight (v1.10.18); grouping includes TigerData spools (v1.10.16) and distinguishes different colours/aspects within the same brand+material (v1.10.31).
@@ -116,6 +117,7 @@ Tiger Studio Manager is the Electron desktop companion to the TigerTag ecosystem
116
117
## Favorites & product identities
117
118
118
119
- Per-user `products` collection — one record per product identity (buy link, tax-free price, minimum stock, note, tags, SKU/EAN, ❤/★ flags, display snapshot, sanitised `cloudSeed`) shared by every matching spool and surviving spool deletion (v2.1.0).
120
+
-**One card per tier, one deck per product** — the card key is the product plus the tier, so a TigerTag spool and a chipless TigerData one of the same filament get their own card and their own unambiguous badge (they are not interchangeable: only a chipped spool can be written, weighed on a TigerScale or proven), while the deck behind either card is keyed on the product alone and gathers every tier. Product state — buy link, price, minimum stock, ❤/★, note — hangs off the product key, so it is shared across tiers and "in stock" counts them together (v2.17.0).
119
121
- Product-info side card — buy link, price, min stock, note, tags, SKU/EAN, ❤/★, and a "+ Material" action minting a chipless TigerData spool of this exact filament, fully auto-saving (v2.1.0; action renamed v2.2.0).
120
122
- Info (ⓘ) button on a product opens its reorder panel (price / buy-link / min-stock) alongside the product card (v2.10.0; present and left-aligned on all three product side-cards, and toggling, v2.12.0).
121
123
- The product card, the spool detail and the grouped-spools deck expose the SAME action row — ⓘ, "+ Material", shopping, add-to-a-list, ❤, ★ — in the same order, whichever card you opened (v2.15.0).
0 commit comments