Skip to content

Commit 3b21a52

Browse files
committed
v2.25.0 — the camera view becomes a plan, and fullscreen means the screen
The camera view stops being a grid: cameras are placed by hand on a pan/zoom surface and resized freely by a corner grip, with an Arrange mode carrying the storage plan's mesh, magnetism, guides and sweep-selection. A placed camera keeps its spot and size while its machine is offline. A card holds a live stream, so it is never recreated — only moved. Fullscreen is handed to the Fullscreen API, so it fills the screen rather than the app window, and its key is a switch again instead of being hard-wired to "fullscreen". Fixes the Intel-Mac crash on camera start: the x64 build shipped an arm64 ffmpeg, detection trusted accessSync (which cannot tell whether THIS cpu can run a file), and both spawn sites were unguarded although spawn throws synchronously on a bad CPU type. One binary is now staged per architecture and installed by an afterPack hook, each candidate is probed before it is trusted, and a camera that will not start is just a camera that will not start.
1 parent 2de5045 commit 3b21a52

26 files changed

Lines changed: 464 additions & 34 deletions

‎.gitignore‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,3 +157,6 @@ CLAUDE.local.md
157157
# scripts/gen-oauth-config.mjs. NEVER commit: the client secret ships inside
158158
# the built app (unavoidable) but must not land in the public repository.
159159
oauth-config.json
160+
161+
# Per-architecture ffmpeg binaries staged at build time (see scripts/prepare-ffmpeg.mjs)
162+
build/ffmpeg/

‎CHANGELOG.md‎

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

66
---
77

