Files
doormile_milderapp/lib/data/load_state.dart
Thiru-tenext d7348e253f 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>
2026-08-22 05:40:35 +05:30

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);
}