mirror of
https://github.com/maziggy/bambuddy.git
synced 2026-10-18 03:54:17 +02:00
fix(ui): stop the Spool Inventory header from scrolling the page sideways (#2813)
Five header buttons in a row that could neither wrap nor shrink came to ~600px, so on a 390px screen the header ran past the viewport and took the whole page with it -- <main> is the scroll container, so everything inside it panned. Stack below sm and wrap the actions, matching the pattern the Statistics, Settings and Archives headers and this page's own filter bar already use. Identical at >=640px. The System Information header had the same construction with one button and gets the same treatment.
This commit is contained in:
@@ -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.
|
||||
|
||||
### Fixed
|
||||
- **The Spool Inventory page scrolled sideways on a phone, taking the whole page with it (#2813, reported by @chitrangdesign)** — The header of **Filament → Spool Inventory** put its title and its five buttons — Import CSV, Export CSV, Locations, Print labels and Add Spool — in a single row that could neither wrap nor shrink. Side by side those buttons come to roughly 600 pixels, so on a 390-pixel screen the header ran past the edge of the viewport, and because the whole page scrolls as one region everything below it went with the header: the statistics bar, the filters and every spool card sat offset, and vertical scrolling fought a horizontal pan the entire time. Longer languages made it worse, since German renders the same two buttons as "CSV importieren" and "Etiketten drucken…". The header now stacks on narrow screens and the buttons wrap onto as many lines as they need, which is the same arrangement the Statistics, Settings and Archives headers have always used, and the one the filter bar further down this very page already used — this header simply never got it. At 640 pixels and wider nothing changes at all. The System Information header had the same construction with a single Refresh button, where it only just overflowed, and is fixed in the same way.
|
||||
- **A spool emptied by AMS Filament Backup was left showing full, and the spool that took over was charged for the whole print** — With backup on, the firmware switches to another slot holding the same material when one runs dry and carries on. Bambuddy has split a print's filament across the trays it really fed from since #957, but everything that split depends on lived only in memory: the plate the job was dispatched for, the slot-to-tray mapping it was dispatched with, the spool assignments as they stood at the start, and the log of tray changes. A print long enough to outlive a Bambuddy restart lost all of it — on the 14-hour print this was reported on, two restarts left the completion path with nothing but what the printer reports *now*, and with backup on "now" is the substitute tray. The full 1122 g went to the neighbouring spool, which then read 0 g of 1000 g, while the spool that had actually run dry was charged nothing and still read full. That context is now written to the database at print start and put back if Bambuddy restarts, with tray changes appended as they happen, so the split survives; a print that started before this release is seeded from the tray feeding at the moment of recovery, so at least the remainder is attributed correctly. Four things around it were wrong in the same direction. The printer's live `mapping` field was consulted ahead of the mapping the queue actually dispatched, and that field is precisely what backup rewrites — the stored one now wins, and the live field is the fallback it always should have been. The plate was read only from the in-memory session, though both the archive and the queue item recorded it; without it the 3MF parser summed every plate in a multi-plate file, so a second plate's filament was billed to a print that never ran it. The per-layer G-code read that measures each segment took no plate at all and used the first `.gcode` member in the zip, which for a Bambu Studio export is not plate 1 — the same member-order trap fixed in the G-code viewer above, here measuring one plate's layers against another plate's total. And when a slot reported empty mid-print, the assignment was auto-unlinked as though the spool had been removed, which is exactly wrong at a runout: the spool is still in the AMS, just consumed, and dropping the link left the completion path with nothing to charge that segment to. Empty slots are now left linked while a print is running; a genuinely different filament still reconciles as before. All of this applies whichever inventory Bambuddy is running against. Spoolman's own durable row has carried its plate-scoped figures and its dispatched mapping since #1820, but not the tray-change log, so the print-start capture now runs for both backends; its per-layer figures came from the same wrong-plate call; and its slot assignments — which are how a tag-less spool assigned through the Bambuddy UI is resolved at completion (#1459) — were being dropped at a runout exactly like the internal ones. Covered by backend tests, including the runout sequence from the reported print.
|
||||
- **A print completion could close the wrong queue item** — When a print ends, Bambuddy finds the queue row to close by asking for whatever is `printing` on that printer. Nothing tied the event to the row: the completion carries the printer's subtask name but no run identifier, so any completion delivered for a printer closed whichever job happened to be printing on it, whether or not the event was about that job. A run closed this way is marked completed with the printer still working, disappears from the queue into history, and takes the rest of its batch with it — the next plate stays pending because the queue rightly will not dispatch onto a busy printer. The handler now checks the completion against the file the row was dispatched with before closing it, and leaves the row alone, with a warning naming both, when they disagree. It only refuses on a positive disagreement: a row with no archive yet, or an event with no subtask name, is unverifiable rather than wrong, and refusing those would strand the item in `printing` and wedge the printer's queue — a worse failure than the one being prevented. Also fixed in the test suite, where this surfaced: it built its own database but the app's module-level engine still resolved `DATABASE_URL`, so tests that reach code opening its own session — the completion path among them — talked to whatever database the developer's `.env` named. That is now redirected to a throwaway file before any app import, and the run aborts rather than starts if the redirect did not take.
|
||||
- **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.
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
/**
|
||||
* The Spool Inventory header must not overflow a phone viewport (#2813).
|
||||
*
|
||||
* Its five buttons come to roughly 600px side by side, and a flex row whose
|
||||
* items cannot shrink is as wide as its contents. At 390px that pushed the
|
||||
* header past the viewport, and since the whole page scrolls as one region
|
||||
* everything below it panned with the header.
|
||||
*
|
||||
* jsdom does no layout, so this asserts the two properties that make the
|
||||
* overflow impossible rather than a measured width: the header stacks below
|
||||
* `sm`, and the button group wraps. Same approach as the VirtualPrinterCard
|
||||
* header test (#2808).
|
||||
*/
|
||||
|
||||
import { describe, it, expect, beforeEach } from 'vitest';
|
||||
import { screen, waitFor } from '@testing-library/react';
|
||||
import { http, HttpResponse } from 'msw';
|
||||
import { render } from '../utils';
|
||||
import InventoryPageRouter from '../../pages/InventoryPage';
|
||||
import { server } from '../mocks/server';
|
||||
|
||||
function setupHandlers() {
|
||||
server.use(
|
||||
http.get('/api/v1/settings/spoolman', () =>
|
||||
HttpResponse.json({
|
||||
spoolman_enabled: 'false',
|
||||
spoolman_url: '',
|
||||
spoolman_sync_mode: 'auto',
|
||||
spoolman_disable_weight_sync: 'false',
|
||||
spoolman_report_partial_usage: 'true',
|
||||
})
|
||||
),
|
||||
http.get('/api/v1/inventory/spools', () => HttpResponse.json([])),
|
||||
http.get('/api/v1/inventory/assignments', () => HttpResponse.json([])),
|
||||
http.get('/api/v1/inventory/catalog', () => HttpResponse.json([])),
|
||||
http.get('/api/v1/printers/', () => HttpResponse.json([])),
|
||||
);
|
||||
}
|
||||
|
||||
describe('InventoryPage — header layout', () => {
|
||||
beforeEach(() => {
|
||||
setupHandlers();
|
||||
});
|
||||
|
||||
it('stacks the header below sm and keeps it a row from sm up', async () => {
|
||||
render(<InventoryPageRouter />);
|
||||
|
||||
const heading = await screen.findByRole('heading', { name: 'Spool Inventory' });
|
||||
// h1 -> title block -> header row
|
||||
const header = heading.parentElement?.parentElement as HTMLElement;
|
||||
|
||||
expect(header.className).toContain('flex-col');
|
||||
expect(header.className).toContain('sm:flex-row');
|
||||
// Desktop is unchanged: the row still spreads title and actions apart.
|
||||
expect(header.className).toContain('sm:justify-between');
|
||||
});
|
||||
|
||||
it('lets the action buttons wrap instead of widening the header', async () => {
|
||||
render(<InventoryPageRouter />);
|
||||
|
||||
// Located through the heading rather than by button name: the empty-state
|
||||
// panel offers its own "Add Spool" further down the page.
|
||||
const heading = await screen.findByRole('heading', { name: 'Spool Inventory' });
|
||||
const header = heading.parentElement?.parentElement as HTMLElement;
|
||||
const group = header.lastElementChild as HTMLElement;
|
||||
await waitFor(() => {
|
||||
expect(group.querySelector('button')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
// Without this the group is as wide as all five buttons laid end to end,
|
||||
// whatever the viewport is.
|
||||
expect(group.className).toContain('flex-wrap');
|
||||
// All five actions stay in that one group -- wrapping them is the fix,
|
||||
// hiding any of them is not.
|
||||
expect(group.querySelectorAll('button')).toHaveLength(5);
|
||||
});
|
||||
});
|
||||
@@ -1308,8 +1308,13 @@ function InventoryPage({ spoolmanMode = false, spoolmanModeReady = true }: { spo
|
||||
|
||||
return (
|
||||
<div className="p-4 md:p-8 space-y-6">
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between">
|
||||
{/* Header. Stacks below sm and the actions wrap (#2813): five buttons
|
||||
side by side are ~600px, and nothing in that row can shrink, so on a
|
||||
phone the header pushed past the viewport and took the whole page
|
||||
with it -- <main> is the scroll container, so everything inside it
|
||||
panned sideways. Same pattern the Statistics, Settings and Archives
|
||||
headers use, and the filter bar further down this page. */}
|
||||
<div className="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold text-white flex items-center gap-3">
|
||||
<Package className="w-7 h-7 text-bambu-green" />
|
||||
@@ -1317,7 +1322,7 @@ function InventoryPage({ spoolmanMode = false, spoolmanModeReady = true }: { spo
|
||||
</h1>
|
||||
<p className="text-bambu-gray mt-1">{t('inventory.subtitle')}</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
{/* CSV import/export (#1576). Operates on Bambuddy's local inventory.
|
||||
In Spoolman mode the buttons stay visible (feature parity) but are
|
||||
disabled with a hint pointing at Spoolman's own CSV export, since
|
||||
|
||||
@@ -201,8 +201,11 @@ export function SystemInfoPage() {
|
||||
|
||||
return (
|
||||
<div className="p-6 space-y-6">
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between">
|
||||
{/* Header. Same stacking as the Spool Inventory header (#2813) -- one
|
||||
button rather than five, so it only just overflows at 390px, but the
|
||||
overflow is the same. items-start keeps Refresh at its own width
|
||||
while stacked instead of stretching it across the page. */}
|
||||
<div className="flex flex-col items-start gap-4 sm:flex-row sm:items-center sm:justify-between">
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold text-white">{t('system.title', 'System Information')}</h1>
|
||||
<p className="text-bambu-gray mt-1">
|
||||
|
||||
File diff suppressed because one or more lines are too long
+1
-1
@@ -26,7 +26,7 @@
|
||||
|
||||
<!-- Splash screens for iOS -->
|
||||
<link rel="apple-touch-startup-image" href="/img/android-chrome-512x512.png" />
|
||||
<script type="module" crossorigin src="/assets/index-Bg0yeHsW.js"></script>
|
||||
<script type="module" crossorigin src="/assets/index-DAVz1Dw0.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-D4EtyCBI.css">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
Reference in New Issue
Block a user