Files
bambuddy/frontend
maziggy 5dd7bd213f Read a print's destination from the report topic, not just the request one (issue #1820)
current_project_url was assigned in exactly one place, _handle_request_message,
and _on_message calls that only for the request topic. A print started from the
printer's own screen publishes nothing there, so the field stayed None for the
one case the storage verdict exists for: the file is already in the printer's
model library under /userdata/model/history/, which port 990 does not serve.

The verdict then fell through to the sdcard flag, and @ojimpo's H2S reports that
flag true -- its "card" is the internal eMMC -- so every such print ran the full
sweep before giving up. He measured one: 16 filename-and-directory attempts over
22 FTPS connections, 18 of them refused, 6.4 seconds, then a fallback archive
holding a name and nothing else.

The printer does announce where the file lives, as an unsolicited project_file
*response* on the report topic about two seconds before gcode_state reaches
PREPARE. _process_message now reads the url off it, gated on result SUCCESS and
a non-empty value so a refused dispatch cannot name a file that was never
written. Reading it there rather than only at the request topic also covers an
install neither of us had in view: some brokers refuse the request-topic
subscription, and on those no print of any kind had ever populated the field.

The new branch captures state and nothing else. The "External project_file
payload" diagnostic stays with the request-topic handler: our own dispatch is
echoed on both topics, the request-topic echo lands first and clears
_own_project_file_key, so reusing the diagnostic here would have logged every
Bambuddy-started print as somebody else's. A test pins that.

What the print names is now what gets tried -- the five directories a copy could
be in, rather than the ~110 connections that cannot succeed. The probe is still
worth running: an H2S keeps recently used jobs under /cache and archives them in
full while they last, which is why the reporter's two prints on the same day
behaved differently. Slicer-sent prints are unchanged.

The banner no longer describes a step that never happened. With no reason
recorded, a blank archive fell back to the original wording -- "Store sent files
on external storage" is off in your slicer -- which on that printer is on, and
which the internal-storage wording from #2780 already explains would not help on
an H2. The archive that most needed that explanation was the only one that could
not be given it.

So file:///userdata/ now earns its own reason, internal_history, separate from
the brtc://emmc dispatch case. A dispatch chose internal storage and can be
aimed elsewhere; a print of a file that was already there had no dispatch at
all, and telling that operator to pick External in Send names a dialog they
never opened. The banner and the connection diagnostic both read the verdict's
reason rather than a fixed one, so the two surfaces cannot give the same printer
different advice. Thirteen locales, and a wiki section the banner links to.

-----

Read the K-profile selection when the mutation runs, not when it is captured

Configure Slot sends cali_idx from selectedKProfile, and the mutation read it
through its own closure. React Query hands a mutation its options from an
effect, so a click landing between a commit and that effect flushing runs the
previous render's mutationFn -- one that captured the selection as it was before
the K-profile query resolved. The payload then carries cali_idx -1 and the
printer binds the default 0.020 instead of the calibrated K, while the dialog
shows the right profile selected throughout.

It surfaced as an intermittent failure of the per-nozzle K-profile test, about
one full-suite run in six. Reproducing it with staggered query resolution showed
the divergence directly: the select element held the correct profile immediately
before and after the click, and the payload still carried -1. That test's slot is
the most exposed case in the file -- a right-hotend slot carrying the left
hotend's index, where the "keep showing the active profile" safety net cannot
repair an empty recompute.

The selection now goes through a ref written during render, so the mutation
resolves it at execute time. An effect would have inherited the same flush
ordering this exists to escape. The K value and the profile's ids travel in the
same payload and had the same exposure, so they move with it.

Measured over a staggered-resolution grid: 2 failures in 15 runs before, 0 in 12
after. api.getSlicerPrinterModels was also missing from the test file's mock, so
that query ran with no query function and rejected in all 37 tests -- mocked now,
though on its own it changed nothing, which is how the ref was confirmed as the
fix rather than assumed.
2026-08-25 11:19:46 +02:00
..
…
…
…

React + TypeScript + Vite

This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.

Currently, two official plugins are available:

React Compiler

The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see this documentation.

Expanding the ESLint configuration

If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules:

export default defineConfig([
  globalIgnores(['dist']),
  {
    files: ['**/*.{ts,tsx}'],
    extends: [
      // Other configs...

      // Remove tseslint.configs.recommended and replace with this
      tseslint.configs.recommendedTypeChecked,
      // Alternatively, use this for stricter rules
      tseslint.configs.strictTypeChecked,
      // Optionally, add this for stylistic rules
      tseslint.configs.stylisticTypeChecked,

      // Other configs...
    ],
    languageOptions: {
      parserOptions: {
        project: ['./tsconfig.node.json', './tsconfig.app.json'],
        tsconfigRootDir: import.meta.dirname,
      },
      // other options...
    },
  },
])

You can also install eslint-plugin-react-x and eslint-plugin-react-dom for React-specific lint rules:

// eslint.config.js
import reactX from 'eslint-plugin-react-x'
import reactDom from 'eslint-plugin-react-dom'

export default defineConfig([
  globalIgnores(['dist']),
  {
    files: ['**/*.{ts,tsx}'],
    extends: [
      // Other configs...
      // Enable lint rules for React
      reactX.configs['recommended-typescript'],
      // Enable lint rules for React DOM
      reactDom.configs.recommended,
    ],
    languageOptions: {
      parserOptions: {
        project: ['./tsconfig.node.json', './tsconfig.app.json'],
        tsconfigRootDir: import.meta.dirname,
      },
      // other options...
    },
  },
])