Commit Graph
34 Commits
Author SHA1 Message Date
maziggy 0070bcc713 Keep the type check out of npm run build so installs fit in 2 GB (issue #3181) 2026-09-29 08:39:47 +02:00
maziggy c16506af32 chore(deps): bump vitest to 4.1.11 for the mocker path-traversal advisory
@vitest/mocker registers a redirect mock's target without checking it
against Vite's file-serving allowlist, and the load hook then returns
readFile(mock.redirect) as the module source. The target is built as
join(root, new URL(redirect).pathname), which confines nothing -- a
non-special scheme keeps ".." in pathname, so the join resolves outside
the project root. GHSA-82fw-gwwq-j7x9, CVSS 5.9, CWE-22.

Not reachable here. The unauthenticated path is the public mockerPlugin
and interceptorPlugin exports, which attach to Vite's unauthenticated HMR
socket for the benefit of third-party dev servers; nothing under
frontend/src imports either. Browser mode, which registers over a
token-authenticated RPC, is not installed -- @vitest/browser is an unmet
optional peer -- and vitest.config.ts runs plain jsdom, so no dev server
listens during a test run. Both packages are devDependencies and reach no
shipped artifact.

vitest and the eight @vitest/* packages go 4.1.8 -> 4.1.11, carrying
es-module-lexer, expect-type, obug, std-env, tinyexec and tinyrainbow.
Fifteen lockfile entries, all dev-scoped, none added or removed. The
^4.1.8 range already admitted the fix, but the declared floor is raised
so a regenerated lockfile cannot resolve back beneath it. No src change,
so the bundle is byte-identical and static/ does not move.
2026-09-14 10:19:11 +02:00
maziggy b3c67c6943 Keep a lookbehind Safari 16 cannot parse out of the bundle (issue #2971)
An iPhone on iOS 16 loaded nothing at all -- no error, no partial render,
just white, over LAN IP and over an HTTPS domain alike, while the same
install was fine on Android, macOS, Windows and Linux. remark-gfm, added
in v1.2.5 for the folder README panel, reaches
mdast-util-gfm-autolink-literal, whose module body carries a lookbehind
assertion. Safari did not support lookbehind until 16.4.

A regex literal is validated when its module is compiled, not when the
function holding it runs, so this was never going to fail as a broken
README panel. FolderReadmePanel -> FileManagerPage -> App is a plain
static import chain, the regex landed in the entry chunk, and the browser
refused to compile all 10 MB of it. Nothing executed, so nothing
rendered. v1.2.4 is the last release that loads on those iOS versions.

The panel now renders GFM through a locally composed plugin holding four
of remark-gfm's five sub-extensions -- tables, strikethrough, task lists,
footnotes -- and omitting autolink literals, the only one carrying the
lookbehind. Composing rather than configuring is forced by the bug:
importing remark-gfm at all is what breaks the page, so no runtime option
could have reached it.

Parity was measured rather than assumed. Serialized ASTs against real
remark-gfm over a 34-case corpus, position data included, are identical
in 29; the five that differ are exactly the autolink cases, where the
only change is link -> text with table and list structure intact. Across
26 hostile inputs -- NUL bytes, a BOM, an RTL override, a lone surrogate,
combining marks, a 200 KB line, 500 stacked tables, 60-deep nesting,
malformed and ragged tables -- neither implementation throws and none
diverge, and applying the plugin twice is idempotent for both.

The visible cost is that a bare https://example.com or foo@example.com
typed into a folder README no longer links itself; [text](url) and
<https://example.com> are core markdown and still do. The wiki claimed
"links all render" and now says which.

remark-gfm, mdast-util-gfm and micromark-extension-gfm leave the
dependency tree and their eight surviving sub-extensions are declared
directly, at ranges equal to or tighter than the ^2.0.0 those two
packages declared, so the resolution surface did not widen. The bundle is
23 KB smaller.

Vite's build.target governs syntax lowering and esbuild does not rewrite
regular expressions -- measured, a lookbehind builds silently under
safari15, safari16.0 and es2020 alike, which is how this shipped and then
sat unnoticed for two months. So the guard is a real check rather than a
compiler setting: npm run build now ends in check-browser-baseline.mjs,
which scans the emitted bundles for syntax Safari 16.0 cannot parse and
fails with the offending snippet. It is scoped to parse-time failures
only -- a missing runtime API breaks one feature, while one of these
takes down the whole app and has no graceful degradation to fall back on.
Verified firing on the stale bundle before the rebuild, and running
correctly inside the Docker frontend stage where only frontend/ is
copied.

Seven renderer tests pin both halves of the trade: each surviving GFM
feature still renders, and both forms of autolinking stay off on purpose
so a future dependency bump cannot quietly bring the lookbehind back.
2026-08-28 08:08:30 +02:00
maziggy 08df660f6c Replace the embedded G-code viewer with the slicer's own renderer
Sliced files previewed through a vendored copy of PrettyGCode in an
iframe. It drew each move as a screen-space line -- a line has no
thickness in the scene, so it cannot occlude the layer behind it, which
is why prints came out stringy and shimmered where layers crossed. Being
a separate app in a frame, it could be neither themed nor translated, and
carried its own machinery for detecting a proxy refusing the embed.

Now built on libvgcode, the renderer OrcaSlicer draws its own preview
with, vendored from three-slicer (AGPL, same as us). It takes the THREE
namespace as an argument and imports nothing, so it runs on our 0.181
rather than the 0.160 its package pins.

The parser is ours; upstream renders its own kernel's output and ships no
G-code parser at all. Two things it has to get right, both found by
checking a real plate rather than assuming:

- BambuStudio does not use the OrcaSlicer/PrusaSlicer annotations. It
  writes "; FEATURE:", "; LINE_WIDTH:", "; CHANGE_LAYER" and
  "; Z_HEIGHT:", not ";TYPE:", ";WIDTH:" and ";LAYER_CHANGE". Reading
  only the latter showed a 52-layer print as 23,165 layers in one colour,
  because with no layer marker recognised every travel Z-hop split a
  layer and every segment took the fallback feature.
- It emits a tenth of its moves as G2/G3 arcs -- 706 extruding ones in a
  single plate. Ignoring them punched holes through curved walls and tree
  supports. Arcs with no X/Y are the helical travel lift and lay down
  nothing, so they interpolate as travels.

Four colour modes: filament (default, from the AMS slots the file was
sliced with), feature, layer height, line width. Speed, fan and
temperature are deliberately absent -- upstream derives those from
settings rather than the toolpath, and guesses dressed as measurements
are worse than an honest omission. The parser now carries the data to do
them properly later.

Legend entries are switches. Hiding removes the records before the mesh
is built rather than recolouring them: the shader packs colour into a
single float with no alpha, so there is no transparent to set, and
removal is the useful behaviour anyway -- a hidden support stops
occluding what it covered.

The scene is built once and only the toolpath rebuilds. Doing otherwise
constructed a new WebGLRenderer on every render, because the buildVolume
default is an object literal and so a fresh identity each time; browsers
cap live WebGL contexts and drop the oldest, which blanked the canvas
after a few interactions.

utils/framing.ts goes with the iframe, along with six now-orphaned
strings in all 13 locales. src/lib/vendor is excluded from eslint --
acting on findings in vendored code makes it impossible to re-copy on the
next upstream release.
2026-08-09 14:10:18 +02:00
maziggy c8e5ecc23a chore(deps): clear every npm audit and pip-audit finding
Frontend:
- react-router/-dom 7.18.1 -> 7.18.2. The RSC-mode CSRF advisory was carried
  as a documented exception in the audit gate because its only fix was the
  8.3.0 major; upstream backported it, so the exemption lapsed on its own --
  an entry only holds while fixAvailable.isSemVerMajor is true. The allowlist
  is now empty; the machinery stays for the next one.
- dompurify 3.4.12 -> 3.4.13. Ships in the app, but the path is unreachable:
  no hooks registered, IN_PLACE never used.
- js-yaml override ^4.3.0 -> ^5.2.3 (fix not backported below 5.x, so a
  major) and nanoid override ^3.3.18. Both dev-only, via eslint and postcss.
  eslintrc calls only load(), on the legacy .eslintrc.yml path this repo does
  not use; eslint, vite build and 2861 frontend tests pass on it.

Backend:
- cryptography >=48.0.1 -> >=50.0.0, aiohttp >=3.14.0 -> >=3.14.3, pyopenssl
  >=26.3.0 -> >=26.4.0. CI resolves from scratch and was already installing
  the fixed releases; the floors cover the case CI does not, an existing venv
  where >= is satisfied and `pip install -r` upgrades nothing. pyOpenSSL has
  to move with cryptography -- each release caps it to a narrow window, so a
  stale pyOpenSSL pins cryptography below its own fix line.
2026-08-08 13:20:18 +02:00
maziggy cb508c5d16 brace-expansion override ^5.0.8 -> ^5.0.9 (GHSA-rgw5-rvv9-x895, DoS).
5.0.8's maxLength cap was applied in combine(), where output is merged, but
not to the two arrays built before it runs: comma alternatives each got their
own full allowance and were concatenated with no running total, and padded
sequences never consulted maxLength at all. So a ~25 KB pattern still OOMs the
process -- fatally, past the reach of try/catch -- and a ~400 KB one blocks the
event loop for over two minutes. 5.0.9 bounds both as they are built.

Dev-only and transitive here: it reaches us as eslint -> minimatch@5 ->
brace-expansion, the only input it sees is our own lint globs, and it is not in
the shipped bundle. The ci.yml audit gate runs --omit=dev, so this never would
have failed CI; it surfaced through Dependabot.

The overrides floor is bumped alongside the lockfile so a clean install can't
resolve back to the vulnerable 5.0.8.
2026-08-05 07:48:23 +02:00
maziggy d90b91604f chore(deps): patch postcss + brace-expansion; pin react-router 7.18.1 with a documented audit exception
- postcss 8.5.15 -> 8.5.23 (GHSA-r28c-9q8g-f849, source-map path traversal)
- brace-expansion override ^5.0.7 -> ^5.0.8 (GHSA-mh99-v99m-4gvg, DoS)

react-router: pin react-router-dom to exact 7.18.1 (direct dep) and react-router
to 7.18.1 via overrides (transitive). 7.18.1 is the most-patched 7.x -- it clears
14 advisories that older 7.x releases carry, several reachable from a SPA (open-
redirect XSS in Link/useNavigate, route-matching DoS). The one remaining advisory,
GHSA-qwww-vcr4-c8h2, is RSC-mode-only; Bambuddy is a Vite SPA using BrowserRouter
with no RSC runtime (@react-router/server not installed), so the path is
unreachable. The only version that fully clears npm audit is the 8.3.0 major
(no react-router-dom 8.x exists; it needs migrating 50 import sites plus a React
peer bump), deferred as its own change.

Because a version pin can't stop npm from reporting the theoretical 7.11.0
downgrade as fixAvailable, the ci.yml (hard) and security.yml (nightly issue)
audit gates gain a narrow, documented allowlist keyed on the GHSA id. It resolves
the react-router-dom -> react-router advisory chain and stays fail-closed: a
different advisory on react-router still fails the gate, and an isSemVerMajor
guard drops the exemption the moment a non-major fix ships, forcing us to take it.
2026-07-27 12:27:36 +02:00
maziggy e609aa2ccb security(frontend): pin brace-expansion and js-yaml to patched versions
Both are transitive dev-only dependencies under eslint (via minimatch and
@eslint/eslintrc) with denial-of-service advisories (GHSA-3jxr-9vmj-r5cp,
GHSA-52cp-r559-cp3m). They are lint/build tooling and not part of the
shipped app, so no running install was exposed. npm audit fix wouldn't move
eslint to the patched releases on its own, so they are pinned through the
existing overrides block in package.json (brace-expansion ^5.0.7,
js-yaml ^4.3.0). npm audit now reports zero vulnerabilities; eslint runs clean.
2026-07-21 12:49:52 +02:00
maziggy 5cbefca6a0 feat(file-manager): recursive subfolder search + per-folder markdown description panel (#1268)
Reporter (@zumik3-del, seconded by @unLieb) asked for three File Manager
  improvements: recursive search, tags, and a markdown preview side panel.
  This commit ships the two scoped ones; tags is held back gated on the
  "give the issue a thumbs up" interest check Martin posted on the issue
  because it's a much larger surface (M2M schema, CRUD endpoints, tag UI +
  filter + autocomplete + i18n for the management surface) and isn't the
  right call without a real demand signal.

  1) Recursive search inside the selected folder.

     Until now, selecting "Toys" and typing "robot" only found files
     directly in Toys/ — anything under Toys/Cars/Race/ stayed invisible.
     The page's client-side filter ran over a server-narrowed listing
     (/library/files?folder_id=X is strict equality on folder_id), so the
     client filter couldn't see what the listing never loaded.

     list_files (backend/app/api/routes/library.py:1729+) gains a
     recursive=true query param. When combined with folder_id, the route
     walks library_folders.parent_id via a recursive CTE rooted at the
     requested folder and returns every descendant folder's files in one
     query. Recursive CTEs work on both SQLite >=3.8.3 (2014, well below
     Bambuddy's runtime floor) and Postgres without dialect branching.
     Default off so the existing folder-browsing call sites (Project /
     Archive detail, the FE's no-search case) keep their narrow scope.

     FE opts in only when both a folder is selected AND searchQuery is
     non-empty (FileManagerPage.tsx — derived as searchExpandsSubfolders,
     threaded through the useQuery key so the cache invalidates on
     toggle). Small "Including subfolders" caption renders under the
     search input when active so the user understands why a file from two
     levels deep showed up.

  2) Per-folder markdown description panel.

     New endpoint GET /library/folders/{folder_id}/readme returns the
     first .md file in the folder as {filename, content, truncated}.
     Selection prefers README.md / readme.md / description.md
     (case-insensitive via func.lower(filename) LIKE '%.md' + an
     in-Python stem-preference sort), falls back to the
     alphabetically-first *.md otherwise. 404 when no markdown is present
     so the FE can hide the side panel — non-users pay no UI cost.

     Bytes are clipped at 512 KiB (_README_BYTES_CAP) with a truncated
     flag so the panel can warn the reader. UTF-8 decode uses
     errors="replace" so one bad byte never blanks the panel.

     New FolderReadmePanel.tsx fetches on folder-select and renders via
     react-markdown@9 + remark-gfm@4 (tables, strikethrough, task lists).
     Collapsible (default expanded), max-height 24rem with internal
     scroll. react-markdown 9 doesn't render raw HTML by default — no
     dompurify needed. Links open in a new tab with rel=noopener
     noreferrer. Tailwind has no typography plugin in this project so
     per-element components map h1/h2/h3/p/ul/ol/code/blockquote/table
     to explicit utility classes that match the rest of the app.

  Scope and permissions.

  Both endpoints reuse the existing LIBRARY_READ_ALL / LIBRARY_READ_OWN
  ownership-aware pair, so a viewer-tier user with read_own only sees
  their own files in recursive listings and can only fetch the README of
  folders containing their own files. No new permission, no DB migration.

  The recursive CTE is a single SQL query — no N+1, no per-folder
  round-trip, scales to deeply-nested model libraries.
2026-06-22 11:40:58 +02:00
BambuMan 5a92115546 feat(api-keys): QR code on key creation encoding server URL + key (#1677) (#1701) 2026-06-19 12:35:39 +02:00
maziggy 249dacbd53 chore(frontend): vite 7 -> 8 + plugin-react 5.2
Major version bump for the frontend build:
  - vite ^7.3.2 -> ^8.0.16
  - @vitejs/plugin-react ^5.1.1 -> ^5.2.0

  Vite 8 swaps Rollup for Rolldown as the default bundler
  (Rust-backed, same plugin contract). The bump also lifts the
  transitive esbuild floor to 0.28.1, closing the last open
  advisory in the audit chain.

  vite.config.ts surface audited and unchanged:
  - defineConfig, Connect type
  - serveGcodeViewer configureServer middleware
  - server.proxy with WebSocket upgrade for /api/v1/ws
  - build.outDir / emptyOutDir / chunkSizeWarningLimit
  - resolve.alias for @
  - base: '/' regression guard from #1221

  vitest@4.1.8 already accepts vite 8 in its peer range
  (^6 || ^7 || ^8); no test-runner bump required.

  Node floor for vite 8 is ^20.19.0 || >=22.12.0; CI Node 20.x
  line satisfies this.

  Not taken: plugin-react v6 — it requires
  babel-plugin-react-compiler and @rolldown/plugin-babel as
  peers and is a separate scope.
2026-06-17 08:27:48 +02:00
maziggy 861de7a0e6 chore(frontend): dependency bumps
Runtime:
  - dompurify 3.4.0 -> 3.4.10 (package.json floor raised from
    ^3.4.0 to ^3.4.10 so fresh installs cannot land on the
    deprecated 3.4.4 release; release notes 3.4.1 -> 3.4.10
    reviewed — the three call sites (MakerworldPage,
    ProjectDetailPage, ProjectPageModal) use string-output
    sanitisation and are unaffected by 3.4.4's widened default
    allow-list)

  Build / lint / test tooling (transitive, dev-only):
  - @babel/core 7.29.0 -> 7.29.7 (via @vitejs/plugin-react and
    eslint-plugin-react-hooks)
  - vite 7.3.2 -> 7.3.5
  - markdown-it 14.1.1 -> 14.2.0 (via @tiptap/extension-link
    -> @tiptap/pm -> prosemirror-markdown; Bambuddy never calls
    markdown-it.render directly)
  - js-yaml 4.1.1 -> 4.2.0 (via eslint)
  - form-data 4.0.5 -> 4.0.6 (via jsdom)
  - ws 8.20.1 -> 8.21.0 (via jsdom)
2026-06-17 08:18:33 +02:00
MartinNYHC 01c402eae9 Merge pull request #1626 from maziggy/dependabot/npm_and_yarn/frontend/npm_and_yarn-813bc8c1b2
chore(deps): bump react-router from 7.13.0 to 7.16.0 in /frontend in the npm_and_yarn group across 1 directory
2026-06-04 11:43:44 +02:00
maziggy 9c8df1744d chore(deps): bump vitest 3.2.4 → 4.1.8 (GHSA-5xrq-8626-4rwp, CVSS 9.8)
The Vitest UI server's /__vitest_attachment__ handler bypasses
  isFileServingAllowed via a path-traversal payload, allowing arbitrary file
  read/execute on the host. Dev-scope only and not exploitable in
  Bambuddy's CI/CLI usage (we don't start the Vitest UI server and
  @vitest/ui is not installed), but bumping clears the Dependabot alert
  and brings us onto the supported 4.x line.

  Bumped:
    vitest                 3.2.4 → 4.1.8
    @vitest/coverage-v8    3.2.4 → 4.1.8

  Migration-required fix:
    StreamOverlayPage.test.tsx mocked `WebSocket` via
    vi.stubGlobal('WebSocket', vi.fn().mockImplementation(() => ({...})))
    and the page does `new WebSocket(url)`. Vitest 4 dropped support for
    arrow-function constructor mocks ("is not a constructor"). Rewrote
    with a plain `function` so `new` resolves correctly.

  All 2043 frontend tests pass; npm run build clean; npm audit shows 0
  vulnerabilities.
2026-06-02 08:38:00 +02:00
maziggy fc116f2f82 Removed unused i18next-http-backend 2026-04-23 08:51:12 +02:00
Minidoracat a584e671ec feat(i18n): add zh-TW locale and sync 74 missing keys in zh-CN (#1017) (#1025)
* fix(i18n): sync zh-CN to match en structure

- Add 74 missing keys covering login.resetPassword, printers.firmwareModal
  badges, settings.spoolbuddy device management, settings.tabs.spoolbuddy,
  spoolbuddy.settings system config
- Fix fileManager.uploadFailed placeholder bug (had stray {{count}} copied
  from zipFilesFailed; en value is plain "Upload failed")

Refs #1017

* feat(i18n): add zh-TW locale and enforce 3-way parity

- Add frontend/src/i18n/locales/zh-TW.ts (Traditional Chinese, Taiwan usage)
  with full key set aligned to en.ts
- Register zh-TW in frontend/src/i18n/index.ts: import, resources,
  supportedLngs, availableLanguages
- Add frontend/scripts/check-i18n-parity.mjs: TypeScript Compiler API-based
  3-way gate checking key set equality, placeholder equality, and legacy
  _plural / _one+_other suffix handling across en / zh-CN / zh-TW
- Wire check:i18n into test:run npm script so frontend-tests CI job
  (ci.yml:227) gates future locale drift

Fixes #1017
2026-04-19 08:17:09 +02:00
maziggy 63b3cad8d8 chore(deps): bump python-multipart 0.0.22→0.0.26 and dompurify 3.3.3→3.4.0
python-multipart 0.0.26 closes CVE-2026-40347 (GHSA-mj87-hwqh-73pj), a
  DoS triggered by large preamble/epilogue data around a multipart
  boundary. Bambuddy consumes python-multipart transitively through
  FastAPI/Starlette for form and file-upload parsing, so multipart routes
  (backup restore, project thumbnail upload, etc.) were exposed.

  dompurify 3.4.0 picks up the fix for GHSA-39q2-94rc-95cp (function-form
  ADD_TAGS could bypass FORBID_TAGS). Bambuddy's two call sites use only
  array-form ALLOWED_TAGS/ALLOWED_ATTR, so the specific bypass was not
  reachable, but the bump still hardens the sanitizer and clears the
  audit warning.

  requirements.txt floor raised to python-multipart>=0.0.26;
  frontend/package.json caret pinned to ^3.4.0; npm audit and pip audit
  both report zero outstanding advisories after the bumps.
2026-04-16 08:47:40 +02:00
dependabot[bot] ed61e756a6 Bump vite in /frontend in the npm_and_yarn group across 1 directory (#909)
Bump vite in /frontend in the npm_and_yarn group across 1 directory (#909)
2026-04-07 09:53:10 +02:00
maziggy fa6edfbcde Fix stored XSS vulnerabilities and unauthenticated auth toggle
- Sanitize project notes with DOMPurify before rendering via
    dangerouslySetInnerHTML (ProjectDetailPage.tsx)
  - Replace hand-rolled HTML sanitizer with DOMPurify in ProjectPageModal
    to prevent attribute injection via crafted 3MF href values
  - Block /api/v1/auth/setup when auth is already enabled to prevent
    unauthenticated clients from disabling authentication remotely
2026-03-15 15:31:49 +01:00
maziggy bffbac54e4 Add on-screen virtual keyboard for SpoolBuddy kiosk UI
The Raspberry Pi kiosk has no physical keyboard and system-level virtual
  keyboards (squeekboard, wvkbd) don't auto-show/hide with labwc/Chromium.
  Add a react-simple-keyboard QWERTY keyboard that auto-shows on input
  focus, with dark theme, shift/caps/backspace, email keys (@, .), and a
  two-phase close that prevents ghost-click passthrough to elements below.
  Inputs with data-vkb="false" opt out (e.g. SpoolBuddySettingsPage numpad).
2026-03-02 10:20:22 +01:00
maziggy 9e317bd775 Fix npm audit high-severity minimatch ReDoS (GHSA-3ppc-4f35-3m26)
by adding an npm override for minimatch@^10.2.1 in package.json.
2026-02-19 08:23:53 +01:00
maziggy bedcd0a73e 1. ajv is only used by eslint to validate config schemas during linting
2. It's a dev dependency, never reaches production
  3. The ReDoS requires crafted $data schema input — not an attack vector in a linting config
2026-02-18 09:30:29 +01:00
maziggy c56a67219e Pinned i18next to exact version 25.6.3 2026-02-10 17:22:47 +01:00
Thomas Rambach c01b5ca864 62942808254 2026-02-09 03:32:20 -05:00
maziggy 8be9bc757c @renovate baseline-browser-mapping@latest 2026-01-31 15:20:47 +01:00
maziggy e74d5be4b8 @renovate
- vitest: ^2.1.0 → ^3.2.4
- @vitest/coverage-v8: upgraded to match
2026-01-29 08:17:53 +01:00
maziggy ead2bfc822 @renovate baseline-browser-mapping@latest 2026-01-28 07:18:19 +01:00
MartinNYHC a9f340f2c9 Revert "Added optional authentication and user management" 2026-01-21 15:58:24 +01:00
JesseFPV 3e1843f834 Updated checks 2026-01-21 14:41:24 +01:00
dependabot[bot] eb125ed378 Bump react-router and react-router-dom in /frontend
Bumps [react-router](https://github.com/remix-run/react-router/tree/HEAD/packages/react-router) to 7.12.0 and updates ancestor dependency [react-router-dom](https://github.com/remix-run/react-router/tree/HEAD/packages/react-router-dom). These dependencies need to be updated together.


Updates `react-router` from 7.9.6 to 7.12.0
- [Release notes](https://github.com/remix-run/react-router/releases)
- [Changelog](https://github.com/remix-run/react-router/blob/main/packages/react-router/CHANGELOG.md)
- [Commits](https://github.com/remix-run/react-router/commits/react-router@7.12.0/packages/react-router)

Updates `react-router-dom` from 7.9.6 to 7.12.0
- [Release notes](https://github.com/remix-run/react-router/releases)
- [Changelog](https://github.com/remix-run/react-router/blob/main/packages/react-router-dom/CHANGELOG.md)
- [Commits](https://github.com/remix-run/react-router/commits/react-router-dom@7.12.0/packages/react-router-dom)

---
updated-dependencies:
- dependency-name: react-router
  dependency-version: 7.12.0
  dependency-type: indirect
- dependency-name: react-router-dom
  dependency-version: 7.12.0
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-18 19:53:13 +00:00
maziggy ff53e62ef8 Add comprehensive automated testing infrastructure
Backend:
  - pytest configuration with async support and coverage
  - Unit tests for notification service (23 tests)
  - Unit tests for smart plug manager (12 tests)
  - Unit tests for archive service (16 tests)
  - Integration tests for API endpoints
  - Fix: notifications now send immediately (digest is summary only)

  Frontend:
  - Vitest configuration with jsdom and coverage
  - MSW for API mocking
  - Component tests for Toggle, Button, Card, ConfirmModal (77 tests)
  - Test utilities with custom render wrapper

  CI/CD:
  - GitHub Actions workflow for automated testing
  - Backend lint, unit tests, integration tests
  - Frontend lint, type-check, unit tests, build
2025-12-11 10:03:40 +01:00
Martin Ziegler f126b0a075 Added auto app update; Added maintenance module with notifications 2025-12-01 08:39:07 +01:00
Martin Ziegler 53c94deade Added project page viewer and editor 2025-11-28 12:41:28 +01:00
Martin Ziegler 09677861ba Added screenshots 2025-11-28 10:23:59 +01:00