|
2 | 2 |
|
3 | 3 | ## Added |
4 | 4 |
|
| 5 | +- **`.ttag` export/import brief** — `docs/TTAG-EXPORT-BRIEF.md`, the frozen spec for backing up inventory materials to a `.ttag` file so they live outside the cloud. A `.ttag` is a verbatim copy of the Firestore doc(s), `records[]` of 1→N (one spool / a twin / the whole inventory, no branching); twins are atomic (exporting one always exports both) and re-paired from `twin_tag_uid` in the data; import validates then previews then accepts, with restore (same account, verbatim) vs import (other account, ids regenerated + twin refs remapped) modes. Frozen identifiers (`.ttag`, UTI `io.tigertag.ttag`, ProgID `TigerTag.ttag`, content marker), and a Phase 1 (export + file-picker import, testable in dev) / Phase 2 (OS association, needs a build) / Phase 3 (whole-inventory) split. The signed TigerTag+ backup (a separate `rfidList` collection, not the inventory doc) rides along in a required `rfidBackups` map so the backup is complete. A multi-material export asks grouped (one file, all records) vs individual (one file per material); a twin is 2 records but one material, so it stays one file and counts as individual — `docs/TTAG-EXPORT-BRIEF.md` |
| 6 | + |
| 7 | +- **Length-limited fields now shake when they reject a keystroke** — a `maxlength` field used to swallow the over-the-limit character in silence; on desktop the equivalent of a phone's haptic "no" is a quick horizontal shake, so the rejection is seen. One delegated `beforeinput` listener covers **every** `[maxlength]` input and textarea in the app (13 today, from the 3-char friend-code boxes to the 500-char list message), present or added later, so nothing is wired per field. It fires only on a genuinely rejected insert — a paste or keystroke that would overflow, accounting for any selected text it would replace. Honours `prefers-reduced-motion`: a single danger-tinted border flash instead of movement. Shared `bumpField()` helper + `fieldBump` keyframe, reusable for any future hard-limit feedback — `renderer/inventory.js`, `renderer/css/00-base.css` |
| 8 | + |
5 | 9 | - **Partner packaging assets** — `assets-src/img/partners/`, the retail boxes from eSun, SUNLU and ROSA3D whose spools carry the TIGER TAG RFID badge, in two variants each (a 1024 cut-out with transparent background for compositing, a 2000 studio shot for standalone use). These are the only assets in the project that prove *adoption* rather than describing the product. Masters only — no shipped copy under `assets/` yet, since the use is not decided. The folder README flags the point that matters before any of them is published: they show other companies' trademarks, so factual use ("filament makers shipping TigerTag") is normally fine while implying partnership is not, and written permission is worth having on file — `assets-src/img/partners/` |
6 | 10 |
|
7 | 11 | ## Changed |
8 | 12 |
|
| 13 | +- **New chipless spools are minted with a `TigerData_` id prefix** (was `CLOUD_`), so an exported `.ttag` never shows a confusing `CLOUD_` id — the file carries the tier name the user knows. The prefix is a *discriminator*, not a display value: `isCloud = spoolId.startsWith("CLOUD_")` decided the whole chipless path. Rather than migrate (a Firestore doc-id rename is delete+recreate across every account) the discriminator now accepts **both** prefixes via a shared `_isChiplessId` — `CLOUD_` stays recognised forever for the docs already out there, `TigerData_` is used going forward. Studio has a single discriminator point (`normalizeRow`) and a single mint point (`_adpCloudId`), both updated. The filter chain is unchanged: `TigerData_` → `isCloud` → `protocol:"TigerCloud"` → shown as "TigerData", so it lands under the existing search selector. **This prefix is read by four codebases** — the same both-prefixes generalisation was applied to the mobile app (`main_inventory.dart`), the Hub (`lib/data/inventory.ts`) and the stats Cloud Function (`functions/index.js`, where `cloudCount` would otherwise under-count), plus the `llms.txt` / backend-README docs. **Deployment order matters**: a `TigerData_` spool created in Studio syncs to Firestore and is read by the phone / Hub / Function immediately, so their both-prefix updates must be live before the Studio release that flips the minting reaches users, or an old client reads a new spool as a real chip and shows the wrong tier — `renderer/inventory.js` (+ mobile, Hub, backend) |
| 14 | + |
| 15 | +- **The Add-Product panel's "NFC Data" preview label is now "TigerData"** — value change across all 9 locales (it's a brand name, identical in every language). The key stays `addProductRfidDataLbl` — `renderer/locales/*` |
| 16 | + |
| 17 | +- **The rack's "Subtitle" field is now labelled "Description"** — clearer for what people actually put there (a location, contents). Value change only, across all 9 locales; the placeholder already described content so it stayed. The i18n keys are still named `rackSubtitle*` internally — a cosmetic mismatch not worth the rename risk for a label change — `renderer/locales/*` |
| 18 | +- **List name and occasion are now capped at 40 characters** (were 80 and 60). Long titles overflowed their line on the shareable public-list web page and in the friend view; 40 is enough for a real list name and keeps the card readable everywhere it renders. The limit lives only in the field's `maxlength` — no server rule or visible counter referenced the old numbers — and the shake feedback added this session already applies to both — `renderer/inventory.html` |
| 19 | + |
9 | 20 | - **README brought back in line with the app, and given the new artwork.** It was advertising **rack presets**, removed in v2.12.0, and showing `Panda_Feed_Rack_Standard.png`, deleted with them — a dead image on the GitHub page. The presets bullet now describes the actual rack builder, and the image points at the artwork that still exists. The new ecosystem render opens the intro *above* the founder's real-bench photo, which stays: the render shows what the software is, the photo proves someone actually uses it. The TigerPOD line-up banner illustrates its section — nine printed colours say "free STL, print it your colour" better than the sentence under it. |
10 | 21 | - **`docs:check` now validates image paths too** (`src="…"` and `` in README / FEATURES / ROADMAP). It only checked paths written in backticks, which is why a deleted illustration sat broken in the README without anything noticing — `scripts/check-docs-drift.mjs` |
11 | 22 | - **`README.md` joined the release checklist** (`CLAUDE.md`). It was the only user-facing doc not in it, so it silently lagged — v2.13.0 shipped container calibration without the README ever mentioning it (added now). Nothing breaks when the README is stale, which is exactly why it gets forgotten, and `docs:check` cannot catch it: completeness of prose is not a derivable fact. The step names the litmus test — *would someone deciding whether to download the app want to know this exists?* — so only real `## Added` capabilities go in, not fixes or internal work — `CLAUDE.md`, `README.md` |
|
19 | 30 |
|
20 | 31 | ## Fixed |
21 | 32 |
|
| 33 | +- **The Add-Product panel's NFC Data preview is now filled on open** (debug mode). The panel builds the preview whenever a field changes, but on open every one of those refreshes ran *before* the section was un-hidden, so each hit the hidden-section early-return and did nothing — the field stayed empty until the first manual change. `openAddProductPanel` now runs one refresh right after un-hiding the section, so the default product (Generic PLA Basic, 1000 g) renders straight away — `renderer/inventory.js` |
| 34 | + |
| 35 | +- **The step badges in the calibration modal's "How to measure" strip now sit at the top of every card** — the head was `align-items: center`, so a caption that wrapped to two lines (steps 2 and 3, "Keep the cardboard core" / "Put it all on the scale") dropped its orange number badge to the vertical middle while step 1's one-line caption kept its badge at the top. Switched to `align-items: flex-start` so the badge pins to the first line regardless of wrap, plus a −2px nudge so the 18px badge's centre sits level with the 14px caption line rather than 2px below it. Verified the three badges land at delta 0.00 from the first-line centre — `renderer/css/60-modals.css` |
| 36 | + |
22 | 37 | - **Printer connections no longer leak on account switch or sign-out** — `unsubscribePrinters` disconnected Elegoo, Bambu Lab and Anycubic but not Snapmaker, FlashForge or Creality, which were torn down only by the manual connect button. Signing in on one account, opening Printers so a FlashForge auto-connects, then switching accounts left its 2-second poll loop and the Snapmaker/Creality sockets running against the *previous* account's printer — and another set was added on every switch. All six brands are now swept, with the Creality camera stopped alongside its socket — `renderer/inventory.js` |
23 | 38 | - **`img:get` no longer fetches whatever it is handed** — it took a URL straight from a Firestore image field (including a friend's custom image URL, which they control), fetched it with no validation and no timeout, then wrote the body to the cache and served it back to the renderer: a server-side request forgery with readback, where an internal service answering on loopback became readable. It now refuses anything that is not a public http(s) destination, resolving the hostname first so a domain the attacker owns cannot point at 127.0.0.1, and rejecting loopback, RFC1918, link-local, CGNAT and IPv6 ULA. Redirects are followed by hand and re-validated at each hop rather than refused outright — refusing them would have silently broken the CDNs and short links real image URLs use. Added an 8-second deadline so a slow host cannot pin the handler open — `main.js` |
24 | 39 |
|
|
0 commit comments