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>
101 lines
3.7 KiB
Dart
101 lines
3.7 KiB
Dart
/// ─────────────────────────────────────────────────────────────────────────
|
|
/// WHAT A SCREEN IS SHOWING, AS ONE VALUE
|
|
///
|
|
/// Screens carried three or four independent booleans — `_firstLoadDone`,
|
|
/// `_fetching`, `_failed`, plus a list that might be empty — and every screen
|
|
/// combined them slightly differently. That is how a page ends up showing an
|
|
/// empty state during a refresh, or a spinner over stale data, or nothing at
|
|
/// all when a request fails.
|
|
///
|
|
/// One value, and the combinations that cannot happen are unrepresentable.
|
|
///
|
|
/// ── Empty is not a failure, and unavailable is neither ──
|
|
///
|
|
/// Three answers a rider acts on differently, which were all `[]` before:
|
|
///
|
|
/// * **[LoadEmpty]** — the hub has given him nothing today. Wait.
|
|
/// * **[LoadFailure]** — the request did not complete. Retry.
|
|
/// * **[LoadUnavailable]** — his line of work has no endpoint behind it. Neither
|
|
/// waiting nor retrying will help, and telling him to do either is a lie.
|
|
/// See `ServiceProfile.hasBookingsEndpoint`.
|
|
/// ─────────────────────────────────────────────────────────────────────────
|
|
library;
|
|
|
|
/// Why a load did not produce data. Kept separate from the HTTP status because
|
|
/// the rider's next action is what differs, not the number.
|
|
enum LoadFailureKind {
|
|
/// No usable connection, a timeout, or a socket that died mid-flight.
|
|
offline,
|
|
|
|
/// The session is over. The shell signs the rider out; the screen should not
|
|
/// offer a retry that cannot succeed.
|
|
unauthorized,
|
|
|
|
/// Too many requests. Retrying immediately makes it worse.
|
|
rateLimited,
|
|
|
|
/// The server answered and refused, or answered with something unreadable.
|
|
server;
|
|
|
|
/// Whether offering "Try again" is honest.
|
|
bool get isRetryable => this != LoadFailureKind.unauthorized;
|
|
}
|
|
|
|
/// The state of one screen's data.
|
|
sealed class LoadState<T> {
|
|
const LoadState();
|
|
|
|
/// The data, when there is any. Null in every other state — so a screen that
|
|
/// forgets to handle a case renders nothing rather than stale content.
|
|
T? get valueOrNull => switch (this) {
|
|
LoadData<T>(:final value) => value,
|
|
_ => null,
|
|
};
|
|
|
|
bool get isLoading => this is LoadLoading<T>;
|
|
}
|
|
|
|
/// First load, or a refresh with nothing to show yet.
|
|
class LoadLoading<T> extends LoadState<T> {
|
|
const LoadLoading();
|
|
}
|
|
|
|
/// Data arrived and there is something in it.
|
|
class LoadData<T> extends LoadState<T> {
|
|
final T value;
|
|
|
|
/// True while a refresh is running behind data that is already on screen.
|
|
///
|
|
/// The distinction the old booleans lost: a pull-to-refresh must not blank
|
|
/// the list it is refreshing.
|
|
final bool refreshing;
|
|
|
|
const LoadData(this.value, {this.refreshing = false});
|
|
}
|
|
|
|
/// The request succeeded and the answer was "nothing today".
|
|
class LoadEmpty<T> extends LoadState<T> {
|
|
const LoadEmpty();
|
|
}
|
|
|
|
/// The request did not complete.
|
|
class LoadFailure<T> extends LoadState<T> {
|
|
final LoadFailureKind kind;
|
|
|
|
/// The server's own sentence where it gave one — it is more useful than
|
|
/// anything this app can invent about a failure it did not cause.
|
|
final String message;
|
|
|
|
const LoadFailure(this.kind, {this.message = ''});
|
|
}
|
|
|
|
/// There is no backend for this rider's line of work.
|
|
///
|
|
/// Not an error and not an empty day: a capability the deployment does not have
|
|
/// yet. Retrying cannot fix it and neither can waiting.
|
|
class LoadUnavailable<T> extends LoadState<T> {
|
|
final String reason;
|
|
|
|
const LoadUnavailable(this.reason);
|
|
}
|