فهرست منبع

Tell the browser which colour scheme the page is in

The parts of a form control the browser draws itself -- a number input's
stepper, a date field's calendar button and popup, a select's dropdown,
scrollbars, the autofill tint -- were painted in the light appearance on
every theme. Bambuddy switches theme by swapping CSS variables under a
`dark` class, which the browser cannot see, so it assumed the page was
light and matched the steppers to a white background that was not there.

Declaring color-scheme alongside the variables fixes all of them at once,
in both directions. It goes on `.dark` rather than the per-palette
classes because the kiosk sets `dark` on the root element directly.

Three date and time fields had been pinned to dark by hand to work
around this and no longer need to be; being pinned, they were wrong
under the light theme anyway.
maziggy 3 هفته پیش
والد
کامیت
4a65abe228

+ 1 - 0
CHANGELOG.md

@@ -28,6 +28,7 @@ All notable changes to Bambuddy will be documented in this file.
 - **Error and warning toasts now stay up twice as long** — Every pop-up notification disappeared after three seconds regardless of what it said. That is about right for "Settings saved", which confirms something you just did and is skimmed rather than read, but errors and warnings are a different kind of message: they carry a reason, often one relayed from the printer or the backend, and they run to a couple of lines. Three seconds was not long enough to finish reading one, and a missed error message is gone for good — there is no notification history to go back to. Errors and warnings now hold for six seconds. Success and informational toasts keep the three-second default, so the common case of clicking something and seeing it confirmed is unchanged, and the close button and the manual dismiss work exactly as before on all of them. The background print-dispatch toast is unaffected: it stays up while it has work in progress and clears itself shortly after the last job settles. Covered by frontend tests.
 - **Error and warning toasts now stay up twice as long** — Every pop-up notification disappeared after three seconds regardless of what it said. That is about right for "Settings saved", which confirms something you just did and is skimmed rather than read, but errors and warnings are a different kind of message: they carry a reason, often one relayed from the printer or the backend, and they run to a couple of lines. Three seconds was not long enough to finish reading one, and a missed error message is gone for good — there is no notification history to go back to. Errors and warnings now hold for six seconds. Success and informational toasts keep the three-second default, so the common case of clicking something and seeing it confirmed is unchanged, and the close button and the manual dismiss work exactly as before on all of them. The background print-dispatch toast is unaffected: it stays up while it has work in progress and clears itself shortly after the last job settles. Covered by frontend tests.
 
 
 ### Fixed
 ### Fixed
