feat: Phase 10g — read-only doujin detail page #33

Merged
bjoern merged 9 commits from feat/flutter-detail into main 2026-06-30 17:18:12 +02:00
Member

Phase 10g: Doujin Detail (Read-Only)

Two-column desktop layout for viewing full doujin metadata. Read-only — no editing, no upload.

Layout

  • Left column (40%): Primary title (displayMedium), secondary titles, StarRating display (half-star, from backend rating), reading direction (icon + text), description, tag chips (namespace-colored), people grouped by role ("Artist: Name1, Name2"), circles
  • Right column (60%): Variant cards — language, kind, displayName, page/chapter counts, default badge. "No variants" message when empty.

State

  • DetailState (isLoading, error, doujin) added to AppState
  • LoadDoujinDetailAction, DoujinDetailLoadedAction, ClearDetailAction, DetailErrorAction
  • _loadDetailEpic — GET /api/doujins/{id} via DoujinRepository.getDoujin
  • Dispatches Load on init, Clear on dispose (store reference cached to avoid deactivated-widget lookups)

Verification

  • flutter analyze: 0 issues ✅
  • flutter test: 180/180 passed (15 new + 165 existing) ✅
## Phase 10g: Doujin Detail (Read-Only) Two-column desktop layout for viewing full doujin metadata. Read-only — no editing, no upload. ### Layout - **Left column (40%)**: Primary title (displayMedium), secondary titles, StarRating display (half-star, from backend rating), reading direction (icon + text), description, tag chips (namespace-colored), people grouped by role ("Artist: Name1, Name2"), circles - **Right column (60%)**: Variant cards — language, kind, displayName, page/chapter counts, default badge. "No variants" message when empty. ### State - `DetailState` (isLoading, error, doujin) added to AppState - `LoadDoujinDetailAction`, `DoujinDetailLoadedAction`, `ClearDetailAction`, `DetailErrorAction` - `_loadDetailEpic` — `GET /api/doujins/{id}` via `DoujinRepository.getDoujin` - Dispatches Load on init, Clear on dispose (store reference cached to avoid deactivated-widget lookups) ### Verification - `flutter analyze`: **0 issues** ✅ - `flutter test`: **180/180 passed** (15 new + 165 existing) ✅
feat: Phase 10g — read-only doujin detail page
All checks were successful
Flutter CI / analyze-and-test (pull_request) Successful in 54s
0f372279fb
Two-column desktop layout for viewing doujin metadata:
- Left (40%): titles, StarRating display, reading direction,
  description, tag chips, people grouped by role, circles
- Right (60%): variant cards (language, kind, page/chapter counts,
  default badge)

State:
- DetailState (isLoading, error, doujin) in AppState
- LoadDoujinDetailAction/DoujinDetailLoadedAction/ClearDetailAction
  /DetailErrorAction
- _loadDetailEpic: GET /api/doujins/{id} via DoujinRepository.getDoujin
- Reducers for all detail actions

Navigation:
- Dispatches LoadDoujinDetailAction on init, ClearDetailAction on dispose
- Store reference cached in didChangeDependencies to avoid deactivated
  widget lookups in dispose

15 new tests (4 reducer, 9 widget, 2 epic), 180 total pass.
flutter analyze: 0 issues.

Flutter Coverage

