Skip to content

Commit 7fd44f9

Browse files
v1.4.13 — custom product image, Clear TD, TD1S in ADP, Cloud stat, dark window
- Custom url_img for DIY/Cloud spools: edit pill in colour square, toolbox entry, broken-link recovery, ADP form field (url_img_user:true discriminant) - Toolbox split-button: hold-to-confirm Clear TD trash (FieldValue.delete) - ADP header TD1S button: not-connected → modal, connected → green glow + auto-fill - Stats bar: TigerTag Cloud purple tile always visible, DIY count excludes Cloud - Dark window chrome: nativeTheme dark, hasShadow:false - Update status icon: orange spinner / green glow with tooltip + click-to-install - Panel shadow bleed fix: detail-panel, sfe-sheet, rp-side (shadow on .open only) - CLAUDE.md: token efficiency workflow, CSS standards, proactive model-fit signals
1 parent 94a1c7a commit 7fd44f9

20 files changed

Lines changed: 543 additions & 33 deletions

‎CLAUDE.md‎

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,37 @@
11
# Tiger Studio Manager — Claude reference
22

3+
## ⚡ Token efficiency — read this first
4+
5+
Every file read and grep costs tokens. Follow these rules on every task to keep context lean:
6+
7+
| Do | Don't |
8+
|----|-------|
9+
| Read `CODEMAP.md` → jump to the exact line range | Read `inventory.js` from the top |
10+
| `grep -n "anchorFn"` → read only that range (`offset`/`limit`) | Read an entire 1000-line file to find one function |
11+
| Re-use content already in context this session | Re-read a file you read 2 messages ago |
12+
| `Read` with `offset`+`limit` to fetch the exact slice | `Read` without limits on files > 200 lines |
13+
| `Edit` with the minimal `old_string` that is unique | Rewrite whole sections when only 3 lines change |
14+
| Run `grep` + `Read` in parallel when targets are independent | Sequential read-then-grep round-trips |
15+
| Check `CODEMAP.md` line ranges before any `inventory.js` read | Blind grep across the 15 000-line file |
16+
17+
**Workflow for any `inventory.js` change:**
18+
1. `CODEMAP.md` → find section + anchor function name
19+
2. `grep -n "anchorFn"` → get exact line number
20+
3. `Read offset=N limit=40` → confirm context, draft edit
21+
4. `Edit` minimal diff
22+
23+
**Workflow for any CSS change:**
24+
1. Identify the right file from the file map (00-base → 70-detail-misc)
25+
2. `grep -n "selector"` in that file → get line
26+
3. `Read offset=N limit=20` → confirm, then `Edit`
27+
28+
> Warn the user when context is getting large (> ~60 k tokens used) so they can start a new session before quality degrades.
29+
30+
**Model fit — signal proactively, don't wait to be asked:**
31+
- **Tâche simple** (CSS tweak, i18n key, valeur à changer, question courte) → suggérer de passer sur **claude-haiku** ou **claude-sonnet** pour économiser. Formulation : *"Cette tâche est simple — tu peux la faire sur Sonnet/Haiku pour économiser des tokens."*
32+
- **Tâche complexe** (refacto multi-fichiers, nouveau système, débogage multi-couches, architecture) → si tu sens que le raisonnement manque de profondeur ou que tu te trompes, demander de passer sur **claude-opus**. Formulation : *"Cette tâche est complexe — passer sur Opus donnera un meilleur résultat."*
33+
- Ne pas attendre que l'utilisateur remarque un problème : signaler dès que le mismatch est évident.
34+
335
## Stack
436
Electron (no bundler) + vanilla HTML/CSS/JS. Entry: `main.js`. Renderer: `renderer/inventory.html` + modular CSS in `renderer/css/` + `renderer/inventory.js`. Preload bridge: `preload.js`.
537

