v0.99.108
Filament systems get the bulk of the attention: CFS and AD5X IFS learn to report a runout
instead of staying silent, the community K2 Box fork gains the commands to go with the schema
0.99.107 taught us to read, Load asks before it homes into a part left on the bed, and the
print-active gate that seven backends already enforced now covers the eighth. Android gets a
batch of fixes that make it behave like a real target rather than a port, including a panel
that can finally power off. The home widgets stop deciding their layout from grid spans and
read physical pixels instead, so the same widget reads correctly on every panel and
orientation. Diagnostics got a lot of work too: a debug bundle now reaches past the incident
that prompted it rather than spending its whole budget on Klipper's per-second stats line.
Added
- CFS reports filament runouts (#1250) - the box always pauses before attempting a refill and announces the failure in plain text, so that is what the backend reads now. The old signal was
filament_useup, a latch the box sets when a spool runs out and clears only on a successful extrude, which meant the runout indicator could light on any unrelated pause. It now requires a false-to-true transition witnessed inside a running job. The runout-modal suppression also narrows from all hub topologies to the backends that actually raise their own fault, which un-silences ACE and QIDI. - AD5X IFS reports filament runouts (#1250) - a head-switch edge, armed only while no operation is in flight, confirmed by a dwell and gated on the print being paused. The gate deliberately does not key off the head filament flag, which the motion sensor also writes and which reads false while a lane is loaded but idle. Recovery offers resume, a bare extruder purge and
IFS_UNLOCK, but no load button, because every AD5X load path homes and_G28on a loadcell-Z machine probes into the part. - AD5X says which auto-switchover plugin it found - so a runout can state plainly that unattended switching needs lessWaste or bambufy, rather than leaving the user to work out why nothing switched.
- Filament commands for the community K2 Box firmware - 0.99.107 taught the CFS backend to read the reimplemented box module's schema and identify its dialect, which stopped a populated 4-bay unit rendering as an empty panel, but every operation still went nowhere. The identified Fork dialect now routes through its own load, swap, unload, clear and slot-metadata commands, with the stock K1/K2 envelopes unchanged and cleanup skipped for state the fork never saves.
- Android can power the panel off at the sleep timeout (#1245) - SDL asserts
FLAG_KEEP_SCREEN_ONat video init and nothing ever cleared it, so the device could not sleep while HelixScreen was in the foreground, and our own Display Sleep just painted a black rectangle over a fully lit panel. It now releases keep-screen-on and lets the OS timeout run. Display Sleep set to Never never releases it, so a wall-mounted tablet stays awake. - A confirmation before HelixScreen injects a G28 you did not ask for -
idle_timeoutruns M84 after ten minutes and motor-off clears homing state, so "unhomed with a part still on the bed" is the normal state ten minutes after any print. Tapping Load homed into the part with no warning. - The home question is asked before the preheat, not after - it previously surfaced roughly 140 seconds in, once the nozzle was already at temperature. Declining now costs nothing and heats nothing.
- An upgrade advisory when AFC predates the v1.2.0 lane fields - detected by capability rather than version, because AFC has no trustworthy version signal:
AFC_VERSIONis hand-bumped and sat at 1.1.37 through the whole v1.2.0 release, and a live BoxTurtle reported 1.0.0 while running v1.1.0. The advisory fires once, is persisted, and re-arms on downgrade. - Bypass can be enabled on firmware that reports none - Happy Hare defaults
has_bypassto 0 formmu_vendor Other, which is what a Qidi Box reports, so owners who do feed filament straight to the extruder got no bypass UI at all. Safe to offer because Happy Hare's ownselect_bypass()never consultshas_bypass(). The row shows only while the firmware says no, and stays visible once enabled so it can be switched back off. - AMS errors show the suggestion that goes with them - every backend populated a suggestion field and nothing ever displayed it, because thirty-odd call sites each open-coded their own error toast. Users read "Cannot run filament operation while printing" and never the half that unsticks them. Toasts gain an optional second line for it, and the verb prefixes survive only where the backend's own message names no operation, so nobody reads "Unload failed: Failed to unload filament".
- A debug bundle can answer "is Moonraker even running" without asking Moonraker - every Moonraker-derived section is fetched through Moonraker, so a bundle uploaded while it is down carried five "No response" entries and none of the logs that would explain why, and could not distinguish nothing-listening-on-7125 from listening-on-the-LAN-address-only, which need opposite advice. A new section reads
/procinstead, listing sockets on the configured port with their bind addresses and any moonraker or klippy process. - Both log collectors fall back to local disk - with the paths derived from the daemons' own argv rather than a guessed list of per-platform data roots, so an empty result is an honest answer where fiction would read as knowledge.
- An explicit Done button on the print-status configure picker - it could previously only be dismissed by tapping the backdrop, with nothing on screen saying so.
- Timestamps in launcher.log - on platforms where
/var/logis tmpfs, the launcher log is the only record surviving a reboot, and its lines could not be correlated to anything. HELIX_REMOTE_CONTROL=1on a deployed device - maps to--remoteso a printer in the field can be driven withhelix-screen ctl. Off by default, since the socket can drive the whole UI.
Fixed
- A failed AFC toolchange took away the Eject button - AFC pins
current_loadingat the top of TOOL_LOAD and clears it only on the success path, so a failed toolchange latches the lane name until the next successful one, which a user with a jammed lane cannot perform. The unload-mode ranking put recovery above Eject, so the failure that makes someone want to eject was the very thing that removed it, swapping it for a lane reset that retracts toward the hub and never returns filament to the spool. Reachable on a Box Turtle, the dominant AFC setup, with no unusual configuration. - Filament operations were refused while paused - pause-then-swap is the runout recovery workflow, and HelixScreen was the only surface that could not perform the recovery Klipper had just asked for. Only AD5X IFS still refuses, because its remove-current macro hides a
_G28that probes a loadcell-Z nozzle into the part. - AFC Eject reported success and moved no filament - it sent LANE_UNLOAD for three conditions upstream discards, including one that logs nothing at all, and Klipper acks the gcode either way. The affordance now greys out mid-print instead of being offered into a certain refusal.
- Load and Unload gating was three partial copies - the filament panel, the AMS sidebar and the context menu each carried their own rules. One shared rule now answers for all three.
- Two filament operations could race through the busy check - the gate read a busy flag and then dispatched, but no backend makes itself busy inside that read: two set it in a later critical section, three inside the hook, and three never set it at all and wait for firmware to echo. It is now a claim taken in the same critical section as the read and held across the dispatch.
- Snapmaker U1 accepted filament operations mid-print - it was the only subscription backend with no print-active gate. Its auto-feed homes and its tool select moves the carriage, neither of which the G28 matcher can see. Paused still passes, so runout recovery is unaffected.
- Declining the pre-operation home wedged AD5X for five minutes - the generic cancel path reset the action but left the phase tracker armed, so the very next extruder-temperature frame flipped it straight back to heating. The operation then latched a fabricated timeout error that refused every AMS command for the whole 300-second window.
- Dismissing the home dialog by backdrop or ESC left the backend stuck busy - permanently on Tool Changer, which has no stuck-action watchdog. Neither dismissal path ever reached the confirm or cancel callback.
- A declined home left a stale operation label on AD5X - the sidebar kept showing "Heating nozzle to 230°C" under an idle action until the next operation overwrote it.
- Happy Hare accepted a bypass select with filament still loaded (#1229) - the command is fire-and-forget, so it returns success before Klipper answers. HelixScreen reported success and changed nothing while Happy Hare replied with a bare "Operation not possible. Filament is loaded" toast contradicting it.
- Nothing explained why Happy Hare hid the bypass controls - the deciding field resolved at trace level and never reached a debug bundle, so 186KB of log from a Qidi Box reporter contained no trace of the decision. The bypass path node was also a one-shot decision taken before the first status frame, and the pre-status default flashed the controls up and then withdrew them.
- A mid-boot read pinned a 4-lane BoxTurtle to one slot for the whole session - AFC empties its lane namespace at the start of every PREP and writes each lane back as that lane's prep finishes, so it is empty and then partial for seconds on every boot. A cold boot landing in that window made lanes 2 through 4 permanently invisible. Klipper's object list is the lane-set authority now, and lane data only bootstraps an empty registry.
- The AFC feature probe read a frame that can never carry the fields it looks for - the standing subscription is field-scoped and lists none of them, so the probe reported legacy against a BoxTurtle confirmed on v1.2.0, and would have nagged an up-to-date printer to upgrade. An explicit unscoped query fetches the lane object instead.
- AFC named the wrong lane on a toolchanger - the guard asked whether any extruder held filament, and parked toolheads routinely grip filament, so it read true on essentially every real multi-tool machine.
- A toolchanger parking its carriage wiped the slot Klipper had just elected - the aggregate current-load field goes null on park, and that was being read as authoritative over the per-extruder lane state contradicting it.
- A Spoolman-linked lane was named from its hex colour - the loaded-filament card gated the Spoolman name on a field no AFC parse path ever writes, so a BoxTurtle lane rendered "Light Pink PLA" when the real name was already on the slot and never read. Brand now comes from Spoolman even though AFC cannot publish it.
- The spool card and the loaded card disagreed about the same lane - on AFC older than v1.2.0, which publishes a spool id but no filament name, a linked lane fell to the untracked "Brand · Material" branch and rendered "Elegoo · PLA" for a spool we could already name.
- A filament name repeated its own material - "Elegoo Black Rapid PLA+ PLA" and "eSUN Silk Blue ePLA Silk PLA", one word worse than what the old builder produced. Materials are a small closed vocabulary, so a name containing one really does make the suffix redundant. Brands keep stricter matching, because Sun must never be stripped out of Sunlu.
- The picked catalog product did not survive reopening the slot editor - only type and brand were saved, so SUNLU "PLA+ 2.0" round-tripped as brand plus material. Reopening reseeded from vendor and material alone and took whatever sorted first alphabetically, so the lane came back relabelled. Reported on AD5X, but the field was missing from every backend.
- The nozzle badge stamped a meaningless "0" on every AMS printer - it was driven by tool count, which expands to one entry per filament slot, so a mock AD5M reported four tools and one hotend. It now counts distinct extruders, so the badge appears only where more than one nozzle needs naming.
- The tool badge covered the nozzle icon it sits on - an 18px badge over a 24px glyph, hiding 56% of it with the label clipped. A disc cannot be smaller than the font inside it, so the badge takes its own size and font and shows the tool index rather than the full name.
- A tool change offered mid-print silently did nothing - the widget asked for confirmation and dispatched into backends that refuse while printing, so the user consented and the pill did not move. Neither call site passed an error handler either, so any refusal that did arrive vanished entirely.
- The AMS context menu rendered off the top of the screen - 313px of card on a 480x272 panel and 520px on 800x480, so the header and the entire Load row were unreachable at every tap point, and Load is the primary action. Two bugs fed it: a content-width card that wasted 73% of a landscape screen, and a clamp that fixed the bottom edge before the top and drove the card to y=-51.
- A tap on the empty half of a menu row dismissed the menu - a content-sized card is as wide as its longest row, but every shorter row kept its natural width, so 77px of the "Load" row was backdrop.
- The MJPEG camera stream stayed dead until you switched tabs (#1245) - the worker clears its running flag only in
stop(), but it also exits on its own when the failure budget runs out or on a bad allocation, so the widget believed a dead stream was still live and skipped the restart. - Android background and resume skipped the panel lifecycle (#1245) - coming back to the foreground repainted the screen, which only redraws what the widgets already hold. Nothing re-seeded subjects, re-bound observers, reloaded content or restarted timers, which is why switching tabs away and back un-stuck a stale print-status panel and a resume did not. Backgrounding now suspends symmetrically, so timers and streams stop instead of running against a frozen LVGL thread.
- A touch during the wake blackout opened edit mode behind the lock screen (#1245) - disabling the input device is a flag write, so a finger still on the glass kept counting toward a long press from the original touch-down. On Android the wake press reaches the home grid directly, so edit mode opened behind the lock screen and was found once the PIN cleared.
- Touch recalibration could not be escaped (#943) - a Q2 whose digitizer over-reports its range compresses every tap into a top-left rectangle, which put Accept, Retry and Cancel out of reach and left the screen cycling to timeout forever with the Back button covered by the capture surface. Verify now installs the just-captured matrix rather than the stored one that is the only reason anyone opens the screen, a press-and-hold aborts from any coordinate a controller can emit, and the unattended loop is bounded.
- A stock Qidi Q2 detected as an Artillery M1 Pro - the database credited a shared
probe_airobject to two siblings as if it were model-unique, so both outscored the Q2's own best signal: 100% confidence on one firmware and a Qidi Max 4 at 97% on the next. All three are demoted to family-level confidence. - The WiFi toggle froze the UI for tens of seconds - it ran the radio change synchronously from the LVGL event callback, and that path issues two control commands each able to spend 5s retrying and 10s in select, then did a synchronous config save on the same thread.
- Switching WiFi off snapped the switch straight back on - NetworkManager and macOS never implemented the radio change at all, so they inherited a silent no-op success plus a read that always returned enabled.
- A forget was reported as verified against a config we never read - the read returns empty both when the network is absent and when the file cannot be opened. The AD5X vendor config is unreadable, so one session logged both "Removal verified on disk" and "did not record this network" about the same path, and the forgotten network was associated again after the next reboot.
- "WiFi scan failed. Try again." 48ms after the user's own tap - forgetting the connected network disassociates and immediately rescans, and a busy response to that scan was being read as a failure.
- A connection-failed prompt hijacked WiFi setup - the escalation is latched and fires 60 seconds in, which on an unreachable printer is exactly when the user is in Settings fixing it. On an AD5X it landed on top of an open password modal and the passphrase was retyped from scratch.
- The connection-failed copy was wrong for a printer running HelixScreen itself - it told someone holding the screen to check that the printer is powered on and that 127.0.0.1 is correct, with Change Address as the primary button.
- The updater was hidden on the most common Pi install there is - self-update was gated purely on write access to the install root's parent, so the default layout of a root-owned tree with an unprivileged service user reported it as physically impossible. The installer escalates with sudo for exactly that swap, so those installs update fine.
- Uninstall left entries behind in moonraker.conf - removal deleted two comment lines by literal text while the generator writes five, so three orphaned on every install and uninstall cycle, and on one CC1 a line ended up in the file twice. The block is matched structurally now, so it cannot drift out of sync with the generator again.
- Uninstall never removed HelixScreen from moonraker.asvc - it stayed in the service allowlist forever.
- The installer's diagnostic hints did not work for the user running them - a service user outside the journal groups gets nothing on stdout, so a reporter chasing a touch-calibration bug sent back a zero-byte capture and lost a round trip to it (#943).
make deploy-piwiped per-slot filament identity - brand, spool name, Spoolman ids and weights, for every lane the user had edited. The overrides file was not in the rsync exclude list, though its sibling already was.- Spoolman config was written to a file the firmware replaces on upgrade - the index answering "which file proves the config root is addressable" was reused as the write target, which only holds while the root config happens to define
[server]. COSMOS 26.07.0 splits the two, so both the setting and its include line landed somewhere an update discards. - MacroManager could never upload on a real printer - an empty destination path means the root of the given root, which is what Moonraker expects, but the path guard refused it outright, so install and update failed validation before any request was built.
- An upload filename was never validated - despite reaching the multipart form verbatim, so a traversal path was accepted.
- Power-loss recovery could not resume a file with a space in its name - Klipper tokenizes extended parameters with shlex, so an unquoted filename split into tokens with no equals sign and the whole command was rejected as malformed. The stored path was also absolute, which the resume branch cannot match against a list relative to the virtual SD root, so it silently discarded the recovery snapshot.
- A bare G28 stopped homing on COSMOS 26.07.0 - the CC1's homing_override branches on rawparams, and Klipper does not strip comments from rawparams, so the trailing provenance comment HelixScreen appended to every command made it truthy and routed G28 down the explicit-axis branch where no axis letter matched. Nothing homed, after the override had already done its Z hop.
- The CC1 start path spent its whole timeout budget doing nothing - the stock COSMOS resonance macro restarts the UI through a shell command with a 5s timeout, and a command killed on timeout takes its whole process group with it, including the supervisor just forked. About 3s of that went on a flat sleep and a settle that kills nothing on a platform where HelixScreen is the configured UI.
- A cut-off stop stranded the user on a cleared framebuffer - the stop can be interrupted with the daemon still exiting, which start read as already-running and no-opped.
- Stopping the UI took six seconds and ended in SIGKILL - the pidfile holds the launcher shell, which spends its whole life blocked in a foreground child, and a shell will not run its TERM trap while that child is alive. Measured on a Centauri Carbon it ignored SIGTERM for 12 seconds and counting. Signalling the watchdog first halves the stop and makes it clean, so buffered log output is flushed rather than lost.
- The watchdog gave up in fifteen seconds on a transient exec failure -
execvof an intact binary can return ENOEXEC under memory pressure, seen on a 128MB Centauri Carbon where the UI restarts one second after the input-shaper FFT. Every exec failure was reported as a flat exit 127, so six in a minute spent the budget and left a black screen until the user rebooted. - A spurious crash-loop refusal to boot on the CC1 - the crash marker was cleared only in the normal shutdown path, which the SIGTERM handler skips, and COSMOS stops and starts the UI by pidfile around resonance calibration.
- Klipper config was not found on COSMOS, which has no printer_data directory - so the CC1 silently skipped update_manager registration, the config symlink and the Klipper include, with every consumer deriving the path the same wrong way.
- The ZMOD app log was not in the archive users attach - it went to a directory
TAR_CONFIGcaptures on neither AD5M nor AD5X, so reporters sent back launcher stderr and nothing else. - An unopenable log path aborted startup - instead of falling back to the platform's own sink.
- Every
ctlshutdown corrupted the heap - five out of five runs ended in SIGSEGV or SIGABRT with ASAN reporting use-after-free. Two ordering faults: the theme subjects were torn down before the XML scopes holding raw observers on them, and the XML styles were freed beforelv_deinit(), whose own teardown runs layout passes over widgets pointing at those styles. - Four other teardown paths kept the old order - including the no-input-device path, which shows a fatal error screen first and therefore has a live widget tree when it tears down.
- A subject name could outlive its subject - the registries kept resolving a name to storage that was already freed, so the next binding that named it read dead memory.
- Observers outlived the objects that owned their subjects - seventy-seven observer sites plus the print-status panel's twenty-five were registered with no lifetime token, so a teardown cycle freed the observer nodes and the next reset dereferenced freed memory.
- A re-entered touch calibration stranded its previous timer - still in LVGL's list, still holding a pointer to the panel, and the destructor could only free the newest handle.
- The navigation backdrop could outlive the screen that owns it - it is a child of that screen, so any teardown deleting the screen freed it without going through the path that clears the pointer.
- Work queued during shutdown was spliced back in and run against destroyed objects - the shutdown drained the pending queue but left the frozen buffer untouched, and a freeze is held across exactly that window.
- Two heap over-reads found by AddressSanitizer - a four-byte magic logged as a C string, where the formatter resolves the length before applying the precision, reachable from any malformed file with debug logging on.
- The crash-report and temperature-graph dialogs could grow past the screen (#1204) - both roots were content-sized with only an inner flat cap bounding them, which the mock's short crash text and near-empty sensor list never reached. A real backtrace and a many-sensor printer are what get there.
- The debug bundle dialog rendered off-screen (#1204) - 479px tall at y=-103 on a 272px panel, with both the header and the button row cut off, because its content area was capped at a flat 400px.
- PIN entry and the hidden-network form overflowed 480x272 (#1204) - the keypad's bottom row sat off the screen with 0 and confirm untappable, and the hidden-network form set a minimum height taller than the whole panel.
- Five dialogs hardcoded an outer width - none has any intrinsic content width, so the number was arbitrary and on a 480px panel a 400px dialog left 40px of backdrop per side.
- The timelapse empty state invented 110px of scroll on the smallest screens - a flat 380px minimum height for one icon and two lines.
- The last page of a scrolling view overshot the content end - a fixed page delta handed to LVGL's unbounded scroll entry point parked a sliver of content up top with dead space filling the rest. Presses also accumulated without limit, because the disabled state does not suppress clicks on the dimmed chevron.
- Runout state sat beside the AMS lane indicator instead of on it - so an AMS printer drew two filament glyphs, and the one next to the lane number was a muted icon saying nothing about that lane.
- The fan and thermistor home widgets used a 16px icon where every sibling uses 48 - measured in a real cell, the fan spent 42 of its 113 vertical pixels next to neighbours using 70. The thermistor also gets a tighter row gap, since a seven-glyph reading plus the icon consumed the whole cell width.
- Humidity and width-sensor icons never resized at all - both indicators were instantiated with no name, so the engine's auto-naming overwrote the declared one and every lookup returned null.
- Fan stack icons never resized either - the restyle looked up a child of the icon object, which is itself a label with no children, so the lookup was always null.
- A plain 1x1 widget was treated as if it had gained a second row - a single grid row on the two largest tiers already clears the tall threshold by itself, which flipped the camera widget out of compact mode and started an MJPEG stream nobody asked for.
- The size constants were each one pixel below the smallest qualifying extent - despite being compared with
>=, so on the largest tier a plain single-column widget read as wide. - The camera stream dropped on genuinely wide cells - requiring both axes to clear their floors silently killed it on every multi-column cell whose row happens to be short, the same shape of bug the both-axes rule was meant to fix, just inverted.
- A freshly built nozzle row disagreed with the rows beside it - it picked its label from the last grid span seen rather than the pixel decision the rest of the widget already uses, and a real touchscreen never gets a second resize to correct it.
- The tool switcher baked a row count its real cell could not support - the widget measures itself before the panel grid is active, so the measurement resolved against the outer panel's content box rather than its eventual cell, and nothing re-ran it afterwards.
- Grid edit mode computed its geometry against the wrong track count - the live container sizes its row axis from rows actually in use, not the breakpoint table, so a page whose widgets occupy only the first row was off by whole tracks rather than sub-pixels.
- Grid cell conversion ignored the gutters between tracks - and the dots overlay landed a full gutter past the right and bottom edges, clipping the last row and column of dots off it.
- A debug bundle reached ten minutes back and spent 97% of it on load averages - Klipper writes an 850-byte stats line every second. On a real AD5X log the dominant noise was a four-line toolhead dump repeated 7916 times, which is not a stats line and sailed straight through the first pass, leaving a payload with less history than the unfiltered tail it replaced. Repeating shapes are collapsed now, so an 11.7MB crash log condenses to 1344 lines carrying the MCU shutdown and all 22 tool changes.
- A misleading clock-step annotation on every line of a bundle - the ring buffer stores raw messages and formats them at dump time, so the monotonic column read the dump instant and one bundle shipped 97 clock-step annotations summing to the entire session.
- The debug ring was one fixed size picked for the smallest board - it is the only place a bundle can recover DEBUG context on a device whose persistent sinks run at WARN, and one AD5X bundle covered 13.5 minutes and arrived with nothing about the shutdown under investigation. It now scales with device RAM, floored at the old value so nothing regresses.
- Warnings were lost when the UI was SIGKILLed - which is routine, by an init script escalating to
kill -9or by a firmware helper freeing RAM. One CC1 left its log frozen for two weeks and truncated mid-line, so a black-screen incident had no app-level evidence at all. - A pending-count debug line filled most of a bundle's ring - it re-fired on every count decrement, so one healthy poll burst emitted a line per in-flight request and a Spoolman printer spent 1275 of 2000 lines on it, capping its reach at about 3.5 hours of nothing.
- The test-mode banner named a config file the run never touches - it printed the default path verbatim, so a run with
HELIX_CONFIG_DIRset sent you editing the wrong file. - A modal opened four LVGL warnings and resolved to nothing - the exclude-object and cancel dialogs passed callback names that were never registered.
- The Performance overlay was filed as an unregistered push - which made a genuine missing registration elsewhere indistinguishable from this benign one in panel telemetry and the crash breadcrumb ring.
- The XML formatter deleted multi-line comments above the root element - sixteen of them, one per file, all state tables and rationale notes. They are restored here, and both directions verified.
- The color picker ignored its own responsive size ladder - constants are scope-local and were only ever registered into one scope, so the copy embedded in the AMS edit overlay fell back to a fixed 128px. A first-write-wins registration separately discarded the 24/28/32 swatch ladder so every screen got 32, and a hex placeholder starting with
#was parsed as a constant reference and dropped at all three sites. - A stale thumbnail path disarmed active-print recovery - and the detail view ignored the source timestamp on its own fetch path, so a cache entry could outlive the file it came from.
- A submodule patch that failed to apply still stamped the tree as patched - the red line scrolled past once and every later build reported nothing to do. That is how the #1212 null-hloop guard went missing from the tree, and the regression test written to catch it segfaulted instead of failing.
- The repair path could not repair one of the two submodules - the reset looped over only one list, so a tree carrying an older revision of a libhv patch was told to run a command that could never fix it.
- The nightly build was killed at the link on a cold ccache - it ran the whole build and test compile in one step capped below what the two-step Build workflow already budgets, and trimmed the cache to 500M right after restoring 1.8GB into it.
- A failed cross-build reported success - the ssh build was joined to the timing lines with
&&, so a failure short-circuited past them and exited with the status of an echo. Make then printed a green banner with a negative duration and rsynced whatever stale binaries sat in the fetch directory, in one case a two-month-old build.
Changed
- Home widgets pick their layout from physical pixels, not grid spans - print status, camera, the tool switcher, the job queue, active spool, print stats, the clock, the fan stack, favorite macro, nozzle temps and the temperature stack all decided their layout from colspan and rowspan, which reads differently on every panel and orientation. They now threshold the pixel width and height they were already being handed. The camera migration was the risky one, since that edge starts and stops an MJPEG stream.
- Widgets declare whether they can occupy half a grid cell - groundwork for half-cell tracks. The small single-action widgets can; anything rendering a chart, image, list or video frame needs a whole cell.
- G-code is sent verbatim - the trailing provenance comment is gone, along with the capability flag and enum that existed only to suppress it. It had already broken M117 and M118 payloads and the AD5X quoted RESPOND echo before it broke homing on COSMOS.
- The AD5X slot poll backs off to 30 seconds during a print - measured on a real session, 3902 downloads produced 3 content changes, two of them the first poll after boot establishing a baseline, and each fetch is a loopback HTTP GET on a two-core board that is simultaneously feeding the MCU step queue. Paused keeps the 5 second cadence, since a pause is when spools actually get relabelled, and the transition to done forces an off-cadence poll.
- Thumbnail fetches go through one guarded path - results are always delivered on the main thread now, where previously which thread they arrived on depended on which internal path produced them, and callers set image sources from them.
- Eight locales are complete again and the CJK glyph set is refreshed - covering the strings added since 0.99.107 closed the previous gap, with twelve obsolete parameterized error strings dropped.
- The i18n sync tools stopped reporting edits they never made - without ruamel installed there are no line numbers to splice against, so
obsolete --action deleteprinted "Deleted 36 key instances" and left all nine files untouched. Mutating operations now refuse to run rather than lie. The extractor also stopped treating an XML constant reference as translatable text. --real-amsselects a real AMS backend under--test- the flag and its predicate already existed and were unit-tested, but nothing ever set it from argv. The mock client now simulates the status frame Happy Hare needs, which it could otherwise never receive.