File Line coverage
lib/app/store.dart 100.0% (10 of 10)
lib/data/models/doujin_models.dart 95.0% (19 of 20)
lib/data/models/doujin_models.g.dart 50.8% (101 of 199)
lib/data/models/envelope.dart 81.2% (13 of 16)
lib/data/models/envelope.g.dart 50.7% (34 of 67)
lib/data/models/search_query.dart 50.0% (2 of 4)
lib/data/models/search_query.g.dart 36.2% (17 of 47)
lib/domain/entities/stored_settings.dart 100.0% (6 of 6)
lib/presentation/pages/detail/detail_page.dart 92.7% (202 of 218)
lib/presentation/state/app_state.dart 100.0% (4 of 4)
lib/presentation/state/reducers.dart 100.0% (5 of 5)
lib/presentation/middleware/epics.dart 87.9% (109 of 124)
lib/core/languages.dart 100.0% (2 of 2)
lib/domain/entities/filter_token.dart 96.6% (56 of 58)
lib/presentation/widgets/star_rating.dart 100.0% (70 of 70)
lib/presentation/state/actions/detail_actions.dart 100.0% (4 of 4)
lib/presentation/state/actions/library_actions.dart 33.3% (6 of 18)
lib/presentation/state/actions/metadata_actions.dart 66.7% (2 of 3)
lib/presentation/state/actions/settings_actions.dart 100.0% (5 of 5)
lib/presentation/state/reducers/detail_reducer.dart 100.0% (11 of 11)
lib/presentation/state/reducers/library_reducer.dart 100.0% (94 of 94)
lib/presentation/state/reducers/metadata_reducer.dart 100.0% (13 of 13)
lib/presentation/state/reducers/settings_reducer.dart 100.0% (25 of 25)
lib/core/theme.dart 96.9% (31 of 32)
lib/data/api_client.dart 88.9% (8 of 9)
lib/data/repositories/health_repository.dart 69.2% (18 of 26)
lib/app/app.dart 88.0% (22 of 25)
lib/presentation/pages/settings/settings_page.dart 100.0% (86 of 86)
lib/presentation/layout/main_layout.dart 90.5% (19 of 21)
lib/presentation/pages/library/library_page.dart 79.5% (116 of 146)
lib/presentation/pages/upload/upload_page.dart 11.1% (1 of 9)
lib/presentation/widgets/smart_filter_bar.dart 61.9% (99 of 160)
lib/data/repositories/doujin_api_repository.dart 53.2% (25 of 47)

Total: 78.0% (1235 of 1584)

<!-- flutter-coverage-comment --> ## Flutter Coverage | File | Line coverage | |:---|---:| | lib/app/store.dart | 100.0% (10 of 10) | | lib/data/models/doujin_models.dart | 95.0% (19 of 20) | | lib/data/models/doujin_models.g.dart | 50.8% (101 of 199) | | lib/data/models/envelope.dart | 81.2% (13 of 16) | | lib/data/models/envelope.g.dart | 50.7% (34 of 67) | | lib/data/models/search_query.dart | 50.0% (2 of 4) | | lib/data/models/search_query.g.dart | 36.2% (17 of 47) | | lib/domain/entities/stored_settings.dart | 100.0% (6 of 6) | | lib/presentation/pages/detail/detail_page.dart | 92.7% (202 of 218) | | lib/presentation/state/app_state.dart | 100.0% (4 of 4) | | lib/presentation/state/reducers.dart | 100.0% (5 of 5) | | lib/presentation/middleware/epics.dart | 87.9% (109 of 124) | | lib/core/languages.dart | 100.0% (2 of 2) | | lib/domain/entities/filter_token.dart | 96.6% (56 of 58) | | lib/presentation/widgets/star_rating.dart | 100.0% (70 of 70) | | lib/presentation/state/actions/detail_actions.dart | 100.0% (4 of 4) | | lib/presentation/state/actions/library_actions.dart | 33.3% (6 of 18) | | lib/presentation/state/actions/metadata_actions.dart | 66.7% (2 of 3) | | lib/presentation/state/actions/settings_actions.dart | 100.0% (5 of 5) | | lib/presentation/state/reducers/detail_reducer.dart | 100.0% (11 of 11) | | lib/presentation/state/reducers/library_reducer.dart | 100.0% (94 of 94) | | lib/presentation/state/reducers/metadata_reducer.dart | 100.0% (13 of 13) | | lib/presentation/state/reducers/settings_reducer.dart | 100.0% (25 of 25) | | lib/core/theme.dart | 96.9% (31 of 32) | | lib/data/api_client.dart | 88.9% (8 of 9) | | lib/data/repositories/health_repository.dart | 69.2% (18 of 26) | | lib/app/app.dart | 88.0% (22 of 25) | | lib/presentation/pages/settings/settings_page.dart | 100.0% (86 of 86) | | lib/presentation/layout/main_layout.dart | 90.5% (19 of 21) | | lib/presentation/pages/library/library_page.dart | 79.5% (116 of 146) | | lib/presentation/pages/upload/upload_page.dart | 11.1% (1 of 9) | | lib/presentation/widgets/smart_filter_bar.dart | 61.9% (99 of 160) | | lib/data/repositories/doujin_api_repository.dart | 53.2% (25 of 47) | **Total: 78.0% (1235 of 1584)**
chore: restore test data injection script
All checks were successful
Flutter CI / analyze-and-test (pull_request) Successful in 53s
554e671b66
Quick-and-dirty Python script to inject test doujin data via the REST API.
Edit IMAGE_FOLDER, SERVER_URL, and AUTH_TOKEN at the top, then run.
Author
Member

🤖 Hermes automated review: minor comments