8+
## v2.25.0 — 2026-08-30
9+
10+
### Changed
11+
12+
- **The camera view is a plan, like the storage and printer boards.** Cameras are placed by hand on a surface you pan and zoom instead of flowing in a grid, and they are RESIZED FREELY by a corner grip — the ½× / 1× / 2× presets are gone, three answers to a question the user can now answer exactly. A camera's width travels with its position (`camWallPlan: {x, y, z, w}`, its own field because the printer board's camera widget already owns `camPlan` and the two are placed independently); only the width is stored, since the picture is 16:9 and the head is fixed, so one number describes the card. It has an **Arrange mode**: the storage plan's own 24 px mesh scaled by the zoom, a card's HEAD as a handle in both modes (it is the part that is not the picture, so taking hold of it can never be read as a click on the video), and arranging extends the handle to the whole card while giving the LEFT BUTTON to the rubber band — sweeping and panning both start on empty surface, so one has to yield, and arranging is precisely when you mean to pick cards rather than travel; the wheel button pans in both modes, so the view is never trapped. Its plan bar is the other plans' bar to the character — same classes, same order, same numeric field with its own `%` beside it — and its zoom is their zoom down to the arithmetic: same bounds, same round-ten ladder, the point under the cursor held still, wheel with no modifier, stray notches from a held-down wheel ignored. Dragging uses the storage plan's magnetism and its guides, the grabbed card being the one the magnetism speaks for while the rest of a group follow at their own offsets; the mesh appears the moment anything is taken or resized, in or out of Arrange, because outside it nothing else says a gesture has become an edit. A card taken in the hand comes to the FRONT and dropped last stays on top — depth is written inline by the layout, which no stylesheet rule can outrank, so the lift is done on the element and restored from the record on release. A swept selection is ringed in the accent, 2 px at the boards' offset, written as one ordered block carrying the view's id because the plain border arranging draws also carries it and silently outranked the selection at lower weight. Moving a camera no longer opens its machine's side panel: arranging suppresses it outright, and outside it the guard goes up the instant the gesture becomes a move — raised on release it was raised after the writes were awaited, and the click following a release fires long before a round-trip to Firestore resolves. Widths are applied in their own pass before any position is computed, since the free-spot search MEASURES a card to find room for it and a card not yet given its width measures whatever the stylesheet last left it. ONE RULE GOVERNS IT: a card holds a live stream, so it is never recreated, only moved — every path positions existing elements, the drag writes the element and only the release touches the record. The drag-to-reorder went with the grid it served; `camSortIndex` now only orders cameras that have never been placed, on their first render. **A placed camera keeps its place when its machine goes quiet** — same spot, same size, dimmed and empty, still movable and resizable; only a camera never placed is absent, there being nothing of it to preserve — `renderer/inventory.js`, `renderer/css/40-printers.css`.
13+
- **A camera sent fullscreen now fills the SCREEN.** `position: fixed` only ever covered the app window, so the title bar, the menu bar and the dock stayed in front of a view whose entire point is to fill the screen. The card is handed to the Fullscreen API instead, which puts it in the top layer at screen size and MOVES NO ELEMENT, so the stream inside it is not interrupted; the window-filling card remains as the fallback when the platform refuses, with an explicit background, a fullscreen element being painted against nothing. Leaving is believed rather than assumed — Escape, the green button and the system all end fullscreen behind the app's back, so the card's state is written from `fullscreenchange` rather than from what was asked for. The layout still refuses to write inline geometry on a card while it is up there: position, size and depth are written inline and no stylesheet, UA sheet included, can outrank that — `renderer/inventory.js`, `renderer/css/40-printers.css`.
14+
15+
### Fixed
16+
17+
- **The app no longer dies on an Intel Mac when a camera starts.** Three defects in a line, each sufficient alone. (1) The Intel build shipped an Apple-Silicon ffmpeg: `ffmpeg-static` downloads ONE binary, for the machine doing the install, and GitHub's macOS runners are Apple Silicon — both DMGs carried the arm64 binary. `scripts/prepare-ffmpeg.mjs` now fetches one per architecture before packaging and `build/afterPack.js` installs the matching one, before signing so it is signed with the bundle; both read the Mach-O header and refuse to continue on a mismatch. The staging also defeats the installer's habit of skipping a download whenever a binary is present, without ever checking its architecture. (2) Detection trusted `fs.accessSync(X_OK)`, which answers "is this file executable" and never "can THIS cpu execute it", so the unusable bundled binary was adopted and the Homebrew one next in line was never reached; each candidate is now probed with `spawnSync(p, ['-version'])` before it is trusted, which also catches a missing library, a quarantined file or a truncated download. (3) Both spawn sites were unguarded: `spawn` throws SYNCHRONOUSLY on a bad CPU type (`EBADARCH`, errno −86), on the assignment itself, so the `proc.on('error')` handler registered on the next line did not yet exist and the failure reached the main process as an uncaught exception — a crash dialog, for a camera. Both are wrapped, and a camera that will not start is now just a camera that will not start — `main.js`, `scripts/prepare-ffmpeg.mjs`, `build/afterPack.js`, `build/sign-and-notarize.sh`, `package.json`.
18+
- **The camera view's fullscreen key is a switch again.** It was hard-wired to `data-size="fs"`, so pressing it while already fullscreen asked for fullscreen a second time — which is nothing; Escape and a click on the picture were the only ways back, and a key that visibly does nothing reads as broken. It now carries the size it will PUT YOU IN, icon and label included, and is flipped in place rather than re-rendered, the card it sits on holding a live stream — `renderer/inventory.js`.
19+
- The bare `ffmpeg` fallback on PATH was never reachable: it was tested with `accessSync`, which resolves a bare name against the working directory rather than PATH — `main.js`.
20+
21+
### i18n
22+
23+
- Added: `camSizeExitFullscreen` — 9 locales.
24+
25+
---
26+
827
## v2.24.0 — 2026-08-30
928

1029
### Added

‎CODEMAP-main.md‎

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -100,19 +100,19 @@ L3810-3870 App lifecycle (whenReady, window-all-closed, activate)
100100

101101
| L | What | IPC |
102102
|---|---|---|
103-
| 2629-2660 | Timelapse video download (`timelapse:download`) | — |
104-
| 2661-2733 | Elegoo MQTT 1883 bridge (`elegoo:connect` / `disconnect` / `publish`) | — |
105-
| 2734-2765 | Shared `ffmpeg` binary detection (Bambu RTSP + Anycubic FLV cameras) | — |
103+
| 3057-3086 | Timelapse video download (`timelapse:download`) | — |
104+
| 3087-3159 | Elegoo MQTT 1883 bridge (`elegoo:connect` / `disconnect` / `publish`) | — |
105+
| 3160-3214 | Shared `ffmpeg` binary detection (Bambu RTSP + Anycubic FLV cameras) — each candidate is PROBED, not just stat'ed | — |
106106

107-
## Bambu Lab — MQTT + JPEG-TCP/RTSP cameras (L3121-3121)
107+
## Bambu Lab — MQTT + JPEG-TCP/RTSP cameras (L3215-3483)
108108

