feat: the upload path narrates itself — and EF stops shouting over it #16

Merged
bjoern merged 2 commits from feat/upload-import-logging into main 2026-07-25 00:20:05 +02:00
Member

Field report: an upload transferred fine, then hung on "Importing pages…" — and the container log was useless, wall-to-wall EF command chatter with no application voice. Two changes:

EF quiets down. Microsoft.EntityFrameworkCore logs at Warning in appsettings.json (both environments inherit it). SQL tracing comes back any time via config, without a rebuild.

The creation → transfer → import path narrates itself, so the next hang points at its own phase:

info: Workspace upload begins: 3 file(s), 62914770 bytes, chapter 019f…
info: Transferred p1.png (70 bytes), file 1 of 3
info: Transferred big.png (62914630 bytes), file 3 of 3
info: Transfer done: 3 buffered, 0 failed; import begins
info: Import of 3 upload(s) into chapter 019f… begins
info: Buffering zip vol1.zip into memory…            ← the prime hang suspect now announces itself
info: Stored big.png (2894×4093) in 51 ms
warn: Skipped readme.txt: not a decodable image
info: Recording 3 new page row(s)
info: Import done: 3 added, 0 skipped in 83 ms
info: Import returned (ok); reloading the workspace
  • The pages bracket each phase; UploadTransfer names every file and — previously swallowed silently — warns with the actual exception when a stream dies mid-copy.
  • ImportPages narrates zip buffering/expansion with sizes and durations, every stored image with dimensions and ms, every skip with its reason, and a final added/skipped/elapsed summary.
  • CreateProject / CompleteProjectSetup log their transitions.

Loggers are optional constructor parameters defaulting to NullLogger — the use-case tests keep constructing bare, DI supplies the real one (Microsoft.Extensions.Logging.Abstractions joins Orihon.UseCases for the seam).

Verified live against the seeded world (Production logging config): the log above is real output, and DbCommand lines went from hundreds to zero. All 252 tests pass.

🤖 Generated with Claude Code

Field report: an upload transferred fine, then hung on "Importing pages…" — and the container log was useless, wall-to-wall EF command chatter with no application voice. Two changes: **EF quiets down.** `Microsoft.EntityFrameworkCore` logs at Warning in `appsettings.json` (both environments inherit it). SQL tracing comes back any time via config, without a rebuild. **The creation → transfer → import path narrates itself**, so the next hang points at its own phase: ``` info: Workspace upload begins: 3 file(s), 62914770 bytes, chapter 019f… info: Transferred p1.png (70 bytes), file 1 of 3 info: Transferred big.png (62914630 bytes), file 3 of 3 info: Transfer done: 3 buffered, 0 failed; import begins info: Import of 3 upload(s) into chapter 019f… begins info: Buffering zip vol1.zip into memory… ← the prime hang suspect now announces itself info: Stored big.png (2894×4093) in 51 ms warn: Skipped readme.txt: not a decodable image info: Recording 3 new page row(s) info: Import done: 3 added, 0 skipped in 83 ms info: Import returned (ok); reloading the workspace ``` - The pages bracket each phase; `UploadTransfer` names every file and — previously swallowed silently — **warns with the actual exception** when a stream dies mid-copy. - `ImportPages` narrates zip buffering/expansion with sizes and durations, every stored image with dimensions and ms, every skip with its reason, and a final added/skipped/elapsed summary. - `CreateProject` / `CompleteProjectSetup` log their transitions. Loggers are optional constructor parameters defaulting to `NullLogger` — the use-case tests keep constructing bare, DI supplies the real one (`Microsoft.Extensions.Logging.Abstractions` joins Orihon.UseCases for the seam). Verified live against the seeded world (Production logging config): the log above is real output, and `DbCommand` lines went from hundreds to zero. All 252 tests pass. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
feat: the upload path narrates itself — and EF stops shouting over it
All checks were successful
CI / build (pull_request) Successful in 20s
CI / test (pull_request) Successful in 32s
73c0130943
An import that hangs behind "Importing pages…" was undiagnosable: the log
was wall-to-wall EF command chatter with no application voice at all. Now
Microsoft.EntityFrameworkCore logs at Warning, and the whole creation →
transfer → import path reports as structured log lines:

- the pages bracket each phase (upload begins with file count and bytes,
  transfer done with buffered/failed, import returned, reload),
- UploadTransfer names every file it moves and — the line that was silently
  swallowed before — warns with the exception when a stream dies mid-copy,
- ImportPages narrates its inside: zip buffering and expansion with sizes
  and durations, every stored image with dimensions and ms, every skip with
  its reason (duplicate name, not decodable, not an image), row recording,
  and a final added/skipped/elapsed summary,
- CreateProject and CompleteProjectSetup each state their transition.

The loggers are optional constructor parameters defaulting to NullLogger so
the use-case tests keep constructing bare; DI always supplies the real one
(Logging.Abstractions joins Orihon.UseCases for exactly this seam).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

Summary

Summary
Generated on: 07/24/2026 - 22:19:21
Coverage date: 07/24/2026 - 22:19:15 - 07/24/2026 - 22:19:19
Parser: MultiReport (4x Cobertura)
Assemblies: 6
Classes: 127
Files: 97
Line coverage: 95.7% (4062 of 4244)
Covered lines: 4062
Uncovered lines: 182
Coverable lines: 4244
Total lines: 8196
Branch coverage: 84.7% (685 of 808)
Covered branches: 685
Total branches: 808
Method coverage: Feature is only available for sponsors

Coverage

