Skip to content

Commit fbe3080

Browse files
committed
v2.27.1 — catalogue pictures survive a search
Fixed - Catalogue images stopped loading permanently once the search box was used. The image loader caps in-flight requests at six and only decremented that count from the load/error handlers -- but an <img> destroyed mid-flight fires neither, and the catalogue search wipes and rebuilds its grid on every keystroke. Six images torn down that way held all six slots for the rest of the session: the queue jammed at the cap and nothing loaded again until restart. In-flight images are now held in a Set rather than counted, so the pump reclaims the slots of anything that left the DOM. Holding the elements instead of counting them removes the class of bug, not just this instance. - The macOS release build could no longer sign itself (carried from the CI fix committed before the tag): electron-builder hands the certificate's import password to a flag that authenticates against the keychain. Upstream's fix is in an alpha only, so the workflow owns the keychain instead. Verified by the reporter in real use. Worth recording: my own automated readings were all zero and would have said the fix failed -- the Electron window was in the background, where visibilityState is 'hidden' and Chromium throttles IntersectionObserver, so nothing loads whatever the code does.
1 parent 6f78b31 commit fbe3080

9 files changed

Lines changed: 84 additions & 25 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,15 @@ Versions follow [Semantic Versioning](https://semver.org/).
55

66
---
77

8+
## v2.27.1 — 2026-09-13
9+
10+
### Fixed
11+
12+
- **Catalogue images stopped loading permanently after using the search box.** The product-image loader caps in-flight requests at six, and the counter was only decremented from the `load` / `error` handlers. An `<img>` destroyed mid-flight fires neither, and `_catViewSearch()` wipes and rebuilds `#catalogViewBody` on every keystroke — so six images torn down that way held all six slots for the lifetime of the session: `_imgPump()` found no free slot, the queue never drained, and no image loaded again until restart. In-flight images are now tracked in a `Set` instead of a counter, and `_imgPump()` reclaims the slots of any entry no longer `isConnected` before filling. Same shape of leak would have been possible from any list that rebuilds itself; holding the elements rather than counting them removes the class, not just the instance — `renderer/inventory.js`.
13+
- **The macOS release build could no longer sign itself.** electron-builder passes the certificate's IMPORT password to `security set-key-partition-list -k`, but that flag authenticates against the KEYCHAIN — a different secret. macOS did not previously verify it; the runner image carrying macOS 26 does, so the v2.27.0 build failed with `SecKeychainUnlock: The user name or passphrase you entered is not correct` with nothing changed on our side (same certificate, valid to 2031; same secrets, untouched since May; same electron-builder, 26.15.3 — only the image, 20260728 → 20260907). Fixed upstream in PR #10101, which is in `27.0.0-alpha.8` and in no stable release (26.15.7 and 26.16.1 both lack it). The workflow therefore builds the signing keychain itself — create, import, authorise `codesign` with the keychain's OWN generated password — and passes `CSC_KEYCHAIN` so electron-builder skips its own path; `CSC_LINK` is deliberately unset, since passing it makes electron-builder rebuild its keychain and take that path again. The step states its own removal condition — `.github/workflows/build.yml`.
14+
15+
---
16+
817
## v2.27.0 — 2026-09-12
918

1019
### Added

‎FEATURES.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Tiger Studio Manager — Feature Catalogue
22

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.27.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.27.1**. Per-version release detail lives in `CHANGELOG.md`; forward-looking / in-progress work lives in `ROADMAP.md`.
44

55
---
66

‎WORKLOG.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
## Changed
66

77
## Fixed
8+
- **Searching the catalogue stopped its pictures loading — for good.** The image loader caps how many requests are in flight, and the counter only came back down when an image fired `load` or `error`. An element destroyed mid-flight fires NEITHER, and the catalogue search wipes and rebuilds its grid on every keystroke: six images torn down that way held all six slots permanently, the queue jammed at the cap, and not one picture loaded again for the rest of the session — hence "never again" rather than "sometimes". In-flight images are now held in a SET rather than counted, so the pump can see the ones that left the DOM and take their slots back. Reported from real use; the mechanism matches the symptom exactly, but the fix was confirmed by the reporter rather than by measurement here — an Electron window left in the background reports `visibilityState: "hidden"`, and Chromium throttles IntersectionObserver there, so every automated reading was zero regardless of the code — `renderer/inventory.js`
89
- **The macOS release build could no longer sign itself.** `security set-key-partition-list -k` was being handed the CERTIFICATE's import password, but that flag authenticates against the KEYCHAIN — two different secrets. macOS used not to check; the runner image that ships macOS 26 does, so v2.27.0 failed with `SecKeychainUnlock: The user name or passphrase you entered is not correct` while nothing on our side had moved: same certificate (valid to 2031), same secrets (untouched since May), same electron-builder (26.15.3) — only the image, 20260728 → 20260907. It is electron-builder's bug, fixed upstream in PR #10101, which sits in `27.0.0-alpha.8` and in NO stable release (26.15.7 and 26.16.1 both lack it). So the workflow builds the signing keychain itself — create, import, authorise codesign with the keychain's OWN password — and hands `CSC_KEYCHAIN` to electron-builder, which then skips the broken path. `CSC_LINK` is deliberately unset: passing it makes electron-builder rebuild its own keychain and take that path again. The keychain password is generated per run and never leaves the runner. The step carries its own removal condition — delete it once the fix reaches a stable electron-builder — `.github/workflows/build.yml`
910

1011
## Removed

‎data/release-notes/v2.27.1.md‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
A quick follow-up to v2.27.0.
2+
3+
## Fixed
4+
5+
- Fixed an issue where product pictures in the Catalogue stopped loading entirely once you used the search box, and stayed blank until the app was restarted.
6+
7+
---
8+
9+
📖 **[Full changelog](https://github.com/TigerTag-Project/TigerTag-Studio-Manager/blob/main/CHANGELOG.md)** · ⬇️ Grab the installer for your OS from the **Assets** below (macOS `.dmg`, Windows `Setup .exe` or `portable .exe`, Linux `.AppImage`).

‎data/whatsnew.json‎

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8887,5 +8887,35 @@
88878887
}
88888888
}
88898889
]
8890+
},
8891+
"2.27.1": {
8892+
"date": "2026-09-13",
8893+
"items": [
8894+
{
8895+
"icon": "image",
8896+
"title": {
8897+
"en": "Catalogue pictures, back for good",
8898+
"fr": "Les images du catalogue, pour de bon",
8899+
"de": "Katalogbilder, endgültig zurück",
8900+
"es": "Las imágenes del catálogo, de vuelta",
8901+
"it": "Le immagini del catalogo, tornate",
8902+
"zh": "产品库图片,回来了",
8903+
"pt": "As imagens do catálogo, de volta",
8904+
"pt-pt": "As imagens do catálogo, de volta",
8905+
"pl": "Zdjęcia w katalogu, na dobre"
8906+
},
8907+
"body": {
8908+
"en": "Searching the catalogue used to leave every picture blank until you restarted. Search away — they stay.",
8909+
"fr": "Une recherche dans le catalogue laissait toutes les images vides jusqu'au redémarrage. Cherche tant que tu veux, elles restent.",
8910+
"de": "Eine Suche im Katalog ließ alle Bilder leer — bis zum Neustart. Such ruhig weiter, sie bleiben da.",
8911+
"es": "Buscar en el catálogo dejaba todas las imágenes en blanco hasta reiniciar. Busca cuanto quieras: se quedan.",
8912+
"it": "Una ricerca nel catalogo lasciava tutte le immagini vuote fino al riavvio. Cerca quanto vuoi, restano.",
8913+
"zh": "以前在产品库里一搜,所有图片就空着,直到重启。现在随便搜,它们都在。",
8914+
"pt": "Buscar no catálogo deixava todas as imagens em branco até reiniciar. Pode buscar à vontade: elas ficam.",
8915+
"pt-pt": "Pesquisar no catálogo deixava todas as imagens em branco até reiniciares. Pesquisa à vontade: ficam.",
8916+
"pl": "Wyszukiwanie w katalogu zostawiało wszystkie zdjęcia puste aż do restartu. Szukaj do woli — zostają."
8917+
}
8918+
}
8919+
]
88908920
}
88918921
}

