Skip to content

feat: support multiple syllabuses per course offering#337

Open
rileychh wants to merge 5 commits into
add-multiple-teachersfrom
add-multiple-syllabuses
Open

feat: support multiple syllabuses per course offering#337
rileychh wants to merge 5 commits into
add-multiple-teachersfrom
add-multiple-syllabuses

Conversation

@rileychh

@rileychh rileychh commented Jun 17, 2026

Copy link
Copy Markdown
Member

What

A course offering can have multiple syllabi — one per teacher who submits one. A syllabus's code is the authoring teacher's code, so each syllabus maps to a teacher.

Changes

  • New Syllabuses table holding per-teacher syllabus content (one row per offering + teacher); it replaces the offering's flat syllabus columns, while the shared header fields (course type, enrolled, withdrawn) stay on CourseOfferings.
  • Syllabi are fetched lazily per teacher via a watchSyllabus/refreshSyllabus pair keyed by (offeringId, teacherId): a row is created on the first successful fetch and deleted when the teacher hasn't submitted one; the refresh also writes the offering header fields and the instructor email.
  • getSyllabus returns null when the teacher hasn't submitted a syllabus (the page shows 尚未登錄, distinct from the session-expiry 尚未登錄入口網站).
  • The course offering detail exposes its teachers; the UI fetches each teacher's syllabus on demand (shows the first for now).

Testing

  • flutter analyze clean; generated Drift code regenerated.
  • Course service integration tests pass against real NTUT; verified a team-taught offering (10 teachers) and the 尚未登錄 (no-syllabus) path.

Temporary syllabus UI (debug)

Temporary debug UI in course_table_detail_sheet.dart listing the offering's teachers, each expandable to lazily fetch and dump its syllabus fields (or 查無大綱 when the teacher hasn't submitted one). Marked // TODO: temporary. Patch:

course_table_detail_sheet.dart.patch
diff --git a/lib/screens/main/course_table/course_table_detail_sheet.dart b/lib/screens/main/course_table/course_table_detail_sheet.dart
index fd0fe91..ded7e5e 100644
--- a/lib/screens/main/course_table/course_table_detail_sheet.dart
+++ b/lib/screens/main/course_table/course_table_detail_sheet.dart
@@ -1,6 +1,10 @@
 import 'package:flutter/material.dart';
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'package:tattoo/database/database.dart';
 import 'package:tattoo/i18n/strings.g.dart';
 import 'package:tattoo/repositories/course_repository.dart';