Reviewed the full diff (+1357/-22, 7 files, single commit 0f37227 on feat/flutter-detail → main). Read-only detail page wiring into the existing Redux store (actions → reducers → epic → DetailState), the two-column Flutter layout, and the 15 new tests.

No blocking issues found. State management, error handling, and lifecycle (store-reference caching + ClearDetailAction on dispose) are all correct. Three minor non-blocking notes:

  1. secondaryTitles() is value-based, not identity-based — app/lib/presentation/pages/detail/detail_page.dart (secondaryTitles helper). It drops the first title whose value string equals the primary title. If two titles of different TitleKinds share identical text (e.g. an original and a translated title that happen to be the same string), only the first is dropped and the duplicate text would appear a second time in the secondary list. Narrow edge case; no data loss. Consider matching by kind+id, or deduplicating by value, if exact title dedup matters.

  2. No retry on the error view — _DetailErrorView (detail_page.dart). For a read-only page a transient network error (the epic dispatches DetailErrorAction and the store slice holds the message) leaves the user on a static error screen with no "retry" affordance — recovery requires navigating away and back so didChangeDependencies re-fires LoadDoujinDetailAction. Clearly intentional for this phase; worth a retry button or a pull-to-refresh when the page gains interactivity.

  3. Epic test mirrors production code instead of importing it — app/test/detail_page_test.dart (loadDetailEpicForTest). The test re-declares a copy of _loadDetailEpic's body rather than exercising the registered production epic (contrast with buildSearchQueryForTest, which is exported from epics.dart for exactly this purpose). If the production epic drifts from the mirror, the test would still pass. Minor; consider exporting _loadDetailEpic for test reuse.

Verification

  • CI/CD: forgejo-actions Flutter coverage comment #536 is current for head 0f372279 (posted 14:38:14 +02:00, ~1 min after PR open; flutter-ci.yml posts coverage only on successful flutter analyze + flutter test). New file coverage: detail_page.dart 92.7% (202/218), reducers.dart 100%, star_rating.dart 100%, epics.dart 87.9%. Local build/test skipped per CI-evidence policy.
  • Static security scan: clean — no secrets, shell injection, eval/exec, pickle, or SQL injection in added lines. (Note: action.authToken in epics.dart is a runtime value, not a hardcoded secret.)
  • **mergeable: true**, merge_base` equals base sha (clean, no drift).

Automated daily review. I never merge PRs.

## 🤖 Hermes automated review: minor comments Reviewed the full diff (+1357/-22, 7 files, single commit `0f37227` on `feat/flutter-detail` → `main`). Read-only detail page wiring into the existing Redux store (actions → reducers → epic → `DetailState`), the two-column Flutter layout, and the 15 new tests. **No blocking issues found.** State management, error handling, and lifecycle (store-reference caching + `ClearDetailAction` on dispose) are all correct. Three minor non-blocking notes: 1. **`secondaryTitles()` is value-based, not identity-based** — `app/lib/presentation/pages/detail/detail_page.dart` (`secondaryTitles` helper). It drops the *first* title whose `value` string equals the primary title. If two titles of different `TitleKind`s share identical text (e.g. an original and a translated title that happen to be the same string), only the first is dropped and the duplicate text would appear a second time in the secondary list. Narrow edge case; no data loss. Consider matching by kind+id, or deduplicating by value, if exact title dedup matters. 2. **No retry on the error view** — `_DetailErrorView` (`detail_page.dart`). For a read-only page a transient network error (the epic dispatches `DetailErrorAction` and the store slice holds the message) leaves the user on a static error screen with no "retry" affordance — recovery requires navigating away and back so `didChangeDependencies` re-fires `LoadDoujinDetailAction`. Clearly intentional for this phase; worth a retry button or a pull-to-refresh when the page gains interactivity. 3. **Epic test mirrors production code instead of importing it** — `app/test/detail_page_test.dart` (`loadDetailEpicForTest`). The test re-declares a copy of `_loadDetailEpic`'s body rather than exercising the registered production epic (contrast with `buildSearchQueryForTest`, which is exported from `epics.dart` for exactly this purpose). If the production epic drifts from the mirror, the test would still pass. Minor; consider exporting `_loadDetailEpic` for test reuse. ### Verification - **CI/CD:** forgejo-actions Flutter coverage comment #536 is **current for head `0f372279`** (posted 14:38:14 +02:00, ~1 min after PR open; `flutter-ci.yml` posts coverage only on successful `flutter analyze` + `flutter test`). New file coverage: `detail_page.dart` 92.7% (202/218), `reducers.dart` 100%, `star_rating.dart` 100%, `epics.dart` 87.9%. Local build/test **skipped per CI-evidence policy**. - **Static security scan:** clean — no secrets, shell injection, `eval`/`exec`, `pickle`, or SQL injection in added lines. (Note: `action.authToken` in `epics.dart` is a runtime value, not a hardcoded secret.) - **`mergeable: true**, `merge_base` equals base sha (clean, no drift). *Automated daily review. I never merge PRs.*
Owner

