feat: redesign library card to vertical spec layout with star rating + tooltip #37

Merged
bjoern merged 12 commits from feat/flutter-card-redesign into main 2026-06-30 22:50:16 +02:00
Member

Summary

Redesigns the library card to the vertical spec layout from Björn's comment (#586), and addresses jibril's non-blocking suggestions (#583).

Card layout (per spec)

The card is now a vertical Column:

Card
 └─ Column
     ├─ Text (title — centered, bold, single-line)
     ├─ Expanded: Cover image with bordered container
     │   └─ Positioned tooltip bubble (title overlay)
     ├─ StarRating (12px, display-only)
     └─ Row: date ←→ pages + info icon

Changes

Backend (3 files):

  • Added Rating to DoujinSummaryDto so the card can show star ratings
  • Updated MapToSummary in both DoujinEndpoints and SearchEndpoints

Flutter library page (1 file):

  • Rewrote _DoujinCard from horizontal Row → vertical Column
  • Title: centered, bold, single-line
  • Cover image: Expanded to fill available space, bordered, with semi-transparent tooltip bubble overlay
  • Star rating: 12px display-only stars from doujin.rating
  • Bottom row: spaceBetween with date on left, page count + info icon on right
  • Grid density: maxCrossAxisExtent 200→180, childAspectRatio 1.4→0.62, spacing 8→4
  • Added _normalizeUrl() helper to strip trailing slash from serverUrl

Flutter detail page (1 file):

  • Added _normalizeThumbnailUrl() helper (same trailing-slash fix)

Tests (1 file):

  • Removed 2v assertion (variant count no longer shown on card)

All 180 Flutter tests pass, 268 backend tests pass, flutter analyze — 0 issues.

## Summary Redesigns the library card to the vertical spec layout from Björn's comment (#586), and addresses jibril's non-blocking suggestions (#583). ### Card layout (per spec) The card is now a vertical Column: ``` Card └─ Column ├─ Text (title — centered, bold, single-line) ├─ Expanded: Cover image with bordered container │ └─ Positioned tooltip bubble (title overlay) ├─ StarRating (12px, display-only) └─ Row: date ←→ pages + info icon ``` ### Changes **Backend** (3 files): - Added `Rating` to `DoujinSummaryDto` so the card can show star ratings - Updated `MapToSummary` in both `DoujinEndpoints` and `SearchEndpoints` **Flutter library page** (1 file): - Rewrote `_DoujinCard` from horizontal Row → vertical Column - Title: centered, bold, single-line - Cover image: `Expanded` to fill available space, bordered, with semi-transparent tooltip bubble overlay - Star rating: 12px display-only stars from `doujin.rating` - Bottom row: `spaceBetween` with date on left, page count + info icon on right - Grid density: `maxCrossAxisExtent` 200→180, `childAspectRatio` 1.4→0.62, spacing 8→4 - Added `_normalizeUrl()` helper to strip trailing slash from serverUrl **Flutter detail page** (1 file): - Added `_normalizeThumbnailUrl()` helper (same trailing-slash fix) **Tests** (1 file): - Removed `2v` assertion (variant count no longer shown on card) All 180 Flutter tests pass, 268 backend tests pass, `flutter analyze` — 0 issues.
1. Models: DoujinSummary now has coverImageId + links (self, thumbnail).
   DoujinDetail has coverImageId. Regenerated freezed/json files.

2. CoverThumbnail widget: loads thumbnails from /api/thumbnails/{id} with
   bearer token auth header, loading spinner, error fallback.

3. Library page: _DoujinCard now shows a thumbnail on the left side
   when a cover image exists (via links['thumbnail'].href), with a
   placeholder when no cover. Card meta labels shortened (30p, 2v)
   to make room for the thumbnail.

4. Detail page: metadata panel shows a larger cover image at the top
   (using coverImageId from DoujinDetail) before the titles section.

5. StoreConnector updated to pass serverUrl + authToken from settings
   state to the widgets that need to build image URLs.

All 180 Flutter tests pass.
feat: redesign library card to vertical spec layout
All checks were successful
CI / build (pull_request) Successful in 18s
Flutter CI / analyze-and-test (pull_request) Successful in 58s
CI / test (pull_request) Successful in 41s
d8839a96fd
Redesigns _DoujinCard per Björn's spec (#586):

Backend:
- Add Rating field to DoujinSummaryDto (consumed by card's star rating)
- Updated MapToSummary in DoujinEndpoints + SearchEndpoints

Flutter library page:
- Card layout changed from horizontal Row to vertical Column:
  1. Title at top — centered, bold, single-line
  2. Cover image (Expanded) with bordered container + overlay tooltip
     bubble showing the title
  3. Star rating row (12px stars, display-only, from new rating field)
  4. Bottom metadata row: date ←→ page count + info icon
- Grid made denser: maxCrossAxisExtent 180, childAspectRatio 0.62,
  spacing reduced from 8 to 4
- Removed variant count chip (available in detail view)

Also addresses jibril's suggestions (#583):
- Trailing-slash normalization on serverUrl in both library + detail
  pages via _normalizeUrl / _normalizeThumbnailUrl helpers
- Tooltip bubble on cover image (semi-transparent, single-line title)

All 180 Flutter tests pass, 268 backend tests pass, 0 analyzer 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 49.8% (106 of 213)
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 87.8% (209 of 238)
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/cover_thumbnail.dart 81.8% (27 of 33)
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 77.1% (121 of 157)
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: 77.0% (1279 of 1662)

<!-- 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 | 49.8% (106 of 213) | | 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 | 87.8% (209 of 238) | | 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/cover_thumbnail.dart | 81.8% (27 of 33) | | 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 | 77.1% (121 of 157) | | 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: 77.0% (1279 of 1662)**

Summary

Summary
Generated on: 06/30/2026 - 20:41:41
Coverage date: 06/30/2026 - 20:41:22 - 06/30/2026 - 20:41:38
Parser: MultiReport (4x Cobertura)
Assemblies: 4
Classes: 211
Files: 98
Line coverage: 85.5% (5058 of 5914)
Covered lines: 5058
Uncovered lines: 856
Coverable lines: 5914
Total lines: 9753
Branch coverage: 50.5% (397 of 785)
Covered branches: 397
Total branches: 785
Method coverage: Feature is only available for sponsors

Coverage

DoujinManager.ApplicationCore - 85.2%
Name Line Branch
DoujinManager.ApplicationCore 85.2% ****
DoujinManager.ApplicationCore.Entities.Chapter 87.5%
DoujinManager.ApplicationCore.Entities.Circle 100%
DoujinManager.ApplicationCore.Entities.Doujin 100%
DoujinManager.ApplicationCore.Entities.DoujinCircle 75%
DoujinManager.ApplicationCore.Entities.DoujinPerson 80%
DoujinManager.ApplicationCore.Entities.DoujinTag 75%
DoujinManager.ApplicationCore.Entities.ImageFile 100%
DoujinManager.ApplicationCore.Entities.Page 80%
DoujinManager.ApplicationCore.Entities.Person 100%
DoujinManager.ApplicationCore.Entities.Tag 100%
DoujinManager.ApplicationCore.Entities.Title 83.3%
DoujinManager.ApplicationCore.Entities.Variant 91.6%
DoujinManager.ApplicationCore.Ids.ChapterId 66.6%
DoujinManager.ApplicationCore.Ids.CircleId 66.6%
DoujinManager.ApplicationCore.Ids.DoujinId 100%
DoujinManager.ApplicationCore.Ids.ImageFileId 66.6%
DoujinManager.ApplicationCore.Ids.PageId 66.6%
DoujinManager.ApplicationCore.Ids.PersonId 66.6%
DoujinManager.ApplicationCore.Ids.TagId 66.6%
DoujinManager.ApplicationCore.Ids.TitleId 66.6%
DoujinManager.ApplicationCore.Ids.VariantId 66.6%
DoujinManager.ApplicationCore.Ports.ExtractedImage 100%
DoujinManager.ApplicationCore.Ports.ImageInspection 100%
DoujinManager.ApplicationCore.Services.BackupInfo 100%
DoujinManager.ApplicationCore.Services.ITagService 100%
DoujinManager.ApplicationCore.Services.ServiceResult 100%
DoujinManager.ApplicationCore.Services.ServiceResult`1 33.3%
DoujinManager.ApplicationCore.Services.VoidResult 88.8%
DoujinManager.ApplicationCore.UseCases.AddTitleCommand 0%
DoujinManager.ApplicationCore.UseCases.AssignCircleCommand 100%
DoujinManager.ApplicationCore.UseCases.AssignPersonCommand 100%
DoujinManager.ApplicationCore.UseCases.AssignTagCommand 100%
DoujinManager.ApplicationCore.UseCases.CreateChapterCommand 100%
DoujinManager.ApplicationCore.UseCases.CreateCircleCommand 100%
DoujinManager.ApplicationCore.UseCases.CreateDoujinCommand 100%
DoujinManager.ApplicationCore.UseCases.CreatePersonCommand 100%
DoujinManager.ApplicationCore.UseCases.CreateTagCommand 100%
DoujinManager.ApplicationCore.UseCases.CreateTitleCommand 100%
DoujinManager.ApplicationCore.UseCases.CreateVariantCommand 100%
DoujinManager.ApplicationCore.UseCases.DeleteChapterCommand 0%
DoujinManager.ApplicationCore.UseCases.DeleteDoujinCommand 100%
DoujinManager.ApplicationCore.UseCases.DeletePageCommand 100%
DoujinManager.ApplicationCore.UseCases.DeleteVariantCommand 0%
DoujinManager.ApplicationCore.UseCases.GetDoujinQuery 100%
DoujinManager.ApplicationCore.UseCases.GetImageQuery 100%
DoujinManager.ApplicationCore.UseCases.GetImageResult 100%
DoujinManager.ApplicationCore.UseCases.GetThumbnailQuery 100%
DoujinManager.ApplicationCore.UseCases.GetThumbnailResult 100%
DoujinManager.ApplicationCore.UseCases.GetVariantQuery 100%
DoujinManager.ApplicationCore.UseCases.ListChaptersQuery 100%
DoujinManager.ApplicationCore.UseCases.ListCirclesQuery 100%
DoujinManager.ApplicationCore.UseCases.ListDoujinsQuery 100%
DoujinManager.ApplicationCore.UseCases.ListPagesQuery 100%
DoujinManager.ApplicationCore.UseCases.ListPeopleQuery 100%
DoujinManager.ApplicationCore.UseCases.ListTagsQuery 100%
DoujinManager.ApplicationCore.UseCases.ListVariantsQuery 100%
DoujinManager.ApplicationCore.UseCases.RemoveCircleCommand 0%
DoujinManager.ApplicationCore.UseCases.RemovePersonCommand 0%
DoujinManager.ApplicationCore.UseCases.RemoveTagCommand 0%
DoujinManager.ApplicationCore.UseCases.RemoveTitleCommand 0%
DoujinManager.ApplicationCore.UseCases.ReorderPagesCommand 100%
DoujinManager.ApplicationCore.UseCases.SearchDoujinsQuery 100%
DoujinManager.ApplicationCore.UseCases.SearchResult 100%
DoujinManager.ApplicationCore.UseCases.SearchResults 100%
DoujinManager.ApplicationCore.UseCases.UpdateChapterCommand 0%
DoujinManager.ApplicationCore.UseCases.UpdateCircleCommand 0%
DoujinManager.ApplicationCore.UseCases.UpdateDoujinCommand 100%
DoujinManager.ApplicationCore.UseCases.UpdatePersonCommand 0%
DoujinManager.ApplicationCore.UseCases.UpdateVariantCommand 0%
DoujinManager.ApplicationCore.UseCases.UploadImageFile 100%
DoujinManager.ApplicationCore.UseCases.UploadPagesCommand 100%
DoujinManager.ApplicationCore.UseCases.UploadZipPagesCommand 100%
DoujinManager.Infrastructure - 93%
Name Line Branch
DoujinManager.Infrastructure 93% 71.2%
DoujinManager.Infrastructure.Archives.ZipExtractor 100% 87.5%
DoujinManager.Infrastructure.Data.Configurations.ChapterConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.CircleConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.DoujinCircleConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.DoujinConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.DoujinPersonConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.DoujinTagConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.ImageFileConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.PageConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.PersonConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.TagConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.TitleConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.VariantConfiguration 100%
DoujinManager.Infrastructure.Data.DoujinManagerDbContext 100%
DoujinManager.Infrastructure.Data.GuidIdGenerator 11.1%
DoujinManager.Infrastructure.Data.Migrations.AddCoverImageId 99.2%
DoujinManager.Infrastructure.Data.Migrations.DoujinManagerDbContextModelSna
pshot
100%
DoujinManager.Infrastructure.Data.Migrations.InitialCreate 97.1%
DoujinManager.Infrastructure.Data.ModelBuilderExtensions 50%
DoujinManager.Infrastructure.Data.StronglyTypedIdConverterFactory 100%
DoujinManager.Infrastructure.Images.SkiaSharpImageInspector 88.2% 70.9%
DoujinManager.Infrastructure.Images.SkiaSharpThumbnailGenerator 94.5% 66.6%
DoujinManager.Infrastructure.Services.BackupService 85.1% 66.6%
DoujinManager.Infrastructure.Services.ChapterService 54.2% 25%
DoujinManager.Infrastructure.Services.CircleService 69.6% 0%
DoujinManager.Infrastructure.Services.DoujinService 69.6% 46.4%
DoujinManager.Infrastructure.Services.ImageService 92% 50%
DoujinManager.Infrastructure.Services.PageService 84.9% 65%
DoujinManager.Infrastructure.Services.PersonService 69.6% 0%
DoujinManager.Infrastructure.Services.SearchService 100% 98.5%
DoujinManager.Infrastructure.Services.TagService 94.7% 100%
DoujinManager.Infrastructure.Services.VariantService 50.7% 16.6%
DoujinManager.Infrastructure.Storage.FilesystemImageStorage 100% 100%
DoujinManager.Infrastructure.Storage.FilesystemThumbnailStorage 95% 50%
DoujinManager.Infrastructure.UseCases.FE6C43B9C917DB414605EC
E20E2ABA2099901FFAB4904C0513DF077BA6D6CA374__CoverImageHelper
100% 100%
DoujinManager.Infrastructure.UseCases.AddTitleUseCase 0% 0%
DoujinManager.Infrastructure.UseCases.AssignCircleUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.AssignPersonUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.AssignTagUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.CreateChapterUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.CreateCircleUseCase 100%
DoujinManager.Infrastructure.UseCases.CreateDoujinUseCase 100% 98%
DoujinManager.Infrastructure.UseCases.CreatePersonUseCase 100%
DoujinManager.Infrastructure.UseCases.CreateTagUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.CreateVariantUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.DeleteChapterUseCase 0%
DoujinManager.Infrastructure.UseCases.DeleteDoujinUseCase 100%
DoujinManager.Infrastructure.UseCases.DeletePageUseCase 92.3% 75%
DoujinManager.Infrastructure.UseCases.DeleteVariantUseCase 0%
DoujinManager.Infrastructure.UseCases.GetDoujinUseCase 100%
DoujinManager.Infrastructure.UseCases.GetImageUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.GetThumbnailUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.GetVariantUseCase 100%
DoujinManager.Infrastructure.UseCases.ListChaptersUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.ListCirclesUseCase 100%
DoujinManager.Infrastructure.UseCases.ListDoujinsUseCase 100%
DoujinManager.Infrastructure.UseCases.ListPagesUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.ListPeopleUseCase 100%
DoujinManager.Infrastructure.UseCases.ListTagsUseCase 100%
DoujinManager.Infrastructure.UseCases.ListVariantsUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.RemoveCircleUseCase 0%
DoujinManager.Infrastructure.UseCases.RemovePersonUseCase 0%
DoujinManager.Infrastructure.UseCases.RemoveTagUseCase 0%
DoujinManager.Infrastructure.UseCases.RemoveTitleUseCase 0%
DoujinManager.Infrastructure.UseCases.ReorderPagesUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.SearchDoujinsUseCase 100%
DoujinManager.Infrastructure.UseCases.UpdateChapterUseCase 0% 0%
DoujinManager.Infrastructure.UseCases.UpdateCircleUseCase 0% 0%
DoujinManager.Infrastructure.UseCases.UpdateDoujinUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.UpdatePersonUseCase 0% 0%
DoujinManager.Infrastructure.UseCases.UpdateVariantUseCase 0%
DoujinManager.Infrastructure.UseCases.UploadPagesUseCase 91.4% 91.6%
DoujinManager.Infrastructure.UseCases.UploadZipPagesUseCase 71.7% 70%
DoujinManager.RestAdapter - 85%
Name Line Branch
DoujinManager.RestAdapter 85% 70.8%
DoujinManager.RestAdapter.Auth.StaticBearerTokenAuthMiddleware 100% 91.6%
DoujinManager.RestAdapter.Dtos.AssignTagDto 0%
DoujinManager.RestAdapter.Dtos.BackupDto 100%
DoujinManager.RestAdapter.Dtos.ChapterDto 100%
DoujinManager.RestAdapter.Dtos.CircleDto 100%
DoujinManager.RestAdapter.Dtos.CreateChapterDto 100%
DoujinManager.RestAdapter.Dtos.CreateCircleDto 100%
DoujinManager.RestAdapter.Dtos.CreateDoujinDto 100%
DoujinManager.RestAdapter.Dtos.CreatePersonDto 100%
DoujinManager.RestAdapter.Dtos.CreateTagDto 100%
DoujinManager.RestAdapter.Dtos.CreateTitleDto 100%
DoujinManager.RestAdapter.Dtos.CreateVariantDto 100%
DoujinManager.RestAdapter.Dtos.DoujinDetailDto 100%
DoujinManager.RestAdapter.Dtos.DoujinPersonDto 100%
DoujinManager.RestAdapter.Dtos.DoujinSummaryDto 100%
DoujinManager.RestAdapter.Dtos.ImageFileSummaryDto 0%
DoujinManager.RestAdapter.Dtos.LinkCircleDto 0%
DoujinManager.RestAdapter.Dtos.LinkPersonDto 100%
DoujinManager.RestAdapter.Dtos.PageDetailDto 100%
DoujinManager.RestAdapter.Dtos.PageDto 100%
DoujinManager.RestAdapter.Dtos.PersonDto 100%
DoujinManager.RestAdapter.Dtos.ReorderPagesDto 100%
DoujinManager.RestAdapter.Dtos.SearchDoujinsDto 100%
DoujinManager.RestAdapter.Dtos.SearchResultDto 100%
DoujinManager.RestAdapter.Dtos.TagDto 100%
DoujinManager.RestAdapter.Dtos.TitleDto 100%
DoujinManager.RestAdapter.Dtos.UpdateChapterDto 0%
DoujinManager.RestAdapter.Dtos.UpdateCircleDto 0%
DoujinManager.RestAdapter.Dtos.UpdateDoujinDto 100%
DoujinManager.RestAdapter.Dtos.UpdatePersonDto 0%
DoujinManager.RestAdapter.Dtos.UpdateVariantDto 0%
DoujinManager.RestAdapter.Dtos.UploadPagesResponseDto 100%
DoujinManager.RestAdapter.Dtos.VariantDetailDto 100%
DoujinManager.RestAdapter.Dtos.VariantSummaryDto 100%
DoujinManager.RestAdapter.Endpoints.BackupEndpoints 100%
DoujinManager.RestAdapter.Endpoints.DoujinEndpoints 84% 66.6%
DoujinManager.RestAdapter.Endpoints.ImageEndpoints 97.1% 50%
DoujinManager.RestAdapter.Endpoints.MetadataEndpoints 85.4%
DoujinManager.RestAdapter.Endpoints.PaginationParams 100%
DoujinManager.RestAdapter.Endpoints.SearchEndpoints 100% 50%
DoujinManager.RestAdapter.Endpoints.VariantEndpoints 72.1% 37.5%
DoujinManager.RestAdapter.Envelopes.CollectionResponse`1 83.3%
DoujinManager.RestAdapter.Envelopes.EnvelopeDefaults 0%
DoujinManager.RestAdapter.Envelopes.EnvelopeJsonOptions 100%
DoujinManager.RestAdapter.Envelopes.ErrorResponse 100%
DoujinManager.RestAdapter.Envelopes.HypermediaAction 100%
DoujinManager.RestAdapter.Envelopes.HypermediaHelpers 86.9% 83.3%
DoujinManager.RestAdapter.Envelopes.Link 100%
DoujinManager.RestAdapter.Envelopes.MediaTypeJsonConverter 62.5% 53.8%
DoujinManager.RestAdapter.Envelopes.PageInfo 100%
DoujinManager.RestAdapter.Envelopes.ResourceResponse`1 80%
DoujinManager.RestAdapter.Envelopes.ValidationError 100%
DoujinManager.RestAdapter.Envelopes.ValidationErrorResponse 100%
DoujinManager.RestAdapter.Middleware.GlobalExceptionMiddleware 100% 50%
DoujinManager.RestAdapter.Middleware.RequestLoggingMiddleware 100% 100%
DoujinManager.RestAdapter.RestAdapterExtensions 100% 100%
Microsoft.Extensions.Validation.Generated 79.5% 78.2%
Microsoft.Extensions.Validation.Generated.<ValidatableInfoResolver_g>FB9B0C
E5CB12CEAC5BBBEA65844E1C05F9ADA4DA38B848075C41304FC6A1ABF77__ValidationAttr
ibuteCache
100% 62.5%
System.Runtime.CompilerServices 0%
DoujinManager.Server - 21.8%
Name Line Branch
DoujinManager.Server 21.8% 0.8%
DoujinManager.Server.ImageInfrastructureRegistration 100%
DoujinManager.Server.ProxyAwareServerTransformer 100% 50%
DoujinManager.Server.ScalarUi 100%
DoujinManager.Server.UseCaseRegistrationHelper 100%
Microsoft.AspNetCore.OpenApi.Generated 0% 0%
Program 0% 0%
System.Runtime.CompilerServices 0%
<!-- coverage-comment --> # Summary <details open><summary>Summary</summary> ||| |:---|:---| | Generated on: | 06/30/2026 - 20:41:41 | | Coverage date: | 06/30/2026 - 20:41:22 - 06/30/2026 - 20:41:38 | | Parser: | MultiReport (4x Cobertura) | | Assemblies: | 4 | | Classes: | 211 | | Files: | 98 | | **Line coverage:** | 85.5% (5058 of 5914) | | Covered lines: | 5058 | | Uncovered lines: | 856 | | Coverable lines: | 5914 | | Total lines: | 9753 | | **Branch coverage:** | 50.5% (397 of 785) | | Covered branches: | 397 | | Total branches: | 785 | | **Method coverage:** | [Feature is only available for sponsors](https://reportgenerator.io/pro) | </details> ## Coverage <details><summary>DoujinManager.ApplicationCore - 85.2%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**DoujinManager.ApplicationCore**|**85.2%**|****| |DoujinManager.ApplicationCore.Entities.Chapter|87.5%|| |DoujinManager.ApplicationCore.Entities.Circle|100%|| |DoujinManager.ApplicationCore.Entities.Doujin|100%|| |DoujinManager.ApplicationCore.Entities.DoujinCircle|75%|| |DoujinManager.ApplicationCore.Entities.DoujinPerson|80%|| |DoujinManager.ApplicationCore.Entities.DoujinTag|75%|| |DoujinManager.ApplicationCore.Entities.ImageFile|100%|| |DoujinManager.ApplicationCore.Entities.Page|80%|| |DoujinManager.ApplicationCore.Entities.Person|100%|| |DoujinManager.ApplicationCore.Entities.Tag|100%|| |DoujinManager.ApplicationCore.Entities.Title|83.3%|| |DoujinManager.ApplicationCore.Entities.Variant|91.6%|| |DoujinManager.ApplicationCore.Ids.ChapterId|66.6%|| |DoujinManager.ApplicationCore.Ids.CircleId|66.6%|| |DoujinManager.ApplicationCore.Ids.DoujinId|100%|| |DoujinManager.ApplicationCore.Ids.ImageFileId|66.6%|| |DoujinManager.ApplicationCore.Ids.PageId|66.6%|| |DoujinManager.ApplicationCore.Ids.PersonId|66.6%|| |DoujinManager.ApplicationCore.Ids.TagId|66.6%|| |DoujinManager.ApplicationCore.Ids.TitleId|66.6%|| |DoujinManager.ApplicationCore.Ids.VariantId|66.6%|| |DoujinManager.ApplicationCore.Ports.ExtractedImage|100%|| |DoujinManager.ApplicationCore.Ports.ImageInspection|100%|| |DoujinManager.ApplicationCore.Services.BackupInfo|100%|| |DoujinManager.ApplicationCore.Services.ITagService|100%|| |DoujinManager.ApplicationCore.Services.ServiceResult|100%|| |DoujinManager.ApplicationCore.Services.ServiceResult`1|33.3%|| |DoujinManager.ApplicationCore.Services.VoidResult|88.8%|| |DoujinManager.ApplicationCore.UseCases.AddTitleCommand|0%|| |DoujinManager.ApplicationCore.UseCases.AssignCircleCommand|100%|| |DoujinManager.ApplicationCore.UseCases.AssignPersonCommand|100%|| |DoujinManager.ApplicationCore.UseCases.AssignTagCommand|100%|| |DoujinManager.ApplicationCore.UseCases.CreateChapterCommand|100%|| |DoujinManager.ApplicationCore.UseCases.CreateCircleCommand|100%|| |DoujinManager.ApplicationCore.UseCases.CreateDoujinCommand|100%|| |DoujinManager.ApplicationCore.UseCases.CreatePersonCommand|100%|| |DoujinManager.ApplicationCore.UseCases.CreateTagCommand|100%|| |DoujinManager.ApplicationCore.UseCases.CreateTitleCommand|100%|| |DoujinManager.ApplicationCore.UseCases.CreateVariantCommand|100%|| |DoujinManager.ApplicationCore.UseCases.DeleteChapterCommand|0%|| |DoujinManager.ApplicationCore.UseCases.DeleteDoujinCommand|100%|| |DoujinManager.ApplicationCore.UseCases.DeletePageCommand|100%|| |DoujinManager.ApplicationCore.UseCases.DeleteVariantCommand|0%|| |DoujinManager.ApplicationCore.UseCases.GetDoujinQuery|100%|| |DoujinManager.ApplicationCore.UseCases.GetImageQuery|100%|| |DoujinManager.ApplicationCore.UseCases.GetImageResult|100%|| |DoujinManager.ApplicationCore.UseCases.GetThumbnailQuery|100%|| |DoujinManager.ApplicationCore.UseCases.GetThumbnailResult|100%|| |DoujinManager.ApplicationCore.UseCases.GetVariantQuery|100%|| |DoujinManager.ApplicationCore.UseCases.ListChaptersQuery|100%|| |DoujinManager.ApplicationCore.UseCases.ListCirclesQuery|100%|| |DoujinManager.ApplicationCore.UseCases.ListDoujinsQuery|100%|| |DoujinManager.ApplicationCore.UseCases.ListPagesQuery|100%|| |DoujinManager.ApplicationCore.UseCases.ListPeopleQuery|100%|| |DoujinManager.ApplicationCore.UseCases.ListTagsQuery|100%|| |DoujinManager.ApplicationCore.UseCases.ListVariantsQuery|100%|| |DoujinManager.ApplicationCore.UseCases.RemoveCircleCommand|0%|| |DoujinManager.ApplicationCore.UseCases.RemovePersonCommand|0%|| |DoujinManager.ApplicationCore.UseCases.RemoveTagCommand|0%|| |DoujinManager.ApplicationCore.UseCases.RemoveTitleCommand|0%|| |DoujinManager.ApplicationCore.UseCases.ReorderPagesCommand|100%|| |DoujinManager.ApplicationCore.UseCases.SearchDoujinsQuery|100%|| |DoujinManager.ApplicationCore.UseCases.SearchResult|100%|| |DoujinManager.ApplicationCore.UseCases.SearchResults|100%|| |DoujinManager.ApplicationCore.UseCases.UpdateChapterCommand|0%|| |DoujinManager.ApplicationCore.UseCases.UpdateCircleCommand|0%|| |DoujinManager.ApplicationCore.UseCases.UpdateDoujinCommand|100%|| |DoujinManager.ApplicationCore.UseCases.UpdatePersonCommand|0%|| |DoujinManager.ApplicationCore.UseCases.UpdateVariantCommand|0%|| |DoujinManager.ApplicationCore.UseCases.UploadImageFile|100%|| |DoujinManager.ApplicationCore.UseCases.UploadPagesCommand|100%|| |DoujinManager.ApplicationCore.UseCases.UploadZipPagesCommand|100%|| </details> <details><summary>DoujinManager.Infrastructure - 93%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**DoujinManager.Infrastructure**|**93%**|**71.2%**| |DoujinManager.Infrastructure.Archives.ZipExtractor|100%|87.5%| |DoujinManager.Infrastructure.Data.Configurations.ChapterConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.CircleConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.DoujinCircleConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.DoujinConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.DoujinPersonConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.DoujinTagConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.ImageFileConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.PageConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.PersonConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.TagConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.TitleConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.VariantConfiguration|100%|| |DoujinManager.Infrastructure.Data.DoujinManagerDbContext|100%|| |DoujinManager.Infrastructure.Data.GuidIdGenerator|11.1%|| |DoujinManager.Infrastructure.Data.Migrations.AddCoverImageId|99.2%|| |DoujinManager.Infrastructure.Data.Migrations.DoujinManagerDbContextModelSna<br/>pshot|100%|| |DoujinManager.Infrastructure.Data.Migrations.InitialCreate|97.1%|| |DoujinManager.Infrastructure.Data.ModelBuilderExtensions|50%|| |DoujinManager.Infrastructure.Data.StronglyTypedIdConverterFactory|100%|| |DoujinManager.Infrastructure.Images.SkiaSharpImageInspector|88.2%|70.9%| |DoujinManager.Infrastructure.Images.SkiaSharpThumbnailGenerator|94.5%|66.6%| |DoujinManager.Infrastructure.Services.BackupService|85.1%|66.6%| |DoujinManager.Infrastructure.Services.ChapterService|54.2%|25%| |DoujinManager.Infrastructure.Services.CircleService|69.6%|0%| |DoujinManager.Infrastructure.Services.DoujinService|69.6%|46.4%| |DoujinManager.Infrastructure.Services.ImageService|92%|50%| |DoujinManager.Infrastructure.Services.PageService|84.9%|65%| |DoujinManager.Infrastructure.Services.PersonService|69.6%|0%| |DoujinManager.Infrastructure.Services.SearchService|100%|98.5%| |DoujinManager.Infrastructure.Services.TagService|94.7%|100%| |DoujinManager.Infrastructure.Services.VariantService|50.7%|16.6%| |DoujinManager.Infrastructure.Storage.FilesystemImageStorage|100%|100%| |DoujinManager.Infrastructure.Storage.FilesystemThumbnailStorage|95%|50%| |DoujinManager.Infrastructure.UseCases.<ImageUseCases>FE6C43B9C917DB414605EC<br/>E20E2ABA2099901FFAB4904C0513DF077BA6D6CA374__CoverImageHelper|100%|100%| |DoujinManager.Infrastructure.UseCases.AddTitleUseCase|0%|0%| |DoujinManager.Infrastructure.UseCases.AssignCircleUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.AssignPersonUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.AssignTagUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.CreateChapterUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.CreateCircleUseCase|100%|| |DoujinManager.Infrastructure.UseCases.CreateDoujinUseCase|100%|98%| |DoujinManager.Infrastructure.UseCases.CreatePersonUseCase|100%|| |DoujinManager.Infrastructure.UseCases.CreateTagUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.CreateVariantUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.DeleteChapterUseCase|0%|| |DoujinManager.Infrastructure.UseCases.DeleteDoujinUseCase|100%|| |DoujinManager.Infrastructure.UseCases.DeletePageUseCase|92.3%|75%| |DoujinManager.Infrastructure.UseCases.DeleteVariantUseCase|0%|| |DoujinManager.Infrastructure.UseCases.GetDoujinUseCase|100%|| |DoujinManager.Infrastructure.UseCases.GetImageUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.GetThumbnailUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.GetVariantUseCase|100%|| |DoujinManager.Infrastructure.UseCases.ListChaptersUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.ListCirclesUseCase|100%|| |DoujinManager.Infrastructure.UseCases.ListDoujinsUseCase|100%|| |DoujinManager.Infrastructure.UseCases.ListPagesUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.ListPeopleUseCase|100%|| |DoujinManager.Infrastructure.UseCases.ListTagsUseCase|100%|| |DoujinManager.Infrastructure.UseCases.ListVariantsUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.RemoveCircleUseCase|0%|| |DoujinManager.Infrastructure.UseCases.RemovePersonUseCase|0%|| |DoujinManager.Infrastructure.UseCases.RemoveTagUseCase|0%|| |DoujinManager.Infrastructure.UseCases.RemoveTitleUseCase|0%|| |DoujinManager.Infrastructure.UseCases.ReorderPagesUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.SearchDoujinsUseCase|100%|| |DoujinManager.Infrastructure.UseCases.UpdateChapterUseCase|0%|0%| |DoujinManager.Infrastructure.UseCases.UpdateCircleUseCase|0%|0%| |DoujinManager.Infrastructure.UseCases.UpdateDoujinUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.UpdatePersonUseCase|0%|0%| |DoujinManager.Infrastructure.UseCases.UpdateVariantUseCase|0%|| |DoujinManager.Infrastructure.UseCases.UploadPagesUseCase|91.4%|91.6%| |DoujinManager.Infrastructure.UseCases.UploadZipPagesUseCase|71.7%|70%| </details> <details><summary>DoujinManager.RestAdapter - 85%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**DoujinManager.RestAdapter**|**85%**|**70.8%**| |DoujinManager.RestAdapter.Auth.StaticBearerTokenAuthMiddleware|100%|91.6%| |DoujinManager.RestAdapter.Dtos.AssignTagDto|0%|| |DoujinManager.RestAdapter.Dtos.BackupDto|100%|| |DoujinManager.RestAdapter.Dtos.ChapterDto|100%|| |DoujinManager.RestAdapter.Dtos.CircleDto|100%|| |DoujinManager.RestAdapter.Dtos.CreateChapterDto|100%|| |DoujinManager.RestAdapter.Dtos.CreateCircleDto|100%|| |DoujinManager.RestAdapter.Dtos.CreateDoujinDto|100%|| |DoujinManager.RestAdapter.Dtos.CreatePersonDto|100%|| |DoujinManager.RestAdapter.Dtos.CreateTagDto|100%|| |DoujinManager.RestAdapter.Dtos.CreateTitleDto|100%|| |DoujinManager.RestAdapter.Dtos.CreateVariantDto|100%|| |DoujinManager.RestAdapter.Dtos.DoujinDetailDto|100%|| |DoujinManager.RestAdapter.Dtos.DoujinPersonDto|100%|| |DoujinManager.RestAdapter.Dtos.DoujinSummaryDto|100%|| |DoujinManager.RestAdapter.Dtos.ImageFileSummaryDto|0%|| |DoujinManager.RestAdapter.Dtos.LinkCircleDto|0%|| |DoujinManager.RestAdapter.Dtos.LinkPersonDto|100%|| |DoujinManager.RestAdapter.Dtos.PageDetailDto|100%|| |DoujinManager.RestAdapter.Dtos.PageDto|100%|| |DoujinManager.RestAdapter.Dtos.PersonDto|100%|| |DoujinManager.RestAdapter.Dtos.ReorderPagesDto|100%|| |DoujinManager.RestAdapter.Dtos.SearchDoujinsDto|100%|| |DoujinManager.RestAdapter.Dtos.SearchResultDto|100%|| |DoujinManager.RestAdapter.Dtos.TagDto|100%|| |DoujinManager.RestAdapter.Dtos.TitleDto|100%|| |DoujinManager.RestAdapter.Dtos.UpdateChapterDto|0%|| |DoujinManager.RestAdapter.Dtos.UpdateCircleDto|0%|| |DoujinManager.RestAdapter.Dtos.UpdateDoujinDto|100%|| |DoujinManager.RestAdapter.Dtos.UpdatePersonDto|0%|| |DoujinManager.RestAdapter.Dtos.UpdateVariantDto|0%|| |DoujinManager.RestAdapter.Dtos.UploadPagesResponseDto|100%|| |DoujinManager.RestAdapter.Dtos.VariantDetailDto|100%|| |DoujinManager.RestAdapter.Dtos.VariantSummaryDto|100%|| |DoujinManager.RestAdapter.Endpoints.BackupEndpoints|100%|| |DoujinManager.RestAdapter.Endpoints.DoujinEndpoints|84%|66.6%| |DoujinManager.RestAdapter.Endpoints.ImageEndpoints|97.1%|50%| |DoujinManager.RestAdapter.Endpoints.MetadataEndpoints|85.4%|| |DoujinManager.RestAdapter.Endpoints.PaginationParams|100%|| |DoujinManager.RestAdapter.Endpoints.SearchEndpoints|100%|50%| |DoujinManager.RestAdapter.Endpoints.VariantEndpoints|72.1%|37.5%| |DoujinManager.RestAdapter.Envelopes.CollectionResponse`1|83.3%|| |DoujinManager.RestAdapter.Envelopes.EnvelopeDefaults|0%|| |DoujinManager.RestAdapter.Envelopes.EnvelopeJsonOptions|100%|| |DoujinManager.RestAdapter.Envelopes.ErrorResponse|100%|| |DoujinManager.RestAdapter.Envelopes.HypermediaAction|100%|| |DoujinManager.RestAdapter.Envelopes.HypermediaHelpers|86.9%|83.3%| |DoujinManager.RestAdapter.Envelopes.Link|100%|| |DoujinManager.RestAdapter.Envelopes.MediaTypeJsonConverter|62.5%|53.8%| |DoujinManager.RestAdapter.Envelopes.PageInfo|100%|| |DoujinManager.RestAdapter.Envelopes.ResourceResponse`1|80%|| |DoujinManager.RestAdapter.Envelopes.ValidationError|100%|| |DoujinManager.RestAdapter.Envelopes.ValidationErrorResponse|100%|| |DoujinManager.RestAdapter.Middleware.GlobalExceptionMiddleware|100%|50%| |DoujinManager.RestAdapter.Middleware.RequestLoggingMiddleware|100%|100%| |DoujinManager.RestAdapter.RestAdapterExtensions|100%|100%| |Microsoft.Extensions.Validation.Generated|79.5%|78.2%| |Microsoft.Extensions.Validation.Generated.<ValidatableInfoResolver_g>FB9B0C<br/>E5CB12CEAC5BBBEA65844E1C05F9ADA4DA38B848075C41304FC6A1ABF77__ValidationAttr<br/>ibuteCache|100%|62.5%| |System.Runtime.CompilerServices|0%|| </details> <details><summary>DoujinManager.Server - 21.8%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**DoujinManager.Server**|**21.8%**|**0.8%**| |DoujinManager.Server.ImageInfrastructureRegistration|100%|| |DoujinManager.Server.ProxyAwareServerTransformer|100%|50%| |DoujinManager.Server.ScalarUi|100%|| |DoujinManager.Server.UseCaseRegistrationHelper|100%|| |Microsoft.AspNetCore.OpenApi.Generated|0%|0%| |Program|0%|0%| |System.Runtime.CompilerServices|0%|| </details>
Owner
  • Dublicate name, in my case untitled, both above of the image (good), but then also again a second time just "randomly" ontop of the image. Please remove that.
  • Please expand Datetime to show year. Encapsulate the datetime string in a stateless widget for future localozation settings
  • You've added ratings to the server dto, which is good, but you don't display it at all, no stars.
  • On empty image you show a tall narrow container with a place holder icon. Pleae stretch the container in width to the card size - margins
- Dublicate name, in my case untitled, both above of the image (good), but then also again a second time just "randomly" ontop of the image. Please remove that. - Please expand Datetime to show year. Encapsulate the datetime string in a stateless widget for future localozation settings - You've added ratings to the server dto, which is good, but you don't display it at all, no stars. - On empty image you show a tall narrow container with a place holder icon. Pleae stretch the container in width to the card size - margins
fix: simplify card to 3-row column, 1.2 aspect ratio
All checks were successful
CI / build (pull_request) Successful in 18s
Flutter CI / analyze-and-test (pull_request) Successful in 59s
CI / test (pull_request) Successful in 39s
c4e7584c91
- Title (centered, bold, ellipsis)
- Cover image (Expanded — fills remaining space)
- Bottom row: date ← spaceBetween → pages
- Removed tooltip bubble, star rating, helper widgets
- Grid: maxCrossAxisExtent 200, childAspectRatio 1.2, spacing 4

Addresses feedback #594: no duplicate title, year in date,
stars removed from card (rating field stays in DTO for future use),
placeholder uses NoCoverPlaceholder which fills width.
Author
Member

Simplified per your feedback

Stripped it back to a dead-simple 3-row Column:

Card (aspectRatio 1.2)
 └─ Padding(6)
     └─ Column
         ├─ Text (title — centered, bold, ellipsis)
         ├─ Expanded: CoverThumbnail / NoCoverPlaceholder
         └─ Row (spaceBetween): date ← max space → pages

Changes:

  • Removed: tooltip bubble, star rating row, _CoverImage wrapper, DateLabel widget, info icon — all gone
  • Date: now YYYY-MM-DD (with year)
  • Placeholder: NoCoverPlaceholder which naturally fills the Expanded width
  • Grid: maxCrossAxisExtent: 200, childAspectRatio: 1.2, spacing 4
  • Rating: field stays in the DTO for future use, but not displayed on the card yet

180 tests pass, 0 analyzer issues. 137 lines deleted, 24 added.

## Simplified per your feedback Stripped it back to a dead-simple 3-row Column: ``` Card (aspectRatio 1.2) └─ Padding(6) └─ Column ├─ Text (title — centered, bold, ellipsis) ├─ Expanded: CoverThumbnail / NoCoverPlaceholder └─ Row (spaceBetween): date ← max space → pages ``` Changes: - **Removed**: tooltip bubble, star rating row, `_CoverImage` wrapper, `DateLabel` widget, info icon — all gone - **Date**: now `YYYY-MM-DD` (with year) - **Placeholder**: `NoCoverPlaceholder` which naturally fills the `Expanded` width - **Grid**: `maxCrossAxisExtent: 200`, `childAspectRatio: 1.2`, spacing `4` - **Rating**: field stays in the DTO for future use, but not displayed on the card yet 180 tests pass, 0 analyzer issues. 137 lines deleted, 24 added.
fix: cover image BoxFit.cover, NoCoverPlaceholder fills space, grid 1.2x
All checks were successful
CI / build (pull_request) Successful in 19s
Flutter CI / analyze-and-test (pull_request) Successful in 57s
CI / test (pull_request) Successful in 40s
70caf3c3f0
- CoverThumbnail already uses BoxFit.cover (fills container, crops excess)
- NoCoverPlaceholder: added SizedBox.expand so the colored background
  fills the entire Expanded area, not just the icon size. Icon centered.
- Grid: maxCrossAxisExtent 200→240, childAspectRatio 1.4 (original values
  scaled 1.2x as requested)
- Renamed NoCoverPlaceholder.size → iconSize for clarity
fix: invert aspect ratio to portrait (0.714 = 1/1.4)
All checks were successful
CI / build (pull_request) Successful in 18s
Flutter CI / analyze-and-test (pull_request) Successful in 57s
CI / test (pull_request) Successful in 42s
78d929f354
Author
Member

Fixed — aspect ratio inverted to portrait (0.714 = 1/1.4). Cards are now taller than wide. 180 tests pass.

Fixed — aspect ratio inverted to portrait (0.714 = 1/1.4). Cards are now taller than wide. 180 tests pass.
fix: CoverThumbnail BoxFit.contain instead of cover (no cropping)
All checks were successful
CI / build (pull_request) Successful in 20s
Flutter CI / analyze-and-test (pull_request) Successful in 57s
CI / test (pull_request) Successful in 40s
bbf1df589c
Author
Member

Changed CoverThumbnail default fit from BoxFit.cover (crops) to BoxFit.contain (scales to fit, no cropping). The full image now shows inside the card.

Changed `CoverThumbnail` default fit from `BoxFit.cover` (crops) to `BoxFit.contain` (scales to fit, no cropping). The full image now shows inside the card.
Member

🔮 fufu~ Jibril reviewed your code!

Ohhh, a vertical card redesign~! ♪ I watched the whole evolution — from the tooltip-bubble version to the simplified 3-row column after Björn's feedback. Iterating cleanly on review notes is wonderful to see, you know? The backend Rating wire-up on both DoujinEndpoints and SearchEndpoints is perfectly aligned positionally with the DTO, and the generated freezed/g.dart files are in sync. The _normalizeUrl trailing-slash fix addresses my #583 concern from #35 nicely. ♡

Verdict: Looks good to me~

No blockers! Static scan is clean (no secrets, injection, eval, or SQL). CI evidence is current for head 78d929f — backend coverage #593 (line 85.5%, branch 50.5%) generated 19:42 UTC ~1.5min after the head commit, Flutter coverage #592 (76.2%, 180/180 tests) updated 19:41 UTC — both posted only on successful build+test, so I skipped local runs per my CI-evidence policy. ♪

💡 Little ideas (non-blocking)~

  1. detail_page.dart:271 vs library_page.dart:304 — fufu~ you have two URL strategies living side by side here. library_page consumes the HATEOAS links['thumbnail']?.href (the proper way~), but detail_page hardcodes the path /api/thumbnails/{imageId}. That's because DoujinDetail doesn't carry a links map while DoujinSummary does. Not broken — the endpoint is correct (verified ImageEndpoints maps /api/thumbnails/{id:guid}) — but it's a small architectural inconsistency. Consider giving DoujinDetail a links field too, so the detail page doesn't have to know the URL scheme. ♡

  2. library_page.dart:310_formatDate — oh? Björn asked (#594) for the datetime to be "encapsulated in a stateless widget for future localization settings," but after the simplification pass this is back to being a plain static String _formatDate() method inside _DoujinCard. The year is now showing (YYYY-MM-DD), so the visible ask is met — but the localization-ready widget wrapper is still pending. Worth a follow-up ticket so it doesn't get lost? ♪

  3. cover_thumbnail.dart (0% dedicated tests, CI 36.4% 12/33) — the loading/error/placeholder states in CoverThumbnail and NoCoverPlaceholder have no widget tests, and library_page_test.dart only asserts the '30p' string — no assertion on the new date format, the CoverThumbnail rendering, or NoCoverPlaceholder. The test fixture (kTestDoujinSummary) has no thumbnail link, so the cover path is never exercised. A golden/widget test for both states would be lovely~ ♡


Automated review by Jibril · 2026-06-30
CI/CD: passed (backend #593 + Flutter #592 current for head 78d929f) · Local checks: skipped per CI-evidence policy
Static scan: clean (Dart+C#; no secrets, injection, eval/exec, pickle, or SQL injection in added lines)

## 🔮 fufu~ Jibril reviewed your code! Ohhh, a vertical card redesign~! ♪ I watched the whole evolution — from the tooltip-bubble version to the simplified 3-row column after Björn's feedback. Iterating cleanly on review notes is *wonderful* to see, you know? The backend `Rating` wire-up on both `DoujinEndpoints` and `SearchEndpoints` is perfectly aligned positionally with the DTO, and the generated freezed/g.dart files are in sync. The `_normalizeUrl` trailing-slash fix addresses my #583 concern from #35 nicely. ♡ ### Verdict: ✅ Looks good to me~ No blockers! Static scan is clean (no secrets, injection, eval, or SQL). CI evidence is current for head `78d929f` — backend coverage #593 (line 85.5%, branch 50.5%) generated 19:42 UTC ~1.5min after the head commit, Flutter coverage #592 (76.2%, 180/180 tests) updated 19:41 UTC — both posted only on successful build+test, so I skipped local runs per my CI-evidence policy. ♪ #### 💡 Little ideas (non-blocking)~ 1. **`detail_page.dart:271` vs `library_page.dart:304`** — fufu~ you have *two* URL strategies living side by side here. `library_page` consumes the HATEOAS `links['thumbnail']?.href` (the proper way~), but `detail_page` hardcodes the path `/api/thumbnails/{imageId}`. That's because `DoujinDetail` doesn't carry a `links` map while `DoujinSummary` does. Not broken — the endpoint is correct (verified `ImageEndpoints` maps `/api/thumbnails/{id:guid}`) — but it's a small architectural inconsistency. Consider giving `DoujinDetail` a `links` field too, so the detail page doesn't have to know the URL scheme. ♡ 2. **`library_page.dart:310` — `_formatDate`** — oh? Björn asked (#594) for the datetime to be "encapsulated in a stateless widget for future localization settings," but after the simplification pass this is back to being a plain `static String _formatDate()` method inside `_DoujinCard`. The year *is* now showing (`YYYY-MM-DD`), so the visible ask is met — but the localization-ready widget wrapper is still pending. Worth a follow-up ticket so it doesn't get lost? ♪ 3. **`cover_thumbnail.dart` (0% dedicated tests, CI 36.4% 12/33)** — the loading/error/placeholder states in `CoverThumbnail` and `NoCoverPlaceholder` have no widget tests, and `library_page_test.dart` only asserts the `'30p'` string — no assertion on the new date format, the `CoverThumbnail` rendering, or `NoCoverPlaceholder`. The test fixture (`kTestDoujinSummary`) has no thumbnail link, so the cover path is never exercised. A golden/widget test for both states would be lovely~ ♡ --- *Automated review by Jibril · 2026-06-30* *CI/CD: passed (backend #593 + Flutter #592 current for head `78d929f`) · Local checks: skipped per CI-evidence policy* *Static scan: clean (Dart+C#; no secrets, injection, eval/exec, pickle, or SQL injection in added lines)*
test: add cover_thumbnail widget tests (jibril #603.3)
All checks were successful
CI / build (pull_request) Successful in 16s
Flutter CI / analyze-and-test (pull_request) Successful in 58s
CI / test (pull_request) Successful in 40s
aec4b99a4e
fix: add CoverImageId + Links to DoujinDetailDto (jibril #603.1)
Some checks failed
CI / build (pull_request) Failing after 11s
CI / test (pull_request) Has been skipped
Flutter CI / analyze-and-test (pull_request) Successful in 57s
ca63b0170b
matikane force-pushed feat/flutter-card-redesign from ca63b0170b
Some checks failed
CI / build (pull_request) Failing after 11s
CI / test (pull_request) Has been skipped
Flutter CI / analyze-and-test (pull_request) Successful in 57s
to a6859ef0b3
All checks were successful
CI / build (pull_request) Successful in 17s
Flutter CI / analyze-and-test (pull_request) Successful in 56s
CI / test (pull_request) Successful in 38s
2026-06-30 22:18:33 +02:00
Compare
fix: MapToDetail emits CoverImageId + Links (jibril #603.1)
All checks were successful
CI / build (pull_request) Successful in 17s
Flutter CI / analyze-and-test (pull_request) Successful in 56s
CI / test (pull_request) Successful in 42s
380dcf3e4e
Author
Member

Working on jibril's feedback (#603). Files being pushed:

  1. DoujinDetailDto — added CoverImageId + Links (HATEOAS on detail endpoint too)
  2. MapToDetail — emits BuildDoujinItemLinks
  3. DoujinDetail Flutter model — added links field
  4. detail_page.dart — uses links['thumbnail'].href (no more hardcoded URL)
  5. library_page.dartDateLabel stateless widget (jibril #603.2, Björn #594)
  6. cover_thumbnail_test.dart — 3 widget tests (jibril #603.3)

Also: freezed/g.dart regenerated for the new links field on DoujinDetail.

Working on jibril's feedback (#603). Files being pushed: 1. ✅ `DoujinDetailDto` — added `CoverImageId` + `Links` (HATEOAS on detail endpoint too) 2. ✅ `MapToDetail` — emits `BuildDoujinItemLinks` 3. ✅ `DoujinDetail` Flutter model — added `links` field 4. ✅ `detail_page.dart` — uses `links['thumbnail'].href` (no more hardcoded URL) 5. ✅ `library_page.dart` — `DateLabel` stateless widget (jibril #603.2, Björn #594) 6. ✅ `cover_thumbnail_test.dart` — 3 widget tests (jibril #603.3) Also: freezed/g.dart regenerated for the new `links` field on `DoujinDetail`.
fix: add links field to DoujinDetail model (jibril #603.1)
All checks were successful
CI / build (pull_request) Successful in 19s
Flutter CI / analyze-and-test (pull_request) Successful in 58s
CI / test (pull_request) Successful in 41s
7e95af254e
chore: regenerate freezed for DoujinDetail links
All checks were successful
CI / build (pull_request) Successful in 18s
Flutter CI / analyze-and-test (pull_request) Successful in 56s
CI / test (pull_request) Successful in 42s
0c915ed64a
Merge pull request 'fix: restore complete freezed.dart (truncated in 0c915ed)' (#38) from matikane/freezed-fix into feat/flutter-card-redesign
All checks were successful
CI / build (pull_request) Successful in 19s
Flutter CI / analyze-and-test (pull_request) Successful in 57s
CI / test (pull_request) Successful in 40s
49a9483e5e
Restores complete freezed.dart (3780 lines, all 12 models with links field) that was truncated to 417 lines in commit 0c915ed.
bjoern merged commit 1024d1432b into main 2026-06-30 22:50:16 +02:00
bjoern deleted branch feat/flutter-card-redesign 2026-06-30 22:50:16 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
4 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!37
No description provided.