109109
| L | What | Anchors / IPC |
110110
|---|---|---|
111-
| 2766-2825 | MQTTS 8883 control bus (`bambulab:connect` / `disconnect` / `publish`) | — |
112-
| 2826-2910 | JPEG-TCP camera, port 6000 (`bambulab:cam-start` / `cam-stop`) — 80-byte auth packet, retry/timeout | `_bambuCamAuthPacket` |
113-
| 2911-3019 | RTSP camera via ffmpeg, port 322 (`bambulab:cam-start-rtsp` / `cam-stop-rtsp`) — 30 fps + low-latency flags | — |
111+
| 3215-3275 | MQTTS 8883 control bus (`bambulab:connect` / `disconnect` / `publish`) | — |
112+
| 3276-3365 | JPEG-TCP camera, port 6000 (`bambulab:cam-start` / `cam-stop`) — 80-byte auth packet, retry/timeout | `_bambuCamAuthPacket` |
113+
| 3366-3483 | RTSP camera via ffmpeg, port 322 (`bambulab:cam-start-rtsp` / `cam-stop-rtsp`) — 30 fps + low-latency flags; the spawn is guarded (it throws synchronously on a bad CPU type) | — |
114114

115-
## Anycubic LAN — MQTT, provisioning, FLV camera (L3585-3593)
115+
## Anycubic LAN — MQTT, provisioning, FLV camera (L3484-3815)
116116

117117
| L | What | Anchors / IPC |
118118
|---|---|---|

‎FEATURES.md‎

Lines changed: 3 additions & 3 deletions
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.24.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.25.0**. Per-version release detail lives in `CHANGELOG.md`; forward-looking / in-progress work lives in `ROADMAP.md`.
44

55
---
66

@@ -203,9 +203,9 @@ Live integrations across six brands, each with real-time temperatures, per-slot
203203
## Printer cameras
204204

205205
- Per-brand camera widget architecture (`renderCamBanner` dispatch, brand-specific `widget_camera.js` modules) supporting MJPEG, WebRTC/RTSP and iframe transports (v1.4.7+).
206-
- Camera wall view — all online printer cameras in one grid, with click-to-open sidecard (v1.7.1).
206+
- Camera view as a free plan — each camera placed by hand on a pan/zoom surface and resized freely by a corner grip, with an Arrange mode carrying the storage plan's mesh, magnetism, alignment guides and sweep-selection; a placed camera keeps its spot and size while its machine is offline (v1.7.1 as a grid, free plan v2.25.0).
207207
- Detached Camera Wall — standalone window showing every camera simultaneously, zero-copy frame forwarding via `BroadcastChannel` (v1.8.2).
208-
- Camera-wall size controls (½×/1×/2×/fullscreen) and CSS-`order`-based drag reorder that never interrupts a live stream (v1.7.2).
208+
- True fullscreen for a single camera via the Fullscreen API — fills the screen rather than the app window, and never recreates the card, so the live stream survives (v1.7.2 as a window-filling card, real fullscreen v2.25.0).
209209
- Shared MJPEG multiplexer (`cam_manager.js`) — one upstream connection serves every consumer (sidecard + cam wall) simultaneously, respecting single-client firmware limits (v1.7.2; FlashForge-specific precursor v1.7.1).
210210
- Creality single shared `RTCPeerConnection` across every surface that shows its camera (v1.8.2).
211211

‎README.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,8 @@ Some brands also expose a **live control panel** (Snapmaker, Elegoo, Anycubic):
147147

148148
The **printer board** lays your fleet out the way your workshop actually looks: place every machine by hand on a plan you zoom and pan, and it stays where you put it — a printer going offline no longer changes place. Beside each machine you can show its **filament units** (Ext., AMS 1, AMS 2, CFS, ACE…) as separate boxes with their slots, colours and materials at the same size as your storage racks, and a **temperature widget** that stays put when the printer sleeps. Cards can be **grouped for good**, so moving one carries the whole set.
149149

150+
The **camera view** works the same way: drag each camera where you want it, pull its corner to give it exactly the size it deserves, and use **Arrange** to snap them against each other with the same grid and guides as the other two plans. A camera keeps its place and its size while its printer sleeps, and one sent fullscreen fills the whole screen.
151+
150152
Each machine's card can also show **what it is printing** — preview, percentage, time left, finish time and layers — and **its camera**: hover the printer's photo, press play, and the photo gives way to the live feed. **Pause and stop** sit on the card while it prints, so you never have to go looking for the machine. Nothing streams until you ask for it, and the board, the camera wall and the printer's own page share a single connection to each machine.
151153

152154
### 📦 Storage / Racks

0 commit comments

Comments
 (0)