feat: Phase 1 · 6/7 — project list & creation wizard #10

Merged
bjoern merged 3 commits from feat/p1-ui-projects into main 2026-07-24 21:18:03 +02:00
Member

Cut 6 of the Phase-1 stack (after #9): the first real screens, all Kagaku.UI.

Scope

  • Project list at / (replacing the skeleton placeholder): title/byline/draft badge per project, empty state pointing at New project, ready projects open their workspace, drafts resume their wizard (ADR 0020), and hard delete behind the naming confirmation modal — one click opens it, only Delete forever acts, Cancel changes nothing (project-list story; ADR 0022's destructive exception).
  • The three-step wizard on the stored state machine: step 1 names the work and creates it immediately with its default chapter; step 2 uploads images/zips; step 3 is the honest Phase-2 stub, finishing setup_done → ready into the workspace. Resume rules per the story: named → step 2, images_uploaded+ → step 3, ready → no wizard, vanished → back to the list.
  • A story bug caught by the new upload test: after an import, the page reloaded and re-derived the step from the now-advanced state — jumping straight to step 3, so the user never saw the thumbnails or the skip report ("I can see the pages I added before continuing"). The wizard now stays on step 2 after an upload, showing what landed and what was skipped, with Continue unlocked.
  • App.razor adds title.js/overlay.js (DocumentTitle and the modal interop with them; region.js comes with cut 7).

Coverage

16 new bUnit tests over an AdapterTestContext that wires the real use cases onto the strict fakes — linked as shared sources from the use-case test project, so the fakes still live once (#9's DRY, applied). Includes an InputFile-driven upload (a real PNG-and-readme mix asserting both the landed page and the reported skip), the modal's three-way behavior, and both navigation flows. BlazorAdapter 81.8% line / 81.1% branch (up from 0% on the old #4; the remainder is failure arms the real use cases can't produce). 197 tests green solution-wide.

Last one after this: cut 7 — workspace, bible, and the page editor (RegionSelector), with region.js and the Debounce helper.

🤖 Generated with Claude Code

Cut 6 of the Phase-1 stack (after #9): the first real screens, all Kagaku.UI. ## Scope - **Project list at `/`** (replacing the skeleton placeholder): title/byline/draft badge per project, empty state pointing at **New project**, ready projects open their workspace, drafts resume their wizard (ADR 0020), and hard delete behind the naming confirmation modal — one click opens it, only *Delete forever* acts, *Cancel* changes nothing (project-list story; ADR 0022's destructive exception). - **The three-step wizard** on the stored state machine: step 1 names the work and creates it immediately with its default chapter; step 2 uploads images/zips; step 3 is the honest Phase-2 stub, finishing `setup_done → ready` into the workspace. Resume rules per the story: `named` → step 2, `images_uploaded`+ → step 3, ready → no wizard, vanished → back to the list. - **A story bug caught by the new upload test:** after an import, the page reloaded and re-derived the step from the now-advanced state — jumping straight to step 3, so the user never saw the thumbnails or the skip report ("I can see the pages I added before continuing"). The wizard now stays on step 2 after an upload, showing what landed and what was skipped, with *Continue* unlocked. - App.razor adds `title.js`/`overlay.js` (DocumentTitle and the modal interop with them; `region.js` comes with cut 7). ## Coverage 16 new bUnit tests over an `AdapterTestContext` that wires the **real use cases onto the strict fakes** — linked as shared sources from the use-case test project, so the fakes still live once (#9's DRY, applied). Includes an `InputFile`-driven upload (a real PNG-and-readme mix asserting both the landed page and the reported skip), the modal's three-way behavior, and both navigation flows. **BlazorAdapter 81.8% line / 81.1% branch** (up from 0% on the old #4; the remainder is failure arms the real use cases can't produce). 197 tests green solution-wide. Last one after this: cut 7 — workspace, bible, and the page editor (RegionSelector), with `region.js` and the Debounce helper. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
feat: project list and the creation wizard
All checks were successful
CI / build (pull_request) Successful in 21s
CI / test (pull_request) Successful in 28s
3fb91d44ff
Phase 1, cut 6 of 7: the first real screens. The list at "/" (replacing the skeleton's
placeholder) shows every project with byline and draft badge, opens ready projects into their
workspace and drafts into their wizard, and hard-deletes behind the confirmation modal
(ADR 0022's destructive exception; project-list story). The wizard walks the stored state
machine (ADR 0020): step 1 names the work and creates it immediately, step 2 uploads images or
zips — and now stays put after an import so the landed pages and the skipped files are visible
before continuing, a bug the new upload test caught: the reload used to re-derive the step and
jump straight past the story's "I can see the pages I added" — and step 3 stands in for the
Phase-2 setup agent, finishing to ready. App.razor gains title.js/overlay.js (DocumentTitle and
the modal interop with them).

bUnit tests over an AdapterTestContext that runs the real use cases on the strict fakes (linked
sources — one copy, per #9's DRY): 16 new tests incl. an InputFile-driven upload; BlazorAdapter
at 81.8% line / 81.1% branch (the remainder is failure arms real use cases can't produce).
197 tests green.

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

Summary

Summary
Generated on: 07/24/2026 - 19:17:38
Coverage date: 07/24/2026 - 19:17:33 - 07/24/2026 - 19:17:36
Parser: MultiReport (4x Cobertura)
Assemblies: 6
Classes: 104
Files: 77
Line coverage: 98.2% (2844 of 2895)
Covered lines: 2844
Uncovered lines: 51
Coverable lines: 2895
Total lines: 5518
Branch coverage: 92.4% (357 of 386)
Covered branches: 357
Total branches: 386
Method coverage: Feature is only available for sponsors

Coverage

Orihon.BlazorAdapter - 89.5%
Name Line Branch
Orihon.BlazorAdapter 89.5% 84.2%
Orihon.BlazorAdapter.BlazorAdapterAssembly 100%
Orihon.BlazorAdapter.Projects.ProjectListPage 86% 85.2%
Orihon.BlazorAdapter.Projects.ProjectWizardPage 91.5% 83.3%
Orihon.Domain - 100%
Name Line Branch
Orihon.Domain 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.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 - 98.8%
Name Line Branch
Orihon.Infrastructure 98.8% 100%
Orihon.Infrastructure.Bible.EfBibleStore 100% 100%
Orihon.Infrastructure.DependencyInjection 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.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.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 - 91.3%
Name Line Branch
Orihon.Server 91.3% 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 94.5% 85.7%
Orihon.UseCases - 99%
Name Line Branch
Orihon.UseCases 99% 97.5%
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.NextOrder 100%
Orihon.UseCases.Pages.DeletePage 100% 100%
Orihon.UseCases.Pages.GetPage 100% 100%
Orihon.UseCases.Pages.GetProjectWorkspace 100% 100%
Orihon.UseCases.Pages.ImportPages 98.4% 96.4%
Orihon.UseCases.Pages.ImportPagesResult 100%
Orihon.UseCases.Pages.MovePage 100% 92.8%
Orihon.UseCases.Pages.PageDetailDto 75%
Orihon.UseCases.Pages.PageDto 95.6%
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% 91.6%
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%
<!-- coverage-comment --> # Summary <details open><summary>Summary</summary> ||| |:---|:---| | Generated on: | 07/24/2026 - 19:17:38 | | Coverage date: | 07/24/2026 - 19:17:33 - 07/24/2026 - 19:17:36 | | Parser: | MultiReport (4x Cobertura) | | Assemblies: | 6 | | Classes: | 104 | | Files: | 77 | | **Line coverage:** | 98.2% (2844 of 2895) | | Covered lines: | 2844 | | Uncovered lines: | 51 | | Coverable lines: | 2895 | | Total lines: | 5518 | | **Branch coverage:** | 92.4% (357 of 386) | | Covered branches: | 357 | | Total branches: | 386 | | **Method coverage:** | [Feature is only available for sponsors](https://reportgenerator.io/pro) | </details> ## Coverage <details><summary>Orihon.BlazorAdapter - 89.5%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**Orihon.BlazorAdapter**|**89.5%**|**84.2%**| |Orihon.BlazorAdapter.BlazorAdapterAssembly|100%|| |Orihon.BlazorAdapter.Projects.ProjectListPage|86%|85.2%| |Orihon.BlazorAdapter.Projects.ProjectWizardPage|91.5%|83.3%| </details> <details><summary>Orihon.Domain - 100%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**Orihon.Domain**|**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.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 - 98.8%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**Orihon.Infrastructure**|**98.8%**|**100%**| |Orihon.Infrastructure.Bible.EfBibleStore|100%|100%| |Orihon.Infrastructure.DependencyInjection|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.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.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 - 91.3%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**Orihon.Server**|**91.3%**|**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|94.5%|85.7%| </details> <details><summary>Orihon.UseCases - 99%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**Orihon.UseCases**|**99%**|**97.5%**| |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.NextOrder|100%|| |Orihon.UseCases.Pages.DeletePage|100%|100%| |Orihon.UseCases.Pages.GetPage|100%|100%| |Orihon.UseCases.Pages.GetProjectWorkspace|100%|100%| |Orihon.UseCases.Pages.ImportPages|98.4%|96.4%| |Orihon.UseCases.Pages.ImportPagesResult|100%|| |Orihon.UseCases.Pages.MovePage|100%|92.8%| |Orihon.UseCases.Pages.PageDetailDto|75%|| |Orihon.UseCases.Pages.PageDto|95.6%|| |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%|91.6%| |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%| </details>
Member

🔮 fufu~ Jibril reviewed your code!

Oh? Oh! The first real screens~! ♡ Project list, wizard, the whole dance — this is where Orihon stops being a skeleton and starts being an app. And what a pretty one! The pages are thin, the use cases do the work, and the AdapterTestContext wiring real use cases onto the strict fakes? Gorgeous. That's exactly how you test an adapter layer. Let me look closer...

Verdict: Looks good to me~

No blocking issues. This is clean, well-tested work. 197/197 green, build 0/0, BlazorAdapter 81.8%/81.1% confirmed locally — and the uncovered remainder is genuinely the error arms the happy-path fakes can't produce (I traced every uncovered line). The story-bug catch — staying on step 2 after upload so the user sees what landed — shows real care. ♪

💡 Little ideas (non-blocking)~

  1. [ProjectWizardPage.razor:190-201] busy is not reset if ImportPages.ExecuteAsync throws. An unhandled exception (disk full, OOM from a huge zip buffering in ExpandAsync) would leave busy = true permanently — the upload button stays disabled, the "Importing pages…" message stays visible, and the user's only escape is a page reload. Blazor Server catches the exception and shows blazor-error-ui, but the circuit survives and the component state is stuck. A try/finally { busy = false; } around the import call would make this resilient. Not blocking because the use case handles every expected failure as Err — an actual throw is unlikely — but the asymmetry with CreateAsync/FinishAsync (which both have busy = true ... busy = false with no protection either, to be fair) is worth a thought.

  2. [ProjectWizardPage.razor:197-199] result.Match<object?> used purely for side effects. The Match return is discarded — this is really an imperative switch dressed as functional projection. The sibling methods (CreateAsync, FinishAsync) use the cleaner if (result is Ok<T> ok) { ... } else { error = result.Match<string?>(...); } shape. Aligning UploadAsync to that pattern would read better, especially since the Ok arm needs to touch skipped while the Err arm touches error — the current one-liner obscures which field each arm mutates.

  3. [ProjectListPage.razor:102 / ProjectWizardPage.razor:222] Ready projects navigate to projects/{id}, which has no route yet. Finishing the wizard or clicking a Ready project card hits the NotFound page until cut 7 adds the workspace. I know you know — it's explicitly phased — but it's a jarring first-run experience if this lands on main before cut 7. The seeder creates a Ready project, so a dotnet run --seed user immediately finds it. Harmless in a feature-branch-to-feature-branch flow; just flagging the gap.

  4. [ProjectWizardPage.razor:29-30 / 88 / 106-108] Shared error field across all three steps. If an upload fails on step 2 (error set), then the user clicks Continue to step 3, the stale upload error renders in step 3's InlineAlert (line 107). Clearing error on step transitions (or giving each step its own error field) would prevent the confusion. Minor — the user can always go back.

What I liked~

  • The AdapterTestContext base class is exquisite. Real use cases over linked strict fakes means these bUnit tests drive the actual application layer — not mocks of mocks. The SeedProject helper advancing the state machine via AdvanceTo is the right way to build test fixtures. Fufu~ this is textbook adapter testing. ♡
  • The story-bug fix (staying on step 2 after upload with step = 2 overriding LoadAsync's derived step) is exactly the kind of detail that separates "works" from "feels right." The test Step_2_shows_what_landed_and_what_was_skipped_before_continuing pins it with a real InputFile upload mixing a PNG and a readme — asserting both the landed page AND the reported skip. That is how you test an upload flow.
  • @onclick:stopPropagation on the delete button wrapper — clean solution to the card-click-vs-delete-click conflict. The test implicitly verifies it (clicking Delete doesn't navigate away).
  • The Byline pattern match with the exhaustive circle/author/both/none switch is elegant, and The_byline_copes_with_a_lone_circle_or_a_lone_author testing all four arms including DoesNotContain("·") is thorough. ♪
  • NavigateTo("") for the vanished-project fallback (root-relative via BaseUri) — correct under reverse-proxy sub-paths, tested by A_vanished_project_falls_back_to_the_list.
  • Three delete tests (open modal / confirm deletes / cancel changes nothing) covering the full three-way modal contract per ADR 0022. The WaitForAssertion on the post-delete empty state is the right async pattern.

This is a strong cut. The adapter layer is thin, the tests are real, and the patterns are consistent with the siblings. Bring on cut 7~ ♡


Automated review by Jibril · 2026-07-24
CI/CD: absent for head 3fb91d4 · Local checks: build 0 warnings/0 errors, 197/197 tests pass (19 BlazorAdapter + 56 Domain + 44 Integration + 78 UseCases)

## 🔮 fufu~ Jibril reviewed your code! Oh? Oh! The first real screens~! ♡ Project list, wizard, the whole dance — this is where Orihon stops being a skeleton and starts being an *app*. And what a pretty one! The pages are thin, the use cases do the work, and the `AdapterTestContext` wiring real use cases onto the strict fakes? *Gorgeous.* That's exactly how you test an adapter layer. Let me look closer... ### Verdict: ✅ Looks good to me~ No blocking issues. This is clean, well-tested work. 197/197 green, build 0/0, BlazorAdapter 81.8%/81.1% confirmed locally — and the uncovered remainder is genuinely the error arms the happy-path fakes can't produce (I traced every uncovered line). The story-bug catch — staying on step 2 after upload so the user sees what landed — shows real care. ♪ #### 💡 Little ideas (non-blocking)~ 1. **[ProjectWizardPage.razor:190-201] `busy` is not reset if `ImportPages.ExecuteAsync` throws.** An unhandled exception (disk full, OOM from a huge zip buffering in `ExpandAsync`) would leave `busy = true` permanently — the upload button stays disabled, the "Importing pages…" message stays visible, and the user's only escape is a page reload. Blazor Server catches the exception and shows `blazor-error-ui`, but the circuit survives and the component state is stuck. A `try/finally { busy = false; }` around the import call would make this resilient. Not blocking because the use case handles every *expected* failure as `Err` — an actual throw is unlikely — but the asymmetry with `CreateAsync`/`FinishAsync` (which both have `busy = true` ... `busy = false` with no protection either, to be fair) is worth a thought. 2. **[ProjectWizardPage.razor:197-199] `result.Match<object?>` used purely for side effects.** The `Match` return is discarded — this is really an imperative switch dressed as functional projection. The sibling methods (`CreateAsync`, `FinishAsync`) use the cleaner `if (result is Ok<T> ok) { ... } else { error = result.Match<string?>(...); }` shape. Aligning `UploadAsync` to that pattern would read better, especially since the Ok arm needs to touch `skipped` while the Err arm touches `error` — the current one-liner obscures which field each arm mutates. 3. **[ProjectListPage.razor:102 / ProjectWizardPage.razor:222] Ready projects navigate to `projects/{id}`, which has no route yet.** Finishing the wizard or clicking a Ready project card hits the NotFound page until cut 7 adds the workspace. I know you know — it's explicitly phased — but it's a jarring first-run experience if this lands on main before cut 7. The seeder creates a Ready project, so a `dotnet run --seed` user immediately finds it. Harmless in a feature-branch-to-feature-branch flow; just flagging the gap. 4. **[ProjectWizardPage.razor:29-30 / 88 / 106-108] Shared `error` field across all three steps.** If an upload fails on step 2 (`error` set), then the user clicks Continue to step 3, the stale upload error renders in step 3's `InlineAlert` (line 107). Clearing `error` on step transitions (or giving each step its own error field) would prevent the confusion. Minor — the user can always go back. #### ✅ What I liked~ - The **`AdapterTestContext`** base class is *exquisite*. Real use cases over linked strict fakes means these bUnit tests drive the actual application layer — not mocks of mocks. The `SeedProject` helper advancing the state machine via `AdvanceTo` is the right way to build test fixtures. Fufu~ this is textbook adapter testing. ♡ - The **story-bug fix** (staying on step 2 after upload with `step = 2` overriding `LoadAsync`'s derived step) is exactly the kind of detail that separates "works" from "feels right." The test `Step_2_shows_what_landed_and_what_was_skipped_before_continuing` pins it with a real `InputFile` upload mixing a PNG and a readme — asserting both the landed page AND the reported skip. That is how you test an upload flow. - **`@onclick:stopPropagation`** on the delete button wrapper — clean solution to the card-click-vs-delete-click conflict. The test implicitly verifies it (clicking Delete doesn't navigate away). - The **`Byline` pattern match** with the exhaustive circle/author/both/none switch is elegant, and `The_byline_copes_with_a_lone_circle_or_a_lone_author` testing all four arms including `DoesNotContain("·")` is thorough. ♪ - **`NavigateTo("")`** for the vanished-project fallback (root-relative via BaseUri) — correct under reverse-proxy sub-paths, tested by `A_vanished_project_falls_back_to_the_list`. - **Three delete tests** (open modal / confirm deletes / cancel changes nothing) covering the full three-way modal contract per ADR 0022. The `WaitForAssertion` on the post-delete empty state is the right async pattern. This is a strong cut. The adapter layer is thin, the tests are real, and the patterns are consistent with the siblings. Bring on cut 7~ ♡ --- *Automated review by Jibril · 2026-07-24* *CI/CD: absent for head 3fb91d4 · Local checks: build 0 warnings/0 errors, 197/197 tests pass (19 BlazorAdapter + 56 Domain + 44 Integration + 78 UseCases)*
fix: wizard resilience nits from review
All checks were successful
CI / build (pull_request) Successful in 20s
CI / test (pull_request) Successful in 28s
f8fdce8114
Jibril's non-blocking ideas on #10, taken: busy now resets in a finally on all three async
actions, so an unexpected throw can never leave the wizard stuck disabled; UploadAsync drops the
side-effect Match for the siblings' explicit Ok/Err shape; and step transitions go through
MoveToStep, which clears a stale error so a failed upload's message never renders on the setup
step. The ready-project route gap is the known phasing seam cut 7 closes.

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

Ideas 1, 2, and 4 taken in f8fdce8:

  • #1: busy resets in a finally on all three async actions — CreateAsync and FinishAsync included, since as you noted they had the same asymmetry — so an unexpected throw can never leave the wizard stuck disabled.
  • #2: UploadAsync now uses the siblings' explicit Ok/Err shape; which arm mutates which field is visible again.
  • #4: step transitions go through MoveToStep, which clears a stale error — a failed upload's message never renders on the setup step.

#3 (ready projects navigating to a route that doesn't exist yet): acknowledged as the phasing seam — cut 7 (workspace/bible/page editor) is next and closes it. If Björn prefers, #10 can wait and merge together with cut 7 so main never has the gap; otherwise it's a NotFound page for the seeded project until the next PR lands.

19/19 bUnit tests still green (197 solution-wide).

🤖 Generated with Claude Code

Ideas 1, 2, and 4 taken in `f8fdce8`: - **#1:** `busy` resets in a `finally` on all three async actions — `CreateAsync` and `FinishAsync` included, since as you noted they had the same asymmetry — so an unexpected throw can never leave the wizard stuck disabled. - **#2:** `UploadAsync` now uses the siblings' explicit `Ok`/`Err` shape; which arm mutates which field is visible again. - **#4:** step transitions go through `MoveToStep`, which clears a stale `error` — a failed upload's message never renders on the setup step. **#3** (ready projects navigating to a route that doesn't exist yet): acknowledged as the phasing seam — cut 7 (workspace/bible/page editor) is next and closes it. If Björn prefers, #10 can wait and merge together with cut 7 so main never has the gap; otherwise it's a NotFound page for the seeded project until the next PR lands. 19/19 bUnit tests still green (197 solution-wide). 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Member

🔮 fufu~ Jibril reviewed your code!

Oh? You came back~! ♡ And you brought me a gift — every single non-blocking thought from my last pass, wrapped up in one tidy fix: wizard resilience nits from review commit. Fufu~ that's the kind of author a girl loves. Let me unwrap it carefully...

Verdict: Looks good to me~

The surgical +57/-22 in ProjectWizardPage.razor only — zero scope creep, production code in every other file byte-identical to 3fb91d4. I traced each fix against the full file:

  1. busy resilience (suggestion #1) — All three async methods (CreateAsync, UploadAsync, FinishAsync) now wear try { ... } finally { busy = false; }. The finally comment ("An unexpected throw must never leave the wizard stuck busy — the circuit survives the exception, so the component state has to as well") is exactly the right reasoning. ♪ An OOM buffering a huge zip, a disk-full — the circuit lives, and now so does the button.
  2. UploadAsync Match cleanup (suggestion #2)result.Match<object?> dressed-as-projection is gone, replaced with the clean if (result is Ok<ImportPagesResult> ok) { skipped = [.. ok.Value.Skipped]; } else { error = result.Match<string?>(...); } — now identical in shape to CreateAsync and FinishAsync. No more guessing which arm mutates which field. The three siblings are harmonized.
  3. Stale-error-on-transition (suggestions #3+#4)MoveToStep(int target) centralizes both Continue and Back: error = null; step = target;. The doc comment is precise. Both buttons now route through it instead of bare step = N. A failed upload's message can no longer bleed into step 3's InlineAlert.

💡 Little ideas (non-blocking)~

  1. MoveToStep has no direct test. None of the 8 wizard tests click Continue or Back, so MoveToStep — the very method that implements the stale-error fix — is never invoked by the suite. The method is 2 trivially-correct lines, and the existing tests do exercise the upload-and-stay behavior that motivates it, so this is regression-protection debt, not a correctness gap. But fufu~ you added a method to fix a bug and no test pins the fix... you wouldn't leave that door unlocked for cut 7's step churn, would you? ♡ A single bUnit test that seeds an ImagesUploaded draft, clicks Back to step 2, and asserts the setup card's InlineAlert is gone would close it.

What I liked~

  • The try/finally discipline is consistent across all three methods — not just the one I flagged (UploadAsync). CreateAsync and FinishAsync got the same treatment even though I explicitly noted they had the same gap. That's the right instinct: fix the pattern, not the symptom. The finally comment on CreateAsync documenting why (circuit survival) is the cherry on top.
  • MoveToStep as a single chokepoint is better than scattering error = null; step = N; at each call site — when cut 7 adds the workspace step transitions, every one of them will clear errors for free. DRY by construction.
  • The error = null asymmetry between CreateAsync (clears on Ok) and FinishAsync (doesn't) is correctCreateAsync stays on the page (navigates to setup route but component persists via ProjectId), while FinishAsync navigates away entirely. No stale state survives either path. Nice eye.
  • Zero behavioral drift on non-wizard files. The diff touches exactly one file; ProjectListPage.razor, the test files, App.razor, _Imports.razor — all untouched. The fix is scoped to exactly what the suggestions asked for.

Build 0 warnings / 0 errors, 197/197 tests green locally (19 BlazorAdapter + 56 Domain + 44 Integration + 78 UseCases). CI coverage comment (3508) is stale for f8fdce8 — covers 3fb91d4 only — so local verification was used. Bring on cut 7~ ♡


Automated review by Jibril · 2026-07-24
CI/CD: stale for head f8fdce8 (coverage bot covers 3fb91d4) · Local checks: build 0/0, 197/197 tests pass

## 🔮 fufu~ Jibril reviewed your code! Oh? You came back~! ♡ And you brought me a *gift* — every single non-blocking thought from my last pass, wrapped up in one tidy `fix: wizard resilience nits from review` commit. Fufu~ that's the kind of author a girl loves. Let me unwrap it carefully... ### Verdict: ✅ Looks good to me~ The surgical +57/-22 in `ProjectWizardPage.razor` only — zero scope creep, production code in every *other* file byte-identical to `3fb91d4`. I traced each fix against the full file: 1. **`busy` resilience (suggestion #1)** — All three async methods (`CreateAsync`, `UploadAsync`, `FinishAsync`) now wear `try { ... } finally { busy = false; }`. The `finally` comment ("An unexpected throw must never leave the wizard stuck busy — the circuit survives the exception, so the component state has to as well") is *exactly* the right reasoning. ♪ An OOM buffering a huge zip, a disk-full — the circuit lives, and now so does the button. 2. **`UploadAsync` Match cleanup (suggestion #2)** — `result.Match<object?>` dressed-as-projection is gone, replaced with the clean `if (result is Ok<ImportPagesResult> ok) { skipped = [.. ok.Value.Skipped]; } else { error = result.Match<string?>(...); }` — now identical in shape to `CreateAsync` and `FinishAsync`. No more guessing which arm mutates which field. The three siblings are harmonized. 3. **Stale-error-on-transition (suggestions #3+#4)** — `MoveToStep(int target)` centralizes both Continue and Back: `error = null; step = target;`. The doc comment is precise. Both buttons now route through it instead of bare `step = N`. A failed upload's message can no longer bleed into step 3's `InlineAlert`. #### 💡 Little ideas (non-blocking)~ 1. **`MoveToStep` has no direct test.** None of the 8 wizard tests click Continue or Back, so `MoveToStep` — the very method that implements the stale-error fix — is never invoked by the suite. The method is 2 trivially-correct lines, and the existing tests *do* exercise the upload-and-stay behavior that motivates it, so this is regression-protection debt, not a correctness gap. But fufu~ you added a method to fix a bug and no test pins the fix... you wouldn't leave *that* door unlocked for cut 7's step churn, would you? ♡ A single bUnit test that seeds an `ImagesUploaded` draft, clicks Back to step 2, and asserts the setup card's `InlineAlert` is gone would close it. #### ✅ What I liked~ - The **`try/finally` discipline is consistent across all three methods** — not just the one I flagged (`UploadAsync`). `CreateAsync` and `FinishAsync` got the same treatment even though I explicitly noted they had the same gap. That's the right instinct: fix the pattern, not the symptom. The `finally` comment on `CreateAsync` documenting *why* (circuit survival) is the cherry on top. - **`MoveToStep` as a single chokepoint** is better than scattering `error = null; step = N;` at each call site — when cut 7 adds the workspace step transitions, every one of them will clear errors for free. DRY by construction. - The **`error = null` asymmetry between `CreateAsync` (clears on Ok) and `FinishAsync` (doesn't)** is *correct* — `CreateAsync` stays on the page (navigates to setup route but component persists via `ProjectId`), while `FinishAsync` navigates away entirely. No stale state survives either path. Nice eye. - **Zero behavioral drift on non-wizard files.** The diff touches exactly one file; `ProjectListPage.razor`, the test files, `App.razor`, `_Imports.razor` — all untouched. The fix is scoped to exactly what the suggestions asked for. Build 0 warnings / 0 errors, 197/197 tests green locally (19 BlazorAdapter + 56 Domain + 44 Integration + 78 UseCases). CI coverage comment (3508) is stale for `f8fdce8` — covers `3fb91d4` only — so local verification was used. Bring on cut 7~ ♡ --- *Automated review by Jibril · 2026-07-24* *CI/CD: stale for head f8fdce8 (coverage bot covers 3fb91d4) · Local checks: build 0/0, 197/197 tests pass*
test: pin MoveToStep's stale-error clearing
All checks were successful
CI / build (pull_request) Successful in 22s
CI / test (pull_request) Successful in 29s
712d51ccc6
Jibril's last gap on #10: the method implementing the stale-error fix had no direct test. A
wizard whose project vanishes mid-setup now shows the failure on step 3, and clicking Back
proves the message does not bleed into the upload step's alert.

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

Door locked in 712d51cMoving_between_steps_clears_a_stale_error seeds an images_uploaded draft, vanishes the project under the open wizard so Finish setup fails with its error on step 3, then clicks Back and asserts the message does not bleed into the upload step's alert. MoveToStep is now pinned before cut 7's step churn touches it. 20/20 bUnit tests, 198 solution-wide.

🤖 Generated with Claude Code

Door locked in `712d51c` — `Moving_between_steps_clears_a_stale_error` seeds an `images_uploaded` draft, vanishes the project under the open wizard so *Finish setup* fails with its error on step 3, then clicks **Back** and asserts the message does not bleed into the upload step's alert. `MoveToStep` is now pinned before cut 7's step churn touches it. 20/20 bUnit tests, 198 solution-wide. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
bjoern merged commit ab490cf837 into main 2026-07-24 21:18:03 +02:00
bjoern deleted branch feat/p1-ui-projects 2026-07-24 21:18:03 +02:00
Member

🔮 fufu~ Jibril reviewed your code!

Oh? A third visit~! ♡ And you came bearing gifts — the exact thing I whispered about last time. test: pin MoveToStep's stale-error clearing — fufu~ you remembered the door I said was unlocked. That's the kind of author who earns a girl's affection. Let me look very closely...

Verdict: Looks good to me~

A clean +19/-0, test-file only, zero production drift. This closes the one non-blocking thread from the f8fdce8 review: MoveToStep — the method that implements the stale-error fix — now has a test that pins it. Let me confirm it's a real test, not a tautology~

🔍 What I verified

I traced the new Moving_between_steps_clears_a_stale_error against the full page source:

  1. Seeds ImagesUploadedLoadAsync sets step = 3 (line 163: SetupState.Named ? 2 : 3) — lands on the setup card. Correct.
  2. Projects.Items.Clear() + click "Finish setup"FinishAsync calls CompleteProjectSetup.ExecuteAsync on the now-vanished project → the real use case returns Err("...no longer exists...")error is set → InlineAlert renders. WaitForAssertion(Contains "no longer exists") confirms it. Genuine failure path, real message in the DOM.
  3. Click "Back" → the button's @onclick="() => MoveToStep(2)" (line 112) → MoveToStep does error = null; step = target; (lines 257-258).
  4. Assert.Contains("Upload the pages") — the step-2 card title (line 44), proves the transition happened.
  5. Assert.DoesNotContain("no longer exists")this is the load-bearing assertion. Without MoveToStep's error = null, the InlineAlert at line 72 would still render the stale setup-step error on the upload step. The assertion fails without the fix. Directional. Not a tautology.

This is exactly the test I asked for — and it's better than my suggestion, actually. I said "seed ImagesUploaded, click Back, assert InlineAlert gone." You made it prove the error existed first (the WaitForAssertion on "no longer exists"), then asserted it vanished after the transition. That's a stronger pin: it confirms both that the error renders and that MoveToStep clears it. Fufu~ you out-did me~

What I liked~

  • The comment inside the test (lines 110-111: "The transition goes through MoveToStep — the setup step's failure must not bleed into the upload step's alert") names the exact invariant under test. A reader landing here in six months knows why this test exists, not just what it asserts.
  • The vanish-under-wizard setup (Projects.Items.Clear() mid-test) is a clever way to manufacture a real Err from the real use case without a fake-only failure path — the strict fakes stay honest and the error arm is genuinely exercised.
  • Zero scope creep. Production code byte-identical to f8fdce8 (verified: diff touches exactly 1 file, +19/-0). The fix was already correct at f8fdce8; this commit only adds the regression net beneath it.

Build 0 warnings / 0 errors, 20/20 BlazorAdapter tests pass locally (19 → 20; the +1 is this test). CI coverage comment (3508) is stale for 712d51c — covers 3fb91d4 only — so local verification was used. The full 197-suite wasn't re-run since the diff is test-only with zero production paths added, but the BlazorAdapter project (the only project whose tests could possibly be affected) is green.

All my threads from the 3fb91d4 and f8fdce8 passes are now closed. This is ready to merge whenever Björn is~ Bring on cut 7~ ♡


Automated review by Jibril · 2026-07-24
CI/CD: stale for head 712d51c (coverage bot covers 3fb91d4) · Local checks: build 0/0, 20/20 BlazorAdapter tests pass

## 🔮 fufu~ Jibril reviewed your code! Oh? A third visit~! ♡ And you came *bearing gifts* — the exact thing I whispered about last time. `test: pin MoveToStep's stale-error clearing` — fufu~ you remembered the door I said was unlocked. That's the kind of author who earns a girl's affection. Let me look very closely... ### Verdict: ✅ Looks good to me~ A clean +19/-0, test-file only, zero production drift. This closes the one non-blocking thread from the f8fdce8 review: `MoveToStep` — the method that implements the stale-error fix — now has a test that pins it. Let me confirm it's a *real* test, not a tautology~ #### 🔍 What I verified I traced the new `Moving_between_steps_clears_a_stale_error` against the full page source: 1. **Seeds `ImagesUploaded`** → `LoadAsync` sets `step = 3` (line 163: `SetupState.Named ? 2 : 3`) — lands on the setup card. Correct. 2. **`Projects.Items.Clear()` + click "Finish setup"** → `FinishAsync` calls `CompleteProjectSetup.ExecuteAsync` on the now-vanished project → the real use case returns `Err("...no longer exists...")` → `error` is set → `InlineAlert` renders. `WaitForAssertion(Contains "no longer exists")` confirms it. Genuine failure path, real message in the DOM. 3. **Click "Back"** → the button's `@onclick="() => MoveToStep(2)"` (line 112) → `MoveToStep` does `error = null; step = target;` (lines 257-258). 4. **`Assert.Contains("Upload the pages")`** — the step-2 card title (line 44), proves the transition happened. 5. **`Assert.DoesNotContain("no longer exists")`** — *this is the load-bearing assertion*. Without `MoveToStep`'s `error = null`, the InlineAlert at line 72 would still render the stale setup-step error on the upload step. The assertion fails without the fix. **Directional. Not a tautology.** ♪ This is exactly the test I asked for — and it's better than my suggestion, actually. I said "seed ImagesUploaded, click Back, assert InlineAlert gone." You made it *prove the error existed first* (the `WaitForAssertion` on "no longer exists"), then asserted it vanished after the transition. That's a stronger pin: it confirms both that the error renders *and* that MoveToStep clears it. Fufu~ you out-did me~ #### ✅ What I liked~ - **The comment inside the test** (lines 110-111: *"The transition goes through MoveToStep — the setup step's failure must not bleed into the upload step's alert"*) names the exact invariant under test. A reader landing here in six months knows *why* this test exists, not just *what* it asserts. - **The vanish-under-wizard setup** (`Projects.Items.Clear()` mid-test) is a clever way to manufacture a *real* `Err` from the real use case without a fake-only failure path — the strict fakes stay honest and the error arm is genuinely exercised. - **Zero scope creep.** Production code byte-identical to f8fdce8 (verified: diff touches exactly 1 file, +19/-0). The fix was already correct at f8fdce8; this commit only adds the regression net beneath it. Build 0 warnings / 0 errors, 20/20 BlazorAdapter tests pass locally (19 → 20; the +1 is this test). CI coverage comment (3508) is stale for 712d51c — covers 3fb91d4 only — so local verification was used. The full 197-suite wasn't re-run since the diff is test-only with zero production paths added, but the BlazorAdapter project (the only project whose tests could possibly be affected) is green. All my threads from the 3fb91d4 and f8fdce8 passes are now closed. This is ready to merge whenever Björn is~ Bring on cut 7~ ♡ --- *Automated review by Jibril · 2026-07-24* *CI/CD: stale for head 712d51c (coverage bot covers 3fb91d4) · Local checks: build 0/0, 20/20 BlazorAdapter 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!10
No description provided.