dueTodayItems function

  1. @riverpod
Future<List<DueTodayItem>> dueTodayItems(
  1. Ref<Object?> ref
)

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).

Implementation

@riverpod
Future<List<DueTodayItem>> dueTodayItems(Ref ref) async {
  final cards = await ref.watch(dueCardsProvider.future);
  if (cards.isEmpty) return const [];
  final ds = ref.watch(supabaseDatasourceProvider);
  // De-dupe topic IDs — multiple cards can share a topic — and batch the
  // fetch so we make one HTTP round-trip regardless of due-list size.
  final topicIds = cards.map((c) => c.topicId).toSet();
  final fetched = await ds.getTopicsByIds(topicIds);
  final topics = {for (final t in fetched) t.id: t};
  // Map domainId → (slug, name) once.
  final domainIds = topics.values
      .map((t) => t.domainId)
      .whereType<String>()
      .toSet();
  final allDomains = await ds.getMyDomains();
  final domainsById = {for (final d in allDomains) d.id: d};
  final missing = domainIds
      .where((id) => !domainsById.containsKey(id))
      .toList();
  if (missing.isNotEmpty) {
    final extra = await ds.getDomainsByIds(missing.toSet());
    for (final d in extra) {
      domainsById[d.id] = d;
    }
  }

  final out = <DueTodayItem>[];
  for (final c in cards) {
    final t = topics[c.topicId];
    if (t == null) continue;
    final domain = domainsById[t.domainId];
    out.add(
      DueTodayItem(
        cardId: c.id,
        topicId: c.topicId,
        topicTitle: t.title,
        domainSlug: domain?.slug ?? '',
        domainName: domain?.name ?? '',
      ),
    );
  }
  return out;
}