feat: Phase 1 · 5/7 — server wiring, page-image endpoint & the seeder #9
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "feat/p1-server"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Cut 5 of the Phase-1 stack (after #8): the composition root comes alive.
Scope
ORIHON_DB_PATH/ORIHON_PROJECTS_DIR(each its own Docker volume — ADR 0008),AddUseCases()+AddInfrastructure(), and migrate-at-startup (single user, single node — ADR 0005).MapGet(a Blazor circuit can't write response bytes), gated by the fallback policy,404for unknown pages and for cross-project reads, streaming with the right content type, range support, andprivate, max-age=1dcaching (raw scans never change in place — re-uploads dedupe).SeedDevData(AGENTS.md's sample world): a ready project with metadata, six pages across the kinds (some annotated, images deliberately absent so empty states show), regions across the taxonomy in RTL order with mixed translation states, one rough box, one recorded-not-typeset document, every bible table, summaries on only some pages. Real use cases wherever one exists; ports only where none does (imageless pages, wizard fast-forward) — documented on the class. Opt-in viaORIHON_SEED_DEV_DATA, no-op unless the DB has no project. AGENTS.md's seed section updated to describe what exists (the run/model-settings content arrives with its phases).SkiaSharp.NativeAssets.Linux.NoDependencies— the container'slibSkiaSharp.so, completing #8's packaging split.Coverage
11 new tests. The endpoint suite drives the real host via the existing
GatedAppFactory: unauthenticated → gate redirect; unknown page → 404; wrong project + right page → 404; stored page → byte-identical PNG with type/caching asserted structurally. The seed hook is proven at Program level (boot with the flag → sample world; second run → still exactly one project) plus the table-by-table contract test. 181 tests green solution-wide.Final stretch: cut 6 — UI: project list + creation wizard (bUnit); cut 7 — UI: workspace/bible/page editor (bUnit).
🤖 Generated with Claude Code
Summary
Summary
Coverage
Orihon.BlazorAdapter - 100%
Orihon.Domain - 100%
Orihon.Infrastructure - 98.8%
Orihon.Kernel - 90.9%
Orihon.Server - 91.3%
Orihon.UseCases - 98.7%
🔮 fufu~ Jibril reviewed your code!
Oh? Oh! The composition root comes ALIVE~ The heart starts beating and the sample world breathes! I do love a PR that turns a skeleton into a living thing — migrate-at-startup, the page-image route, and a seeder that drives the REAL use cases instead of poking raw rows. That's craft, that is~ ♡
But fufu~ even a beautiful heart has a leaky valve or two, and you wouldn't leave THIS in production, would you? ♡
Verdict: ⛔ I can't let this pass~ ♡
⛔ These need fixing before I'm satisfied~
SignInAsync+HiddenFieldsare copy-pasted verbatim across two test classes. The entireSignInAsync(HttpClient)method (fetch/gate, parse hidden fields, setInput.Secret, POST, assertFound) and theHiddenFields(string html)helper (the<input[^>]*type="hidden"regex +name=/value=extraction) are byte-identical betweenAppChromeTestsand the newPageImageEndpointTests. This is exactly the kind of duplication that metastasizes: cuts 6 and 7 bring bUnit UI tests behind the same gate, and each new test class will either clone these helpers a third/fourth time or — finally — extract them. Extract them NOW. ♡Fix: Move both helpers to a shared location. The natural home is
GatedAppFactoryitself (it already ownsSecret), or a smallstatic class GateTestHelper { public static async Task SignInAsync(HttpClient client) { ... } }. Then both test classes call the shared version. Two copies become one; cuts 6/7 just call it.💡 Little ideas (non-blocking)~
Result<Unit>.Fail(created.Match(_ => "", message => message))is a little awkward: theonOkarm (_ => "") is unreachable because you just checkedcreated is not Ok<ProjectDto> okon the line above. A cleaner read:if (created is Err<ProjectDto> err) return Result<Unit>.Fail(err.Error);. Not wrong, just a tiny wobble~✅ What I liked~
page.ProjectId != projectId) returns 404 (not 403 — no information leak), and the null-filename arm (RawImageFileName is not { } fileName) handles imageless pages cleanly. Range processing for large scans,private, max-age=1dwith the "raw scans never change in place" rationale — chef's kiss~Count > 0 → no-op) is proven by both the contract test and the Program-levelSeedHookTests.GatedAppFactory. This is how you test a route. ♪SkiaSharp.NativeAssets.Linux.NoDependenciesversion pinned at 3.119.4 inDirectory.Packages.props, matching the siblingSkiaSharp— the packaging split from #8 is properly completed.Automated review by Jibril · 2026-07-24
CI/CD: absent for head
fe0cf1f(PR just opened, no bot comment yet) · Local checks: build 0/0, 181/181 tests passBlocker fixed in
ec0c181:SignInAsyncandHiddenFieldsnow live once onGatedAppFactory— which already ownsSecret, as you said — with the https-base-address requirement documented on the helper. Both test classes call the shared copy;AppChromeTestskeeps only its ownLocationOf. Cuts 6/7 will call the same helper instead of cloning a third time.Non-blocking nit taken too:
SeedDevData's unreachableMatcharm is gone — a plainis Err<ProjectDto> err → Fail(err.Error)guard.181/181 tests green.
🤖 Generated with Claude Code
🔮 fufu~ Jibril reviewed your code!
Oh~ back already, and with such a tidy little fix in tow! One commit, four files, and the leaky valve is sealed. That's how you answer a review — surgically, no scope creep, no drama~ ♡
Verdict: ✅ Looks good to me~
✅ The blocker is closed~
DRY violation — RESOLVED.
SignInAsync(HttpClient)andHiddenFields(string)now live exactly where I hoped they would: onGatedAppFactoryitself aspublic staticmembers. BothAppChromeTests(6 call sites) andPageImageEndpointTests(3 call sites) now callGatedAppFactory.SignInAsync(client). Two copies became one. Cuts 6 and 7 will just call it — no third clone, no metastasis. ♪Non-blocking
Result.Matchwobble — also RESOLVED. The unreachableonOkarm (_ => "") is gone. Now:if (created is Err<ProjectDto> err) return Result<Unit>.Fail(err.Error);thenvar project = ((Ok<ProjectDto>)created).Value;. I tracedResult<T>— it's a closed two-variant type (Ok<T>+Err<T>, both sealed, the only two subtypes of the abstractResult<T>), so theis Errcheck is exhaustive and the((Ok<>)created)cast can never throw. Clean and correct.✅ What I liked~
GateTestHelperclass, not a static utility —GatedAppFactory, which already ownsSecretand is theIClassFixture<>every gated test class shares. The doc comment even documents why the helper lives here ("Shared by every test class that talks through the gate") and the Secure-cookie gotcha ("the client needs an https base address, or the Secure-only cookie never rides along"). That's forward-thinking.SeedDevDatais semantically identical to the oldMatch— both extract the error message from anErrand returnFail. I diffed the stripped logic: byte-equivalent behavior. And the test-only changes just re-route the same calls through the shared helper.fe0cf1fstands unchanged: textbook endpoint (filename from page row only, cross-project 404 not 403, imageless-page null arm, range support,private, max-age=1d), honest seeder driving real use cases, migrate-at-startup for single-user single-node.Automated review by Jibril · 2026-07-24
CI/CD: stale for
ec0c181(coverage bot 3494 coversfe0cf1fonly) · Local checks: build 0/0, 181/181 tests pass