+- **Number steppers, date pickers and dropdowns stayed in their light-mode colours on a dark theme** — The little up/down arrows on a number field -- the print dialog's per-plate quantity, and every other one in Bambuddy -- were drawn as a pale grey chip inside a dark input, and the same applied to the calendar button on a date field, the popup a `<select>` opens, scrollbars and the yellow tint a browser paints over an autofilled field. Those parts of a form control are drawn by the browser rather than by us, and the browser was never told the page had gone dark: Bambuddy switches theme by swapping CSS variables under a class, which is invisible to it, so it assumed light and painted them to match a page it thought was white. It is now told which appearance is in use, and follows the theme in both directions. Two date fields that had been pinned to dark by hand to work around this -- in the statistics timeframe picker and the local-backup schedule -- no longer need to be, and had been wrong under the light theme. A `<select>` opened on a dark theme is the change worth knowing about: its list used to render as white-on-white in places, since the popup took the light appearance while the options inherited the theme's white text.
 - **The G-code viewer opened a multi-plate file on the wrong plate, with no way to reach the others** — Previewing a sliced multi-plate 3MF from the File Manager showed a plate nobody had asked for, and nothing on screen said which one it was or offered another. Three things were behind it. The route that serves a library file's G-code took no plate parameter at all, so the plate the viewer had always been putting in the URL was discarded — an unknown query parameter is dropped without a word. With nothing to go on, both this route and the archive one fell back to the first `.gcode` member in the file, and a 3MF's member order is whatever the slicer wrote rather than plate order: the two-plate file this was reported on stores plate 2 ahead of plate 1, which is exactly the plate that kept appearing. And no path into the viewer from the File Manager passes a plate in the first place, so even once the parameter worked there was no way to ask for plate 1. All three are fixed: the library route accepts `plate` exactly as the archive route does, an unqualified request returns the lowest-numbered plate rather than whichever came first in the zip, and the viewer has a plate switcher in its header for any file with more than one. The plate lives in the viewer's URL, so a link to one particular plate keeps working and a reload stays put. Filament colours follow the switcher too, which they previously did not — plate 2's toolpath could be drawn in plate 1's colours. The same first-member assumption sat under G-code injection and the finish-photo bed-drop height, both of which now resolve the lowest plate when no plate is named. Covered by backend and frontend tests, pinned to the member order of a real Bambu Studio export.
 - **The G-code viewer opened a multi-plate file on the wrong plate, with no way to reach the others** — Previewing a sliced multi-plate 3MF from the File Manager showed a plate nobody had asked for, and nothing on screen said which one it was or offered another. Three things were behind it. The route that serves a library file's G-code took no plate parameter at all, so the plate the viewer had always been putting in the URL was discarded — an unknown query parameter is dropped without a word. With nothing to go on, both this route and the archive one fell back to the first `.gcode` member in the file, and a 3MF's member order is whatever the slicer wrote rather than plate order: the two-plate file this was reported on stores plate 2 ahead of plate 1, which is exactly the plate that kept appearing. And no path into the viewer from the File Manager passes a plate in the first place, so even once the parameter worked there was no way to ask for plate 1. All three are fixed: the library route accepts `plate` exactly as the archive route does, an unqualified request returns the lowest-numbered plate rather than whichever came first in the zip, and the viewer has a plate switcher in its header for any file with more than one. The plate lives in the viewer's URL, so a link to one particular plate keeps working and a reload stays put. Filament colours follow the switcher too, which they previously did not — plate 2's toolpath could be drawn in plate 1's colours. The same first-member assumption sat under G-code injection and the finish-photo bed-drop height, both of which now resolve the lowest plate when no plate is named. Covered by backend and frontend tests, pinned to the member order of a real Bambu Studio export.
 - **A print was refused for lack of filament that AMS Filament Backup had covered all along** — The print dialog weighs each plate against the spool assigned to the slot it maps to, adding up the plates that draw on the same slot before it weighs any of them. What it did not know about was AMS Filament Backup: with that on, the firmware switches to another slot holding the same material once one runs out, so what a print can draw on is the whole matching pool and not the one slot. Queueing both plates of a job needing 1441 g of ABS was refused with "A3: needs 1441g, remaining 1000g" while the identical full spool in A4 sat next to it, doing nothing. The queue dispatcher has pooled matching spools since #1762 and would have started this print without complaint — the dialog was the only thing in the way, and "Print anyway" was always the correct answer to it. The dialog now pools the same way the dispatcher does: same filament profile and colour, and only within one nozzle's reach on the dual-extruder machines, since the firmware cannot cross nozzles even with backup on. Where a pool really is too small the warning quotes the pooled figures instead of the mapped slot's, because "needs 1441g, remaining 1000g" written next to a second full spool reads as a contradiction. Which spools back each other up is now worked out in one place on the server, by the code the dispatcher itself uses, and handed to the browser rather than decided a second time there — the two answers drifting apart is the whole of this bug. Doing it that way also gives the warning to Spoolman users for the first time: it used to resolve spools through the internal inventory only, so in Spoolman mode it had quietly approved everything, including prints that were genuinely short. Covered by backend and frontend tests.
 - **A print was refused for lack of filament that AMS Filament Backup had covered all along** — The print dialog weighs each plate against the spool assigned to the slot it maps to, adding up the plates that draw on the same slot before it weighs any of them. What it did not know about was AMS Filament Backup: with that on, the firmware switches to another slot holding the same material once one runs out, so what a print can draw on is the whole matching pool and not the one slot. Queueing both plates of a job needing 1441 g of ABS was refused with "A3: needs 1441g, remaining 1000g" while the identical full spool in A4 sat next to it, doing nothing. The queue dispatcher has pooled matching spools since #1762 and would have started this print without complaint — the dialog was the only thing in the way, and "Print anyway" was always the correct answer to it. The dialog now pools the same way the dispatcher does: same filament profile and colour, and only within one nozzle's reach on the dual-extruder machines, since the firmware cannot cross nozzles even with backup on. Where a pool really is too small the warning quotes the pooled figures instead of the mapped slot's, because "needs 1441g, remaining 1000g" written next to a second full spool reads as a contradiction. Which spools back each other up is now worked out in one place on the server, by the code the dispatcher itself uses, and handed to the browser rather than decided a second time there — the two answers drifting apart is the whole of this bug. Doing it that way also gives the warning to Spoolman users for the first time: it used to resolve spools through the internal inventory only, so in Spoolman mode it had quietly approved everything, including prints that were genuinely short. Covered by backend and frontend tests.
 - **Slicing a model stored on a network share wrote the result into Bambuddy's internal library instead of onto the share (#2810, reported by @zevulos)** — Server-side slicing files kept in an external folder produced a `.gcode.3mf` that appeared in the correct folder in the File Manager but never arrived in the folder itself, so it could not be opened from any other machine on the share. The output was always written to Bambuddy's own storage while the new entry was filed under the external folder, which is why the file looked present in the web interface and was missing everywhere else — and why it could not be reproduced without looking at the mount from outside Bambuddy. Uploads and moves already wrote to external folders correctly; slicing was the last path that did not, which is why moving a sliced file to another folder on the same share made it appear there. The result is now written next to its source, under a name that is readable on the share rather than an internal identifier. An existing file of the same name is never overwritten — a re-slice becomes `Model (2).gcode.3mf` — because the target is somebody's NAS and the file being replaced may not be Bambuddy's. If the external folder cannot accept the file at all, because it is mounted read-only, unreachable or not writable, the slice is kept rather than thrown away: it goes to the internal library as before, and now says so with a warning, since a file quietly filed somewhere you are not looking is the whole of this bug.
 - **Slicing a model stored on a network share wrote the result into Bambuddy's internal library instead of onto the share (#2810, reported by @zevulos)** — Server-side slicing files kept in an external folder produced a `.gcode.3mf` that appeared in the correct folder in the File Manager but never arrived in the folder itself, so it could not be opened from any other machine on the share. The output was always written to Bambuddy's own storage while the new entry was filed under the external folder, which is why the file looked present in the web interface and was missing everywhere else — and why it could not be reproduced without looking at the mount from outside Bambuddy. Uploads and moves already wrote to external folders correctly; slicing was the last path that did not, which is why moving a sliced file to another folder on the same share made it appear there. The result is now written next to its source, under a name that is readable on the share rather than an internal identifier. An existing file of the same name is never overwritten — a re-slice becomes `Model (2).gcode.3mf` — because the target is somebody's NAS and the file being replaced may not be Bambuddy's. If the external folder cannot accept the file at all, because it is mounted read-only, unreachable or not writable, the slice is kept rather than thrown away: it goes to the internal library as before, and now says so with a warning, since a file quietly filed somewhere you are not looking is the whole of this bug.