It feels to me that app/lib/presentation/state/actions.dart is becoming very large. Looking forward to the editing, it is very likely that this will only grow more and more, specifically once we go to the editor. Could you spread the action definitions out into individual files for each logical unit, e.g. general navigation and error, settings, library view, detail view, ...? I think that would make this a lot better handable

And also take a look at the reducer as well. I think the same concern is also true there as well.

It feels to me that app/lib/presentation/state/actions.dart is becoming very large. Looking forward to the editing, it is very likely that this will only grow more and more, specifically once we go to the editor. Could you spread the action definitions out into individual files for each logical unit, e.g. general navigation and error, settings, library view, detail view, ...? I think that would make this a lot better handable And also take a look at the reducer as well. I think the same concern is also true there as well.
Author
Member

Good call — I'm on it. Splitting both actions.dart and reducers.dart into per-domain files with barrel re-exports so existing imports keep working unchanged.

Structure:

state/actions/
  settings_actions.dart
  library_actions.dart
  metadata_actions.dart
  filter_token_actions.dart
  detail_actions.dart
state/reducers/
  settings_reducer.dart
  library_reducer.dart
  metadata_reducer.dart
  detail_reducer.dart

actions.dart and reducers.dart become barrel files that re-export everything. This prep for the editor phase cleanly.

Good call — I'm on it. Splitting both `actions.dart` and `reducers.dart` into per-domain files with barrel re-exports so existing imports keep working unchanged. Structure: ``` state/actions/ settings_actions.dart library_actions.dart metadata_actions.dart filter_token_actions.dart detail_actions.dart state/reducers/ settings_reducer.dart library_reducer.dart metadata_reducer.dart detail_reducer.dart ``` `actions.dart` and `reducers.dart` become barrel files that re-export everything. This prep for the editor phase cleanly.
refactor: split actions.dart and reducers.dart into per-domain files
All checks were successful
Flutter CI / analyze-and-test (pull_request) Successful in 55s
ac027e8006
Split the monolithic state files into organized per-domain files:
- actions/settings_actions.dart, library_actions.dart, metadata_actions.dart,
  filter_token_actions.dart, detail_actions.dart
- reducers/settings_reducer.dart, library_reducer.dart,
  metadata_reducer.dart, detail_reducer.dart

Barrel re-exports in actions.dart and reducers.dart ensure all existing
imports keep working unchanged. Each domain reducer returns AppState?
(nullable) so appReducer composes them via null-coalescing.

180 tests pass, 0 analyze issues.
Author
Member

Done — commit ac027e8:

Split into per-domain files:

  • actions/: settings, library, metadata, filter_token, detail
  • reducers/: settings, library, metadata, detail

actions.dart and reducers.dart are now barrel files that re-export everything. All existing imports keep working unchanged — no test or page files modified.

180 tests pass, 0 analyze issues.

Done — commit `ac027e8`: Split into per-domain files: - `actions/`: settings, library, metadata, filter_token, detail - `reducers/`: settings, library, metadata, detail `actions.dart` and `reducers.dart` are now barrel files that re-export everything. All existing imports keep working unchanged — no test or page files modified. 180 tests pass, 0 analyze issues.
fix: merge filter_token_actions into library_actions for 1:1 mapping
All checks were successful
Flutter CI / analyze-and-test (pull_request) Successful in 56s
ddf034b402
Filter token actions and reducers both live in their respective library
files now, so the action/reducer file pairs match 1:1:

  settings_actions  ↔  settings_reducer
  library_actions   ↔  library_reducer  (includes filter tokens)
  metadata_actions  ↔  metadata_reducer
  detail_actions    ↔  detail_reducer

180 tests pass, 0 analyze issues.
Author
Member

Done — merged filter token actions into library_actions.dart (ddf034b). Now action and reducer files match 1:1:

Actions Reducer
settings_actions.dart settings_reducer.dart
library_actions.dart library_reducer.dart
metadata_actions.dart metadata_reducer.dart
detail_actions.dart detail_reducer.dart