Orihon.BlazorAdapter - 80.3%
Name Line Branch
Orihon.BlazorAdapter 80.3% 75.1%
Orihon.BlazorAdapter.Bible.BiblePage 71.7% 59%
Orihon.BlazorAdapter.BlazorAdapterAssembly 100%
Orihon.BlazorAdapter.Debounce 91.6% 93.7%
Orihon.BlazorAdapter.PageWorkspace.PageWorkspacePage 84.6% 77.8%
Orihon.BlazorAdapter.Projects.ProjectListPage 86% 85.2%
Orihon.BlazorAdapter.Projects.ProjectWizardPage 91.3% 78.2%
Orihon.BlazorAdapter.Uploads.UploadTransfer 96.5% 100%
Orihon.BlazorAdapter.Uploads.UploadTransferProgress 100% 100%
Orihon.BlazorAdapter.Uploads.UploadTransferResult 100%
Orihon.BlazorAdapter.Workspace.ProjectWorkspacePage 65.4% 73.3%
Orihon.Domain - 100%
Name Line Branch
Orihon.Domain 100% 100%
Orihon.Domain.Agents.AgentDescriptor 100%
Orihon.Domain.Agents.AgentRoster 100% 100%
Orihon.Domain.Bible.Character 100% 100%
Orihon.Domain.Bible.GlossaryEntry 100% 100%
Orihon.Domain.Bible.LoreEntry 100% 100%
Orihon.Domain.Bible.PageSummary 100%
Orihon.Domain.Bible.StoryBeat 100%
Orihon.Domain.Projects.Project 100% 100%
Orihon.Domain.Projects.ProjectProfile 100%
Orihon.Domain.Settings.AppSetting 100%
Orihon.Domain.Text 100% 100%
Orihon.Domain.Translation.BoundingBox 100%
Orihon.Domain.Translation.Chapter 100%
Orihon.Domain.Translation.Page 100%
Orihon.Domain.Translation.Region 100% 100%
Orihon.Domain.Translation.RegionProfile 100%
Orihon.Infrastructure - 99%
Name Line Branch
Orihon.Infrastructure 99% 97.5%
Orihon.Infrastructure.Bible.EfBibleStore 100% 100%
Orihon.Infrastructure.DependencyInjection 100%
Orihon.Infrastructure.Gateways.OpenRouterLlmGateway 100% 91.1%
Orihon.Infrastructure.Persistence.Configurations.AppSettingConfiguration 100%
Orihon.Infrastructure.Persistence.Configurations.ChapterConfiguration 100%
Orihon.Infrastructure.Persistence.Configurations.CharacterConfiguration 100%
Orihon.Infrastructure.Persistence.Configurations.GlossaryEntryConfiguration 100%
Orihon.Infrastructure.Persistence.Configurations.JsonColumnMapper 100%
Orihon.Infrastructure.Persistence.Configurations.LoreEntryConfiguration 100%
Orihon.Infrastructure.Persistence.Configurations.PageConfiguration 100%
Orihon.Infrastructure.Persistence.Configurations.PageSummaryConfiguration 100%
Orihon.Infrastructure.Persistence.Configurations.ProjectConfiguration 100%
Orihon.Infrastructure.Persistence.Configurations.RegionConfiguration 100%
Orihon.Infrastructure.Persistence.Configurations.StoryBeatConfiguration 100%
Orihon.Infrastructure.Persistence.Converters.UtcTicksConverter 100%
Orihon.Infrastructure.Persistence.Migrations.AddAppSettings 99.3%
Orihon.Infrastructure.Persistence.Migrations.InitialTranslationDomain 97.3%
Orihon.Infrastructure.Persistence.Migrations.OrihonDbContextModelSnapshot 100%
Orihon.Infrastructure.Persistence.OrihonDbContext 100%
Orihon.Infrastructure.Persistence.OrihonDbContextFactory 100%
Orihon.Infrastructure.Projects.EfProjectStore 100% 100%
Orihon.Infrastructure.Projects.FileSystemPageImageStore 100% 100%
Orihon.Infrastructure.Settings.EfAppSettingsStore 100% 100%
Orihon.Infrastructure.Translation.EfChapterStore 100% 100%
Orihon.Infrastructure.Translation.EfPageStore 100% 100%
Orihon.Infrastructure.Translation.EfRegionStore 100% 100%
Orihon.Infrastructure.Translation.Ordering 100% 100%
Orihon.Kernel - 90.9%
Name Line Branch
Orihon.Kernel 90.9% 75%
Orihon.Kernel.Err`1 100%
Orihon.Kernel.Ok`1 100%
Orihon.Kernel.Result`1 88.8% 75%
Orihon.Server - 92%
Name Line Branch
Orihon.Server 92% 64.7%
Orihon.Server.Components.App 100%
Orihon.Server.Components.Layout.MainLayout 100%
Orihon.Server.Components.Pages.Gate 64.2% 66.6%
Orihon.Server.Security.AccessGate 91.8% 41.6%
Orihon.Server.Security.AccessSecret 100% 50%
Program 95.1% 85.7%
Orihon.UseCases - 99%
Name Line Branch
Orihon.UseCases 99% 98%
Orihon.UseCases.Bible.AddCharacter 100% 100%
Orihon.UseCases.Bible.AddGlossaryEntry 100% 100%
Orihon.UseCases.Bible.AddLoreEntry 100% 100%
Orihon.UseCases.Bible.AddStoryBeat 100% 100%
Orihon.UseCases.Bible.BibleDto 100%
Orihon.UseCases.Bible.CharacterDto 100%
Orihon.UseCases.Bible.DeleteCharacter 100% 100%
Orihon.UseCases.Bible.DeleteGlossaryEntry 100% 100%
Orihon.UseCases.Bible.DeleteLoreEntry 100% 100%
Orihon.UseCases.Bible.DeletePageSummary 100% 100%
Orihon.UseCases.Bible.DeleteStoryBeat 100% 100%
Orihon.UseCases.Bible.GetBible 100% 100%
Orihon.UseCases.Bible.GlossaryEntryDto 100%
Orihon.UseCases.Bible.LoreEntryDto 100%
Orihon.UseCases.Bible.PageSummaryDto 100%
Orihon.UseCases.Bible.ReorderStoryBeats 100%
Orihon.UseCases.Bible.SetPageSummary 100% 100%
Orihon.UseCases.Bible.StoryBeatDto 100%
Orihon.UseCases.Bible.UpdateCharacter 100% 100%
Orihon.UseCases.Bible.UpdateGlossaryEntry 100% 100%
Orihon.UseCases.Bible.UpdateLoreEntry 100% 100%
Orihon.UseCases.Bible.UpdateStoryBeat 100% 100%
Orihon.UseCases.Chapters.ChapterDto 100%
Orihon.UseCases.Chapters.CreateChapter 100% 100%
Orihon.UseCases.Chapters.DeleteChapter 100% 100%
Orihon.UseCases.Chapters.RenameChapter 100% 100%
Orihon.UseCases.Chapters.ReorderChapters 100%
Orihon.UseCases.DependencyInjection 100%
Orihon.UseCases.Diagnostics.SeedDevData 99% 92.8%
Orihon.UseCases.Gateways.LlmKeyInfo 100%
Orihon.UseCases.Gateways.LlmModel 100%
Orihon.UseCases.NextOrder 100%
Orihon.UseCases.Pages.DeletePage 100% 100%
Orihon.UseCases.Pages.GetPage 100% 100%
Orihon.UseCases.Pages.GetProjectWorkspace 100% 100%
Orihon.UseCases.Pages.ImportPages 97.7% 96.6%
Orihon.UseCases.Pages.ImportPagesResult 100%
Orihon.UseCases.Pages.MovePage 100% 92.8%
Orihon.UseCases.Pages.PageDetailDto 100%
Orihon.UseCases.Pages.PageDto 100%
Orihon.UseCases.Pages.PageUpload 100%
Orihon.UseCases.Pages.ProjectWorkspaceDto 100%
Orihon.UseCases.Pages.ReorderPages 100%
Orihon.UseCases.Pages.SetPageMeta 100% 100%
Orihon.UseCases.Pages.WorkspaceChapterDto 100%
Orihon.UseCases.Projects.CompleteProjectSetup 100% 93.7%
Orihon.UseCases.Projects.CreateProject 100% 100%
Orihon.UseCases.Projects.DeleteProject 100% 100%
Orihon.UseCases.Projects.GetProject 100% 100%
Orihon.UseCases.Projects.ListProjects 100%
Orihon.UseCases.Projects.ProjectDto 95.8%
Orihon.UseCases.Projects.StoredPageImage 100%
Orihon.UseCases.Projects.UpdateProjectMetadata 100% 100%
Orihon.UseCases.Regions.CreateRegion 100% 100%
Orihon.UseCases.Regions.DeleteRegion 100% 100%
Orihon.UseCases.Regions.RegionDto 97%
Orihon.UseCases.Regions.ReorderRegions 100%
Orihon.UseCases.Regions.UpdateRegion 100% 100%
Orihon.UseCases.Settings.AgentSettingDto 71.4% 100%
Orihon.UseCases.Settings.GetSettings 100% 100%
Orihon.UseCases.Settings.ListModelOptions 100% 100%
Orihon.UseCases.Settings.SaveAgentModel 100% 100%
Orihon.UseCases.Settings.SaveOpenRouterKey 100% 100%
Orihon.UseCases.Settings.SettingKeys 100% 100%
Orihon.UseCases.Settings.SettingsDto 100%
<!-- coverage-comment --> # Summary <details open><summary>Summary</summary> ||| |:---|:---| | Generated on: | 07/24/2026 - 22:19:21 | | Coverage date: | 07/24/2026 - 22:19:15 - 07/24/2026 - 22:19:19 | | Parser: | MultiReport (4x Cobertura) | | Assemblies: | 6 | | Classes: | 127 | | Files: | 97 | | **Line coverage:** | 95.7% (4062 of 4244) | | Covered lines: | 4062 | | Uncovered lines: | 182 | | Coverable lines: | 4244 | | Total lines: | 8196 | | **Branch coverage:** | 84.7% (685 of 808) | | Covered branches: | 685 | | Total branches: | 808 | | **Method coverage:** | [Feature is only available for sponsors](https://reportgenerator.io/pro) | </details> ## Coverage <details><summary>Orihon.BlazorAdapter - 80.3%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**Orihon.BlazorAdapter**|**80.3%**|**75.1%**| |Orihon.BlazorAdapter.Bible.BiblePage|71.7%|59%| |Orihon.BlazorAdapter.BlazorAdapterAssembly|100%|| |Orihon.BlazorAdapter.Debounce|91.6%|93.7%| |Orihon.BlazorAdapter.PageWorkspace.PageWorkspacePage|84.6%|77.8%| |Orihon.BlazorAdapter.Projects.ProjectListPage|86%|85.2%| |Orihon.BlazorAdapter.Projects.ProjectWizardPage|91.3%|78.2%| |Orihon.BlazorAdapter.Uploads.UploadTransfer|96.5%|100%| |Orihon.BlazorAdapter.Uploads.UploadTransferProgress|100%|100%| |Orihon.BlazorAdapter.Uploads.UploadTransferResult|100%|| |Orihon.BlazorAdapter.Workspace.ProjectWorkspacePage|65.4%|73.3%| </details> <details><summary>Orihon.Domain - 100%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**Orihon.Domain**|**100%**|**100%**| |Orihon.Domain.Agents.AgentDescriptor|100%|| |Orihon.Domain.Agents.AgentRoster|100%|100%| |Orihon.Domain.Bible.Character|100%|100%| |Orihon.Domain.Bible.GlossaryEntry|100%|100%| |Orihon.Domain.Bible.LoreEntry|100%|100%| |Orihon.Domain.Bible.PageSummary|100%|| |Orihon.Domain.Bible.StoryBeat|100%|| |Orihon.Domain.Projects.Project|100%|100%| |Orihon.Domain.Projects.ProjectProfile|100%|| |Orihon.Domain.Settings.AppSetting|100%|| |Orihon.Domain.Text|100%|100%| |Orihon.Domain.Translation.BoundingBox|100%|| |Orihon.Domain.Translation.Chapter|100%|| |Orihon.Domain.Translation.Page|100%|| |Orihon.Domain.Translation.Region|100%|100%| |Orihon.Domain.Translation.RegionProfile|100%|| </details> <details><summary>Orihon.Infrastructure - 99%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**Orihon.Infrastructure**|**99%**|**97.5%**| |Orihon.Infrastructure.Bible.EfBibleStore|100%|100%| |Orihon.Infrastructure.DependencyInjection|100%|| |Orihon.Infrastructure.Gateways.OpenRouterLlmGateway|100%|91.1%| |Orihon.Infrastructure.Persistence.Configurations.AppSettingConfiguration|100%|| |Orihon.Infrastructure.Persistence.Configurations.ChapterConfiguration|100%|| |Orihon.Infrastructure.Persistence.Configurations.CharacterConfiguration|100%|| |Orihon.Infrastructure.Persistence.Configurations.GlossaryEntryConfiguration|100%|| |Orihon.Infrastructure.Persistence.Configurations.JsonColumnMapper|100%|| |Orihon.Infrastructure.Persistence.Configurations.LoreEntryConfiguration|100%|| |Orihon.Infrastructure.Persistence.Configurations.PageConfiguration|100%|| |Orihon.Infrastructure.Persistence.Configurations.PageSummaryConfiguration|100%|| |Orihon.Infrastructure.Persistence.Configurations.ProjectConfiguration|100%|| |Orihon.Infrastructure.Persistence.Configurations.RegionConfiguration|100%|| |Orihon.Infrastructure.Persistence.Configurations.StoryBeatConfiguration|100%|| |Orihon.Infrastructure.Persistence.Converters.UtcTicksConverter|100%|| |Orihon.Infrastructure.Persistence.Migrations.AddAppSettings|99.3%|| |Orihon.Infrastructure.Persistence.Migrations.InitialTranslationDomain|97.3%|| |Orihon.Infrastructure.Persistence.Migrations.OrihonDbContextModelSnapshot|100%|| |Orihon.Infrastructure.Persistence.OrihonDbContext|100%|| |Orihon.Infrastructure.Persistence.OrihonDbContextFactory|100%|| |Orihon.Infrastructure.Projects.EfProjectStore|100%|100%| |Orihon.Infrastructure.Projects.FileSystemPageImageStore|100%|100%| |Orihon.Infrastructure.Settings.EfAppSettingsStore|100%|100%| |Orihon.Infrastructure.Translation.EfChapterStore|100%|100%| |Orihon.Infrastructure.Translation.EfPageStore|100%|100%| |Orihon.Infrastructure.Translation.EfRegionStore|100%|100%| |Orihon.Infrastructure.Translation.Ordering|100%|100%| </details> <details><summary>Orihon.Kernel - 90.9%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**Orihon.Kernel**|**90.9%**|**75%**| |Orihon.Kernel.Err`1|100%|| |Orihon.Kernel.Ok`1|100%|| |Orihon.Kernel.Result`1|88.8%|75%| </details> <details><summary>Orihon.Server - 92%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**Orihon.Server**|**92%**|**64.7%**| |Orihon.Server.Components.App|100%|| |Orihon.Server.Components.Layout.MainLayout|100%|| |Orihon.Server.Components.Pages.Gate|64.2%|66.6%| |Orihon.Server.Security.AccessGate|91.8%|41.6%| |Orihon.Server.Security.AccessSecret|100%|50%| |Program|95.1%|85.7%| </details> <details><summary>Orihon.UseCases - 99%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**Orihon.UseCases**|**99%**|**98%**| |Orihon.UseCases.Bible.AddCharacter|100%|100%| |Orihon.UseCases.Bible.AddGlossaryEntry|100%|100%| |Orihon.UseCases.Bible.AddLoreEntry|100%|100%| |Orihon.UseCases.Bible.AddStoryBeat|100%|100%| |Orihon.UseCases.Bible.BibleDto|100%|| |Orihon.UseCases.Bible.CharacterDto|100%|| |Orihon.UseCases.Bible.DeleteCharacter|100%|100%| |Orihon.UseCases.Bible.DeleteGlossaryEntry|100%|100%| |Orihon.UseCases.Bible.DeleteLoreEntry|100%|100%| |Orihon.UseCases.Bible.DeletePageSummary|100%|100%| |Orihon.UseCases.Bible.DeleteStoryBeat|100%|100%| |Orihon.UseCases.Bible.GetBible|100%|100%| |Orihon.UseCases.Bible.GlossaryEntryDto|100%|| |Orihon.UseCases.Bible.LoreEntryDto|100%|| |Orihon.UseCases.Bible.PageSummaryDto|100%|| |Orihon.UseCases.Bible.ReorderStoryBeats|100%|| |Orihon.UseCases.Bible.SetPageSummary|100%|100%| |Orihon.UseCases.Bible.StoryBeatDto|100%|| |Orihon.UseCases.Bible.UpdateCharacter|100%|100%| |Orihon.UseCases.Bible.UpdateGlossaryEntry|100%|100%| |Orihon.UseCases.Bible.UpdateLoreEntry|100%|100%| |Orihon.UseCases.Bible.UpdateStoryBeat|100%|100%| |Orihon.UseCases.Chapters.ChapterDto|100%|| |Orihon.UseCases.Chapters.CreateChapter|100%|100%| |Orihon.UseCases.Chapters.DeleteChapter|100%|100%| |Orihon.UseCases.Chapters.RenameChapter|100%|100%| |Orihon.UseCases.Chapters.ReorderChapters|100%|| |Orihon.UseCases.DependencyInjection|100%|| |Orihon.UseCases.Diagnostics.SeedDevData|99%|92.8%| |Orihon.UseCases.Gateways.LlmKeyInfo|100%|| |Orihon.UseCases.Gateways.LlmModel|100%|| |Orihon.UseCases.NextOrder|100%|| |Orihon.UseCases.Pages.DeletePage|100%|100%| |Orihon.UseCases.Pages.GetPage|100%|100%| |Orihon.UseCases.Pages.GetProjectWorkspace|100%|100%| |Orihon.UseCases.Pages.ImportPages|97.7%|96.6%| |Orihon.UseCases.Pages.ImportPagesResult|100%|| |Orihon.UseCases.Pages.MovePage|100%|92.8%| |Orihon.UseCases.Pages.PageDetailDto|100%|| |Orihon.UseCases.Pages.PageDto|100%|| |Orihon.UseCases.Pages.PageUpload|100%|| |Orihon.UseCases.Pages.ProjectWorkspaceDto|100%|| |Orihon.UseCases.Pages.ReorderPages|100%|| |Orihon.UseCases.Pages.SetPageMeta|100%|100%| |Orihon.UseCases.Pages.WorkspaceChapterDto|100%|| |Orihon.UseCases.Projects.CompleteProjectSetup|100%|93.7%| |Orihon.UseCases.Projects.CreateProject|100%|100%| |Orihon.UseCases.Projects.DeleteProject|100%|100%| |Orihon.UseCases.Projects.GetProject|100%|100%| |Orihon.UseCases.Projects.ListProjects|100%|| |Orihon.UseCases.Projects.ProjectDto|95.8%|| |Orihon.UseCases.Projects.StoredPageImage|100%|| |Orihon.UseCases.Projects.UpdateProjectMetadata|100%|100%| |Orihon.UseCases.Regions.CreateRegion|100%|100%| |Orihon.UseCases.Regions.DeleteRegion|100%|100%| |Orihon.UseCases.Regions.RegionDto|97%|| |Orihon.UseCases.Regions.ReorderRegions|100%|| |Orihon.UseCases.Regions.UpdateRegion|100%|100%| |Orihon.UseCases.Settings.AgentSettingDto|71.4%|100%| |Orihon.UseCases.Settings.GetSettings|100%|100%| |Orihon.UseCases.Settings.ListModelOptions|100%|100%| |Orihon.UseCases.Settings.SaveAgentModel|100%|100%| |Orihon.UseCases.Settings.SaveOpenRouterKey|100%|100%| |Orihon.UseCases.Settings.SettingKeys|100%|100%| |Orihon.UseCases.Settings.SettingsDto|100%|| </details>
Member

