Skip to content

Commit 3170ec8

Browse files
committed
fix: remaining-time in the Bambu Lab and Snapmaker side cards
Both cards showed "0m" next to the clock while the printers table showed the right figure, for two different reasons — and one shared root cause: each card derived the remaining time itself instead of reading the one the table uses. - Bambu Lab: `mc_remaining_time` is MINUTES, fed straight into a seconds formatter, so a 32-minute job floored to 0. PROTOCOL.md documented the field as seconds — the source of the mistake — and is corrected. - Snapmaker: the card printed `printDuration`, which is the time ELAPSED. It now reads the shared normalised job (new `ctx.getPrinterJob`) and shows "—" rather than a wrong "0m" when there is nothing to derive from yet. Also: extend the on-demand test build to all three platforms (renamed test-build-win.yml → test-build.yml, matrix, fail-fast off, 7-day artifacts) and add the two missing `build:{mac,linux}:nopublish` scripts it needs.
1 parent 90c37a9 commit 3170ec8

9 files changed

Lines changed: 141 additions & 56 deletions

File tree

‎.github/workflows/test-build-win.yml‎

Lines changed: 0 additions & 50 deletions
This file was deleted.

‎.github/workflows/test-build.yml‎

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
name: Test build (all platforms)
2+
3+
# On-demand build of all three platforms for handing a binary to a tester,
4+
# WITHOUT cutting a release: artifacts only — no git tag, no GitHub Release, no
5+
# auto-update feed, nothing published. Every binary is UNSIGNED:
6+
# • Windows → SmartScreen warns ("More info" → "Run anyway")
7+
# • macOS → Gatekeeper blocks it (right-click → Open, or `xattr -dr
8+
# com.apple.quarantine <app>`); it is NOT notarized
9+
# • Linux → AppImage, chmod +x and run
10+
#
11+
# Two ways to run it:
12+
# • push any branch named test/** (this file must be on that branch)
13+
# • Actions tab → "Test build (all platforms)" → Run workflow
14+
#
15+
# Native module note: nfc-pcsc is a C++ addon, so each platform MUST build on
16+
# its own runner — none of this can be cross-compiled.
17+
#
18+
# Artifacts are throwaway: 7-day retention, and delete them by hand once the
19+
# test is done rather than leaving installers lying around.
20+
21+
on:
22+
push:
23+
branches:
24+
- 'test/**'
25+
workflow_dispatch: {}
26+
27+
permissions:
28+
contents: read
29+
30+
# A second push to the same test branch supersedes the first — three full
31+
# Electron builds is not something to queue up twice by accident.
32+
concurrency:
33+
group: test-build-${{ github.ref }}
34+
cancel-in-progress: true
35+
36+
jobs:
37+
build:
38+
strategy:
39+
# One platform failing must not cancel the other two — the whole point is
40+
# to see which ones break.
41+
fail-fast: false
42+
matrix:
43+
include:
44+
- os: windows-latest
45+
platform: win
46+
artifact: TigerStudio-Windows-test
47+
path: dist/*.exe
48+
- os: macos-latest
49+
platform: mac
50+
artifact: TigerStudio-macOS-test
51+
path: dist/*.dmg
52+
- os: ubuntu-latest
53+
platform: linux
54+
artifact: TigerStudio-Linux-test
55+
path: dist/*.AppImage
56+
57+
runs-on: ${{ matrix.os }}
58+
59+
steps:
60+
- uses: actions/checkout@v5
61+
62+
- uses: actions/setup-node@v5
63+
with:
64+
node-version: 24
65+
66+
# Same list as the release workflow — nfc-pcsc needs the PC/SC headers,
67+
# and electron-builder needs the packaging tools.
68+
- name: Install Linux system dependencies
69+
if: matrix.os == 'ubuntu-latest'
70+
run: |
71+
sudo apt-get update -y
72+
sudo apt-get install -y \
73+
libpcsclite-dev \
74+
libusb-1.0-0-dev \
75+
libudev-dev \
76+
build-essential \
77+
python3 \
78+
rpm \
79+
fakeroot
80+
81+
- run: npm ci
82+
83+
- name: Build ${{ matrix.platform }} (unsigned, no publish)
84+
run: npm run build:${{ matrix.platform }}:nopublish
85+
env:
86+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
87+
# Baked in so Google sign-in works in the test build.
88+
TIGERTAG_GOOGLE_DESKTOP_CLIENT_ID: ${{ secrets.TIGERTAG_GOOGLE_DESKTOP_CLIENT_ID }}
89+
TIGERTAG_GOOGLE_DESKTOP_CLIENT_SECRET: ${{ secrets.TIGERTAG_GOOGLE_DESKTOP_CLIENT_SECRET }}
90+
# Belt and braces on macOS: without this electron-builder still hunts
91+
# the keychain for an identity and can fail the build on a runner that
92+
# has none, even with identity=null in the script.
93+
CSC_IDENTITY_AUTO_DISCOVERY: false
94+
95+
- name: Upload ${{ matrix.platform }} artifact
96+
uses: actions/upload-artifact@v5
97+
with:
98+
name: ${{ matrix.artifact }}
99+
path: ${{ matrix.path }}
100+
if-no-files-found: error
101+
retention-days: 7

‎WORKLOG.md‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,18 @@
55
## Changed
66

77
## Fixed
8+
- Snapmaker printer side card showed "0m" next to the clock on a running print while the table
9+
showed the real 21m — the card printed `printDuration`, which is the time ELAPSED, not remaining.
10+
Moonraker sends no remaining time, so the card now reads the same normalised job the table does
11+
(slicer estimate − elapsed, else extrapolated from progress) via a new `ctx.getPrinterJob`, and
12+
shows "—" rather than a wrong "0m" while there is nothing to derive from yet —
13+
`renderer/printers/snapmaker/cards.js`, `renderer/printers/context.js`, `renderer/inventory.js`
14+
- Bambu Lab printer side card showed "0m" remaining on a running print while the table column
15+
showed the real figure. `mc_remaining_time` is reported in MINUTES, but the card fed it straight
16+
into a seconds formatter, so a 32-minute job floored to 0. The table was right because it goes
17+
through `_getPrinterJob`, which converts. `PROTOCOL.md` documented the field as seconds — the
18+
source of the mistake — and is corrected with the observed value — `renderer/printers/bambulab/cards.js`,
19+
`renderer/printers/bambulab/PROTOCOL.md`
820
- Dependency refresh — 18 advisories (1 critical, 17 high) down to 0, entirely within the existing
921
semver ranges: `package.json` is untouched, only the lockfile was stale. Covers the reported
1022
**CVE-2026-54673** / GHSA-p2f4-r6v6-j797 (`builder-util-runtime` 9.5.1 → 9.7.0 — electron-updater

‎package.json‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@
1414
"build:mac": "node scripts/gen-oauth-config.mjs && electron-builder --mac --publish always",
1515
"build:mac:local": "bash build/sign-and-notarize.sh",
1616
"build:mac:unsigned": "node scripts/gen-oauth-config.mjs && electron-builder --mac -c.mac.identity=null -c.mac.notarize=false",
17+
"build:mac:nopublish": "node scripts/gen-oauth-config.mjs && electron-builder --mac -c.mac.identity=null -c.mac.notarize=false --publish never",
1718
"build:linux": "node scripts/gen-oauth-config.mjs && electron-builder --linux --publish always",
19+
"build:linux:nopublish": "node scripts/gen-oauth-config.mjs && electron-builder --linux --publish never",
1820
"build:all": "node scripts/gen-oauth-config.mjs && electron-builder -wml --publish always",
1921
"i18n:add": "node scripts/i18n-add.mjs",
2022
"i18n:check": "node scripts/check-i18n-consistency.mjs",

‎renderer/inventory.js‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23217,6 +23217,11 @@ import { elgFanStep } from './printers/elegoo/widget_control.js';
2321723217
toast: (msg, type) => toast(msg, type),
2321823218
isForcedOffline: (key) => _ppForcedOfflineKeys.has(key),
2321923219
snapFmtTempPair, snapFmtDuration, snapTextColor,
23220+
// One reading of the live job for both surfaces. The cards used to derive
23221+
// their own remaining time and drifted from the table (Snapmaker showed
23222+
// elapsed, Bambu read minutes as seconds) — deriving it twice is what
23223+
// caused both.
23224+
getPrinterJob: (p) => _getPrinterJob(p),
2322023225
findPrinterModel, printerImageUrl, printerImageUrlFor,
2322123226
snapFilenameRel,
2322223227
SNAP_ICON_NOZZLE, SNAP_ICON_BED, SNAP_ICON_CHAMBER, SNAP_ICON_CLOCK,

‎renderer/printers/bambulab/PROTOCOL.md‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -300,8 +300,8 @@ Tous les messages reçus sur `device/{serialNumber}/report` sont du JSON. La rac
300300
"gcode_state": "RUNNING",
301301
"mc_percent": 45,
302302
"mc_print_stage": 2,
303-
"mc_remaining_time": 3600,
304-
"remaining_time": 3600,
303+
"mc_remaining_time": 32,
304+
"remaining_time": 32,
305305
"layer_num": 120,
306306
"total_layer_num": 300,
307307
"subtask_name": "my_print.gcode",
@@ -374,8 +374,8 @@ Tous les messages reçus sur `device/{serialNumber}/report` sont du JSON. La rac
374374
| `status` | string | Alternative à `gcode_state` |
375375
| `mc_percent` | int/string | Progression 0–100 (%) |
376376
| `mc_print_stage` | int | Étape interne. `0` = pas d'impression en cours |
377-
| `mc_remaining_time` | int | Temps restant en secondes (alias de `remaining_time`) |
378-
| `remaining_time` | int | Temps restant en secondes |
377+
| `mc_remaining_time` | int | Temps restant en **MINUTES** (alias de `remaining_time`). Vérifié sur matériel réel : une impression à 32 min restantes envoie `32`, pas `1920`. Cette ligne annonçait des secondes et a produit un « 0m » dans la carte latérale (v2.19.1) — convertir en secondes avant tout formatage |
378+
| `remaining_time` | int | Temps restant en **minutes** (voir ci-dessus) |
379379
| `layer_num` | int | Couche actuelle |
380380
| `total_layer_num` | int | Nombre total de couches |
381381
| `subtask_name` | string | Nom du sous-job / fichier en cours |

‎renderer/printers/bambulab/cards.js‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,11 @@ export function renderBambuJobCard(p, conn) {
2626

2727
const layerText = isActive && (d.layerNum || d.totalLayerNum)
2828
? `${d.layerNum || 0}/${d.totalLayerNum || 0}` : "";
29-
const timeText = isActive ? _bblFmtDuration(d.remainingTime) : "—";
29+
// `remainingTime` is `mc_remaining_time`, which Bambu reports in MINUTES —
30+
// the formatter takes seconds, so a 32-minute job read as 32 seconds and
31+
// rendered "0m". (The table column got this right via _getPrinterJob, which
32+
// is why the two views disagreed.)
33+
const timeText = isActive ? _bblFmtDuration(d.remainingTime * 60) : "—";
3034
const stateLabel = ctx.t("snapState_" + state) || state;
3135

3236
const nameLine = leafName

‎renderer/printers/context.js‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,11 @@ export const ctx = {
2020
snapFmtDuration: s => `${Math.floor((s || 0) / 60)}m`,
2121
snapTextColor: () => "#fff",
2222

23+
// Normalised live job for a printer — the SAME reading the printers table
24+
// uses (state, pct, remainSec…), so a card never has to re-derive it and
25+
// drift from the table. Returns null when the printer isn't connected.
26+
getPrinterJob: () => null,
27+
2328
// Printer model helpers (read state.db.printerModels at call time)
2429
findPrinterModel: () => null,
2530
printerImageUrl: () => null,

‎renderer/printers/snapmaker/cards.js‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,13 @@ export function renderSnapJobCard(p, conn) {
2222
const thumbUrl = ((isActive || isDone) && d.printPreviewUrl) ? d.printPreviewUrl : (fallbackImg || "");
2323
const layerText = isActive && (d.currentLayer || d.totalLayer)
2424
? `${d.currentLayer || 0}/${d.totalLayer || 0}` : "";
25-
const durationText = isActive ? ctx.snapFmtDuration(d.printDuration) : "0m";
25+
// Time REMAINING, like the printers table — this used to print `printDuration`,
26+
// which is the time ELAPSED, so a job that had just started showed "0m" next to
27+
// a clock while the table said 21m. Moonraker sends no remaining time, so the
28+
// shared reading derives it (slicer estimate − elapsed, else extrapolated from
29+
// progress); "—" while it has nothing to go on yet, never a wrong "0m".
30+
const remainSec = ctx.getPrinterJob(p)?.remainSec;
31+
const durationText = isActive ? (remainSec > 0 ? ctx.snapFmtDuration(remainSec) : "—") : "0m";
2632
const stateLabel = ctx.t("snapState_" + jobState) || jobState;
2733
const isPaused = jobState === "paused";
2834
const isPrinting = jobState === "printing";

0 commit comments

Comments
 (0)