180 tests pass, 0 analyze issues.

Done — merged filter token actions into `library_actions.dart` (`ddf034b`). Now action and reducer files match 1:1: | Actions | Reducer | |---------|---------| | `settings_actions.dart` | `settings_reducer.dart` | | `library_actions.dart` | `library_reducer.dart` | | `metadata_actions.dart` | `metadata_reducer.dart` | | `detail_actions.dart` | `detail_reducer.dart` | 180 tests pass, 0 analyze issues.
Author
Member

🤖 Hermes automated review: follow-up (minor comments)

Reviewed the incremental diff since my last review (head 0f37227 → ddf034b, 3 commits: 554e671 inject.py, ac027e8 actions/reducers split, ddf034b filter_token merge). This addresses @bjoern's request to split the large actions.dart/reducers.dart into per-domain files.

Refactor verification — faithful, no regressions ✅

I verified the split moved every declaration with zero loss:

Domain Action classes (new) Reducer is XAction cases (new)
settings 5 5
library (incl. merged filter_token) 18 18
metadata 3 3
detail 4 4
Total 30 30

Both totals match the pre-split monolithic files exactly (30 action classes, 30 reducer cases). The appReducer barrel keeps the same first-non-null chain (settingsReducer ?? libraryReducer ?? metadataReducer ?? detailReducer ?? state). The ddf034b merge of filter_token_actions into library_actions was clean — no orphaned filter_token_actions.dart remains, and all 3 filter-token action classes (AddFilterTokenAction, RemoveFilterTokenAction, SetFilterTokensAction) are present in library_actions.dart. Barrel re-exports preserve all existing imports.

Findings (1 new minor, non-blocking)

  1. scripts/inject.py leaks file handles on upload failure — scripts/inject.py:87-94. The page-upload loop opens N image files with open(img, "rb") (line 87) into a list, but the cleanup loop that closes them (line 100) only runs on the success path. If resp.raise_for_status() at line 94 throws (upload HTTP error, network failure, etc.), the exception propagates and the for loop at line 100 is skipped, leaking all N file descriptors. This is a dev-only utility script, so impact is low, but the fix is a one-liner: wrap the upload + raise_for_status in try/finally, or use a context manager / ExitStack. Suggested:
    files = [("files", (img.name, open(img, "rb"), "image/jpeg")) for img in images]
    try:
        resp = requests.post(f"{base}/api/variants/{variant_id}/pages", headers=headers, files=files)
        resp.raise_for_status()
        result = resp.json()["data"]
        print(f"  ✓ Uploaded {len(result)} pages")
    finally:
        for _, (_, f, _) in files:
            f.close()
    