@@ -525,3 +557,36 @@ Source: `scripts/check-i18n-consistency.mjs` + `.githooks/pre-commit`.
525557
- **CSS**: split across `renderer/css/00-base.css … 70-detail-misc.css` (loaded in numeric order). Add new rules in the section file that matches the feature — e.g. Snapmaker tweaks go in `50-snapmaker.css`, modal tweaks in `60-modals.css`. Asset URLs use `url('../../assets/svg/icons/…')` (two `..` because we're in `renderer/css/`). Scoped IDs (`#editAccountModalOverlay`, `#addAccountModalOverlay`, etc.) still apply where needed.
526558
- **displayName**: always read from Firestore `users/{uid}.displayName` (pseudo). Never use Firebase Auth `user.displayName` for UI display — it contains the Google real name.
527559
- **Admin fields**: `roles` and `Debug` in `users/{uid}` must only be written via Firebase Admin SDK / Cloud Function. The client toggle is a UX convenience for admins already authenticated.
560+
561+
## CSS coding standards
562+
563+
### SVG icons via `-webkit-mask-image`
564+
Always constrain **one dimension only** and derive the other from the SVG's intrinsic `viewBox` ratio using `aspect-ratio`. Never hard-code both `width` and `height` — doing so silently distorts the icon if the SVG is ever edited.
565+
566+
```css
567+
/* ✅ correct — only height forced, width auto-derived */
568+
.my-icon {
569+
height: 18px;
570+
aspect-ratio: 52 / 22; /* viewBox="0 0 52 22" */
571+
background-color: var(--muted);
572+
-webkit-mask-image: url('../../assets/svg/icons/icon_foo.svg');
573+
-webkit-mask-size: contain;
574+
-webkit-mask-repeat: no-repeat;
575+
-webkit-mask-position: center;
576+
}
577+
578+
/* ❌ wrong — both dimensions hard-coded, ratio broken if SVG changes */
579+
.my-icon { width: 40px; height: 18px; … }
580+
```
581+
582+
To find the ratio: `head -1 assets/svg/icons/icon_foo.svg` → read `viewBox="0 0 W H"` → use `aspect-ratio: W / H`.
583+
584+
### CSS specificity
585+
When a global rule (e.g. `input[type="text"]` — specificity 0,1,1) overrides a class rule (0,1,0), **double the class selector** to reach 0,2,0 rather than adding `!important`:
586+
```css
587+
/* beats input[type="text"] without !important */
588+
.my-sheet .my-input { background: transparent; }
589+
```
590+
591+
### Hold-to-confirm buttons
592+
Use `setupHoldToConfirm(el, durationMs, callback)`. The element must contain a `<span class="hold-progress"></span>` child. Duration guideline: 1200 ms for reversible actions, 1500 ms for hard-destructive (delete/unlink).

‎README.md‎

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
## Features
1818

19+
- 🖼️ **Custom product image (v1.4.13)** — DIY and TigerTag Cloud spools can now display a product image from any external URL. Set it from the detail panel (Edit button in the colour square, or toolbox when already set) or directly from the Add Product form. Broken URLs fall back to the colour placeholder automatically; the edit trigger reappears so you can fix the link.
1920
- 🌥️ **TigerTag Cloud — 100 % digital filaments, no chip required (v1.4.12)** — Create a fully-formed inventory entry without an RFID chip via the **Add Product** side panel. The doc gets a `CLOUD_<10-digit>` id and the new **TigerTag Cloud** tier badge (purple, distinct from TigerTag+ orange and TigerTag grey). Track filaments you bought before adopting TigerTag, wishlist rolls you plan to buy, or onboard family / studio members who don't own a reader yet. When you eventually program a chip, the doc is atomically renamed to its real 7-byte hex UID and every field you already entered is promoted in place — no second entry, no copy / paste
2021
- **Inventory management** — Real-time sync with your TigerTag inventory via Firebase/Firestore
2122
- **Table & Grid views** — Switch between a compact table or a visual card grid
@@ -51,6 +52,32 @@
5152

5253
## Changelog
5354

55+
### v1.4.13 — 2026-05-07
56+
57+
#### Custom product image for DIY & Cloud spools
58+
- **`url_img` + `url_img_user: true`** — DIY and TigerTag Cloud spools can now carry a product image from an external URL. The same `url_img` field used by TigerTag+ catalogue entries is reused; a companion boolean `url_img_user: true` flags the value as user-supplied so `isPlus` stays `false` and the spool keeps its DIY/Cloud identity. TigerTag+ spools are not editable (their image comes from the online catalogue).
59+
- **Edit pill in the colour square** — a unified pill bar sits at the bottom of the image/colour square. The edit icon (✏️) on the left is the trigger; tapping it expands the pill rightward to reveal the URL input and a confirm button (●) on the right. Keyboard: `Enter` = confirm, `Escape` = dismiss. The pill collapses back to just the icon when closed.
60+
- **Toolbox entry** — when a valid user image is already set, the edit action moves to the spool toolbox ("Edit image" row) and disappears from the colour square.
61+
- **Broken-link recovery** — an `onerror` handler detects failed image loads, swaps in the colour placeholder, and surfaces the edit trigger so the user can fix the URL without hunting for it.
62+
- **Add Product integration** — the ADP advanced section now has an image URL field; the value is written alongside `url_img_user: true` when the spool is created.
63+
64+
#### Toolbox — Clear TD value
65+
- New split-button on the "Scan TD" toolbox row: a hold-to-confirm trash button (1 200 ms) appears to the right when `r.td != null`. Holding it deletes the `TD` field from Firestore via `FieldValue.delete()`. The row is hidden entirely when no TD is set.
66+
67+
#### Add Product panel — TD1S sensor button
68+
- TD1S icon added to the ADP header (left of the close button). **Not connected** → opens the TD1S connect modal. **Connected** → glows green; scanning a filament auto-fills the colour HEX and TD value fields in the form. Button state syncs on `onStatus` events and on every `openAddProductPanel()` call.
69+
70+
#### Stats bar — TigerTag Cloud counter
71+
- New purple stat tile ("TigerTag Cloud") always visible in the inventory header bar, showing the count of `CLOUD_` spools. DIY count now correctly excludes Cloud entries.
72+
73+
#### Window chrome
74+
- **Dark title bar** — `nativeTheme.themeSource = 'dark'` forces the native macOS/Windows title bar to render in dark mode (dark background, white text, dark traffic lights).
75+
- **No window shadow** — `hasShadow: false` removes the OS-level drop shadow that was appearing along the right and bottom edges of the window.
76+
- **Update status icon** — a new icon sits to the right of the cloud health indicator. Hidden when up to date; orange + spinning during download; green + glow when a restart is ready. Tooltip on hover; clicking the green icon triggers the install.
77+
- **Panel shadow bleed fix** — `detail-panel`, `sfe-sheet` (Snapmaker filament edit) and `rp-side` (rack unranked panel) were leaking their `box-shadow` outside the viewport when off-screen. Shadow now applied only on `.open` / `.is-open` state.
78+
79+
---
80+
5481
### v1.4.12 — 2026-05-06
5582

5683
> 🌥️ **The big one: TigerTag goes Cloud.** This release crosses a threshold the project has been building towards for a year — **you can now create a filament in your inventory without owning an RFID chip**. Open Add Product, fill the form, hit Make → a fully-formed inventory entry lands in Firestore with a `CLOUD_<10-digit>` doc id and the new **TigerTag Cloud** tier badge. No reader required. No chip burned. When you eventually get a physical chip and program it, the same doc gets renamed to its 7-byte hex UID and ALL the data you already entered (brand, material, colour, weight, temps, TD, RFID Data preview, …) is promoted in place — no copy / paste, no second entry, no friction.

‎main.js‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
const { app, BrowserWindow, ipcMain, shell, session } = require('electron');
1+
const { app, BrowserWindow, ipcMain, shell, session, nativeTheme } = require('electron');
22
const { autoUpdater } = require('electron-updater');
33
const path = require('path');
44
const fs = require('fs');
@@ -106,6 +106,7 @@ function createWindow() {
106106
minWidth: 900,
107107
minHeight: 600,
108108
title: 'Tiger Studio Manager',
109+
hasShadow: false,
109110
webPreferences: {
110111
preload: path.join(__dirname, 'preload.js'),
111112
contextIsolation: true,
@@ -961,6 +962,9 @@ app.whenReady().then(async () => {
961962
imgCacheDir = path.join(app.getPath('userData'), 'img_cache');
962963
fs.mkdirSync(imgCacheDir, { recursive: true });
963964

965+
// Force dark window chrome (title bar, traffic lights) on all platforms
966+
nativeTheme.themeSource = 'dark';
967+
964968
// macOS native "About Tiger Studio Manager" menu (Apple menu → About)
965969
if (process.platform === 'darwin') {
966970
app.setAboutPanelOptions({

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

‎renderer/css/00-base.css‎

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -331,6 +331,30 @@
331331
line-height: 1.2;
332332
margin-top: 2px;
333333
}
334+
/* TigerTag Cloud stat — purple gradient accent */
335+
.sb-stat--cloud {
336+
border-color: rgba(124,77,255,.35) !important;
337+
background: linear-gradient(135deg, rgba(124,77,255,.12), rgba(163,123,255,.07)) !important;
338+
}
339+
.sb-stat--cloud:hover {
340+
border-color: rgba(124,77,255,.55) !important;
341+
background: linear-gradient(135deg, rgba(124,77,255,.18), rgba(163,123,255,.12)) !important;
342+
}
343+
.sb-stat--cloud .value { color: #a37bff; }
344+
.sb-stat--cloud .label { color: rgba(163,123,255,.7); }
345+
/* collapsed sidebar: keep the purple tint on the mini tile */
346+
.sidebar.collapsed .sb-stat--cloud::before { color: #a37bff; }
347+
.sidebar.collapsed .sb-stat--cloud::after { color: rgba(163,123,255,.6); }
348+
/* top-stats (header) cloud variant */
349+
.top-stats .sb-stat--cloud {
350+
border-color: rgba(124,77,255,.35) !important;
351+
background: linear-gradient(135deg, rgba(124,77,255,.08), rgba(163,123,255,.04)) !important;
352+
}
353+
.top-stats .sb-stat--cloud:hover {
354+
border-color: rgba(124,77,255,.55) !important;
355+
}
356+
.top-stats .sb-stat--cloud .value { color: #7c4dff; }
357+
.top-stats .sb-stat--cloud .label { color: rgba(124,77,255,.75); }
334358

335359

336360
/* Sidebar friends quick-access list — plain menu items, no card.

‎renderer/css/30-racks.css‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -826,9 +826,9 @@
826826
z-index: 30; /* above content, below modals */
827827
transform: translateX(100%);
828828
transition: transform .28s cubic-bezier(.4, 0, .2, 1);
829-
box-shadow: -8px 0 24px rgba(0,0,0,.12);
829+
box-shadow: none;
830830
}
831-
.rp-side.is-open { transform: translateX(0); }
831+
.rp-side.is-open { transform: translateX(0); box-shadow: -8px 0 24px rgba(0,0,0,.12); }
832832
/* While dragging a spool out of the panel, slide it off-screen so the racks
833833
behind become available as drop targets. The persistent open/close state
834834
is left untouched — the panel slides back in on dragend. */

‎renderer/css/50-snapmaker.css‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -690,14 +690,14 @@
690690
border-right: none;
691691
border-bottom-left-radius: 14px;
692692
border-top-left-radius: 18px;
693-
box-shadow: -8px -4px 32px rgba(0,0,0,.18);
693+
box-shadow: none;
694694
z-index: 103;
695695
transform: translateY(calc(100% + 8px));
696-
transition: transform .28s cubic-bezier(.4, 0, .2, 1);
696+
transition: transform .28s cubic-bezier(.4, 0, .2, 1), box-shadow .28s;
697697
display: flex; flex-direction: column;
698698
overflow: hidden;
699699
}
700-
.sfe-sheet.open { transform: translateY(0); }
700+
.sfe-sheet.open { transform: translateY(0); box-shadow: -8px -4px 32px rgba(0,0,0,.18); }
701701

702702
/* Sub-picker sheets (Filament + Colour) — both stack ON TOP of the
703703
summary sheet (higher z-index). They open taller than the summary

‎renderer/css/60-modals.css‎

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1273,6 +1273,29 @@
12731273
.health .icon { color: var(--neutral); transition: color .25s, filter .25s; }
12741274
.health.ok .icon { color: var(--success); filter: drop-shadow(0 0 6px var(--success)); }
12751275
.health.bad .icon { color: var(--danger); filter: drop-shadow(0 0 6px var(--danger)); }
1276+
1277+
/* ── update status icon ── */
1278+
.update-health-icon {
1279+
display: block; height: 18px; aspect-ratio: 1;
1280+
background-color: var(--neutral);
1281+
-webkit-mask-image: url('../../assets/svg/icons/icon_download.svg');
1282+
-webkit-mask-size: contain; -webkit-mask-repeat: no-repeat; -webkit-mask-position: center;
1283+
transition: background-color .25s, filter .25s;
1284+
}
1285+
/* downloading: orange + spinning refresh */
1286+
.update-health.downloading .update-health-icon {
1287+
background-color: var(--primary);
1288+
-webkit-mask-image: url('../../assets/svg/icons/icon_refresh.svg');
1289+
animation: updateSpin .9s linear infinite;
1290+
}
1291+
/* ready: green glow + download arrow */
1292+
.update-health.ready { cursor: pointer; }
1293+
.update-health.ready .update-health-icon {
1294+
background-color: var(--success);
1295+
filter: drop-shadow(0 0 5px var(--success));
1296+
}
1297+
@keyframes updateSpin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }
1298+
12761299
.health::after {
12771300
content: attr(data-tooltip);
12781301
position: absolute; top: calc(100% + 7px); right: 0;
@@ -1387,6 +1410,35 @@
13871410
align-items: flex-start !important;
13881411
}
13891412
.adp-head-text { flex: 1; min-width: 0; }
1413+
/* TD1S sensor button — sits between the title and the close button.
1414+
The mask-image reuses the same icon_td1s.svg asset as the sidebar
1415+
health icon. Green glow when the sensor is connected. */
1416+
.adp-td1s-btn {
1417+
align-self: center;
1418+
flex-shrink: 0;
1419+
display: flex; align-items: center; justify-content: center;
1420+
background: transparent; border: 0; border-radius: 8px;
1421+
cursor: pointer; padding: 4px 6px;
1422+
transition: background .12s;
1423+
}
1424+
.adp-td1s-btn:hover { background: rgba(255,255,255,.08); }
1425+
.adp-td1s-btn::before {
1426+
content: "";
1427+
display: block;
1428+
/* Only height is forced — width derives from the SVG viewBox ratio
1429+
(52 × 22) so the icon is never distorted regardless of future
1430+
SVG edits. Same visual size as the sidebar td1s-health-icon. */
1431+
height: 18px;
1432+
aspect-ratio: 52 / 22;
1433+
background-color: rgba(255,255,255,.35);
1434+
-webkit-mask-image: url('../../assets/svg/icons/icon_td1s.svg');
1435+
-webkit-mask-size: contain; -webkit-mask-repeat: no-repeat; -webkit-mask-position: center;
1436+
transition: background-color .3s, filter .3s;
1437+
}
1438+
.adp-td1s-btn.td1s-connected::before {
1439+
background-color: var(--success);
1440+
filter: drop-shadow(0 0 5px var(--success));
1441+
}
13901442
.adp-head-sub {
13911443
margin-top: 3px;
13921444
font-size: 12px;

0 commit comments

Comments
 (0)