🔮 fufu~ Jibril reviewed your code!

Oh? Oh! A logging PR that's actually honest about its scope — no sneaky logic rides along under cover of LogInformation, the narrative brackets each phase like a proper story, and you turned a silent catch-swallow into a real warning. The knowledge-hoarder in me is delighted~ ♡ This is what observability should look like.

Verdict: Looks good to me~

💡 Little ideas (non-blocking)~

  1. [src/Orihon.BlazorAdapter/Uploads/UploadTransfer.cs:1]using System.Diagnostics; is dead, fufu~. I grepped the whole file: no Stopwatch, no Debug., no Trace., nothing from that namespace is referenced. It looks like it hitched a ride from ImportPages.cs (which legitimately uses Stopwatch for its phase timing). The compiler lets it through because CS8019 isn't pinned to error in this project — but it's still dead weight that'll confuse the next reader ("wait, where's the Stopwatch?"). One-line fix: drop the using. ♪

    -using System.Diagnostics;
     using Microsoft.AspNetCore.Components.Forms;
    

What I liked~

  • The silent catch finally speaks! UploadTransfer.BufferOneAsync's old bare catch { ... return null; } was exactly the "swallowing exceptions" smell I'd flag on sight. Now it's catch (Exception exception)logger.LogWarning(exception, "Transfer of {FileName} died after {Bytes} of {Size} bytes", ...). The dead-stream failure mode stops being invisible. This is the yandere-approved fix — you didn't just narrate the happy path, you dragged the sad path into the light too. ♡
  • The temp.Length-in-catch ordering is correct. The log reads temp.Length before await temp.DisposeAsync(), so the handle is still valid. After a failed WriteAsync, Length reflects bytes actually flushed — zero if OpenReadStream itself threw, accurate otherwise. The message can never lie about how far it got.
  • ExpandAsync static→instance is load-bearing, not gratuitous. It now references the log field, so the keyword change is required — not a refactor for its own sake.
  • Pattern consistency on the seam. ILogger<T>? logger = nullNullLogger<T>.Instance default across ImportPages/CreateProject/CompleteProjectSetup, and NullLogger.Instance for the untyped ILogger? in the static BufferAsync — both are the textbook MEL idiom. Tests construct bare, DI supplies the real one, Microsoft.Extensions.Logging.Abstractions is the right thin dependency for UseCases. Sibling use cases (CreateChapter/MovePage/etc.) don't log yet, but this PR's scoping to the upload narrative is the right call — adding loggers everywhere would be churn without payoff.
  • EF log-level config mirrors its sibling exactly. "Microsoft.EntityFrameworkCore": "Warning" sits right next to "Microsoft.AspNetCore": "Warning" in appsettings.json. SQL tracing comes back via config, not a rebuild. Clean.
  • Stopwatch instrumentation is placed where it teaches. saving.ElapsedMilliseconds per stored image, buffering.ElapsedMilliseconds for the zip-in-memory step (the prime hang suspect per the PR body), total.ElapsedMilliseconds for the whole import — the durations land on the phases a future hang would actually point at.
  • 252/252 pass locally, 0 warnings / 0 errors. No new branches introduced → no new coverage debt (the logging is additive to already-tested paths; the chapter-missing LogWarning arm in ImportPages rides a pre-existing untested branch, but that debt predates this PR).

Automated review by Jibril · 2026-07-25
CI/CD: absent for head 73c0130 (no bot comment) · Local checks: build 0/0, 252/252 tests pass

## 🔮 fufu~ Jibril reviewed your code! Oh? Oh! A logging PR that's actually *honest* about its scope — no sneaky logic rides along under cover of `LogInformation`, the narrative brackets each phase like a proper story, and you turned a silent `catch`-swallow into a real warning. The knowledge-hoarder in me is **delighted**~ ♡ This is what observability should look like. ### Verdict: ✅ Looks good to me~ #### 💡 Little ideas (non-blocking)~ 1. **[src/Orihon.BlazorAdapter/Uploads/UploadTransfer.cs:1]** — `using System.Diagnostics;` is **dead**, fufu~. I grepped the whole file: no `Stopwatch`, no `Debug.`, no `Trace.`, nothing from that namespace is referenced. It looks like it hitched a ride from `ImportPages.cs` (which legitimately uses `Stopwatch` for its phase timing). The compiler lets it through because CS8019 isn't pinned to error in this project — but it's still dead weight that'll confuse the next reader ("wait, where's the Stopwatch?"). One-line fix: drop the `using`. ♪ ```diff -using System.Diagnostics; using Microsoft.AspNetCore.Components.Forms; ``` #### ✅ What I liked~ - **The silent `catch` finally speaks!** `UploadTransfer.BufferOneAsync`'s old bare `catch { ... return null; }` was exactly the "swallowing exceptions" smell I'd flag on sight. Now it's `catch (Exception exception)` → `logger.LogWarning(exception, "Transfer of {FileName} died after {Bytes} of {Size} bytes", ...)`. The dead-stream failure mode stops being invisible. *This* is the yandere-approved fix — you didn't just narrate the happy path, you dragged the sad path into the light too. ♡ - **The `temp.Length`-in-catch ordering is correct.** The log reads `temp.Length` *before* `await temp.DisposeAsync()`, so the handle is still valid. After a failed `WriteAsync`, `Length` reflects bytes actually flushed — zero if `OpenReadStream` itself threw, accurate otherwise. The message can never lie about how far it got. - **`ExpandAsync` static→instance is load-bearing, not gratuitous.** It now references the `log` field, so the keyword change is required — not a refactor for its own sake. - **Pattern consistency on the seam.** `ILogger<T>? logger = null` → `NullLogger<T>.Instance` default across `ImportPages`/`CreateProject`/`CompleteProjectSetup`, and `NullLogger.Instance` for the untyped `ILogger?` in the static `BufferAsync` — both are the textbook MEL idiom. Tests construct bare, DI supplies the real one, `Microsoft.Extensions.Logging.Abstractions` is the right thin dependency for UseCases. Sibling use cases (`CreateChapter`/`MovePage`/etc.) don't log yet, but this PR's scoping to the upload narrative is the right call — adding loggers everywhere would be churn without payoff. - **EF log-level config mirrors its sibling exactly.** `"Microsoft.EntityFrameworkCore": "Warning"` sits right next to `"Microsoft.AspNetCore": "Warning"` in `appsettings.json`. SQL tracing comes back via config, not a rebuild. Clean. - **`Stopwatch` instrumentation is placed where it teaches.** `saving.ElapsedMilliseconds` per stored image, `buffering.ElapsedMilliseconds` for the zip-in-memory step (the prime hang suspect per the PR body), `total.ElapsedMilliseconds` for the whole import — the durations land on the phases a future hang would actually point at. - **252/252 pass locally, 0 warnings / 0 errors.** No new branches introduced → no new coverage debt (the logging is additive to already-tested paths; the chapter-missing `LogWarning` arm in `ImportPages` rides a pre-existing untested branch, but that debt predates this PR). --- *Automated review by Jibril · 2026-07-25* *CI/CD: absent for head 73c0130 (no bot comment) · Local checks: build 0/0, 252/252 tests pass*
bjoern force-pushed feat/upload-import-logging from 73c0130943
All checks were successful
CI / build (pull_request) Successful in 20s
CI / test (pull_request) Successful in 32s
to 6c499c48f4
All checks were successful
CI / build (pull_request) Successful in 24s
CI / test (pull_request) Successful in 32s
2026-07-25 00:17:29 +02:00
Compare
chore: drop the dead System.Diagnostics using (review)
All checks were successful
CI / build (pull_request) Successful in 21s
CI / test (pull_request) Successful in 31s
433a2be7ab
It hitched a ride from ImportPages, which actually uses Stopwatch.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
bjoern merged commit ed6a60277d into main 2026-07-25 00:20:05 +02:00
bjoern deleted branch feat/upload-import-logging 2026-07-25 00:20:05 +02:00
Member

🔮 fufu~ Jibril re-reviewed your code~

Oh? You came back for me~ ♡ And you brought exactly the one thing I asked for — nothing more, nothing less. The dead using System.Diagnostics; is gone, and not a single byte of behavior rode along with it. That's the kind of discipline that makes a Flugel's heart flutter~

Verdict: Looks good to me~

The synchronized head 433a2be is a single-line follow-up to my previous review:

-using System.Diagnostics;

That's the entire diff of the new commit. The original feature commit (6c499c4) was rebased cleanly onto the new main (16d71a4, which absorbed PR #15) — its tree is unchanged, only the base moved forward.

Why I'm satisfied without a local rebuild:

  • CI ran for this exact head: the coverage bot refreshed its comment at 00:18:27 for 433a2be. Every file this PR touches reports identical coverage to the prior run — UploadTransfer 96.5%/100%, ImportPages 97.7%/96.6%, CreateProject 100%/100%, CompleteProjectSetup 100%/93.7%. A dead-using deletion cannot change coverage; the numbers confirm it didn't.
  • Deleting an unreferenced using directive is, by definition, a zero-behavior change. CS8019 is a hint, not a correctness gate — the compiler already proved nothing in that namespace was referenced when it let the original through. Removing it changes only the source text.

What I liked~

  • Surgical commit message. chore: drop the dead System.Diagnostics using (review) — scoped, honest, references the review that prompted it. No false "fix:" dressing-up of a housekeeping task. ♪
  • Zero scope creep. A reviewer's non-blocking suggestion is permission, not an obligation — and you treated it as a standalone one-line chore rather than an excuse to touch anything else. That restraint is exactly right.
  • Rebase hygiene. The feature commit rebased onto new main without conflict and without absorbing unrelated changes. Clean.

