Skip to content

Commit 2de5045

Browse files
committed
feat: the camera wall becomes a plan, like the two other boards
Cameras are placed by hand on a surface you pan and zoom instead of flowing in a grid, and resized freely by a corner grip — the ½x/1x/2x presets go, three answers to a question the grip answers exactly. Width travels with the position in camWallPlan; a camera already placed keeps its place and its size when its machine goes quiet, since the arrangement is the point of this view. It reuses the shared primitives rather than resembling them: the same rubber band, panning, free-spot search, magnetism and guides, the same Arrange mode and the same zoom arithmetic, down to holding the point under the cursor still. The plan bar is the other plans' bar to the character. One rule governs it throughout: a card holds a live stream, so it is never recreated, only moved — every path positions existing elements, and only the release touches the record. Checkpoint commit, no release.
1 parent 74f53f6 commit 2de5045

7 files changed

Lines changed: 592 additions & 77 deletions

File tree

‎WORKLOG.md‎

Lines changed: 2 additions & 19 deletions
Large diffs are not rendered by default.

‎llms.txt‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ Current version: **2.24.0** — macOS (Intel + Apple Silicon, signed & notarized
1515
## Architecture
1616

1717
- **Entry point**: `main.js` (Electron main process)
18-
- **Renderer**: `renderer/inventory.html` + `renderer/inventory.js` (a single ES module, ~30 900 lines, already importing ~29 sub-modules) + modular CSS in `renderer/css/`
18+
- **Renderer**: `renderer/inventory.html` + `renderer/inventory.js` (a single ES module, ~36 500 lines, already importing ~29 sub-modules) + modular CSS in `renderer/css/`
1919
- **Preload bridge**: `preload.js` — exposes IPC handlers to renderer via `window.electronAPI`
2020
- **NFC**: `services/nfc-process.js` runs as a `utilityProcess` (not a Worker Thread) to avoid SIGABRT on macOS; communicates via IPC
2121
- **Printer integrations**: `renderer/printers/<brand>/index.js` — one file per brand, loaded dynamically

‎package-lock.json‎

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

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

‎renderer/CODEMAP.md‎

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -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 (L23860-27340)
305+
## Printer detail side panel (L23860-27540)
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 (L26321-27900)
315+
## Add-printer flow (L26321-28100)
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 (L27114-30350)
327+
## Racks CRUD + slots (L27114-30560)
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 (L29437-31900)
339+
## Storage view render + DnD (L29437-32440)
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 (L31146-32700)
349+
## Friend view (L31146-33590)
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 (L31928-33760)
359+
## Display name + friend requests (L31928-34640)
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 (L32785-33900)
368+
## Keys + profile sync (L32785-34790)
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 (L33013-34450)
376+
## Custom avatar (L33013-34990)
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 (L33328-35560)
384+
## User doc sync + telemetry + bootstrap (L33328-36440)
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 (L34465-35700)
394+
## Electron RFID integration (L34465-36690)
395395
| L | What | Anchors |
396396
|---|---|---|
397397
| 29714-29801 | Reader indicator (topbar), reader connect/disconnect, card present/removed badge | `renderRfidReaderBadges` |

‎renderer/css/40-printers.css‎

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2955,3 +2955,148 @@ details[open] .pba-step-chev { transform: rotate(90deg); }
29552955
/* The stale dimming the other widgets get would double up on a frame that is
29562956
already empty, so the camera says it once. */
29572957
.printers-grid--plan .cam-card.is-stale { opacity: 1; }
2958+
2959+
/* ── The camera wall as a PLAN ───────────────────────────────────────────
2960+
Same surface as the storage and printer boards: cards placed by hand, a
2961+
scroller that pans over them, a zoom that scales the whole thing from its
2962+
top-left. The grid, the flow order and the ½×/1×/2× presets are gone — a
2963+
grip answers the size question exactly instead of in three steps. */
2964+
.cam-scroll { position: relative; overflow: auto; height: 100%; }
2965+
.cam-wall--plan {
2966+
position: relative;
2967+
display: block; /* not a grid any more — cards are placed */
2968+
transform-origin: 0 0;
2969+
min-height: 240px;
2970+
}
2971+
.cam-wall--plan .cam-wall-card {
2972+
position: absolute;
2973+
grid-column: auto; /* neutralise the old span rules */
2974+
margin: 0;
2975+
/* The card is only as tall as its head plus a 16:9 picture, so ONE number —
2976+
the width — describes it. */
2977+
height: auto;
2978+
}
2979+
/* No z-index here: the layout writes each card's depth inline, which this
2980+
could never outrank. The lift is done in the drag itself, on the element. */
2981+
.cam-wall--plan .cam-wall-card.is-plan-dragging { cursor: grabbing; }
2982+
.cam-wall--plan .cam-wall-card.is-plan-selected,
2983+
.cam-wall--plan .cam-wall-card.is-resizing {
2984+
outline: 2px solid var(--primary);
2985+
outline-offset: 2px;
2986+
}
2987+
/* The move handle sits in the head, first, exactly like a rack's and a
2988+
machine's — the card itself cannot be the handle here, because it is a video
2989+
the user will click, drag across and put fullscreen. */
2990+
/* The grip. Bottom-right, quiet until the card is hovered — a permanent
2991+
handle on every camera would read as clutter on a wall of ten. */
2992+
.cam-resize {
2993+
position: absolute; right: 0; bottom: 0;
2994+
width: 18px; height: 18px;
2995+
cursor: nwse-resize;
2996+
opacity: 0; transition: opacity .15s;
2997+
z-index: 3;
2998+
}
2999+
.cam-resize::after {
3000+
content: ""; position: absolute; right: 3px; bottom: 3px;
3001+
width: 9px; height: 9px;
3002+
border-right: 2px solid var(--on-dark); border-bottom: 2px solid var(--on-dark);
3003+
border-bottom-right-radius: 3px; opacity: .75;
3004+
}
3005+
.cam-wall--plan .cam-wall-card:hover .cam-resize,
3006+
.cam-wall--plan .cam-wall-card.is-resizing .cam-resize { opacity: 1; }
3007+
/* Fullscreen leaves the plan entirely — pinned to the window, not placed on
3008+
the surface beneath it. The layout stops writing inline geometry on this
3009+
card while it is up, which is what lets these rules apply at all. */
3010+
.cam-wall--plan .cam-wall-card--fs {
3011+
position: fixed; inset: 0; width: auto; height: auto;
3012+
outline: none; z-index: 2000;
3013+
}
3014+
3015+
/* ── Arranging the camera wall ───────────────────────────────────────────
3016+
The same mesh the storage plan draws, at the same 24 px step, scaled by the
3017+
zoom so it keeps matching the snap. Only while arranging: a wall you are
3018+
watching should show pictures, not graph paper. */
3019+
#invPrinterView.is-plan-edit .cam-scroll,
3020+
#invPrinterView.is-plan-moving .cam-scroll {
3021+
background-attachment: local;
3022+
background-image:
3023+
linear-gradient(var(--grid-major) 1px, transparent 1px),
3024+
linear-gradient(90deg, var(--grid-major) 1px, transparent 1px),
3025+
linear-gradient(var(--grid-line) 1px, transparent 1px),
3026+
linear-gradient(90deg, var(--grid-line) 1px, transparent 1px);
3027+
background-size:
3028+
calc(120px * var(--plan-z, 1)) calc(120px * var(--plan-z, 1)),
3029+
calc(120px * var(--plan-z, 1)) calc(120px * var(--plan-z, 1)),
3030+
calc(24px * var(--plan-z, 1)) calc(24px * var(--plan-z, 1)),
3031+
calc(24px * var(--plan-z, 1)) calc(24px * var(--plan-z, 1));
3032+
}
3033+
/* Zoomed well out the fine mesh is graph paper rather than a guide. */
3034+
#invPrinterView.is-plan-coarse.is-plan-edit .cam-scroll,
3035+
#invPrinterView.is-plan-coarse.is-plan-moving .cam-scroll {
3036+
background-image:
3037+
linear-gradient(var(--grid-major) 1px, transparent 1px),
3038+
linear-gradient(90deg, var(--grid-major) 1px, transparent 1px);
3039+
background-size:
3040+
calc(120px * var(--plan-z, 1)) calc(120px * var(--plan-z, 1)),
3041+
calc(120px * var(--plan-z, 1)) calc(120px * var(--plan-z, 1));
3042+
}
3043+
/* While arranging the card is a thing you move, so it says so — and its head
3044+
stops hiding, because that is where its name and its controls live and you
3045+
are now working with the cards rather than watching them. */
3046+
#invPrinterView.is-plan-edit .cam-wall--plan .cam-wall-card { cursor: grab; outline: 1px solid var(--border); }
3047+
#invPrinterView.is-plan-edit .cam-wall--plan .cam-wall-card:hover { outline-color: var(--border-strong); }
3048+
#invPrinterView.is-plan-edit .cam-wall--plan .cam-wall-card-head { opacity: 1; }
3049+
#invPrinterView.is-plan-edit .cam-wall--plan .cam-resize { opacity: 1; }
3050+
#invPrinterView.is-plan-edit .cam-wall--plan .cam-wall-card.is-plan-dragging { cursor: grabbing; }
3051+
/* The head offers the grab, in both modes — it is the part of the card that is
3052+
not the picture, so taking hold of it is never ambiguous. */
3053+
.cam-wall--plan .cam-wall-card-head { cursor: grab; }
3054+
.cam-wall--plan .cam-wall-card-head .cam-size-btns { cursor: default; }
3055+
.cam-wall--plan .cam-wall-card.is-plan-dragging .cam-wall-card-head { cursor: grabbing; }
3056+
3057+
/* A placed camera whose machine is quiet keeps its place and its size, drawn
3058+
empty and set back. Dimmed rather than hidden: the arrangement is the point
3059+
of this view, and a card that vanished when a printer slept would take a
3060+
piece of it with it — while a solid black rectangle would read as a feed
3061+
that had failed. Its head stays reachable, so it can still be moved and
3062+
resized while the machine is off. */
3063+
.cam-wall-card--offline { opacity: .5; }
3064+
.cam-wall-card--offline:hover { opacity: .8; }
3065+
.cam-wall-offline {
3066+
width: 100%; aspect-ratio: 16 / 9;
3067+
display: flex; align-items: center; justify-content: center;
3068+
background: var(--surface-2);
3069+
border: 1px dashed var(--border-strong);
3070+
border-radius: 10px;
3071+
}
3072+
.cam-wall-offline .icon { background: var(--muted); opacity: .6; }
3073+
3074+
/* ── The states of a camera card, in the order they must win ─────────────
3075+
Arranging draws a plain border on every card, and that rule carries the
3076+
view's id — so at lower specificity a SELECTION was silently overruled by
3077+
it, and hovering a selected card overruled it again. Written here at the
3078+
same weight and after it, so the outcome is decided by this order rather
3079+
than by which selector happens to carry an extra class. Same accent, same
3080+
2px, same offset as the storage and printer boards: a selection has to look
3081+
identical wherever it is made. */
3082+
#invPrinterView.is-plan-edit .cam-wall--plan .cam-wall-card.is-plan-selected,
3083+
#invPrinterView.is-plan-edit .cam-wall--plan .cam-wall-card.is-plan-selected:hover,
3084+
#invPrinterView .cam-wall--plan .cam-wall-card.is-plan-selected,
3085+
#invPrinterView.is-plan-edit .cam-wall--plan .cam-wall-card.is-resizing,
3086+
#invPrinterView .cam-wall--plan .cam-wall-card.is-resizing {
3087+
outline: 2px solid var(--primary); outline-offset: 2px;
3088+
}
3089+
/* Held beats selected: while a set is moving, what is in the hand is what you
3090+
need to see. */
3091+
#invPrinterView.is-plan-edit .cam-wall--plan .cam-wall-card.is-plan-dragging,
3092+
#invPrinterView.is-plan-edit .cam-wall--plan .cam-wall-card.is-plan-dragging:hover,
3093+
#invPrinterView .cam-wall--plan .cam-wall-card.is-plan-dragging {
3094+
outline: 2px solid var(--primary); outline-offset: 2px;
3095+
box-shadow: 0 14px 34px rgba(0, 0, 0, .5);
3096+
}
3097+
3098+
/* The alignment guides must sit above the card being dragged. On the storage
3099+
plan its cards live at a low depth, so the shared 1001 clears them; here a
3100+
held card is lifted to the front deliberately, and the guides have to clear
3101+
THAT — a guide hidden behind the thing it is guiding explains nothing. */
3102+
.cam-wall--plan .rp-plan-guides { z-index: 9500; }

0 commit comments

Comments
 (0)