‎llms.txt‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ It is neutral and agnostic by design: TigerTag takes no side between filament br
1010
distributors. The protocol is open and anyone can build a different ecosystem on it; nothing in this repo is a
1111
prerequisite for doing so.
1212

13-
Current version: **2.27.0** — macOS (Intel + Apple Silicon, signed & notarized), Windows, Linux.
13+
Current version: **2.27.1** — macOS (Intel + Apple Silicon, signed & notarized), Windows, Linux.
1414

1515
## Architecture
1616

‎package.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "tigertag-inventory",
3-
"version": "2.27.0",
3+
"version": "2.27.1",
44
"description": "Tiger Studio Manager — desktop app",
55
"main": "main.js",
66
"engines": {

‎renderer/CODEMAP.md‎

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,7 @@ Manual spool creation: full chip-schema editor with bottom-sheets. All helpers p
197197

198198
---
199199

200-
## Inventory render (L6835-11422)
200+
## Inventory render (L6835-11432)
201201
| L | What | Anchors |
202202
|---|---|---|
203203
| 5038-5252 | **`renderInventory()`** — welcome card, rack-view priority, table/grid dispatch | `renderInventory` |
@@ -209,7 +209,7 @@ Manual spool creation: full chip-schema editor with bottom-sheets. All helpers p
209209

210210
---
211211

212-
## RFID encode / burn modal — cem (L11422-11922)
212+
## RFID encode / burn modal — cem (L11432-11932)
213213
| L | What | Anchors |
214214
|---|---|---|
215215
| 5864-5953 | **`_burnRfid(r)`** — writes a chip from a row | `_burnRfid` |
@@ -218,7 +218,7 @@ Manual spool creation: full chip-schema editor with bottom-sheets. All helpers p
218218

219219
---
220220

221-
## TigerTag+ catalogue (L11759-14219)
221+
## TigerTag+ catalogue (L11769-14229)
222222
| L | What | Anchors |
223223
|---|---|---|
224224
| 10900-10993 | Refresh API data for a spool | `_refreshApiData` |
@@ -240,7 +240,7 @@ Manual spool creation: full chip-schema editor with bottom-sheets. All helpers p
240240

241241
---
242242

243-
## Spool detail panel (L6064-26554)
243+
## Spool detail panel (L6064-26564)
244244
| L | What | Anchors |
245245
|---|---|---|
246246
| 6499-6652 | Structural signature (patch vs rebuild), weight patch, saved check | `_detailStructuralSig`, `_patchDetailWeight` |
@@ -255,7 +255,7 @@ Manual spool creation: full chip-schema editor with bottom-sheets. All helpers p
255255

256256
---
257257

258-
## Panels / debug / auto-update (L19478-20698)
258+
## Panels / debug / auto-update (L19488-20708)
259259
| L | What | Anchors |
260260
|---|---|---|
261261
| 8193-8266 | Resizable panels (detail + debug) — drag handle, persisted width | `makePanelResizable`, `openDebug` |
@@ -267,7 +267,7 @@ Manual spool creation: full chip-schema editor with bottom-sheets. All helpers p
267267

268268
---
269269

270-
## Friends rendering (L20698-21288)
270+
## Friends rendering (L20708-21298)
271271
| L | What | Anchors |
272272
|---|---|---|
273273
| 8660-8744 | Sidebar friends quick-list + hover tooltip | `renderSidebarFriends`, `showSbFriendTip` |
@@ -276,7 +276,7 @@ Manual spool creation: full chip-schema editor with bottom-sheets. All helpers p
276276

277277
---
278278

279-
## Racks + printers subscriptions (L21288-21658)
279+
## Racks + printers subscriptions (L21298-21668)
280280
| L | What | Anchors |
281281
|---|---|---|
282282
| 8956-8998 | Racks subscription | `subscribeRacks`, `unsubscribeRacks` |
@@ -286,7 +286,7 @@ Manual spool creation: full chip-schema editor with bottom-sheets. All helpers p
286286

287287
---
288288

289-
## Printers views (L21498-25762)
289+
## Printers views (L21508-25772)
290290
| L | What | Anchors |
291291
|---|---|---|
292292
| 9163-9332 | **Job status helpers** + surgical grid patches (job card, online badge, grid signature) | `_getPrinterJob`, `_patchGridJobs`, `_jobCardHtml`, `_isPrinterOnline`, `_patchGridStatus` |
@@ -302,7 +302,7 @@ Manual spool creation: full chip-schema editor with bottom-sheets. All helpers p
302302

303303
---
304304

305-
## Printer detail side panel (L24118-28198)
305+
## Printer detail side panel (L24128-28208)
306306
| L | What | Anchors |
307307
|---|---|---|
308308
| 10087-10649 | Open/close lifecycle (connect/disconnect per brand), conn button, refresh | `openPrinterDetail`, `closePrinterDetail`, `refreshOpenPrinterDetail` |
@@ -312,7 +312,7 @@ Manual spool creation: full chip-schema editor with bottom-sheets. All helpers p
312312

313313
---
314314

315-
## Add-printer flow (L26579-28589)
315+
## Add-printer flow (L26589-28599)
316316
Per-brand scan/manual flows live in `printers/<brand>/add-flow.js`; `inventory.js` owns the shell.
317317

318318
| L | What | Anchors |
@@ -324,7 +324,7 @@ Per-brand scan/manual flows live in `printers/<brand>/add-flow.js`; `inventory.j
324324

325325
---
326326

327-
## Racks CRUD + slots (L27372-31119)
327+
## Racks CRUD + slots (L27382-31129)
328328
| L | What | Anchors |
329329
|---|---|---|
330330
| 12392-12526 | Rack create / update / delete / empty + orphan ref cleanup | `createRack`, `updateRack`, `deleteRack`, `emptyRack` |
@@ -336,7 +336,7 @@ Per-brand scan/manual flows live in `printers/<brand>/add-flow.js`; `inventory.j
336336

337337
---
338338

339-
## Storage view render + DnD (L29706-32889)
339+
## Storage view render + DnD (L29716-32899)
340340
| L | What | Anchors |
341341
|---|---|---|
342342
| 13246-13885 | **`renderRackView()`** — biggest function in the file: stats bar + filter chips, two-column layout, masonry, kebab menus, live search, read-only friend mode, rack reorder DnD | `renderRackView` |
@@ -346,7 +346,7 @@ Per-brand scan/manual flows live in `printers/<brand>/add-flow.js`; `inventory.j
346346

347347
---
348348

349-
## Friend view (L31415-33859)
349+
## Friend view (L31425-33869)
350350
| L | What | Anchors |
351351
|---|---|---|
352352
| 14329-14410 | Friend inventory open/close (one-shot read, no live updates) | `openFriendInventory`, `closeFriendInventory` |
@@ -356,7 +356,7 @@ Per-brand scan/manual flows live in `printers/<brand>/add-flow.js`; `inventory.j
356356

357357
---
358358

359-
## Display name + friend requests (L32197-34909)
359+
## Display name + friend requests (L32207-34919)
360360
| L | What | Anchors |
361361
|---|---|---|
362362
| 14796-14839 | **Display-name setup modal** (first-login pseudo picker) | `openDisplayNameSetup` |
@@ -365,23 +365,23 @@ Per-brand scan/manual flows live in `printers/<brand>/add-flow.js`; `inventory.j
365365

366366
---
367367

368-
## Keys + profile sync (L33054-35059)
368+
## Keys + profile sync (L33064-35069)
369369
| L | What | Anchors |
370370
|---|---|---|
371371
| 14993-15037 | **`claimPublicKey(uid, oldKey)`** atomic transaction (10 retries) + regenerate + send friend request | `claimPublicKey`, `sendFriendRequest` |
372372
| 15062-15112 | Key generators (`XXX-XXX`, 40-char hex) + `userProfiles/{uid}` sync | `generatePublicKey`, `generatePrivateKey`, `syncUserProfile` |
373373

374374
---
375375

376-
## Custom avatar (L33282-35259)
376+
## Custom avatar (L33292-35269)
377377
| L | What | Anchors |
378378
|---|---|---|
379379
| 15600-15774 | File pick, image decode, alpha detection, resize to blob, upload, remove | `uploadCustomAvatar`, `removeCustomAvatar` |
380380
| 15775-15960 | **Discord-style cropper** — crop / zoom / rotate + cropped upload | `openAvatarCropper`, `uploadCroppedAvatar` |
381381

382382
---
383383

384-
## User doc sync + telemetry + bootstrap (L33597-36723)
384+
## User doc sync + telemetry + bootstrap (L33607-36733)
385385
| L | What | Anchors |
386386
|---|---|---|
387387
| 16253-16695 | **`syncUserDoc(uid)`** — displayName/roles/Debug/keys/isPublic + **client telemetry** (studio* fields + `telemetry/studio` aggregates, fire-and-forget) | `syncUserDoc`, `hydrateUserDocCache` |
@@ -391,7 +391,7 @@ Per-brand scan/manual flows live in `printers/<brand>/add-flow.js`; `inventory.j
391391

392392
---
393393

394-
## Electron RFID integration (L34734-36918)
394+
## Electron RFID integration (L34744-36928)
395395
| L | What | Anchors |
396396
|---|---|---|
397397
| 29714-29801 | Reader indicator (topbar), reader connect/disconnect, card present/removed badge | `renderRfidReaderBadges` |

‎renderer/inventory.js‎

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7693,20 +7693,30 @@ import { elgFanStep } from './printers/elegoo/widget_control.js';
76937693
const IMG_MAX_INFLIGHT = 6;
76947694
const IMG_RETRY_MAX = 2;
76957695
const IMG_RETRY_MS = [400, 1400];
7696-
let _imgInflight = 0;
7696+
// In flight as a SET, not a counter. A counter only comes back down when the
7697+
// element's load/error fires — and an element destroyed mid-flight never
7698+
// fires either. The catalogue search wipes and rebuilds its body on every
7699+
// keystroke, so six images torn down that way used to hold all six slots
7700+
// FOREVER: the queue jammed at the cap and not one picture loaded again for
7701+
// the rest of the session. Holding the elements lets the pump see they are
7702+
// gone and take the slots back.
7703+
const _imgInflight = new Set();
76977704
const _imgQueue = [];
76987705

76997706
function _imgPump() {
7700-
while (_imgInflight < IMG_MAX_INFLIGHT && _imgQueue.length) {
7707+
for (const img of _imgInflight) {
7708+
if (!img.isConnected) { _imgInflight.delete(img); delete img.dataset.imgBusy; }
7709+
}
7710+
while (_imgInflight.size < IMG_MAX_INFLIGHT && _imgQueue.length) {
77017711
const img = _imgQueue.shift();
77027712
const url = img.dataset.imgSrc;
77037713
if (!img.isConnected || !url) continue;
7704-
_imgInflight++;
7714+
_imgInflight.add(img);
77057715
img.src = url;
77067716
}
77077717
}
77087718
function _imgDone(img) {
7709-
if (img.dataset.imgBusy) { delete img.dataset.imgBusy; _imgInflight = Math.max(0, _imgInflight - 1); }
7719+
if (img.dataset.imgBusy) { delete img.dataset.imgBusy; _imgInflight.delete(img); }
77107720
_imgPump();
77117721
}
77127722
// Only ask for an image once it is near the viewport. 600 px of margin so a

0 commit comments

Comments
 (0)