+import 'package:tattoo/screens/main/course_table/course_table_providers.dart';
+import 'package:tattoo/utils/localized.dart';
 
 Future<void> showCourseTableDetailSheet(
   BuildContext context, {
@@ -19,7 +23,7 @@ Future<void> showCourseTableDetailSheet(
   );
 }
 
-class CourseTableDetailSheet extends StatelessWidget {
+class CourseTableDetailSheet extends ConsumerWidget {
   const CourseTableDetailSheet({
     super.key,
     required this.cell,
@@ -28,12 +32,12 @@ class CourseTableDetailSheet extends StatelessWidget {
   final CourseTableCellData cell;
 
   @override
-  Widget build(BuildContext context) {
+  Widget build(BuildContext context, WidgetRef ref) {
     final theme = Theme.of(context);
 
     return SafeArea(
       top: false,
-      child: Padding(
+      child: SingleChildScrollView(
         padding: .fromLTRB(16, 8, 16, 16),
         child: Column(
           mainAxisSize: .min,
@@ -81,6 +85,8 @@ class CourseTableDetailSheet extends StatelessWidget {
                 ),
               ),
             ),
+            // TODO: temporary — replace with proper syllabus UI.
+            _SyllabiSection(offeringId: cell.id),
             SizedBox(height: MediaQuery.viewInsetsOf(context).bottom),
             // TODO: scroll up to show more course query features like classmate, course outline, etc.
           ],
@@ -89,3 +95,123 @@ class CourseTableDetailSheet extends StatelessWidget {
     );
   }
 }
+
+/// Temporary debug UI: lists the offering's teachers, each expandable to fetch
+/// and show that teacher's syllabus via [syllabusProvider].
+class _SyllabiSection extends ConsumerWidget {
+  const _SyllabiSection({required this.offeringId});
+
+  final int offeringId;
+
+  @override
+  Widget build(BuildContext context, WidgetRef ref) {
+    final detail = ref.watch(courseOfferingProvider(offeringId));
+
+    return switch (detail) {
+      AsyncData(:final value?) when value.teachers.isNotEmpty => Column(
+        crossAxisAlignment: .start,
+        spacing: 8,
+        children: [
+          Text(
+            '課程大綱 (${value.teachers.length})',
+            style: Theme.of(context).textTheme.titleMedium,
+          ),
+          for (final teacher in value.teachers)
+            _SyllabusTile(offeringId: offeringId, teacher: teacher),
+        ],
+      ),
+      _ => const SizedBox.shrink(),
+    };
+  }
+}
+
+class _SyllabusTile extends ConsumerWidget {
+  const _SyllabusTile({required this.offeringId, required this.teacher});
+
+  final int offeringId;
+  final ({String code, String nameZh, String? nameEn}) teacher;
+
+  @override
+  Widget build(BuildContext context, WidgetRef ref) {
+    final theme = Theme.of(context);
+    final teacherName = localized(teacher.nameZh, teacher.nameEn);
+
+    return Card(
+      margin: .zero,
+      elevation: 0,
+      shape: RoundedRectangleBorder(
+        borderRadius: .circular(12),
+        side: BorderSide(color: theme.colorScheme.outlineVariant),
+      ),
+      color: theme.colorScheme.surfaceContainer,
+      clipBehavior: .antiAlias,
+      child: ExpansionTile(
+        title: Text(teacherName),
+        subtitle: Text(teacher.code),
+        childrenPadding: .fromLTRB(16, 0, 16, 16),
+        expandedCrossAxisAlignment: .start,
+        children: [
+          _SyllabusContent(offeringId: offeringId, teacherId: teacher.code),
+        ],
+      ),
+    );
+  }
+}
+
+class _SyllabusContent extends ConsumerWidget {
+  const _SyllabusContent({required this.offeringId, required this.teacherId});
+
+  final int offeringId;
+  final String teacherId;
+
+  @override
+  Widget build(BuildContext context, WidgetRef ref) {
+    final syllabus = ref.watch(
+      syllabusProvider((offeringId: offeringId, teacherId: teacherId)),
+    );
+
+    return switch (syllabus) {
+      AsyncData(:final value?) => _SyllabusFields(syllabus: value),
+      AsyncData() => const Text('查無大綱'),
+      AsyncError(:final error) => Text('載入失敗: $error'),
+      _ => const Padding(
+        padding: .all(8),
+        child: Center(child: CircularProgressIndicator()),
+      ),
+    };
+  }
+}
+
+class _SyllabusFields extends StatelessWidget {
+  const _SyllabusFields({required this.syllabus});
+
+  final Syllabus syllabus;
+
+  @override
+  Widget build(BuildContext context) {
+    final fields = <(String, String?)>[
+      ('最後更新', syllabus.updatedAt?.toString()),
+      ('課程大綱', syllabus.objective),
+      ('課程進度', syllabus.weeklyPlan),
+      ('評量方式', syllabus.evaluation),
+      ('使用教材', syllabus.textbooks),
+      ('備註', syllabus.remarks),
+    ];
+
+    return Column(
+      crossAxisAlignment: .start,
+      spacing: 8,
+      children: [
+        for (final (label, value) in fields)
+          if (value != null && value.isNotEmpty)
+            Column(
+              crossAxisAlignment: .start,
+              children: [
+                Text(label, style: Theme.of(context).textTheme.labelLarge),
+                Text(value),
+              ],
+            ),
+      ],
+    );
+  }
+}

@github-actions

github-actions Bot commented Jun 17, 2026

Copy link
Copy Markdown

PR Preview Builds

Build Number: 1148
Commit: 53c9a20
Message: fix: emit refreshed syllabus after blocking fetch, not stale null

@rileychh rileychh force-pushed the add-multiple-syllabuses branch 2 times, most recently from 3c99c67 to 315392e Compare June 17, 2026 10:35
@coderabbitai

coderabbitai Bot commented Jun 17, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Syllabus content fields are extracted from CourseOfferings into a new normalized Syllabuses Drift table keyed by (courseOffering, teacher). ScheduleDto.syllabusId becomes syllabusIds: List<String>?. The service layer adds _parseSyllabusIds parsing and handles nullable syllabus responses. The repository gains dedicated watchSyllabus/refreshSyllabus methods for lazy per-teacher syllabus loading with background refresh. A new syllabusProvider Riverpod provider exposes per-syllabi streaming keyed by offering and teacher identifiers.

Changes

Syllabus Extraction and Multi-ID Refactor

Layer / File(s) Summary
ScheduleDto: syllabusIdsyllabusIds
lib/services/course/course_service.dart
ScheduleDto replaces the single syllabusId field with syllabusIds: List<String>?. CourseService.getSyllabus now returns Future<SyllabusDto?> and documents how the syllabusId parameter maps to teacher code from available identifiers on ScheduleDto.
NtutCourseService: extract multiple syllabus IDs from parsing
lib/services/course/ntut_course_service.dart
NtutCourseService adds _parseSyllabusIds helper to extract all syllabus link codes from the "查詢" cell, forwards syllabusIds list through the merge step, and handles the "尚未登錄" marker by returning null instead of throwing.
MockCourseService: syllabusIds fixtures and nullable syllabus
lib/services/course/mock_course_service.dart
Mock course-table fixtures are updated to use syllabusIds (null, single-element, or multi-element lists) across all semester cases. MockCourseService.getSyllabus returns Future<SyllabusDto?> and includes null-return cases for selected syllabusId inputs.
Service tests: refactor to shared syllabus discovery helper
test/services/course_service_test.dart
Tests introduce a shared firstSyllabus() async helper that scans course fixtures for a submitted syllabus by iterating schedules and teachers. Three getSyllabus tests are updated to fetch via firstSyllabus() instead of duplicating schedule/teacher selection logic.
DB schema: Syllabuses table extraction
lib/database/schema.dart, lib/database/database.dart, lib/database/views.dart
CourseOfferings drops syllabusId, syllabusUpdatedAt, and all content fields. A new Syllabuses table is added with foreign keys to CourseOfferings (cascade delete) and Teachers, a (courseOffering, teacher) uniqueness constraint, and moved content fields. AppDatabase registers the new table. CourseOfferingOverviews view replaces syllabus columns with enrolled/withdrawn/fetchedAt.
DB actions: remove syllabusId from upsert
lib/database/actions.dart
upsertCourseOffering removes its optional syllabusId parameter and its use in both CourseOfferingsCompanion.insert(...) and onConflict: DoUpdate(...) paths, leaving other course-offering fields unchanged.
Repository: watchCourseOffering, watchSyllabus, and refreshSyllabus
lib/repositories/course_repository.dart
watchCourseOffering is rewritten as database-only, sourcing offering overview with schedule/teachers/classes without network fetches. watchSyllabus streams a single Syllabus? row per offering+teacher, triggering refreshSyllabus once on stub rows. refreshSyllabus fetches content from CourseService, upserts Syllabuses, updates CourseOfferings header fields, and writes TeacherSemesters email. refreshCourseOffering is removed.
syllabusProvider Riverpod wiring
lib/screens/main/course_table/course_table_providers.dart
syllabusProvider is added as a StreamProvider.autoDispose.family keyed by (offeringId, teacherId) tuple, delegating to watchSyllabus. courseOfferingProvider documentation is updated to describe DB-composed offering detail and lazy, separately-resolved syllabus fetching.

Sequence Diagram(s)

sequenceDiagram
  participant UI
  participant syllabusProvider
  participant CourseRepository
  participant AppDatabase
  participant CourseService

  UI->>syllabusProvider: watch(offeringId, teacherId)
  syllabusProvider->>CourseRepository: watchSyllabus(offeringId, teacherId)
  CourseRepository->>AppDatabase: stream Syllabuses row

  alt fetchedAt is null (stub row)
    CourseRepository->>CourseService: getSyllabus(courseNumber, teacherId)
    CourseService-->>CourseRepository: SyllabusDto?
    alt SyllabusDto is null
      CourseRepository->>AppDatabase: delete Syllabuses row
    else SyllabusDto provided
      CourseRepository->>AppDatabase: upsert Syllabuses content
      CourseRepository->>AppDatabase: update CourseOfferings headers
      CourseRepository->>AppDatabase: update TeacherSemesters email
    end
  else fetchedAt present
    CourseRepository->>CourseRepository: refreshSyllabus in background
  end

  AppDatabase-->>CourseRepository: Syllabus?
  CourseRepository-->>syllabusProvider: Syllabus?
  syllabusProvider-->>UI: AsyncValue<Syllabus?>
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title 'support multiple syllabuses per course offering' accurately and concisely summarizes the main architectural change—enabling multiple syllabi per offering instead of one.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description check ✅ Passed The PR description clearly describes the changeset: implementing support for multiple syllabi per course offering with a new Syllabuses table, lazy fetching via watchSyllabus/refreshSyllabus, and updated ScheduleDto parsing.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch add-multiple-syllabuses

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
test/services/course_service_test.dart (1)

395-398: ⚡ Quick win

Exercise non-first syllabus IDs in integration tests.

These assertions always use the first ID, so regressions affecting secondary IDs in multi-syllabus offerings can pass undetected. Randomizing (or iterating) the selected ID improves coverage for this PR’s core behavior.

♻️ Proposed update
-        final syllabus = await courseService.getSyllabus(
-          courseNumber: course.number!,
-          syllabusId: course.syllabusIds!.first,
-        );
+        final syllabus = await courseService.getSyllabus(
+          courseNumber: course.number!,
+          syllabusId: course.syllabusIds!.pickRandom(),
+        );

Also applies to: 453-456, 503-506

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/services/course_service_test.dart` around lines 395 - 398, The test
always uses the first syllabus ID from course.syllabusIds!.first when calling
getSyllabus, which means secondary syllabus IDs are never tested and regressions
affecting them can pass undetected. Instead of always selecting the first ID,
randomize or iterate through different indices of the course.syllabusIds list to
exercise multiple syllabus IDs in the assertions. Apply this change to all three
occurrences of the getSyllabus call in the test file (around lines 395-398,
453-456, and 503-506).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@lib/repositories/course_repository.dart`:
- Around line 757-767: After the refreshSyllabus(id) call succeeds, the code
currently yields the old syllabus stub instead of the refreshed data. To fix
this, fetch the updated syllabus from the repository after the successful
refreshSyllabus(id) call within the try block, and then yield that refreshed
syllabus. Only yield the original syllabus stub in the catch block to maintain
the current error-handling behavior. This ensures the UI receives the latest
data after a successful refresh rather than the stale pre-refresh version.

---

Nitpick comments:
In `@test/services/course_service_test.dart`:
- Around line 395-398: The test always uses the first syllabus ID from
course.syllabusIds!.first when calling getSyllabus, which means secondary
syllabus IDs are never tested and regressions affecting them can pass
undetected. Instead of always selecting the first ID, randomize or iterate
through different indices of the course.syllabusIds list to exercise multiple
syllabus IDs in the assertions. Apply this change to all three occurrences of
the getSyllabus call in the test file (around lines 395-398, 453-456, and
503-506).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 1327bad3-f0f1-413a-9464-6887e881b801

📥 Commits

Reviewing files that changed from the base of the PR and between a10c34b and 315392e.

📒 Files selected for processing (11)
  • lib/database/actions.dart
  • lib/database/database.dart
  • lib/database/database.g.dart
  • lib/database/schema.dart
  • lib/database/views.dart
  • lib/repositories/course_repository.dart
  • lib/screens/main/course_table/course_table_providers.dart
  • lib/services/course/course_service.dart
  • lib/services/course/mock_course_service.dart
  • lib/services/course/ntut_course_service.dart
  • test/services/course_service_test.dart
💤 Files with no reviewable changes (2)
  • lib/database/views.dart
  • lib/database/actions.dart

Comment thread lib/repositories/course_repository.dart Outdated
rileychh added 3 commits June 17, 2026 18:56
A course offering can have multiple syllabi — one per teacher who submits
one. Each 查詢 link in the course list carries the authoring teacher's code,
so a syllabus maps to a teacher (built on multiple-teacher support).

Add a Syllabuses table (one row per offering+teacher) holding the per-teacher
syllabus content, and move the shared header fields off the flat
CourseOfferings columns. refreshCourseTable records which syllabi are
available as stub rows; their content is fetched lazily and per-teacher via a
new watchSyllabus/refreshSyllabus pair (which also writes the offering-level
header fields and the instructor email). The course offering detail exposes
the available syllabi; the UI shows the first for now.
Pick a random syllabus id from syllabusIds instead of always the first,
so secondary syllabus ids are exercised and regressions affecting them
are caught.
The stub branch of watchSyllabus awaited refreshSyllabus then yielded
the empty stub row, flashing empty content until the DB write's re-emit
delivered fresh data — defeating the branch's stated purpose. Re-read
the row after a successful refresh and yield that; yield the stub only
on failure so the UI still exits its loading state.
@rileychh rileychh force-pushed the add-multiple-syllabuses branch from 315392e to 57ee454 Compare June 17, 2026 12:28

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
lib/database/schema.dart (1)

499-545: 💤 Low value

Consider adding an index on courseOffering for query consistency and performance.

Other tables with a courseOffering foreign key (Schedules, Materials) declare a @TableIndex on that column. Adding one here would maintain consistency and improve lookup performance when fetching syllabi for an offering.

♻️ Suggested change
 /// A teacher-authored syllabus for a course offering (教學大綱與進度).
 ...
 // Without `@DataClassName`, Drift names the row class 'Syllabuse'.
 `@DataClassName`('Syllabus')
+@TableIndex(name: 'syllabus_course_offering', columns: {`#courseOffering`})
 class Syllabuses extends Table with AutoIncrementId, Fetchable {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/database/schema.dart` around lines 499 - 545, The Syllabuses table is
missing a `@TableIndex` annotation on its courseOffering column, whereas other
tables with courseOffering foreign keys like Schedules and Materials declare
this index. Add a `@TableIndex` annotation on the courseOffering field in the
Syllabuses class to maintain consistency with other tables and improve query
performance when fetching syllabi for a specific course offering.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@lib/database/schema.dart`:
- Around line 499-545: The Syllabuses table is missing a `@TableIndex` annotation
on its courseOffering column, whereas other tables with courseOffering foreign
keys like Schedules and Materials declare this index. Add a `@TableIndex`
annotation on the courseOffering field in the Syllabuses class to maintain
consistency with other tables and improve query performance when fetching
syllabi for a specific course offering.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 66c33263-1a70-4f29-a845-3411afaa33ce

📥 Commits

Reviewing files that changed from the base of the PR and between 315392e and 57ee454.

📒 Files selected for processing (11)
  • lib/database/actions.dart
  • lib/database/database.dart
  • lib/database/database.g.dart
  • lib/database/schema.dart
  • lib/database/views.dart
  • lib/repositories/course_repository.dart
  • lib/screens/main/course_table/course_table_providers.dart
  • lib/services/course/course_service.dart
  • lib/services/course/mock_course_service.dart
  • lib/services/course/ntut_course_service.dart
  • test/services/course_service_test.dart
💤 Files with no reviewable changes (2)
  • lib/database/views.dart
  • lib/database/actions.dart
🚧 Files skipped from review as they are similar to previous changes (7)
  • lib/database/database.dart
  • lib/services/course/course_service.dart
  • lib/screens/main/course_table/course_table_providers.dart
  • test/services/course_service_test.dart
  • lib/repositories/course_repository.dart
  • lib/services/course/mock_course_service.dart
  • lib/services/course/ntut_course_service.dart

@rileychh rileychh marked this pull request as ready for review June 17, 2026 14:06
@Dao-you Dao-you requested a review from Copilot June 17, 2026 15:07

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds support for multiple syllabi per course offering (one per teacher) by normalizing syllabus content into a new Syllabuses table and fetching syllabus content lazily, while keeping shared “header” fields on CourseOfferings. This fits the app’s existing pattern of caching parsed NTUT data in Drift and exposing reactive watchX() streams to the UI.

Changes:

  • Replace single ScheduleDto.syllabusId with syllabusIds and parse all 查詢 links from the course list.
  • Add Syllabuses table and repository flows to reconcile per-offering syllabus stubs and lazily watchSyllabus/refreshSyllabus.
  • Update course service integration tests and mock data to reflect multiple available syllabi.

Reviewed changes

Copilot reviewed 10 out of 11 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
test/services/course_service_test.dart Adjusts integration tests to use syllabusIds and pick an available syllabus ID.
lib/services/course/course_service.dart Updates DTO contract and getSyllabus docs to describe teacher-code-based syllabus IDs.
lib/services/course/ntut_course_service.dart Parses multiple syllabus IDs from the 查詢 column and plumbs them into DTOs.
lib/services/course/mock_course_service.dart Updates mock course table data to provide syllabusIds lists.
lib/database/schema.dart Removes offering-level flat syllabus content columns; adds new Syllabuses table keyed by offering+teacher.
lib/database/database.dart Registers the new Syllabuses table with the Drift database.
lib/database/views.dart Removes syllabus content fields from CourseOfferingOverviews view selection.
lib/database/actions.dart Updates course offering upsert helper to no longer accept/store a single syllabusId.
lib/repositories/course_repository.dart Adds offering-level syllabi composition, stub reconciliation, and lazy watchSyllabus/refreshSyllabus.
lib/screens/main/course_table/course_table_providers.dart Adds syllabusProvider and updates offering provider docs to reflect DB-composed detail + lazy syllabus fetch.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread lib/repositories/course_repository.dart Outdated
Comment thread lib/repositories/course_repository.dart Outdated
A syllabus's code is its author's teacher code, so any teacher may have one.
Stop enumerating syllabusIds; key a syllabus by (offering, teacher) and fetch
it lazily per teacher.

- getSyllabus returns null on 尚未登錄 (red marker, distinct from the session
  尚未登錄入口網站).
- watch/refreshSyllabus take (offeringId, teacherId); rows are upserted on a
  successful fetch, deleted on 尚未登錄, no stubs pre-created.
- Drop OfferingSyllabus/CourseOfferingDetail.syllabi and unused
  Syllabuses.fetchedAt; the UI drives syllabi off the offering's teachers.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@lib/repositories/course_repository.dart`:
- Around line 696-705: After the `refreshSyllabus` call succeeds (within the try
block), re-fetch the syllabus value from the database before yielding it instead
of yielding the stale `null` value. This ensures that after a successful
refresh, the code queries the updated data from the database and emits that
fresh value, preventing the UI from seeing a stale null state before the
database stream re-emits.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 8d959898-ec95-49bf-9a3a-4ef1f7b70f9f

📥 Commits

Reviewing files that changed from the base of the PR and between 57ee454 and 497ec05.

📒 Files selected for processing (8)
  • lib/database/database.g.dart
  • lib/database/schema.dart
  • lib/repositories/course_repository.dart
  • lib/screens/main/course_table/course_table_providers.dart
  • lib/services/course/course_service.dart
  • lib/services/course/mock_course_service.dart
  • lib/services/course/ntut_course_service.dart
  • test/services/course_service_test.dart
🚧 Files skipped from review as they are similar to previous changes (1)
  • lib/screens/main/course_table/course_table_providers.dart

Comment thread lib/repositories/course_repository.dart
watchSyllabus re-queries after a successful first fetch so the UI gets the
inserted row instead of the stale null snapshot before the stream re-emits.
Also reword the doc — it shows the cached row first, so drop the
contradictory "rather than caching".
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants