Phase 4: convert live pipeline to Result chains (functional refactor plan)#74
Merged
Merged
Conversation
- Pages return Result<T, ScrapeError>; ImagePageOutcome DU (ScrapedImage | SkippedImage + RawTags) replaces ImagePageResult - Tag persistence pinned to ImagePageService (single save); retry-once expressed as Result failure, no throw - IImageDimensionReader + ImageDimensionReadFailed; ImportFailed/ValidationFailed ScrapeError cases - ConfigurationSaver Result surface + pure CategoryMerger - Pure ClassificationMatcher; single SaveChangesAsync pinned behaviourally (sealed AppDbContext blocks received-calls pin) - ImportExportService ScrapeError migration + Validation<T> multi-error accumulation (all invalid rows reported) - SearchWorkflow.RunAsync(ct) -> Result<Unit, ScrapeError> - FunctionalParadigm RetryOnceAsync extension Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- All page public methods return Result<T, ScrapeError>; Playwright failures wrapped via Try -> PageLoadFailed; EnsurePageAsync replaces inline page ??= init - ImagePageOutcome DU (ScrapedImage | SkippedImage + RawTags) replaces ImagePageResult; tag persistence moved to ImagePageService (single save); ImagePage drops IScrapedTagRepository - ImagePageService: Result chain, RetryOnceAsync around IImageRetriever (failure = ImageDownloadFailed Result, no throw), IImageDimensionReader + ImageDimensions/Factory extraction, FileSize from downloaded bytes - ConfigurationSaver -> Result + pure CategoryMerger; FileClassificationService -> Result + pure ClassificationMatcher, stray early SaveChangesAsync removed, EnsureTracked prevents duplicate category inserts - ImportExportService -> ScrapeError + Validation<T> multi-error DTO validation (ImportFailed/ValidationFailed cases) - SearchWorkflow.RunAsync(ct) -> Result<Unit, ScrapeError> with LogFailure; Subscriptions/TopWallpapers minimally adapted, failure propagation pinned (Phase 5 does full conversion) - FunctionalParadigm: RetryOnceAsync; Result/Exceptional hierarchies closed (private protected ctor) so Match is exhaustive and test doubles cannot proxy foreign subtypes - Views/MainWindow bridge via ToStringError (full rework Phase 6) - SqliteConnectionStringProvider test aligned with new config key Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
astar-development-jb
approved these changes
Jul 6, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Phase 4 of
docs/wallpaper-scrapper-functional-refactor-plan.md— the live scrape pipeline now flows throughResult<T, ScrapeError>end to end.Pipeline conversion
SearchResultsPage,SubscriptionsImagesListPage,TopWallpapersPage,ImagePage): every public method returnsResult<T, ScrapeError>; Playwright failures wrapped once viaTry→PageLoadFailed; try/catch-log-rethrow shells removed (logging moves toLogFailureat workflow level);page ??=lazy init →EnsurePageAsync()per classImagePageOutcomeDU (ScrapedImage|SkippedImage, both carryingRawTags) replacesImagePageResult; scraped-tag persistence moved toImagePageService— exactly one save per page (permanently fixes the historical B2 double-save);ImagePagedropsIScrapedTagRepositoryImagePageService.ProcessImagePageAsync→ Result chain (outcome → download → save → broadcast → persist → classify); retry-once expressed via newRetryOnceAsyncaroundIImageRetrieveronly; final failure surfaces asImageDownloadFailedResult (retires the Phase 3InvalidOperationExceptiondeviation); SkiaSharp probe extracted to injectableIImageDimensionReaderConfigurationSaver→Result<Unit, ScrapeError>+ pureCategoryMerger.MergeCategoriesFileClassificationService→Result<Unit, ScrapeError>+ pureClassificationMatcher; stray earlySaveChangesAsyncremoved (single trailing save);EnsureTrackedstops EF re-inserting existing categories reached via navigationImportExportService→ScrapeErrorerrors +Validation<T>DTO validation reporting ALL invalid rows at once (ImportFailed/ValidationFailedcases)SearchWorkflow.RunAsync(ct)→Result<Unit, ScrapeError>, singleLogFailure, redundant logger param droppedFunctionalParadigm
RetryExtensions.RetryOnceAsync(pinned: 1 attempt on success, fail-then-success = 2 attempts + 1 retry callback, fail-fail = second failure returned)Result<T,E>andExceptional<T>hierarchies closed (private protectedctor):Matchis now genuinely exhaustive and test doubles can no longer manufacture foreign subtypes — root-cause fix that kept the "no pattern-matching on Result" style rule intactScope boundaries (per plan)
GetTheImagePagesAsyncrestored + pinned (full conversion is Phase 5)MainWindowbridge viaToStringError(full composition rework is Phase 6)SqliteConnectionStringProviderconfig-key change (ScrapeConfiguration:ConnectionStrings:Sqlite) and its test alignmentReview
c-sharp-reviewer: 2 blockers (missing
IImageDimensionReaderDI registration; swallowed Results in Subscriptions/TopWallpapers loops), 4 should-fix, 3 nits — all fixed and re-verified. Repeat-offence check vs Phases 0–3: 3 repeats found (2× undeclared/unpinned deviation, 1× factory skip), all corrected; guard-clause and consistency-sweep classes were clean this phase.Test plan
TreatWarningsAsErrors🤖 Generated with Claude Code