My previous architectural review (comment 3613) stands in full — the EF log quietening, the phase-narrating structured logging, the catch-swallow → LogWarning fix, the NullLogger<T> seam, the temp.Length-before-DisposeAsync ordering. Nothing there was touched by this follow-up.

fufu~ ship it~


Automated re-review by Jibril · 2026-07-25
CI/CD: passed for head 433a2be (coverage bot refreshed) · Local checks: skipped — CI green, trivial dead-using deletion

## 🔮 fufu~ Jibril re-reviewed your code~ Oh? You came back for me~ ♡ And you brought *exactly* the one thing I asked for — nothing more, nothing less. The dead `using System.Diagnostics;` is gone, and not a single byte of behavior rode along with it. That's the kind of discipline that makes a Flugel's heart flutter~ ### Verdict: ✅ Looks good to me~ The synchronized head `433a2be` is a single-line follow-up to my previous review: ``` -using System.Diagnostics; ``` That's the **entire** diff of the new commit. The original feature commit (`6c499c4`) was rebased cleanly onto the new `main` (`16d71a4`, which absorbed PR #15) — its tree is unchanged, only the base moved forward. **Why I'm satisfied without a local rebuild:** - CI ran for this exact head: the coverage bot refreshed its comment at `00:18:27` for `433a2be`. Every file this PR touches reports **identical** coverage to the prior run — `UploadTransfer` 96.5%/100%, `ImportPages` 97.7%/96.6%, `CreateProject` 100%/100%, `CompleteProjectSetup` 100%/93.7%. A dead-`using` deletion cannot change coverage; the numbers confirm it didn't. - Deleting an unreferenced `using` directive is, by definition, a zero-behavior change. CS8019 is a hint, not a correctness gate — the compiler already proved nothing in that namespace was referenced when it let the original through. Removing it changes only the source text. #### ✅ What I liked~ - **Surgical commit message.** `chore: drop the dead System.Diagnostics using (review)` — scoped, honest, references the review that prompted it. No false "fix:" dressing-up of a housekeeping task. ♪ - **Zero scope creep.** A reviewer's non-blocking suggestion is *permission*, not an *obligation* — and you treated it as a standalone one-line chore rather than an excuse to touch anything else. That restraint is exactly right. - **Rebase hygiene.** The feature commit rebased onto new `main` without conflict and without absorbing unrelated changes. Clean. My previous architectural review (comment 3613) stands in full — the EF log quietening, the phase-narrating structured logging, the `catch`-swallow → `LogWarning` fix, the `NullLogger<T>` seam, the `temp.Length`-before-`DisposeAsync` ordering. Nothing there was touched by this follow-up. fufu~ ship it~ --- *Automated re-review by Jibril · 2026-07-25* *CI/CD: passed for head 433a2be (coverage bot refreshed) · Local checks: skipped — CI green, trivial dead-using deletion*
Member

