Skip to content

Commit 2b918d6

Browse files
committed
v2.26.1 — a cloud printer shows its print, from anywhere
The preview had exactly one source: implicit FTPS to the printer's own LAN address. That works on the printer's network and nowhere else, so a machine added by signing in — whose whole point is reporting from anywhere — never showed one, indefinitely rather than as a delay. Cloud printers now read the account's task list, where each entry carries a plate image on Bambu's CDN. The endpoint came from the maintained Home Assistant integration, not from inference; the first one tried here, taken from our own protocol notes, was wrong. The preview and the job's name also survive the print now, instead of the card reverting to a stock photo of the printer the moment the machine goes quiet. A LAN printer still drops its preview when idle: the .3mf it was read from does not outlive the job. And the image is fetched once and kept as bytes rather than pointed at by URL, because every redraw was re-requesting it and blanking the frame meanwhile — the media-reload trap, arriving through the data rather than the markup. If that decode fails the URL is used anyway: the worst case is the flicker this set out to remove, never a blank card.
1 parent 7be46f3 commit 2b918d6

15 files changed

Lines changed: 9220 additions & 2557 deletions

File tree

‎CHANGELOG.md‎

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

66
---
77

8+
## v2.26.1 — 2026-08-31
9+
10+
### Fixed
11+
12+
- **A cloud Bambu printer never showed the print's preview away from home.** There was exactly one way to fetch it — implicit FTPS to the printer's own LAN address, pulling `metadata/plate_N.png` out of the active `.3mf` — which works only on the printer's network. A machine added by signing in, whose whole point is reporting from anywhere, therefore had no preview anywhere else, indefinitely rather than as a start-up delay. Cloud printers now read the account's task list (`GET /v1/user-service/my/tasks`), where each entry carries `cover`: a plain HTTPS image on Bambu's CDN needing no auth of its own. They take that route and ONLY that one — FTPS to a machine on another network does not fail fast, it hangs to its timeout, once per retry window, for nothing. The endpoint and its payload shape were taken from the maintained Home Assistant integration rather than inferred; the first endpoint tried here, lifted from our own protocol notes, was the wrong one. `hits` holds the ACCOUNT's ~20 most recent tasks rather than this machine's current one, so it is filtered by `deviceId` and then narrowed by the job's own name — already known from telemetry — falling back to the most recently started, since taking the first match would eventually show a previous print — `main.js`, `preload.js`, `renderer/printers/bambulab/index.js`.
13+
- **A cloud printer keeps showing what it last made, and what it was called.** The preview and the job name now survive the print, on the board card and in the side panel, instead of the card reverting to a stock photo of the printer the moment the machine goes quiet. Both come from the cloud for a cloud machine — `cover` and `title` off the task list, which still hold them once telemetry has stopped naming anything. A LAN printer still loses its preview when it goes idle: the `.3mf` it was read from does not outlive the job, so keeping the image would eventually be a lie. The board card was gating on print state too and had to learn the same rule — the driver decides, the view trusts the field — `renderer/inventory.js`, `renderer/printers/bambulab/{index,cards}.js`.
14+
- **The cloud preview blinked out and back on every redraw.** The FTPS route always handed the renderer image BYTES, so the preview field held something instant and self-contained; the cloud route handed it a CDN URL instead, and every DOM rebuild re-requested it, leaving the frame blank for the length of the round-trip — the media-reload trap the surgical-update rules warn about, arriving through the data rather than through the markup. The image is fetched once in the main process and returned as a data-URI, cached by source URL (64 entries, 4 MB ceiling), so both routes deliver the same kind of value. The fetch decision compares the SOURCE URL, not the stored value, or the decoded image would never match and every pass would fetch again. If the decode fails for any reason the raw URL is used instead: the worst case is then the flicker this set out to remove, never a blank card — `main.js`, `preload.js`, `renderer/printers/bambulab/index.js`.
15+
16+
---
17+
818
## v2.26.0 — 2026-08-31
919

