Miler rider app: surface system, visible design language, backend lifecycle

Design system
- MilerSurface ladder (canvas → working → raised → floating) with MilerPanel
  as layer 1; canvas moved to #DEE3EA so white separates at 1.290:1.
- Visible vocabulary applied across Home, Deliveries, Activity, Account and
  the sheets: hero heads (tabular numeral + small caption, clamped at 1.3x),
  canvas wells for anything that opens, small filled tags for shelf labels,
  demoted placeholders. Recorded in DESIGN_SYSTEM.md §6.
- One icon family: 222 Material glyphs migrated to Lucide; none left outside
  lib/xpress.
- Colour semantics corrected: amber only for what is genuinely owed, brand red
  reserved for the live stop, disabled primaries go neutral rather than pale.

Data and lifecycle
- lib/data/lifecycle.dart reads mutations for what they prove; route_order.dart
  makes admin sequence the single ordering authority; service_day.dart, and
  stop_area.dart rewritten against live Coimbatore addresses (digit-token
  stripping, city stoplist, street suffixes, stammer collapse).
- countLabel states the load once, in bags.

Testing
- 1440 tests passing; golden shot harnesses for Home, Deliveries, Activity,
  sheets and verify, with test/failures/ now gitignored (diff debris).
- New pins: home_gutter_test, stop_area_test, plus updated structural bounds.

Note: this commit also carries pre-existing working-tree deletions that were
present before this work (API_SPEC.md, README.md, demo test fixtures).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-22 05:40:35 +05:30
parent c8350563d9
commit d7348e253f
387 changed files with 80693 additions and 12272 deletions

View File

@@ -62,7 +62,11 @@ void main() {
child: Container(key: barKey, height: 96, color: Colors.white),
),
SliverToBoxAdapter(
child: Container(key: contentKey, height: 2000, color: Colors.grey),
child: Container(
key: contentKey,
height: 2000,
color: Colors.grey,
),
),
],
),
@@ -80,8 +84,11 @@ void main() {
// Not merely pushed off-screen — a sliver that leaves the viewport and its
// cache extent is unmounted outright, so the duty toggle stopped existing
// rather than just moving. This is the behaviour being fixed.
expect(find.byKey(barKey), findsNothing,
reason: 'this is the behaviour that hid the duty toggle');
expect(
find.byKey(barKey),
findsNothing,
reason: 'this is the behaviour that hid the duty toggle',
);
});
testWidgets('as a Column child, the bar does not move', (tester) async {
@@ -92,15 +99,21 @@ void main() {
await tester.dragFrom(inScrollArea, const Offset(0, -300));
await tester.pumpAndSettle();
expect(tester.getRect(find.byKey(barKey)), before,
reason: 'the app bar is fixed');
expect(
tester.getRect(find.byKey(barKey)),
before,
reason: 'the app bar is fixed',
);
// And the page really did scroll underneath it.
expect(tester.getRect(find.byKey(contentKey)).top,
lessThan(contentBefore - 200));
expect(
tester.getRect(find.byKey(contentKey)).top,
lessThan(contentBefore - 200),
);
});
testWidgets('the bar stays put through a long scroll and back',
(tester) async {
testWidgets('the bar stays put through a long scroll and back', (
tester,
) async {
await tester.pumpWidget(pinned());
final rect = tester.getRect(find.byKey(barKey));
@@ -111,13 +124,18 @@ void main() {
}
});
testWidgets('pull to refresh still fires below the fixed bar',
(tester) async {
testWidgets('pull to refresh still fires below the fixed bar', (
tester,
) async {
var fired = 0;
await tester.pumpWidget(pinned(onRefresh: () async {
fired++;
await Future<void>.delayed(const Duration(milliseconds: 50));
}));
await tester.pumpWidget(
pinned(
onRefresh: () async {
fired++;
await Future<void>.delayed(const Duration(milliseconds: 50));
},
),
);
final rect = tester.getRect(find.byKey(barKey));
await tester.flingFrom(inScrollArea, const Offset(0, 320), 1200);
@@ -131,11 +149,16 @@ void main() {
await tester.pumpAndSettle();
});
testWidgets('the refresh spinner comes down below the bar, not behind it',
(tester) async {
await tester.pumpWidget(pinned(onRefresh: () async {
await Future<void>.delayed(const Duration(milliseconds: 400));
}));
testWidgets('the refresh spinner comes down below the bar, not behind it', (
tester,
) async {
await tester.pumpWidget(
pinned(
onRefresh: () async {
await Future<void>.delayed(const Duration(milliseconds: 400));
},
),
);
await tester.flingFrom(inScrollArea, const Offset(0, 320), 1200);
await tester.pump();
@@ -145,8 +168,10 @@ void main() {
expect(spinner, findsOneWidget);
// `edgeOffset` is no longer needed precisely because the scroll viewport now
// starts under the bar — so the indicator is already in clear space.
expect(tester.getRect(spinner).top,
greaterThanOrEqualTo(tester.getRect(find.byKey(barKey)).bottom - 0.01));
expect(
tester.getRect(spinner).top,
greaterThanOrEqualTo(tester.getRect(find.byKey(barKey)).bottom - 0.01),
);
await tester.pumpAndSettle();
});
}