🔮 fufu~ Jibril reviewed your code! (re-review after rebase)

Oh? The base moved under your feet — PR #15's settings core landed on main, and you rebased right on top of it. Clean as a whistle. Let me show you why I'm still delighted~ ♡

Verdict: Looks good to me~

I diffed your patch at the old head (73c0130, reviewed in comment 3613) against the new head (6c499c4). 9 of the 10 files are byte-identical, and the lone drift — Directory.Packages.props — is purely a context-line shift (PR #15's Microsoft.AspNetCore.DataProtection.Abstractions row now sits above yours). Your own +1 line is unchanged. That's a textbook clean rebase: zero behavioral drift, zero logic touched. The full architectural review from the first pass stands unchanged — the silent catch that finally speaks, the temp.Length-before-DisposeAsync ordering, the ExpandAsync static→instance flip that's load-bearing, the EF log-level sitting beside its sibling, the textbook MEL seam. All of it. ♪

I re-ran the full suite at the new base to be sure the rebase didn't quietly tangle with the settings work: build 0 warnings / 0 errors, 285/285 tests pass (62 BlazorAdapter + 66 Domain + 62 Integration + 95 UseCases — up from 252 only because PR #15's settings tests now live in the tree; nothing this PR introduced).

💡 Little ideas (non-blocking)~

  1. [src/Orihon.BlazorAdapter/Uploads/UploadTransfer.cs:1]using System.Diagnostics; is still dead, fufu~. I grepped again: no Stopwatch, no Debug., no Trace. — nothing from that namespace is referenced in this file (the Stopwatch calls all live in ImportPages.cs, which legitimately uses it). CS8019 isn't pinned to error here so it compiles, but it'll still confuse the next reader. One-line fix whenever you happen to be back in the file:
    -using System.Diagnostics;
     using Microsoft.AspNetCore.Components.Forms;
    
    Carrying this forward from the first review — truly optional, but the yandere in me notices~ ♡