1020
### 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.26.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.26.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: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@
1616
- **The camera wall is 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 they are RESIZED FREELY by a grip in the corner — 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}`, a field of its own since the printer board's camera widget already owns `camPlan` and the two are placed independently); only the width is stored, because the picture is 16:9 and the head is fixed, so one number describes the card. It has an **Arrange mode** like the other two, with the storage plan's own 24 px mesh scaled by the zoom: a card's HEAD is a handle in both modes — it is the part that is not the picture, so taking hold of it can never be mistaken for a click on the video, and no separate grip button is needed; arranging then extends the handle to the whole card, keeps the head visible and gives 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. Outside it the wall is a wall — pictures, heads on hover, and a small handle for the occasional move. Its plan bar is the other plans' bar to the character — same classes, same order, same numeric field with its own `%` beside it. Written differently it LOOKED different: a text field carrying the percent inside it takes the default width, which is why this one sprawled while theirs stayed a neat pill. Three views that behave alike have to be built alike. Its zoom is the other plans' zoom, down to the arithmetic: the same bounds, the same round-ten ladder, and the point under the cursor held still — a zoom that lets the surface slide under the pointer reads as something having MOVED, which on a view whose whole purpose is where things are is the wrong impression entirely. The wheel zooms with no modifier, as it does everywhere else, and ignores the stray notches a held-down wheel produces while panning. Moving a camera no longer opens its machine's side panel: the click that ends a drag lands on the card, and a panel sliding open over the wall you are laying out is the opposite of what you came for. Arranging suppresses it outright — the whole card is a handle there — and outside it the guard goes up the instant the gesture becomes a move, not when it ends: raised at the end it was raised after the writes were awaited, and the click following a release fires long before a round-trip to the database resolves, so it was always too late. The mesh appears the moment anything is taken or resized, in or out of Arrange: outside it nothing else says a gesture has become an edit — the card simply starts following the pointer — and a grid arriving under your hand explains the snapping before it happens. Dragging uses the storage plan's own magnetism and its guides: a card snaps flush to a neighbour's edge, or lines its edges and centres up with one, and a thin line says WHY it clicked into place — without it the magnetism reads as the card resisting. In a group the card actually grabbed is the one the magnetism speaks for and the rest follow at their own offsets, so the set keeps its spacing exactly. A card taken in the hand comes to the FRONT, and dropped last means dropped on top for good — the depth is written inline by the layout, which no stylesheet rule can outrank, so the lift is done on the element itself and restored from the record on release. A swept selection is ringed in the accent, 2 px at the same offset as the storage and printer boards — a selection has to look identical wherever it is made. Its states are written as one ordered block carrying the view's id, because the plain border arranging draws also carries it: at lower weight the selection was silently overruled, and hovering a selected card overruled it again. It reuses the shared primitives — the rubber band, the panning, the free-spot search — so the three views cannot drift apart in feel. 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. 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 what the browser actually did — otherwise the key would go on claiming fullscreen over a card that had come back down. Widths are applied in their own pass before any position is computed: 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, which dealt every camera a slot the wrong size and piled them on top of each other. 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, and the wall's existing patch mode is what keeps a size or state change from rebuilding anything. 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** — drawn at the same spot and the same size, dimmed and empty, and still movable and resizable while the printer is off. A wall that dropped a card the moment a machine slept destroyed the arrangement it exists to hold, and the hole it left was indistinguishable from a camera never placed. Only a camera that has never been placed is absent: there is nothing of it to preserve. Fullscreen still leaves the plan entirely: the layout stops writing inline geometry on a card while it is up there and clears what it wrote, because position, size and depth are written inline and no stylesheet can outrank that — a card sent fullscreen simply grew inside the surface instead of covering the window. The detached window is untouched — `renderer/inventory.js`, `renderer/css/40-printers.css`
1717

1818
## Fixed
19+
- **The cloud preview blinked out and back on every redraw.** The FTPS route always handed the renderer image BYTES, so the preview field held something instant and self-contained; the cloud route handed it a URL on Bambu's CDN instead, and every DOM rebuild re-requested it, leaving the picture blank for as long as the round-trip took — the media-reload trap the surgical-update rules warn about, arriving through the data rather than through the markup. The image is now fetched once in the main process and returned as a data-URI, cached by source URL, so both routes deliver the same kind of value and a card can be redrawn as often as it likes. The comparison that decides whether to fetch is made on the source URL, not on what is stored, or the decoded image would never match and every pass would fetch again. If the decode fails for any reason the raw URL is used instead: the worst case is then the flicker this set out to remove, never a blank card — `main.js`, `preload.js`, `renderer/printers/bambulab/index.js`
20+
- **A cloud Bambu printer keeps showing what it last made.** The preview and the job's name survive the print now, on the board card and in the side panel, instead of the card falling back to a stock photo of the printer the moment the machine goes quiet — the space stays filled and the last thing a printer made is more use than a picture of the printer. Both come from the cloud for a cloud machine: `cover` and `title` off the account's task list, which still holds them once telemetry has stopped naming anything. A LAN printer still loses its preview when it goes idle: the `.3mf` it was read from does not outlive the job, so keeping the image would eventually be a lie. The board card was gating on the print state too and had to learn the same thing — the driver decides, the view trusts the field — `renderer/inventory.js`, `renderer/printers/bambulab/{index,cards}.js`
21+
- **A cloud Bambu printer never showed the print's preview away from home.** There was exactly one way to fetch it — FTPS straight to the machine — which works only on the printer's own network, so a printer added by signing in, whose whole point is reporting from anywhere, had no preview anywhere else. A cloud printer now takes the account's task list (`/v1/user-service/my/tasks`), where each entry carries `cover`: a plain HTTPS image on Bambu's CDN, no auth of its own. Cloud printers take that route and only that one — FTPS to a machine on another network does not fail fast, it hangs until it times out, once per retry window, for nothing. The list is the ACCOUNT's twenty most recent tasks rather than this machine's current one, so it is filtered by device and then narrowed by the job's own name, falling back to the most recently started; taking the first match would eventually show a previous print. Endpoint and payload shape were taken from the maintained Home Assistant integration rather than guessed — the first endpoint written here, lifted from our own spec notes, was the wrong one — `main.js`, `preload.js`, `renderer/printers/bambulab/index.js`
1922
- **Resizing a camera opened — or closed — its machine's side panel.** The drag already guarded against this; the resize grip never did, so the click the release produces landed on the card and was taken for a request to open the printer. The guard now goes up on the first MOVEMENT of a resize, as it does for a drag and for the same reason: raised on release it would be raised after the write is awaited, and the click fires long before that round-trip resolves. It is lowered on a timer scheduled BEFORE that await too — lowered after it, a slow write would leave the guard up and swallow the next honest click — `renderer/inventory.js`
2023
- **A Bambu print showed no preview, and carried the wrong name.** The hint used to find the model was `gcode_file` — a path INSIDE the archive, `/data/Metadata/plate_1.gcode` — while the `.3mf` sitting on the printer is named after the job: `TigerPod_Mini_A1.3mf`. The lookup therefore searched for `plate_1.3mf`, which exists nowhere, and no preview was ever found; the same value put `plate_1.gcode` on the card where Bambu's own app shows the project's name. The job name comes first now, the gcode path remaining the fallback for a print started without one. A cloud printer gets its preview too, on the same network — it holds an address and an access code like any other — `renderer/printers/bambulab/index.js`
2124
- **A Bambu printer stopped reporting anything the moment a print FAILED** — filament, temperatures, job, all of it. `print.state` is a NUMBER on this firmware (7 on an X1C), and the state normaliser called `.toLowerCase()` on it. That runs on the FIRST line of the merge, so the exception took the whole message down: nothing after it was ever read. The board fell back to the external spool alone. It only fires when the printer reports FAILED or ERROR — the branch that looks for a second opinion on a failure — which is why it lay dormant for months and surfaced the day a print failed. Only string candidates are considered now — `renderer/printers/bambulab/index.js`

‎assets/db/tigertag/id_brand.json‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,13 @@
288288
142
289289
]
290290
},
291+
{
292+
"id": 11750,
293+
"name": "VOXELPLA",
294+
"type_ids": [
295+
142
296+
]
297+
},
291298
{
292299
"id": 12345,
293300
"name": "MakerBot",
@@ -811,6 +818,14 @@
811818
173
812819
]
813820
},
821+
{
822+
"id": 53146,
823+
"name": "EPAX",
824+
"type_ids": [
825+
142,
826+
173
827+
]
828+
},
814829
{
815830
"id": 53640,
816831
"name": "Magigoo",

0 commit comments

Comments
 (0)