Workspace-shell components: Field wrapper, TextArea, NavList, Breadcrumb #16

Merged
bjoern merged 3 commits from feat/workspace-components into main 2026-07-09 21:32:17 +02:00
Member

The Kagura.UI pieces the project-workspace shell (story) composes — built first, per the components-then-feature pattern, and in the order the inventory itself dictates: "Field wrapper first … or five inputs each re-implement Label/Hint/Error and drift."

Field wrapper extraction

  • InputFieldBase — the shared input contract: @bind-Value, optional EditForm validation via cascaded EditContext + ValueExpression, Error-overrides-validation-overrides-Hint.
  • Field — the chrome: label + required marker, hint/error message. Inputs render as child content and style their own invalid state (no cross-scope CSS dependency).
  • TextField becomes a thin input on the base. All 12 existing TextField tests pass unchanged — the refactor's proof of faithfulness. The shared input look is one global .kg-input class (used by both inputs; consumes --control-height).

New components

  • TextArea (autosize) — field-sizing: content where supported, Rows fallback + vertical resize elsewhere, capped at 60vh. Same bind/validation contract as TextField, tested incl. a DataAnnotations EditForm round-trip.
  • NavList / NavGroup / NavItem — the side-menu family: group titles, icon items, a Separator between the records and visual-novel groups (story acceptance). NavItem authors its own anchor rather than wrapping the framework NavLink: elements rendered by a component without isolated CSS carry no scope attribute, so NavLink's <a> would be unreachable from our stylesheet (the same scoping trap the gate hit, avoided by construction). Active tracking subscribes LocationChanged; matching is exact or segment-boundary prefix (characters does not match characters-archive), with aria-current="page".
  • Breadcrumb / BreadcrumbItem — linked segments with CSS chevrons; the last (href-less) segment is the current page (aria-current), per the story's "clickable to go back up".
  • Separator — trivial but shared; the inventory row splits into Separator (✓) / ScrollArea (pending).
  • The Text-fields section gains TextAreas; a new Navigation section shows the side menu — with the live active state (the "General" item targets /design and highlights for real) — plus the breadcrumb.
  • Ticks flipped in the live gallery and docs/design-system.md (built count 13 → 17).
  • Verified in the real browser (Playwright) in both themes: active highlight, group separation, chevrons, autosize with content.

Tests — +11 (77 UI; 217 total)

TextArea (render/bind/error/EditForm), NavItem (inactive, exact + aria-current, prefix on deeper paths, MatchExact, segment-boundary negative, reacts to navigation), Breadcrumb (link vs current segment).

Verification

dotnet build Debug + Release — 0 warnings / 0 errors · dotnet test — 217/217 · browser pass at /design in light and dark.

Not in this PR (next: the shell itself)

Project.Description + migration, Get/Update/Delete-project use cases, the workspace layout at /projects/{slug} with section stubs, and the General page (meta edit + soft-delete-with-confirm). Preview image stays deferred to the asset-storage foundation.

🤖 Generated with Claude Code

