Skip to content

Commit 20c42b2

Browse files
committed
v1.8.16 — Detail side panel: signature guard skips rebuild on unrelated Firestore snapshots
1 parent 1280c85 commit 20c42b2

4 files changed

Lines changed: 45 additions & 8 deletions

File tree

‎CHANGELOG.md‎

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

66
---
77

8+
## v1.8.16 — 2026-06-03
9+
10+
### Fixed
11+
12+
- **Spool detail side panel no longer flashes when something else changes in Firestore.** Editing a different spool — from the mobile app, from another device, or even just a write echo coming back — used to tear down and rebuild the entire side panel, flashing the product photo and every SVG icon (badges, twin link, chip status). The panel now compares the displayed spool against the last render and skips the rebuild when the visible fields haven't changed. Editing the open spool's own weight still triggers one rebuild instead of two or three, because the server-commit echo carries the same signature as the pending-write update and is now ignored.
13+
14+
---
15+
816
## v1.8.15 — 2026-06-03
917

1018
### Changed

‎WORKLOG.md‎

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
1-
# Worklog — v1.8.15 (in progress)
1+
# Worklog — v1.8.16 (in progress)
22

33
## Added
4-
- Firestore IndexedDB offline persistence on every Firebase app instance (default + per-account named apps). Snapshot listeners now replay from the local cache on cold start and only deltas hit the network — cuts cold-start reads from ~130 (100 spools + 10 racks + 5×printer brands + scales + friends) to near-zero on repeat boots and makes the UI usable while offline. Implemented via `enablePersistence({ synchronizeTabs: true })` right next to `firebase.initializeApp` so it runs before any other Firestore call — `renderer/firebase.js`.
54

65
## Changed
7-
- Image cache no longer returns base64 `data:` URLs to the renderer. The main-process `img:get` IPC now returns a stable HTTP URL (`/img-cache/<md5>.<ext>`) served by the local dev server straight from the on-disk cache. With a stable HTTP URL, Chromium can keep the decoded bitmap alive across DOM operations — destroying and re-creating an `<img>` element no longer forces a re-decode, so view switches and full rebuilds no longer flash the product thumbnails. The HTTP response carries a 1-day Cache-Control header for good measure — `main.js`.
86

97
## Fixed
8+
- Spool detail side panel: stop tearing down the panel on every Firestore snapshot. Editing a different spool elsewhere (mobile app, another device, anything firing a Firestore write) used to flash the open side panel — every image and every SVG icon was destroyed and re-created because `subscribeInventory.onSnapshot` unconditionally called `openDetail(state.selected)` → `panelBody.innerHTML = buildPanelHTML(r)`. The snapshot listener now calls `refreshOpenDetail()` which compares a `_rowSignature` of the displayed spool against the last render and only rebuilds the panel when the displayed spool's visible fields actually changed. Editing the open spool's weight still triggers one rebuild (pending-write echo) instead of two or three, since the server-commit echo carries the same signature and is now skipped — `renderer/inventory.js`.
109

1110
## Removed
1211

‎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": "1.8.15",
3+
"version": "1.8.16",
44
"description": "Tiger Studio Manager — desktop app",
55
"main": "main.js",
66
"engines": {

‎renderer/inventory.js‎

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3947,10 +3947,9 @@ import { elgFanStep } from './printers/elegoo/widget_control.js';
39473947
saveInventory(raw);
39483948
preCacheImages(state.rows).then(() => {
39493949
sortStateRows(); renderStats(); renderInventory();
3950-
// Refresh open detail panel with latest data
3951-
if (state.selected && $("detailPanel").classList.contains("open")) {
3952-
openDetail(state.selected);
3953-
}
3950+
// Refresh open detail panel — short-circuits when the displayed
3951+
// spool hasn't actually changed (no flash on unrelated edits).
3952+
refreshOpenDetail();
39543953
// Refresh racks panel if open (positions/fills may have changed)
39553954
if ($("racksPanel")?.classList.contains("open")) renderRacksList();
39563955
});
@@ -5925,11 +5924,21 @@ import { elgFanStep } from './printers/elegoo/widget_control.js';
59255924
}
59265925

59275926
/* ── detail panel ── */
5927+
// Signature of the spool currently displayed in the side panel. Used by
5928+
// `refreshOpenDetail()` to skip the full `panelBody.innerHTML = ...` rebuild
5929+
// when a Firestore snapshot arrives that doesn't actually change the
5930+
// displayed spool — e.g. another filament was edited elsewhere, or a write
5931+
// came back as a pending/commit echo with no visible diff. Before this
5932+
// guard, every snapshot tore down all images (large product photo, SVG
5933+
// icons, badges) and rebuilt them, flashing the sidecard.
5934+
let _lastDetailSig = "";
5935+
59285936
function openDetail(spoolId) {
59295937
state.selected = spoolId;
59305938
document.querySelectorAll("[data-id]").forEach(el => el.classList.toggle("selected", el.dataset.id === spoolId));
59315939
const r = state.rows.find(x => x.spoolId === spoolId);
59325940
if (!r) return;
5941+
_lastDetailSig = _rowSignature(r);
59335942
$("panelBody").innerHTML = buildPanelHTML(r);
59345943
// Hold-to-confirm "Delete spool" — 1.5s hold, irreversible hard delete.
59355944
// Removes the Firestore doc (+ twin) permanently — ISO with printer deletion.
@@ -6337,6 +6346,27 @@ import { elgFanStep } from './printers/elegoo/widget_control.js';
63376346
// Stop any playing video
63386347
const vp = $("panelVideoPlayer"); if (vp) vp.innerHTML = "";
63396348
$("detailPanel").classList.remove("open"); $("panelOverlay").classList.remove("open");
6349+
_lastDetailSig = "";
6350+
}
6351+
6352+
// Re-evaluate the open detail panel after a Firestore snapshot. Skips the
6353+
// full rebuild when nothing visible to the panel actually changed (e.g.
6354+
// another spool was edited, or this snapshot is just a write echo with the
6355+
// same fields). Replaces the unconditional `openDetail(state.selected)`
6356+
// that fired on every snapshot and flashed every image (large product
6357+
// photo, SVG badges) of the sidecard.
6358+
function refreshOpenDetail() {
6359+
if (!state.selected) return;
6360+
if (!$("detailPanel")?.classList.contains("open")) return;
6361+
const r = state.rows.find(x => x.spoolId === state.selected);
6362+
if (!r) {
6363+
// Spool removed (hard delete) — close the panel instead of rebuilding.
6364+
closeDetail();
6365+
return;
6366+
}
6367+
const newSig = _rowSignature(r);
6368+
if (newSig === _lastDetailSig) return; // visible content unchanged → skip
6369+
openDetail(state.selected); // sig will be updated inside openDetail
63406370
}
63416371

63426372
/* ── TD1S module init ────────────────────────────────────────────────────

0 commit comments

Comments
 (0)