StoreManagement
Tablet-first layout
The tablet is the device this runs on. The phone has to work; the tablet has to be good.
Three layouts, chosen by width
Not by device type. A half-width desktop window is a tablet as far as the layout is concerned, and treating it as one means there’s no such thing as a size that falls between the cases.
| Width | Layout | Typical device |
|---|---|---|
≥ 1000 | Rail + section list + page, all visible | iPad landscape, desktop |
≥ 620 | Rail + page; sections in a drawer | iPad portrait |
< 620 | Page + bottom bar; sections in a drawer | Phone |
The rail is the point
As separate apps, moving from the day’s cash to the stock it implies meant going back to a portal and loading another application. The rail carries all three features at every width — a bottom bar on a phone, a vertical rail above that — so switching is one tap from anywhere. It replaced the launcher screen outright: there is nowhere to go “back” to.
The pane must tell the truth about its width
The bug this prevents
isNarrow(context) reads MediaQuery, which describes the window. On an 820pt iPad the rail takes about 80 and the section list 236, leaving roughly 500 for the page — but the page would still read 820, decide it had room for three columns, and overflow.
Rather than change every page, the shell wraps the content in a MediaQuery describing the pane. Every existing responsive helper then works untouched, because the number they read is finally the number that matters.
testWidgets('pane width excludes the rail and the section list',
(tester) async {
await _pump(tester, _tabletLandscape, '/cash/entry/daily');
final pane = _paneWidth(tester);
expect(pane, lessThan(_tabletLandscape.width),
reason: 'the rail and section list take real width');
// Rail (~80) + list (236) + dividers. Anything close to the full 1180
// means a page would lay out columns that do not fit.
expect(pane, lessThan(_tabletLandscape.width - 300));
});What pages get for free
mblrc_shared/responsive.dart holds the pieces that respond to the pane width, so individual pages contain almost no layout branching:
| Widget | Wide | Narrow |
|---|---|---|
PageHeader | Title left, actions right | Actions wrap to their own line rather than squeezing the title away |
FieldRow | Fields side by side | Stacked — three peso fields on one line are unreadable |
StatCards | One card per figure | A single card, headline figure first, the rest as rows |
HorizontalTable | Table as-is | Scrolls sideways at a legible minimum width instead of crushing columns |
Checking it without a database
The layouts are covered by widget tests at real device sizes — iPhone 390×844, iPad 820×1180 and 1180×820 — and a RenderFlex overflow fails a widget test, so anything that doesn’t fit is caught before it ships.
For looking at it rather than asserting on it, there’s a throwaway entrypoint that mounts the shell with stub pages and needs no Supabase credentials:
flutter build web -t tool/shell_preview.dart -o build/preview