Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-03-22
41 changes: 41 additions & 0 deletions openspec/changes/migrate-course-card-duration-to-content/design.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
## Context

The `CourseCard` currently displays `totalDuration` (e.g., "180 hrs") as part of its metadata. The Testpress API version 3 provides a more accurate metric, `total_contents`, which counts all learning items including lessons, tests, and assessments. This design outlines the migration to show this new metric.

## Goals / Non-Goals

**Goals:**
- Update the domain model and remote parser to include `totalContents`.
- Persist `totalContents` in the local Drift database.
- Update the UI to display "X contents" instead of "Y hrs".
- Ensure backward compatibility and smooth database migration.

**Non-Goals:**
- Removing `totalDuration` entirely from the codebase in this PR (marked as deprecated).
- Changing the layout of the `CourseCard` beyond the metadata text.

## Decisions

### 1. Data Model Update
Add `totalContents` as an `int` to `CourseDto`.
- Mapping: `total_contents` (API) -> `totalContents` (DTO).
- Deprecation: Mark `totalDuration` as `@Deprecated` to signal its replacement.

### 2. Database Schema (v8)
Add an `IntColumn` for `totalContents` in `CoursesTable`.
- Rationale: Storing it as an integer allows for future sorting/filtering by content volume.
- Migration: Use `m.addColumn(coursesTable, coursesTable.totalContents)` in `AppDatabase.onUpgrade`.

### 3. Localization
Add `labelContentsPlural` to `app_en.arb` (and other languages) to handle the "contents" suffix.
- Value: `"contents"` (English).

### 4. Mock Data
Update `MockDataSource` to provide `totalContents` values. For existing mock data, we will use a base value (e.g., 100) or calculate it if feasible.

## Risks / Trade-offs

- **[Risk]** Missing `total_contents` in some API responses.
- **Mitigation**: Use a default value of 0 in `RemoteCourseDto.fromJson` and the database column.
- **[Risk]** Database migration overhead.
- **Mitigation**: `addColumn` is an O(1) operation in SQLite and won't block the UI significantly.
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
## Why

The current `CourseCard` displays `totalDuration` (e.g., "180 hrs"), which is often an approximation and less informative than the actual count of learning items. To provide students with a better sense of course volume, we are migrating to `totalContents`. This provides a more granular metric that includes lessons, assessments, and tests, aligning with the Testpress API standard.

## What Changes

- **Core Data Model**: Update `CourseDto` to include a `totalContents` field.
- **API Mapping**: Update `RemoteCourseDto` to parse `total_contents` from the Testpress API and map it to `CourseDto`.
- **Local Persistence**: Add a `total_contents` column to `CoursesTable` in Drift and implement a non-destructive migration in `AppDatabase`.
- **Mock Data**: Update `MockDataSource` to provide realistic `totalContents` values for development.
- **UI Components**: Update `CourseCard` in the `courses` package to display the content count (e.g., "120 contents") instead of the duration string.

## Capabilities

### New Capabilities
- None.

### Modified Capabilities
- `study-curriculum-list`: The metadata displayed for each course in the curriculum list is updated to show the total number of contents instead of the total duration.

## Impact

- **Drift DB**: Requires a schema migration (v7 -> v8).
- **Domain Models**: `CourseDto` modification affects any consumers of this DTO.
- **UI**: Direct change to `CourseCard`'s metadata row.
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
## ADDED Requirements

### Requirement: Course Content Count Visibility
The system SHALL display the total number of contents (lessons, tests, etc.) for each course in the curriculum list. This metric replaces the total duration string to provide a more granular view of the course volume.

#### Scenario: Displaying content count in CourseCard
- **WHEN** a course has 120 total contents
- **THEN** the course card shows "120 contents" in its metadata section instead of "X hrs"
13 changes: 13 additions & 0 deletions openspec/changes/migrate-course-card-duration-to-content/tasks.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
## 1. Data Layer (Core Package)

- [x] 1.1 Update `CourseDto` to include the `totalContents` (int) field and deprecate `totalDuration`.
- [x] 1.2 Update `RemoteCourseDto` to map the API field `total_contents` to `totalContents`.
- [x] 1.3 Add `totalContents` column to `CoursesTable` in the Drift database schema.
- [x] 1.4 Increment `AppDatabase` schema version and implement the non-destructive migration in `onUpgrade`.
- [x] 1.5 Run `build_runner` to regenerate Drift database code.
- [x] 1.6 Update `MockDataSource` to provide `totalContents` for development environments.

## 2. UI & Localization (Domain Package)

- [x] 2.1 Add `labelContentsPlural` to localization files (`app_en.arb`, etc.).
- [x] 2.2 Update `CourseCard` to display `totalContents` from `CourseDto` instead of `totalDuration`.
18 changes: 12 additions & 6 deletions packages/core/lib/data/db/app_database.dart
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ class AppDatabase extends _$AppDatabase {
AppDatabase([QueryExecutor? executor]) : super(executor ?? _openConnection());

@override
int get schemaVersion => 7;
int get schemaVersion => 8;

@override
MigrationStrategy get migration => MigrationStrategy(
Expand Down Expand Up @@ -81,11 +81,17 @@ class AppDatabase extends _$AppDatabase {
}
}

if (from < 7) {
await m.addColumn(coursesTable, coursesTable.image);
}
},
);
if (from < 7) {
await m.addColumn(coursesTable, coursesTable.image);
}

if (from < 8) {
await m.addColumn(coursesTable, coursesTable.totalContents);
// Fill existing rows with lessons count as a fallback until they refresh.
await customStatement('UPDATE courses_table SET total_contents = total_lessons');
}
},
);

// ── App Settings ─────────────────────────────────────────────────────────

Expand Down
Loading