Status of prior findings (from comment #538)

All 3 prior minor notes were about the original detail-page feature, not the refactor. They remain valid but non-blocking and are unaffected by this refactor:

  1. secondaryTitles() value-based dedup edge case — still applies.
  2. No retry on _DetailErrorView — still applies (read-only phase).
  3. Epic test mirrors production code — still applies.

Verification

  • CI/CD: forgejo-actions Flutter coverage comment #536 is current for head ddf034b (updated 15:20:42 +02:00, ~43s after head commit at 15:19:59; flutter-ci.yml posts coverage only on successful flutter analyze + flutter test; 180/180 tests). New per-domain files appear in the coverage report (e.g. detail_actions.dart 100%, library_reducer.dart 100%, settings_reducer.dart 100%). Local build/test skipped per CI-evidence policy.
  • Static security scan (incremental diff): clean — no hardcoded secrets, shell injection, eval/exec, pickle, or SQL injection. The AUTH_TOKEN="your-token-here" in inject.py is a literal placeholder, not a real credential (verified via raw byte inspection).
  • mergeable: true; merge_base equals base sha (no drift).

Automated daily review. I never merge PRs.

## 🤖 Hermes automated review: follow-up (minor comments) Reviewed the **incremental diff** since my last review (head `0f37227` → `ddf034b`, 3 commits: `554e671` inject.py, `ac027e8` actions/reducers split, `ddf034b` filter_token merge). This addresses @bjoern's request to split the large `actions.dart`/`reducers.dart` into per-domain files. ### Refactor verification — faithful, no regressions ✅ I verified the split moved **every** declaration with zero loss: | Domain | Action classes (new) | Reducer `is XAction` cases (new) | |:---|:---:|:---:| | settings | 5 | 5 | | library (incl. merged filter_token) | 18 | 18 | | metadata | 3 | 3 | | detail | 4 | 4 | | **Total** | **30** | **30** | Both totals match the pre-split monolithic files exactly (30 action classes, 30 reducer cases). The `appReducer` barrel keeps the same first-non-null chain (`settingsReducer ?? libraryReducer ?? metadataReducer ?? detailReducer ?? state`). The `ddf034b` merge of `filter_token_actions` into `library_actions` was clean — no orphaned `filter_token_actions.dart` remains, and all 3 filter-token action classes (`AddFilterTokenAction`, `RemoveFilterTokenAction`, `SetFilterTokensAction`) are present in `library_actions.dart`. Barrel re-exports preserve all existing imports. ### Findings (1 new minor, non-blocking) 1. **`scripts/inject.py` leaks file handles on upload failure** — `scripts/inject.py:87-94`. The page-upload loop opens N image files with `open(img, "rb")` (line 87) into a list, but the cleanup loop that closes them (line 100) only runs on the **success** path. If `resp.raise_for_status()` at line 94 throws (upload HTTP error, network failure, etc.), the exception propagates and the `for` loop at line 100 is skipped, leaking all N file descriptors. This is a dev-only utility script, so impact is low, but the fix is a one-liner: wrap the upload + `raise_for_status` in `try/finally`, or use a context manager / `ExitStack`. Suggested: ```python files = [("files", (img.name, open(img, "rb"), "image/jpeg")) for img in images] try: resp = requests.post(f"{base}/api/variants/{variant_id}/pages", headers=headers, files=files) resp.raise_for_status() result = resp.json()["data"] print(f" ✓ Uploaded {len(result)} pages") finally: for _, (_, f, _) in files: f.close() ``` ### Status of prior findings (from comment #538) All 3 prior minor notes were about the original detail-page feature, not the refactor. They remain valid but non-blocking and are unaffected by this refactor: 1. `secondaryTitles()` value-based dedup edge case — still applies. 2. No retry on `_DetailErrorView` — still applies (read-only phase). 3. Epic test mirrors production code — still applies. ### Verification - **CI/CD:** forgejo-actions Flutter coverage comment #536 is **current for head `ddf034b`** (updated 15:20:42 +02:00, ~43s after head commit at 15:19:59; `flutter-ci.yml` posts coverage only on successful `flutter analyze` + `flutter test`; 180/180 tests). New per-domain files appear in the coverage report (e.g. `detail_actions.dart` 100%, `library_reducer.dart` 100%, `settings_reducer.dart` 100%). Local build/test **skipped per CI-evidence policy**. - **Static security scan (incremental diff):** clean — no hardcoded secrets, shell injection, `eval`/`exec`, `pickle`, or SQL injection. The `AUTH_TOKEN="your-token-here"` in `inject.py` is a literal placeholder, not a real credential (verified via raw byte inspection). - `mergeable: true`; `merge_base` equals base sha (no drift). *Automated daily review. I never merge PRs.*
chore: add recursive inject script with chapter support
All checks were successful
Flutter CI / analyze-and-test (pull_request) Successful in 57s
3f0f99af94
Scans a folder: root-level images go into the variant directly, each
subfolder with images becomes a chapter. Creates doujin + variant +
chapters, uploads all images as pages.

Note: pages are uploaded without chapter assignment (the current API
endpoint POST /api/variants/{id}/pages passes null for chapterId).
fix: correct CreateVariantDto fields — languageCode + displayName, not title
All checks were successful
Flutter CI / analyze-and-test (pull_request) Successful in 54s
734838bdce
The variant creation was sending {"title": ...} but the DTO expects
languageCode (mandatory), displayName, and isDefault.
fix: handle upload response shape defensively
All checks were successful
Flutter CI / analyze-and-test (pull_request) Successful in 56s
c4706850ef
The upload endpoint returns UploadPagesResponseDto { pages: [...], count: N }
but the exact JSON field might differ. Now tries 'pages' list length, then
'count' field, then falls back to the number of files sent.
chore: remove throwaway inject script
All checks were successful
Flutter CI / analyze-and-test (pull_request) Successful in 56s
f53b562e54
The debug injection script served its purpose for testing. Removing
it so the branch is clean for merge.
chore: delete throwaway inject script
All checks were successful
Flutter CI / analyze-and-test (pull_request) Successful in 53s
1e656e5fb5
bjoern merged commit 49ce57770a into main 2026-06-30 17:18:12 +02:00
bjoern deleted branch feat/flutter-detail 2026-06-30 17:18:12 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
3 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
TeamAI/doujin-manager!33
No description provided.