Minimal value object for the Home "Due today" inline list. Joins a
review card with its topic title + domain slug + human domain name
so DueTodayCard can render without further lookups.
Tone applied to a milestone pill — picked by mastery transition or
streak event. forest for mastery/evaluation jumps, ochre for streak
or retention milestones, plum for evaluation entries.
Set of domain IDs that have at least one topic with content. Powers the
goal-creation domain filter and the "no topics available" warning on
goal cards whose linked domain has no studyable content.
Joined view of due cards with their topic + domain metadata. Keeps the
join in one place so the Home screen widget tree stays presentational.
Topics are batch-fetched in a single round-trip (was N+1 by topic).
On-device LLM inference (llama.cpp via llama_cpp_dart). null on web
— there's no isolate / dart:io / GGUF runtime in the browser. Also
null when the feature flag EnvConfig.enableLocalLlm is off, which
hides the provider from users while keeping the implementation in the
tree. On native with the flag on, isConfigured() returns false until
the user downloads + activates a model in AI settings, so chat sessions
cleanly skip the local provider when nothing is installed.
Domains the user has personal access to: owned + shared + assigned
(via curriculum_assignments). Does NOT include public-visibility domains
the user hasn't engaged with. Used by progress-focused screens (home
card, progress dashboard, domain browse) so "no curriculum" means
"no domains shown".
Append-only queue of pending Supabase writes that the device couldn't
flush online. null on web (no sqlite3 / dart:io). Initialized
lazily — callers must await initialize() before use; eager-init at
app startup is a future polish (see riverpod-eager-initialization).
Helper that funnels Supabase writes through the OfflineOutbox. On
web (no outbox), it falls through to direct Supabase calls. Screens
and notifiers can write through this without re-implementing the
enqueue / call / mark-synced dance per call site.
Up to 6 most recent advancement events derived from allProgressProvider.
Heuristic: pick the latest progress rows with non-unstarted mastery,
sorted by lastStudiedAt (or updatedAt) descending. Topic titles are
resolved on the fly via getTopicById. If a streak ≥ 10 falls inside the
window it's added as an extra ochre milestone at the head.
Root SupabaseDatasource singleton. Every screen/provider that reads
Supabase data watches this provider — it's the one-and-only instance
wired to Supabase.instance.client. Kept alive for the app lifetime.
Supabase-backed source of truth for the user's AI provider settings.
Synced across devices once the user accepts the cloud-sync disclaimer
(see AiSyncConsentDialog). FlutterSecureStorage caches the same
values locally for offline use.
26-week × 7-day activity grid (182 buckets, oldest first). Each entry is
a 0..4 intensity bucket derived from session count for that calendar day.
The grid is laid out column-major: index = weekIndex * 7 + dayOfWeek,
with dayOfWeek == 0 Monday and weekIndex == 25 the current week.
Set of domain IDs that have at least one topic with content. Powers the
goal-creation domain filter and the "no topics available" warning on
goal cards whose linked domain has no studyable content.
Joined view of due cards with their topic + domain metadata. Keeps the
join in one place so the Home screen widget tree stays presentational.
Topics are batch-fetched in a single round-trip (was N+1 by topic).
Read the user's credential for the given provider.
API key for Claude, OAuth access token for OpenAI/Gemini, active model
ID for local (functions as the "credential" the proxy/datasource needs
to load the correct GGUF — though local never goes through the proxy).
On-device LLM inference (llama.cpp via llama_cpp_dart). null on web
— there's no isolate / dart:io / GGUF runtime in the browser. Also
null when the feature flag EnvConfig.enableLocalLlm is off, which
hides the provider from users while keeping the implementation in the
tree. On native with the flag on, isConfigured() returns false until
the user downloads + activates a model in AI settings, so chat sessions
cleanly skip the local provider when nothing is installed.
Domains the user has personal access to: owned + shared + assigned
(via curriculum_assignments). Does NOT include public-visibility domains
the user hasn't engaged with. Used by progress-focused screens (home
card, progress dashboard, domain browse) so "no curriculum" means
"no domains shown".
Append-only queue of pending Supabase writes that the device couldn't
flush online. null on web (no sqlite3 / dart:io). Initialized
lazily — callers must await initialize() before use; eager-init at
app startup is a future polish (see riverpod-eager-initialization).
Helper that funnels Supabase writes through the OfflineOutbox. On
web (no outbox), it falls through to direct Supabase calls. Screens
and notifiers can write through this without re-implementing the
enqueue / call / mark-synced dance per call site.
Up to 6 most recent advancement events derived from allProgressProvider.
Heuristic: pick the latest progress rows with non-unstarted mastery,
sorted by lastStudiedAt (or updatedAt) descending. Topic titles are
resolved on the fly via getTopicById. If a streak ≥ 10 falls inside the
window it's added as an extra ochre milestone at the head.
Root SupabaseDatasource singleton. Every screen/provider that reads
Supabase data watches this provider — it's the one-and-only instance
wired to Supabase.instance.client. Kept alive for the app lifetime.
Supabase-backed source of truth for the user's AI provider settings.
Synced across devices once the user accepts the cloud-sync disclaimer
(see AiSyncConsentDialog). FlutterSecureStorage caches the same
values locally for offline use.
26-week × 7-day activity grid (182 buckets, oldest first). Each entry is
a 0..4 intensity bucket derived from session count for that calendar day.
The grid is laid out column-major: index = weekIndex * 7 + dayOfWeek,
with dayOfWeek == 0 Monday and weekIndex == 25 the current week.