+ 1 - 1
frontend/src/components/GitHubBackupSettings.tsx

@@ -1270,7 +1270,7 @@ export function GitHubBackupSettings() {
                       <input
                       <input
                         type="time"
                         type="time"
                         value={localBackupStatus?.time ?? '03:00'}
                         value={localBackupStatus?.time ?? '03:00'}
-                        className="w-full h-10 px-3 py-2 bg-bambu-dark border border-bambu-dark-tertiary rounded-lg text-white focus:border-bambu-green focus:outline-none [color-scheme:dark]"
+                        className="w-full h-10 px-3 py-2 bg-bambu-dark border border-bambu-dark-tertiary rounded-lg text-white focus:border-bambu-green focus:outline-none"
                         onChange={async (e) => {
                         onChange={async (e) => {
                           try {
                           try {
                             await api.updateSettings({ local_backup_time: e.target.value });
                             await api.updateSettings({ local_backup_time: e.target.value });

+ 17 - 0
frontend/src/index.css

@@ -101,6 +101,17 @@ html {
   --card-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
   --card-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
   --glow-color: transparent;
   --glow-color: transparent;
 
 
+  /* Tell the browser which appearance the page is in, so the parts of a form
+     control it draws itself follow the theme.  Bambuddy themes by
+     swapping CSS variables under a `.dark` class, which the browser cannot
+     see: without this declaration it assumes light and paints every native
+     widget internal that way -- number-input steppers, the date picker's
+     calendar button and its popup, `<select>` dropdowns, scrollbars, the
+     autofill highlight.  That is why the quantity steppers in the print
+     dialog were pale grey chips inside a dark field.  Only the widget
+     internals are affected; anything we set ourselves still wins. */
+  color-scheme: light;
+
   font-family: 'Inter', system-ui, sans-serif;
   font-family: 'Inter', system-ui, sans-serif;
   line-height: 1.5;
   line-height: 1.5;
   font-weight: 400;
   font-weight: 400;
@@ -112,6 +123,12 @@ html {
 
 
 /* Dark mode base */
 /* Dark mode base */
 .dark {
 .dark {
+  /* Pairs with `color-scheme: light` on :root -- see the note there.  This
+     sits on `.dark` rather than on each `.dark.bg-*` palette because the
+     kiosk adds `dark` to <html> on its own (SpoolBuddyLayout), and every
+     palette is a dark one regardless of which background it picks. */
+  color-scheme: dark;
+
   --bg-primary: #1a1a1a;
   --bg-primary: #1a1a1a;
   --bg-secondary: #2d2d2d;
   --bg-secondary: #2d2d2d;
   --bg-tertiary: #3d3d3d;
   --bg-tertiary: #3d3d3d;

+ 2 - 2
frontend/src/pages/StatsPage.tsx

@@ -1370,7 +1370,7 @@ export function StatsPage() {
                           value={timeframe.dateFrom || ''}
                           value={timeframe.dateFrom || ''}
                           max={timeframe.dateTo || new Date().toISOString().split('T')[0]}
                           max={timeframe.dateTo || new Date().toISOString().split('T')[0]}
                           onChange={(e) => setTimeframe(prev => ({ ...prev, dateFrom: e.target.value || undefined }))}
                           onChange={(e) => setTimeframe(prev => ({ ...prev, dateFrom: e.target.value || undefined }))}
-                          className="w-full bg-bambu-dark border border-bambu-dark-tertiary rounded-md px-3 py-1.5 text-sm text-white [color-scheme:dark]"
+                          className="w-full bg-bambu-dark border border-bambu-dark-tertiary rounded-md px-3 py-1.5 text-sm text-white"
                         />
                         />
                       </div>
                       </div>
                       <div>
                       <div>
@@ -1381,7 +1381,7 @@ export function StatsPage() {
                           min={timeframe.dateFrom}
                           min={timeframe.dateFrom}
                           max={new Date().toISOString().split('T')[0]}
                           max={new Date().toISOString().split('T')[0]}
                           onChange={(e) => setTimeframe(prev => ({ ...prev, dateTo: e.target.value || undefined }))}
                           onChange={(e) => setTimeframe(prev => ({ ...prev, dateTo: e.target.value || undefined }))}
-                          className="w-full bg-bambu-dark border border-bambu-dark-tertiary rounded-md px-3 py-1.5 text-sm text-white [color-scheme:dark]"
+                          className="w-full bg-bambu-dark border border-bambu-dark-tertiary rounded-md px-3 py-1.5 text-sm text-white"
                         />
                         />
                       </div>
                       </div>
                       <Button
                       <Button

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 0 - 0
static/assets/index-Bg0yeHsW.js


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 0 - 0
static/assets/index-D4EtyCBI.css


+ 2 - 2
static/index.html

@@ -26,8 +26,8 @@
 
 
     <!-- Splash screens for iOS -->
     <!-- Splash screens for iOS -->
     <link rel="apple-touch-startup-image" href="/img/android-chrome-512x512.png" />
     <link rel="apple-touch-startup-image" href="/img/android-chrome-512x512.png" />
-    <script type="module" crossorigin src="/assets/index-BlVotyTj.js"></script>
-    <link rel="stylesheet" crossorigin href="/assets/index-BkuH4t27.css">
+    <script type="module" crossorigin src="/assets/index-Bg0yeHsW.js"></script>
+    <link rel="stylesheet" crossorigin href="/assets/index-D4EtyCBI.css">
   </head>
   </head>
   <body>
   <body>
     <div id="root"></div>
     <div id="root"></div>

برخی فایل ها در این مقایسه diff نمایش داده نمی شوند زیرا تعداد فایل ها بسیار زیاد است