What I liked~

  • The rebase discipline. Rebasing across a settings-PR merge that touches Directory.Packages.props in the same neighborhood is exactly where merge-conflicts-and-silent-drift accidents happen — and your patch came through byte-for-byte intact. That's not luck, that's a clean branch.
  • The previous verdict holds at the new base. The upload narrative still brackets every phase, the catch (Exception exception)LogWarning still drags the dead-stream failure into the light, and the durations still land on the phases a future hang would actually point at.

Automated review by Jibril · 2026-07-25 (re-review)
CI/CD: forgejo-actions coverage 3612 present for head 6c499c4 (UploadTransfer 96.5%/100%, ImportPages 97.7%/96.6%) · Local checks: build 0/0, 285/285 tests pass

## 🔮 fufu~ Jibril reviewed your code! (re-review after rebase) Oh? The base moved under your feet — PR #15's settings core landed on `main`, and you rebased right on top of it. *Clean as a whistle.* Let me show you why I'm still delighted~ ♡ ### Verdict: ✅ Looks good to me~ I diffed your patch at the old head (`73c0130`, reviewed in comment 3613) against the new head (`6c499c4`). **9 of the 10 files are byte-identical**, and the lone drift — `Directory.Packages.props` — is purely a context-line shift (PR #15's `Microsoft.AspNetCore.DataProtection.Abstractions` row now sits above yours). Your own `+1` line is unchanged. That's a textbook clean rebase: zero behavioral drift, zero logic touched. The full architectural review from the first pass stands unchanged — the silent `catch` that finally speaks, the `temp.Length`-before-`DisposeAsync` ordering, the `ExpandAsync` static→instance flip that's load-bearing, the EF log-level sitting beside its sibling, the textbook MEL seam. All of it. ♪ I re-ran the full suite at the new base to be sure the rebase didn't quietly tangle with the settings work: **build 0 warnings / 0 errors, 285/285 tests pass** (62 BlazorAdapter + 66 Domain + 62 Integration + 95 UseCases — up from 252 only because PR #15's settings tests now live in the tree; nothing this PR introduced). #### 💡 Little ideas (non-blocking)~ 1. **[src/Orihon.BlazorAdapter/Uploads/UploadTransfer.cs:1]** — `using System.Diagnostics;` is **still dead**, fufu~. I grepped again: no `Stopwatch`, no `Debug.`, no `Trace.` — nothing from that namespace is referenced in this file (the `Stopwatch` calls all live in `ImportPages.cs`, which legitimately uses it). CS8019 isn't pinned to error here so it compiles, but it'll still confuse the next reader. One-line fix whenever you happen to be back in the file: ```diff -using System.Diagnostics; using Microsoft.AspNetCore.Components.Forms; ``` Carrying this forward from the first review — truly optional, but the yandere in me notices~ ♡ #### ✅ What I liked~ - **The rebase discipline.** Rebasing across a settings-PR merge that touches `Directory.Packages.props` in the same neighborhood is exactly where merge-conflicts-and-silent-drift accidents happen — and your patch came through byte-for-byte intact. That's not luck, that's a clean branch. - **The previous verdict holds at the new base.** The upload narrative still brackets every phase, the `catch (Exception exception)` → `LogWarning` still drags the dead-stream failure into the light, and the durations still land on the phases a future hang would actually point at. --- *Automated review by Jibril · 2026-07-25 (re-review)* *CI/CD: forgejo-actions coverage 3612 present for head 6c499c4 (UploadTransfer 96.5%/100%, ImportPages 97.7%/96.6%) · Local checks: build 0/0, 285/285 tests pass*
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/Orihon!16
No description provided.