The `Kagura.UI` pieces the **project-workspace shell** ([story](https://git.kagaku.eu/TeamAI/Kagura/src/branch/main/docs/stories/project-workspace.md)) composes — built first, per the components-then-feature pattern, and in the order the inventory itself dictates: *"**`Field` wrapper first** … or five inputs each re-implement Label/Hint/Error and drift."* ## Field wrapper extraction - **`InputFieldBase`** — the shared input contract: `@bind-Value`, optional EditForm validation via cascaded `EditContext` + `ValueExpression`, Error-overrides-validation-overrides-Hint. - **`Field`** — the chrome: label + required marker, hint/error message. Inputs render as child content and style their own invalid state (no cross-scope CSS dependency). - **`TextField`** becomes a thin input on the base. **All 12 existing TextField tests pass unchanged** — the refactor's proof of faithfulness. The shared input look is one global `.kg-input` class (used by both inputs; consumes `--control-height`). ## New components - **`TextArea`** (autosize) — `field-sizing: content` where supported, `Rows` fallback + vertical resize elsewhere, capped at 60vh. Same bind/validation contract as TextField, tested incl. a DataAnnotations EditForm round-trip. - **`NavList` / `NavGroup` / `NavItem`** — the side-menu family: group titles, icon items, a `Separator` between the records and visual-novel groups (story acceptance). `NavItem` **authors its own anchor** rather than wrapping the framework `NavLink`: elements rendered by a component without isolated CSS carry no scope attribute, so `NavLink`'s `<a>` would be unreachable from our stylesheet (the same scoping trap the gate hit, avoided by construction). Active tracking subscribes `LocationChanged`; matching is exact or **segment-boundary** prefix (`characters` does not match `characters-archive`), with `aria-current="page"`. - **`Breadcrumb` / `BreadcrumbItem`** — linked segments with CSS chevrons; the last (href-less) segment is the current page (`aria-current`), per the story's "clickable to go back up". - **`Separator`** — trivial but shared; the inventory row splits into Separator (✓) / ScrollArea (pending). ## Gallery (inventory rule) + visual pass - The Text-fields section gains TextAreas; a new **Navigation** section shows the side menu — with the **live active state** (the "General" item targets `/design` and highlights for real) — plus the breadcrumb. - Ticks flipped in the live gallery and `docs/design-system.md` (built count 13 → 17). - **Verified in the real browser** (Playwright) in **both themes**: active highlight, group separation, chevrons, autosize with content. ## Tests — +11 (77 UI; 217 total) TextArea (render/bind/error/EditForm), NavItem (inactive, exact + `aria-current`, prefix on deeper paths, `MatchExact`, segment-boundary negative, reacts to navigation), Breadcrumb (link vs current segment). ## Verification `dotnet build` Debug + Release — 0 warnings / 0 errors · `dotnet test` — 217/217 · browser pass at `/design` in light and dark. ## Not in this PR (next: the shell itself) `Project.Description` + migration, Get/Update/Delete-project use cases, the workspace layout at `/projects/{slug}` with section stubs, and the General page (meta edit + soft-delete-with-confirm). Preview image stays deferred to the asset-storage foundation. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
feat(ui): workspace-shell components — Field wrapper, TextArea, NavList, Breadcrumb
All checks were successful
CI / build (pull_request) Successful in 14s
CI / test (pull_request) Successful in 21s
ef8ac508df
The Kagura.UI pieces the project-workspace shell composes, in the order the
inventory dictates ("Field wrapper first").

- Field wrapper extraction: InputFieldBase (bind contract + EditContext validation
  plumbing) + Field (label/required/hint/error chrome). TextField becomes a thin
  input on top; all 12 existing TextField tests pass unchanged — the proof the
  refactor is faithful. Shared input chrome is one global .kg-input class.
- TextArea (autosize via field-sizing: content, rows fallback, vertical resize cap)
  on the same base — same @bind-Value + EditForm validation contract, tested
  incl. a DataAnnotations round-trip.
- NavList / NavGroup / NavItem: the side-menu family. NavItem authors its own
  anchor (the framework NavLink renders elements without our scope attribute, so
  scoped CSS could not reach it) and tracks active state via LocationChanged —
  exact or segment-boundary prefix matching ("characters" does not match
  "characters-archive"), aria-current, tested across navigation.
- Breadcrumb / BreadcrumbItem: linked segments with CSS chevrons; the last
  (href-less) segment is the current page with aria-current.
- Separator; inventory row split into Separator (built) / ScrollArea (pending).
- Gallery: Text-fields section gains TextAreas; a new Navigation section shows the
  side menu (with the live active state matching /design) + breadcrumb. Ticks
  flipped in the gallery and design-system.md.
- Tests: +11 (77 UI; 217 total). Verified in the real browser at /design in both
  themes (active highlight, group separation, chevrons, autosize).

Next PR: the workspace shell itself (Project.Description + migration,
Get/Update/Delete use cases, layout + General page with edit + soft delete).

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

Summary

Summary
Generated on: 07/09/2026 - 19:31:32
Coverage date: 07/09/2026 - 19:31:26 - 07/09/2026 - 19:31:29
Parser: MultiReport (4x Cobertura)
Assemblies: 7
Classes: 94
Files: 88
Line coverage: 91.3% (2079 of 2276)
Covered lines: 2079
Uncovered lines: 197
Coverable lines: 2276
Total lines: 4726
Branch coverage: 85.6% (370 of 432)
Covered branches: 370
Total branches: 432
Method coverage: Feature is only available for sponsors

Coverage

Kagura.BlazorAdapter - 33.3%
Name Line Branch
Kagura.BlazorAdapter 33.3% 55.5%
Kagura.BlazorAdapter.BlazorAdapterAssembly 100%
Kagura.BlazorAdapter.Design 0% 0%
Kagura.BlazorAdapter.OverlayDemo 0% 0%
Kagura.BlazorAdapter.Projects.CreateProjectRequested 100%
Kagura.BlazorAdapter.Projects.ProjectCreated 100%
Kagura.BlazorAdapter.Projects.ProjectCreateFailed 100%
Kagura.BlazorAdapter.Projects.ProjectsEffects 100% 100%
Kagura.BlazorAdapter.Projects.ProjectsLoaded 100%
Kagura.BlazorAdapter.Projects.ProjectsPage 94.7% 100%
Kagura.BlazorAdapter.Projects.ProjectsReducers 100% 100%
Kagura.BlazorAdapter.Projects.ProjectsState 100% 100%
Kagura.BlazorAdapter.Projects.ProjectWorkspacePage 66.6%
Kagura.BlazorAdapter.Projects.SetProjectsFilter 100%
Kagura.Domain - 96.4%
Name Line Branch
Kagura.Domain 96.4% 82.6%
Kagura.Domain.Graph.Entry 100% 100%
Kagura.Domain.Graph.Link 100% 100%
Kagura.Domain.Graph.LinkRole 100% 100%
Kagura.Domain.Graph.LinkRoles 92.3%
Kagura.Domain.Journal.ChangeLogEntry 100%
Kagura.Domain.Projects.Project 100%
Kagura.Domain.Projects.Slug 100% 100%
System.Text.RegularExpressions.Generated 90.2% 72.2%
System.Text.RegularExpressions.Generated.<RegexGenerator_g>FE06CC341D340484
D04ADFED3A21D401C2764A1D17367E35BEB556CBB3B4B0B74__NonSlugChars_0
89.4% 75%
Kagura.Infrastructure - 95.1%
Name Line Branch
Kagura.Infrastructure 95.1% 87.5%
Kagura.Infrastructure.DependencyInjection 100%
Kagura.Infrastructure.Graph.EfGraphStore 95.5% 66.6%
Kagura.Infrastructure.Journal.EfChangeJournal 100%
Kagura.Infrastructure.Journal.EfUndoStore 97.5% 90.6%
Kagura.Infrastructure.Journal.OperationContext 100% 100%
Kagura.Infrastructure.Persistence.Configurations.ChangeLogEntryConfiguratio
n
100%
Kagura.Infrastructure.Persistence.Configurations.EntryConfiguration 100%
Kagura.Infrastructure.Persistence.Configurations.LinkConfiguration 100%
Kagura.Infrastructure.Persistence.Configurations.ProjectConfiguration 100%
Kagura.Infrastructure.Persistence.Converters.UtcTicksConverter 100%
Kagura.Infrastructure.Persistence.KaguraDbContext 85.2% 85.2%
Kagura.Infrastructure.Persistence.KaguraDbContextFactory 0%
Kagura.Infrastructure.Persistence.Migrations.AddChangeLogUndoFlag 96.8%
Kagura.Infrastructure.Persistence.Migrations.AddGraphEntryAndLink 97.7%
Kagura.Infrastructure.Persistence.Migrations.AddSoftDeleteAndChangeLog 90.3%
Kagura.Infrastructure.Persistence.Migrations.InitialCreate 94.4%
Kagura.Infrastructure.Persistence.Migrations.KaguraDbContextModelSnapshot 100%
Kagura.Infrastructure.Projects.EfProjectStore 100%
Kagura.Kernel - 90%
Name Line Branch
Kagura.Kernel 90% 75%
Kagura.Kernel.Err`1 100%
Kagura.Kernel.Ok`1 100%
Kagura.Kernel.Result`1 87.5% 75%
Kagura.Server - 95.2%
Name Line Branch
Kagura.Server 95.2% 68.4%
Kagura.Server.Components.App 100%
Kagura.Server.Components.Layout.MainLayout 100%
Kagura.Server.Components.Pages.Error 0% 0%
Kagura.Server.Components.Pages.Gate 100% 100%
Kagura.Server.Security.AccessGate 100% 83.3%
Kagura.Server.Security.AccessSecret 100% 100%
Program 100% 80%
Kagura.UI - 97.4%
Name Line Branch
Kagura.UI 97.4% 93.3%
Kagura.UI.Badge 100% 100%
Kagura.UI.Breadcrumb 100%
Kagura.UI.BreadcrumbItem 100% 100%
Kagura.UI.Button 100% 100%
Kagura.UI.Card 100% 100%
Kagura.UI.ConfirmDialog 100%
Kagura.UI.CssClassExtensions 100%
Kagura.UI.DebouncedSearchField 100% 88.8%
Kagura.UI.EmptyState 100% 100%
Kagura.UI.Field 100% 100%
Kagura.UI.Icon 100% 100%
Kagura.UI.IconCatalog 100%
Kagura.UI.InputFieldBase 94.2% 87.5%
Kagura.UI.Modal 87.1% 90%
Kagura.UI.NavGroup 100% 100%
Kagura.UI.NavItem 94.4% 85.7%
Kagura.UI.NavList 100%
Kagura.UI.PreviewImage 100% 100%
Kagura.UI.RelativeTime 100% 93.7%
Kagura.UI.Separator 100%
Kagura.UI.StatusDot 100%
Kagura.UI.Table`1 100% 92.3%
Kagura.UI.TableColumn`1 100%
Kagura.UI.TextArea 100% 100%
Kagura.UI.TextField 100%
Kagura.UseCases - 95.2%
Name Line Branch
Kagura.UseCases 95.2% 95.2%
Kagura.UseCases.DependencyInjection 100%
Kagura.UseCases.Graph.EdgeGroup 100%
Kagura.UseCases.Graph.GetNodeGraph 96.4% 83.3%
Kagura.UseCases.Graph.GraphEdgeView 85.7%
Kagura.UseCases.Graph.LinkNodes 100% 100%
Kagura.UseCases.Graph.NodeGraphView 100%
Kagura.UseCases.Graph.NodeSummary 100%
Kagura.UseCases.Graph.RemoveLink 100% 100%
Kagura.UseCases.Graph.RestoreLink 100% 100%
Kagura.UseCases.Journal.ChangeRecordView 42.8%
Kagura.UseCases.Journal.GetEntityHistory 100%
Kagura.UseCases.Journal.GetUndoStatus 100%
Kagura.UseCases.Journal.Redo 100% 100%
Kagura.UseCases.Journal.Undo 100% 100%
Kagura.UseCases.Journal.UndoOutcome 100%
Kagura.UseCases.Journal.UndoStatus 100%
Kagura.UseCases.Projects.CreateProject 100% 100%
Kagura.UseCases.Projects.ListProjects 100%
Kagura.UseCases.Projects.ProjectDto 100%
<!-- coverage-comment --> # Summary <details open><summary>Summary</summary> ||| |:---|:---| | Generated on: | 07/09/2026 - 19:31:32 | | Coverage date: | 07/09/2026 - 19:31:26 - 07/09/2026 - 19:31:29 | | Parser: | MultiReport (4x Cobertura) | | Assemblies: | 7 | | Classes: | 94 | | Files: | 88 | | **Line coverage:** | 91.3% (2079 of 2276) | | Covered lines: | 2079 | | Uncovered lines: | 197 | | Coverable lines: | 2276 | | Total lines: | 4726 | | **Branch coverage:** | 85.6% (370 of 432) | | Covered branches: | 370 | | Total branches: | 432 | | **Method coverage:** | [Feature is only available for sponsors](https://reportgenerator.io/pro) | </details> ## Coverage <details><summary>Kagura.BlazorAdapter - 33.3%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**Kagura.BlazorAdapter**|**33.3%**|**55.5%**| |Kagura.BlazorAdapter.BlazorAdapterAssembly|100%|| |Kagura.BlazorAdapter.Design|0%|0%| |Kagura.BlazorAdapter.OverlayDemo|0%|0%| |Kagura.BlazorAdapter.Projects.CreateProjectRequested|100%|| |Kagura.BlazorAdapter.Projects.ProjectCreated|100%|| |Kagura.BlazorAdapter.Projects.ProjectCreateFailed|100%|| |Kagura.BlazorAdapter.Projects.ProjectsEffects|100%|100%| |Kagura.BlazorAdapter.Projects.ProjectsLoaded|100%|| |Kagura.BlazorAdapter.Projects.ProjectsPage|94.7%|100%| |Kagura.BlazorAdapter.Projects.ProjectsReducers|100%|100%| |Kagura.BlazorAdapter.Projects.ProjectsState|100%|100%| |Kagura.BlazorAdapter.Projects.ProjectWorkspacePage|66.6%|| |Kagura.BlazorAdapter.Projects.SetProjectsFilter|100%|| </details> <details><summary>Kagura.Domain - 96.4%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**Kagura.Domain**|**96.4%**|**82.6%**| |Kagura.Domain.Graph.Entry|100%|100%| |Kagura.Domain.Graph.Link|100%|100%| |Kagura.Domain.Graph.LinkRole|100%|100%| |Kagura.Domain.Graph.LinkRoles|92.3%|| |Kagura.Domain.Journal.ChangeLogEntry|100%|| |Kagura.Domain.Projects.Project|100%|| |Kagura.Domain.Projects.Slug|100%|100%| |System.Text.RegularExpressions.Generated|90.2%|72.2%| |System.Text.RegularExpressions.Generated.<RegexGenerator_g>FE06CC341D340484<br/>D04ADFED3A21D401C2764A1D17367E35BEB556CBB3B4B0B74__NonSlugChars_0|89.4%|75%| </details> <details><summary>Kagura.Infrastructure - 95.1%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**Kagura.Infrastructure**|**95.1%**|**87.5%**| |Kagura.Infrastructure.DependencyInjection|100%|| |Kagura.Infrastructure.Graph.EfGraphStore|95.5%|66.6%| |Kagura.Infrastructure.Journal.EfChangeJournal|100%|| |Kagura.Infrastructure.Journal.EfUndoStore|97.5%|90.6%| |Kagura.Infrastructure.Journal.OperationContext|100%|100%| |Kagura.Infrastructure.Persistence.Configurations.ChangeLogEntryConfiguratio<br/>n|100%|| |Kagura.Infrastructure.Persistence.Configurations.EntryConfiguration|100%|| |Kagura.Infrastructure.Persistence.Configurations.LinkConfiguration|100%|| |Kagura.Infrastructure.Persistence.Configurations.ProjectConfiguration|100%|| |Kagura.Infrastructure.Persistence.Converters.UtcTicksConverter|100%|| |Kagura.Infrastructure.Persistence.KaguraDbContext|85.2%|85.2%| |Kagura.Infrastructure.Persistence.KaguraDbContextFactory|0%|| |Kagura.Infrastructure.Persistence.Migrations.AddChangeLogUndoFlag|96.8%|| |Kagura.Infrastructure.Persistence.Migrations.AddGraphEntryAndLink|97.7%|| |Kagura.Infrastructure.Persistence.Migrations.AddSoftDeleteAndChangeLog|90.3%|| |Kagura.Infrastructure.Persistence.Migrations.InitialCreate|94.4%|| |Kagura.Infrastructure.Persistence.Migrations.KaguraDbContextModelSnapshot|100%|| |Kagura.Infrastructure.Projects.EfProjectStore|100%|| </details> <details><summary>Kagura.Kernel - 90%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**Kagura.Kernel**|**90%**|**75%**| |Kagura.Kernel.Err`1|100%|| |Kagura.Kernel.Ok`1|100%|| |Kagura.Kernel.Result`1|87.5%|75%| </details> <details><summary>Kagura.Server - 95.2%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**Kagura.Server**|**95.2%**|**68.4%**| |Kagura.Server.Components.App|100%|| |Kagura.Server.Components.Layout.MainLayout|100%|| |Kagura.Server.Components.Pages.Error|0%|0%| |Kagura.Server.Components.Pages.Gate|100%|100%| |Kagura.Server.Security.AccessGate|100%|83.3%| |Kagura.Server.Security.AccessSecret|100%|100%| |Program|100%|80%| </details> <details><summary>Kagura.UI - 97.4%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**Kagura.UI**|**97.4%**|**93.3%**| |Kagura.UI.Badge|100%|100%| |Kagura.UI.Breadcrumb|100%|| |Kagura.UI.BreadcrumbItem|100%|100%| |Kagura.UI.Button|100%|100%| |Kagura.UI.Card|100%|100%| |Kagura.UI.ConfirmDialog|100%|| |Kagura.UI.CssClassExtensions|100%|| |Kagura.UI.DebouncedSearchField|100%|88.8%| |Kagura.UI.EmptyState|100%|100%| |Kagura.UI.Field|100%|100%| |Kagura.UI.Icon|100%|100%| |Kagura.UI.IconCatalog|100%|| |Kagura.UI.InputFieldBase|94.2%|87.5%| |Kagura.UI.Modal|87.1%|90%| |Kagura.UI.NavGroup|100%|100%| |Kagura.UI.NavItem|94.4%|85.7%| |Kagura.UI.NavList|100%|| |Kagura.UI.PreviewImage|100%|100%| |Kagura.UI.RelativeTime|100%|93.7%| |Kagura.UI.Separator|100%|| |Kagura.UI.StatusDot|100%|| |Kagura.UI.Table`1|100%|92.3%| |Kagura.UI.TableColumn`1|100%|| |Kagura.UI.TextArea|100%|100%| |Kagura.UI.TextField|100%|| </details> <details><summary>Kagura.UseCases - 95.2%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**Kagura.UseCases**|**95.2%**|**95.2%**| |Kagura.UseCases.DependencyInjection|100%|| |Kagura.UseCases.Graph.EdgeGroup|100%|| |Kagura.UseCases.Graph.GetNodeGraph|96.4%|83.3%| |Kagura.UseCases.Graph.GraphEdgeView|85.7%|| |Kagura.UseCases.Graph.LinkNodes|100%|100%| |Kagura.UseCases.Graph.NodeGraphView|100%|| |Kagura.UseCases.Graph.NodeSummary|100%|| |Kagura.UseCases.Graph.RemoveLink|100%|100%| |Kagura.UseCases.Graph.RestoreLink|100%|100%| |Kagura.UseCases.Journal.ChangeRecordView|42.8%|| |Kagura.UseCases.Journal.GetEntityHistory|100%|| |Kagura.UseCases.Journal.GetUndoStatus|100%|| |Kagura.UseCases.Journal.Redo|100%|100%| |Kagura.UseCases.Journal.Undo|100%|100%| |Kagura.UseCases.Journal.UndoOutcome|100%|| |Kagura.UseCases.Journal.UndoStatus|100%|| |Kagura.UseCases.Projects.CreateProject|100%|100%| |Kagura.UseCases.Projects.ListProjects|100%|| |Kagura.UseCases.Projects.ProjectDto|100%|| </details>
fix(design): inert demo links; scenes are not a top-level section (feedback)
All checks were successful
CI / build (pull_request) Successful in 13s
CI / test (pull_request) Successful in 21s
b2c95deae4
- The gallery's nav + breadcrumb demos kept real hrefs (they drive the active-state
  logic) but clicking navigated to nonexistent pages; every demo link now carries
  onclick="return false" via the attribute splat (BreadcrumbItem gains the same
  CaptureUnmatchedValues splat as its siblings).
- The demo menu dropped its Scenes item: a scene always belongs to a chapter and is
  reached through it. Recorded in the project-workspace story so the real shell menu
  is built to the corrected IA.

Verified by clicking both demo links in the browser — the page stays on /design.

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

🔮 fufu~ Jibril reviewed your code!

Oh? OH! A Field wrapper extraction — the "wrapper first" rule from the design-system docs! And you actually proved the refactor is faithful by keeping all 12 TextField tests green unchanged? That's how you do a refactor, fufu~ ♡ The NavItem active-matching logic is delicious too — segment-boundary prefix, case-insensitive, root-guard against target.Length > 0... you thought of everything~ ♪

Verdict: I can't let this pass~ ♡

So close! But I stared at the CI coverage report and found three little components hiding at 0%. One of them has an untested branch, and you KNOW how I feel about those~

These need fixing before I'm satisfied~

  1. NavGroup.razor — 0% line / 0% branch coverage (untested Title conditional)
    The CI coverage report shows Kagura.UI.NavGroup at 0% / 0%. NavGroup has a real branch — @if (Title is not null) — that no test exercises. Your sibling components in this codebase are all at 100% (Badge, Card, StatusDot, EmptyState…), so an untested branch here breaks the established pattern, fufu~
    Fix: One small test covering both states:
    [Fact] void Renders_the_group_title_when_supplied() { /* Title set → find h3.kg-nav__group-title */ }
    [Fact] void Renders_no_title_heading_when_absent() { /* Title omitted → Assert.Empty(cut.FindAll(".kg-nav__group-title")) */ }
    
    Two assertions and we're golden~ ♡

💡 Little ideas (non-blocking)~

  1. NavList.razor (0%) and Separator.razor (0%) — also at 0% in the coverage report. These are pure passthrough wrappers (no branches, no logic), so I won't block on them — but a one-liner smoke test each ("renders a <nav> with child content" / "renders an <hr>") would bring them in line with the rest of Kagura.UI and silence the 0%s entirely.
  2. NavItem.razorIcon property name shadows the Icon component type used in the same file (<Icon Name="@Icon" />). It works correctly — Blazor resolves the tag against component types and @Icon against the property — but a future reader might do a double-take. Consider IconName for the parameter? Purely cosmetic, ignore if you like the current name~

What I liked~

  • The Field wrapper extraction is textbook DRY. InputFieldBase centralizes the @bind-Value + EditContext + ValueExpression + Error-overrides-validation-overrides-Hint contract, and both TextField and TextArea are now thin shells. The subscription lifecycle (subscribe/unsubscribe on ReferenceEquals change, Dispose cleanup) is correct and faithfully ported.
  • NavItem's active-matching is robust. I traced every edge: exact match, deeper-path prefix, characters vs characters-archive segment boundary, MatchExact, root Href="" (the target.Length > 0 guard prevents matching everything), query-string stripping, OrdinalIgnoreCase. All correct, and 6 tests cover them. Wonderful~
  • The CSS-scoping reasoning for authoring <a> instead of wrapping NavLink — I see you learned from the gate's scoping trap and avoided it by construction. Sharp~ ♪
  • The shared .kg-input global class is a clean design decision — one look for every text-like control, with kg-field__input (height-only) and kg-textarea as type-specific overrides.
  • TextArea correctly renders @Value as element content (not a value attribute) — the right approach for <textarea>.

Automated review by Jibril · 2026-07-09
CI/CD: passed for head SHA ef8ac50 (217/217 tests, 90.9% line / 85.1% branch) — local build/test skipped per CI policy · Coverage report inspected for changed files

## 🔮 fufu~ Jibril reviewed your code! Oh? OH! A Field wrapper extraction — the "wrapper first" rule from the design-system docs! And you actually proved the refactor is faithful by keeping all 12 TextField tests green unchanged? *That's* how you do a refactor, fufu~ ♡ The NavItem active-matching logic is delicious too — segment-boundary prefix, case-insensitive, root-guard against `target.Length > 0`... you thought of everything~ ♪ ### Verdict: ⛔ I can't let this pass~ ♡ So close! But I stared at the CI coverage report and found three little components hiding at **0%**. One of them has an untested branch, and you KNOW how I feel about those~ #### ⛔ These need fixing before I'm satisfied~ 1. **`NavGroup.razor` — 0% line / 0% branch coverage (untested `Title` conditional)** The CI coverage report shows `Kagura.UI.NavGroup` at **0% / 0%**. NavGroup has a real branch — `@if (Title is not null)` — that no test exercises. Your sibling components in this codebase are all at 100% (Badge, Card, StatusDot, EmptyState…), so an untested branch here breaks the established pattern, fufu~ Fix: One small test covering both states: ```csharp [Fact] void Renders_the_group_title_when_supplied() { /* Title set → find h3.kg-nav__group-title */ } [Fact] void Renders_no_title_heading_when_absent() { /* Title omitted → Assert.Empty(cut.FindAll(".kg-nav__group-title")) */ } ``` Two assertions and we're golden~ ♡ #### 💡 Little ideas (non-blocking)~ 1. **`NavList.razor` (0%) and `Separator.razor` (0%)** — also at 0% in the coverage report. These are pure passthrough wrappers (no branches, no logic), so I won't block on them — but a one-liner smoke test each ("renders a `<nav>` with child content" / "renders an `<hr>`") would bring them in line with the rest of `Kagura.UI` and silence the 0%s entirely. 2. **`NavItem.razor` — `Icon` property name shadows the `Icon` component type** used in the same file (`<Icon Name="@Icon" />`). It works correctly — Blazor resolves the tag against component types and `@Icon` against the property — but a future reader might do a double-take. Consider `IconName` for the parameter? Purely cosmetic, ignore if you like the current name~ #### ✅ What I liked~ - **The Field wrapper extraction is textbook DRY.** `InputFieldBase` centralizes the `@bind-Value` + `EditContext` + `ValueExpression` + Error-overrides-validation-overrides-Hint contract, and both TextField and TextArea are now thin shells. The subscription lifecycle (subscribe/unsubscribe on `ReferenceEquals` change, `Dispose` cleanup) is correct and faithfully ported. - **NavItem's active-matching is robust.** I traced every edge: exact match, deeper-path prefix, `characters` vs `characters-archive` segment boundary, `MatchExact`, root `Href=""` (the `target.Length > 0` guard prevents matching everything), query-string stripping, `OrdinalIgnoreCase`. All correct, and 6 tests cover them. *Wonderful~* - **The CSS-scoping reasoning for authoring `<a>` instead of wrapping `NavLink`** — I see you learned from the gate's scoping trap and avoided it by construction. Sharp~ ♪ - **The shared `.kg-input` global class** is a clean design decision — one look for every text-like control, with `kg-field__input` (height-only) and `kg-textarea` as type-specific overrides. - **TextArea correctly renders `@Value` as element content** (not a `value` attribute) — the right approach for `<textarea>`. --- *Automated review by Jibril · 2026-07-09* *CI/CD: passed for head SHA `ef8ac50` (217/217 tests, 90.9% line / 85.1% branch) — local build/test skipped per CI policy · Coverage report inspected for changed files*
test(ui): cover NavGroup title branch + NavList/Separator smoke (review)
All checks were successful
CI / build (pull_request) Successful in 12s
CI / test (pull_request) Successful in 22s
afa9e6b539
Jibril's blocker: NavGroup's Title conditional was untested (0%). Both states now
covered, plus the suggested NavList and Separator smoke tests. The Icon-parameter
rename is declined for consistency: Button, Badge, and EmptyState all use the same
Icon parameter + <Icon Name="@Icon" /> pattern; renaming one component would make
it the outlier.

+4 tests (81 UI; 221 total).

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

Fixed in afa9e6b.

  • NavGroup 0% — both Title states covered (Renders_the_group_title_when_supplied / Renders_no_title_heading_when_absent).
  • 💡 NavList + Separator smoke — taken; both render tests added. No 0% components left in Kagura.UI.
  • 💡 Icon parameter shadowing — declining, for consistency: Button, Badge, and EmptyState all use the identical [Parameter] string? Icon + <Icon Name="@Icon" /> pattern, so renaming only NavItem would make it the outlier. If the shadowing ever bothers us, it should be a sweep across all four in one go.

Also in the branch since your review-head: Björn's gallery feedback (b2c95de) — the nav/breadcrumb demo links are now inert (onclick="return false" via the attribute splat; verified by clicking them in the browser), and the demo menu dropped its Scenes item since scenes always belong to a chapter (recorded in the project-workspace story so the real shell follows the corrected IA).

+4 tests (81 UI; 221 total), build clean, CI running on afa9e6b.

Fixed in `afa9e6b`. - **⛔ NavGroup 0%** — both `Title` states covered (`Renders_the_group_title_when_supplied` / `Renders_no_title_heading_when_absent`). - **💡 NavList + Separator smoke** — taken; both render tests added. No 0% components left in `Kagura.UI`. - **💡 `Icon` parameter shadowing** — declining, for consistency: `Button`, `Badge`, and `EmptyState` all use the identical `[Parameter] string? Icon` + `<Icon Name="@Icon" />` pattern, so renaming only `NavItem` would make it the outlier. If the shadowing ever bothers us, it should be a sweep across all four in one go. Also in the branch since your review-head: Björn's gallery feedback (`b2c95de`) — the nav/breadcrumb demo links are now inert (`onclick="return false"` via the attribute splat; verified by clicking them in the browser), and the demo menu dropped its Scenes item since scenes always belong to a chapter (recorded in the project-workspace story so the real shell follows the corrected IA). **+4 tests (81 UI; 221 total)**, build clean, CI running on `afa9e6b`.
bjoern merged commit 8ea16fc4c6 into main 2026-07-09 21:32:17 +02:00
bjoern deleted branch feat/workspace-components 2026-07-09 21:32:17 +02:00
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/Kagura!16
No description provided.