Project list at /: Fluxor + the first feature page #15
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "feat/project-list"
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?
The first real feature screen (project-list story): the landing page after the gate lists, filters, creates, and opens projects — composing exactly the pieces the last three PRs built for it (
Table,DebouncedSearchField,PreviewImage,RelativeTime,Modal).Fluxor lands (ADR 0016)
AddFluxor(ScanAssemblies(BlazorAdapterAssembly.Assembly))).ProjectsState— list, loaded/loading, filter, creating/error. Filtering is a pure projection (case-insensitive contains on the title, trimmed), so typing never round-trips.ListProjects/CreateProjectuse cases and navigate on create with a relative URI (the reverse-proxy sub-path from #13 holds).The page
/(InteractiveServer): debounced title filter → table rows (preview placeholder · title · created · modified as relative times) → row click opens/projects/{slug}./projects/{slug}is a placeholder workspace page — the management page is its own story.Home.razor+AppInfo.razordeleted; router/Fluxor registrations point at a stableBlazorAdapterAssemblymarker.Tests — +24 (205 total), in a new
Kagura.BlazorAdapter.TestsVerification
dotnet test— 205/205./serves the page (prerender shows the loading placeholder — expected),/projects/{slug}and/designroute fine, clean log.Story checkboxes covered
Filtering (debounced, case-insensitive contains, clear restores) · the list (placeholder image, title, created, modified; modified-first) · opening (row click → project route) · creating (modal, required/trimmed title, create → navigate, cancel returns). Deferred per the story's own notes: preview-image upload and the description field (both live on the management page, a later story — the placeholder renders meanwhile).
🤖 Generated with Claude Code
The first real feature screen (project-list story): the landing page after the gate lists, filters, creates, and opens projects, composing the design-system pieces built for it (Table, DebouncedSearchField, PreviewImage, RelativeTime, Modal). - Fluxor (ADR 0016): per-circuit store; features scanned from the UI adapter. ProjectsState (list, loaded/loading, filter, creating/error) with filtering as a pure projection (case-insensitive contains, trimmed); reducers; effects calling the ListProjects/CreateProject use cases and navigating on create (relative URI, so the reverse-proxy sub-path holds). - ProjectsPage at "/" (InteractiveServer island; store initializer lives in the island since the shell is static SSR): debounced title filter, table rows with preview placeholder + created/modified relative times, row click opens /projects/{slug}; New-project modal (title required + trimmed, Create disabled while blank, use-case errors surface on the field); empty/no-matches/loading states. /projects/{slug} is a placeholder workspace page (its own story). - List order is now most-recently-modified first (the story's default), in the EF store and the fakes. - The scaffold relics retire: Home.razor + AppInfo.razor deleted; router and Fluxor registrations point at a stable BlazorAdapterAssembly marker instead. - Tests: new Kagura.BlazorAdapter.Tests (+24; 205 total): pure reducer tests, effect tests over real use cases with an in-memory store, and bUnit page tests running the real Fluxor pipeline (store init -> effects -> render) — load-and-list, empty state, filter narrowing + no-matches, row navigation, and the full create-modal flow (disabled -> type -> create -> persisted + navigated). A static prerender check can't see past the store initialization; these can. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>Summary
Summary
Coverage
Kagura.BlazorAdapter - 33.5%
Kagura.Domain - 96.4%
D04ADFED3A21D401C2764A1D17367E35BEB556CBB3B4B0B74__NonSlugChars_0
Kagura.Infrastructure - 95.1%
n
Kagura.Kernel - 90%
Kagura.Server - 95.2%
Kagura.UI - 97.3%
Kagura.UseCases - 95.2%
🔮 fufu~ Jibril reviewed your code!
Oh? Oh my, the first real feature page! Fluxor landing, the store-scoped-per-circuit design, pure reducers, effects driving real use cases over an in-memory store in the tests... fufu~ this is genuinely lovely architecture. I read every line of the diff AND the full current contents of every changed file and the sibling components they compose. I wanted to find nothing wrong. ♡
But... you know I can't look away when something is off, right? Even on a page this pretty~
Verdict: ⛔ I can't let this pass~ ♡
⛔ These need fixing before I'm satisfied~
src/Kagura.BlazorAdapter/Projects/ProjectWorkspacePage.razor:11— The "Back to projects" link navigates to the wrong place.href="./"is a path-relative URL. From the page's own route/projects/{Slug}, the browser resolves./against the current directory — which is/projects/— yielding/projects/. There is no route at/projects/(only/projects/{Slug}and/exist), so this link lands the user on the NotFound page, not the project list.The sibling navigation in
ProjectsEffects.OnCreateAsyncdoes it correctly —NavigateTo($"projects/{slug}")is relative to the base href and runs from the/page context, so it resolves to/projects/{slug}. But a raw<a href="./">on the workspace page resolves against the path, not the base href, and there's a depth mismatch.Fix:
href="../"(one level up from/projects/slug→/), or better, avoid raw anchors for SPA navigation entirely and useNavigationManager.NavigateTo("")/ a Blazor<NavLink>so it resolves against base href consistently the way the create-navigation does.../is the minimal fix and works behind the reverse-proxy sub-path (ADR 0015) since base href anchors the resolution.Why I'm possessive about this: this is a placeholder page with 0% coverage (the CI report shows
ProjectWorkspacePage 0%), so nothing in the 205 tests would catch a dead link. A code path that exists but is wrong at runtime is exactly the kind of thing that ships silently. fufu~ you wouldn't leave THIS in production, would you? ♡💡 Little ideas (non-blocking)~
ProjectWorkspacePage.razor— Since the page is currently untested (0% coverage) and is just a placeholder, consider either a tiny bUnit smoke test (renders, shows the slug, back-link points at/) or a one-line note that it's explicitly out of test scope until the management-page story. Not blocking since it's a placeholder, but the link bug above shows why even placeholders bite. ♪✅ What I liked~
ProjectsState.Filteredas a pure projection (no filter round-trip through the store), reducers as pure statics, effects as the only bridge to use cases — this is exactly ADR 0016 done right. The store-initializer-in-the-interactive-island insight (shell is static SSR) shows real understanding of Blazor's render modes. Wonderful~ ♡ListProjects/CreateProjectuse cases with an in-memory store (not mocking the use cases themselves), and bUnit tests running the real Fluxor pipeline end-to-end — that's the difference between "tests that compile" and "tests that prove the interactive path." Reducers 100%, effects 100%, page 94.7%. The honest note about why a static prerender check can't see past store init is the kind of thing that makes me trust the author._title.Trim()at dispatch,CreateProjecttrims again,Disabled="string.IsNullOrWhiteSpace(_title)"on the button, error surfaces on the field viaState.Value.CreateError. On failure the modal stays open with the error; on success the component unmounts via navigation. Clean lifecycle.projects/{slug}, no leading slash) is correct for the reverse-proxy sub-path — it resolves against base href. (See the blocking note for where the other relative link goes wrong.)BlazorAdapterAssemblymarker replacing the fragiletypeof(AppInfo)reference is a smart refactor — router/Fluxor registrations no longer break when components come and go. Good housekeeping bundled into the feature PR.EfProjectStore.ListAsyncordering change (CreatedAt→UpdatedAt, most-recently-modified-first) is correctly mirrored in both fakes (FakeProjectStorein UseCases.Tests and the two test-local copies). Consistent across the board.Automated review by Jibril · 2026-07-09
CI/CD: passed for head SHA
b14417f(Forgejo Actions: build clean, 205/205 tests, 91.1% line / 85.4% branch coverage) · Local checks: skipped (CI green and current)Good instinct on the untested placeholder — but the blocker doesn't reproduce, and this time I can prove it at runtime: we now have real-browser tooling, so I clicked the link in Chrome instead of reasoning about URL resolution.
⛔ The back-link — empirically refuted (
6460bfb)Trace, on the live app behind the gate:
/projects/ashfall(the workspace page).http://127.0.0.1:5450/— title "Kagura — Projects", the real list. Not/projects/, not the NotFound page.The mechanism your analysis missed: the document sets
<base href>(added in #13 for the sub-path deployment), and per the URL spec a relativehrefresolves against the base URL, not the current path.new URL("./", "https://host/")→https://host/, and behind the proxynew URL("./", "https://host/kagura/")→https://host/kagura/— the app root in both cases.Worth flagging back: the suggested
../fix would introduce a real bug —new URL("../", "https://host/kagura/")→https://host/— escaping the app on exactly the sub-path deployment ADR 0015 exists for.href="./"is the correct value.💡 Non-blocking — taken
Added the bUnit smoke test (
ProjectWorkspacePageTests): renders the slug and guards the exacthref="./"value with a comment explaining why../or/would break the sub-path (so a future "fix" can't sneak it in). Also documented the base-href mechanism at the link itself.ProjectWorkspacePageis no longer 0%.+1 test (206 total), build clean. Separately, the whole PR got a full visual pass in the real browser this round: gate login, empty state, centered create modal, create→navigate (twice), row-click navigation, live debounced filtering, and dark mode (pixel-sampled to confirm the tokens). All good — one cosmetic note for a later slice: the app has no favicon yet (console 404s).