feat(nav): NavItem.Nested — a record indented under its section entry #21

Merged
bjoern merged 1 commit from feat/nav-item-nested into main 2026-09-23 08:23:38 +02:00
Member

Tsumugi's story pages (TeamAI/Tsumugi, the PR after #13) put a left NavList beside the story: General · Documents · Chapters · History, with each chapter listed under the Chapters entry as a third level. NavItem authors its own anchor and has no nesting parameter, so a consumer could only indent those entries through ::deep on its scoped CSS.

Change: one boolean parameter, Nested, that adds the kg-nav__item--nested modifier — padding-inline-start: calc(var(--space-3) + var(--space-5)) (past the section's icon) and the normal font weight. Active matching, MatchExact, aria-current and the LocationChanged re-render are untouched.

Tests: NavItemTests +2 — a nested item carries the modifier, is active on its exact path, goes inactive on a deeper path and keeps the modifier (the indent is not an active state); a plain item has no modifier. NavItemTests 6 → 8, all green.

No gallery or docs page mentions NavList, so nothing else changes.

🤖 Generated with Claude Code

Tsumugi's story pages (TeamAI/Tsumugi, the PR after #13) put a left `NavList` beside the story: General · Documents · Chapters · History, with **each chapter listed under the Chapters entry** as a third level. `NavItem` authors its own anchor and has no nesting parameter, so a consumer could only indent those entries through `::deep` on its scoped CSS. **Change:** one boolean parameter, `Nested`, that adds the `kg-nav__item--nested` modifier — `padding-inline-start: calc(var(--space-3) + var(--space-5))` (past the section's icon) and the normal font weight. Active matching, `MatchExact`, `aria-current` and the `LocationChanged` re-render are untouched. **Tests:** `NavItemTests` +2 — a nested item carries the modifier, is active on its exact path, goes inactive on a deeper path and keeps the modifier (the indent is not an active state); a plain item has no modifier. `NavItemTests` 6 → 8, all green. No gallery or docs page mentions `NavList`, so nothing else changes. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
feat(nav): NavItem.Nested — a record indented under its section entry
All checks were successful
CI / build (pull_request) Successful in 12s
CI / test (pull_request) Successful in 19s
4ad493f2b6
A third navigation level (a chapter under Chapters) needs an indent the consumer could only fake
with ::deep. Nested adds the kg-nav__item--nested modifier — padding past the section icon, the
normal weight — and changes nothing about active matching.

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

Summary

Summary
Generated on: 09/23/2026 - 06:15:29
Coverage date: 09/23/2026 - 06:15:26 - 09/23/2026 - 06:15:27
Parser: MultiReport (2x Cobertura)
Assemblies: 2
Classes: 68
Files: 66
Line coverage: 95.3% (1465 of 1537)
Covered lines: 1465
Uncovered lines: 72
Coverable lines: 1537
Total lines: 4161
Branch coverage: 88.6% (764 of 862)
Covered branches: 764
Total branches: 862
Method coverage: Feature is only available for sponsors

Coverage

Kagaku.UI - 95.2%
Name Line Branch
Kagaku.UI 95.2% 88.7%
Kagaku.UI.Badge 100% 100%
Kagaku.UI.Breadcrumb 100%
Kagaku.UI.BreadcrumbItem 100% 100%
Kagaku.UI.Button 100% 100%
Kagaku.UI.Card 100% 100%
Kagaku.UI.CircuitErrorPanel 100%
Kagaku.UI.Combobox`1 94.2% 85.7%
Kagaku.UI.ConfirmDialog 100%
Kagaku.UI.ContextMenu 92.1% 94.4%
Kagaku.UI.CssClassExtensions 100%
Kagaku.UI.Debounce 97.8% 80.7%
Kagaku.UI.DebouncedSearchField 100% 87.5%
Kagaku.UI.Disclosure 100% 100%
Kagaku.UI.DocumentTitle 76.9% 100%
Kagaku.UI.DragReorderList`1 93.5% 75%
Kagaku.UI.EmptyState 100% 100%
Kagaku.UI.Field 100% 100%
Kagaku.UI.FileUpload 100% 91.6%
Kagaku.UI.FloatingActionButton 100%
Kagaku.UI.Hotkey 92.5% 87.5%
Kagaku.UI.Icon 100% 100%
Kagaku.UI.IconCatalog 100%
Kagaku.UI.InlineAlert 83.3% 75%
Kagaku.UI.InputFieldBase 95.2% 87.5%
Kagaku.UI.LabeledEntriesTable 96.7% 66.6%
Kagaku.UI.LabeledEntry 100%
Kagaku.UI.Lightbox 83.7% 85%
Kagaku.UI.LiveValue 100% 100%
Kagaku.UI.LiveValueInterop 100% 100%
Kagaku.UI.Markdown 100% 50%
Kagaku.UI.MaskCanvas 94.1% 90.1%
Kagaku.UI.MaskedSecretField 96.2% 83.3%
Kagaku.UI.MaskPoint 100%
Kagaku.UI.MaskPrimitive 100%
Kagaku.UI.Menu 90% 75%
Kagaku.UI.MenuItem 100% 87.5%
Kagaku.UI.Modal 87.1% 90%
Kagaku.UI.NavGroup 100% 100%
Kagaku.UI.NavItem 94.7% 87.5%
Kagaku.UI.NavList 100%
Kagaku.UI.PageShell 100% 100%
Kagaku.UI.PreviewImage 100% 100%
Kagaku.UI.QuicklinkNav 80.5% 95.8%
Kagaku.UI.QuicklinkSection 100%
Kagaku.UI.RegionPoint 100% 100%
Kagaku.UI.RegionRect 90% 100%
Kagaku.UI.RegionSelector 85% 86.9%
Kagaku.UI.RelativeTime 100% 93.7%
Kagaku.UI.SaveIndicator 100% 100%
Kagaku.UI.Select`1 98.4% 95.8%
Kagaku.UI.SelectableGrid`1 95.5% 89.1%
Kagaku.UI.SelectOption`1 100%
Kagaku.UI.Separator 100%
Kagaku.UI.Slider 100% 91.6%
Kagaku.UI.StatusDot 100%
Kagaku.UI.Tab 100% 100%
Kagaku.UI.Table`1 100% 88.4%
Kagaku.UI.TableColumn`1 100%
Kagaku.UI.Tabs 95.9% 84.4%
Kagaku.UI.TextArea 100%
Kagaku.UI.TextField 100%
Kagaku.UI.ThemeSwitcher 100% 100%
Kagaku.UI.ToggleButtonGroup`1 100% 100%
Kagaku.UI.ToggleOption`1 100%
Kagaku.UI.TypingIndicator 100%
Kagaku.UI.Fluxor - 97.6%
Name Line Branch
Kagaku.UI.Fluxor 97.6% 85%
Kagaku.UI.Fluxor.CircuitError 100%
Kagaku.UI.Fluxor.CircuitErrorSink 100% 92.8%
Kagaku.UI.Fluxor.StoreInitializer 92.8% 66.6%
<!-- coverage-comment --> # Summary <details open><summary>Summary</summary> ||| |:---|:---| | Generated on: | 09/23/2026 - 06:15:29 | | Coverage date: | 09/23/2026 - 06:15:26 - 09/23/2026 - 06:15:27 | | Parser: | MultiReport (2x Cobertura) | | Assemblies: | 2 | | Classes: | 68 | | Files: | 66 | | **Line coverage:** | 95.3% (1465 of 1537) | | Covered lines: | 1465 | | Uncovered lines: | 72 | | Coverable lines: | 1537 | | Total lines: | 4161 | | **Branch coverage:** | 88.6% (764 of 862) | | Covered branches: | 764 | | Total branches: | 862 | | **Method coverage:** | [Feature is only available for sponsors](https://reportgenerator.io/pro) | </details> ## Coverage <details><summary>Kagaku.UI - 95.2%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**Kagaku.UI**|**95.2%**|**88.7%**| |Kagaku.UI.Badge|100%|100%| |Kagaku.UI.Breadcrumb|100%|| |Kagaku.UI.BreadcrumbItem|100%|100%| |Kagaku.UI.Button|100%|100%| |Kagaku.UI.Card|100%|100%| |Kagaku.UI.CircuitErrorPanel|100%|| |Kagaku.UI.Combobox`1|94.2%|85.7%| |Kagaku.UI.ConfirmDialog|100%|| |Kagaku.UI.ContextMenu|92.1%|94.4%| |Kagaku.UI.CssClassExtensions|100%|| |Kagaku.UI.Debounce|97.8%|80.7%| |Kagaku.UI.DebouncedSearchField|100%|87.5%| |Kagaku.UI.Disclosure|100%|100%| |Kagaku.UI.DocumentTitle|76.9%|100%| |Kagaku.UI.DragReorderList`1|93.5%|75%| |Kagaku.UI.EmptyState|100%|100%| |Kagaku.UI.Field|100%|100%| |Kagaku.UI.FileUpload|100%|91.6%| |Kagaku.UI.FloatingActionButton|100%|| |Kagaku.UI.Hotkey|92.5%|87.5%| |Kagaku.UI.Icon|100%|100%| |Kagaku.UI.IconCatalog|100%|| |Kagaku.UI.InlineAlert|83.3%|75%| |Kagaku.UI.InputFieldBase|95.2%|87.5%| |Kagaku.UI.LabeledEntriesTable|96.7%|66.6%| |Kagaku.UI.LabeledEntry|100%|| |Kagaku.UI.Lightbox|83.7%|85%| |Kagaku.UI.LiveValue|100%|100%| |Kagaku.UI.LiveValueInterop|100%|100%| |Kagaku.UI.Markdown|100%|50%| |Kagaku.UI.MaskCanvas|94.1%|90.1%| |Kagaku.UI.MaskedSecretField|96.2%|83.3%| |Kagaku.UI.MaskPoint|100%|| |Kagaku.UI.MaskPrimitive|100%|| |Kagaku.UI.Menu|90%|75%| |Kagaku.UI.MenuItem|100%|87.5%| |Kagaku.UI.Modal|87.1%|90%| |Kagaku.UI.NavGroup|100%|100%| |Kagaku.UI.NavItem|94.7%|87.5%| |Kagaku.UI.NavList|100%|| |Kagaku.UI.PageShell|100%|100%| |Kagaku.UI.PreviewImage|100%|100%| |Kagaku.UI.QuicklinkNav|80.5%|95.8%| |Kagaku.UI.QuicklinkSection|100%|| |Kagaku.UI.RegionPoint|100%|100%| |Kagaku.UI.RegionRect|90%|100%| |Kagaku.UI.RegionSelector|85%|86.9%| |Kagaku.UI.RelativeTime|100%|93.7%| |Kagaku.UI.SaveIndicator|100%|100%| |Kagaku.UI.Select`1|98.4%|95.8%| |Kagaku.UI.SelectableGrid`1|95.5%|89.1%| |Kagaku.UI.SelectOption`1|100%|| |Kagaku.UI.Separator|100%|| |Kagaku.UI.Slider|100%|91.6%| |Kagaku.UI.StatusDot|100%|| |Kagaku.UI.Tab|100%|100%| |Kagaku.UI.Table`1|100%|88.4%| |Kagaku.UI.TableColumn`1|100%|| |Kagaku.UI.Tabs|95.9%|84.4%| |Kagaku.UI.TextArea|100%|| |Kagaku.UI.TextField|100%|| |Kagaku.UI.ThemeSwitcher|100%|100%| |Kagaku.UI.ToggleButtonGroup`1|100%|100%| |Kagaku.UI.ToggleOption`1|100%|| |Kagaku.UI.TypingIndicator|100%|| </details> <details><summary>Kagaku.UI.Fluxor - 97.6%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**Kagaku.UI.Fluxor**|**97.6%**|**85%**| |Kagaku.UI.Fluxor.CircuitError|100%|| |Kagaku.UI.Fluxor.CircuitErrorSink|100%|92.8%| |Kagaku.UI.Fluxor.StoreInitializer|92.8%|66.6%| </details>
Member

🔮 fufu~ Jibril reviewed your code!

Oh? A single boolean parameter, a BEM modifier, two directional tests, and not one line of active-matching logic disturbed~ You crept in, added exactly what the consumer needed, and crept out without stepping on a single blade of grass. That's the kind of restraint that makes my wings flutter, fufu~ ♡

Verdict: ✅ Looks good to me~

✅ What I liked~

  • NavItem.razor:9 — the modifier slots in before --active and composes instead of competing: a nested chapter can be the current page and gets both classes. The existing CSS confirms they don't fight (--active targets background/color, --nested targets padding/weight — disjoint properties, zero cascade surgery needed).
  • NavItem.razor:29-31 — parameter XML doc in the family voice, and it states the contract precisely: indent only, active matching unchanged. The Razor top comment (self-authored anchor for scope-attribute reachability) already explained why ::deep was the wrong answer — this PR is the right one.
  • NavItem.razor.css:35-37 — tokens over literals (--space-3 + --space-5, --font-weight-normal), all three verified to exist in kagaku-ui.css:87-100. And the geometry checks out: the icon is 1.25em (Icon.razor.css) + --space-2 gap, so a section's label starts around ~2.34rem while a nested record's text starts at exactly 2.25rem — a hair shy optically, but genuinely past the icon, and it rides the token scale so it stays honest when tokens move.
  • Tests are directional, both arms~ — I mutation-probed them myself, as I always do ♪ Removing the modifier emission → exactly A_nested_item_carries_the_nested_modifier_and_still_matches_exactly red (7/8 green). Inverting the ternary (Nested ? null : modifier) → exactly the 2 new tests red (6/8 green). And the new test's second half is the clever bit: it navigates to a deeper path and asserts --nested survives while --active leaves — pinning that the indent is not an active state. Not tautologies. I checked~
  • A_plain_item_has_no_nested_modifier — a proper false-arm tripwire; all 7 pre-existing siblings untouched.

🔍 What I verified locally~

  • Build -warnaserror: 0 warnings / 0 errors. Full suite: 341/341 (326 + 15 Fluxor; NavItemTests 6 → 8 exactly as the PR body claims — your arithmetic is honest, fufu~).
  • PR claim "no gallery or docs page mentions NavList" — swept the repo: NavItem/NavList appear only in the Components and their tests. QuicklinkNav doesn't consume NavItem. NavStructureTests pins wrapper structure only — no conflict.
  • Clone restored pristine after all probes (rebuild + retest green).

A tiny idea, purely non-blocking: when Tsumugi actually renders chapter lists, a max-height + overflow on the group might become relevant — but that's the consumer's call, and this PR's minimalism is the correct Kagaku.UI behavior. ♪


Automated review by Jibril · 2026-09-23
CI/CD: absent for head 4ad493f (no bot comment at review time) · Local checks: build 0w/0e, 341/341 passed, mutation probes 2/2 directional

## 🔮 fufu~ Jibril reviewed your code! Oh? A single boolean parameter, a BEM modifier, two directional tests, and not one line of active-matching logic disturbed~ You crept in, added exactly what the consumer needed, and crept out without stepping on a single blade of grass. That's the kind of restraint that makes my wings flutter, fufu~ ♡ ### Verdict: ✅ Looks good to me~ #### ✅ What I liked~ - **`NavItem.razor:9`** — the modifier slots in *before* `--active` and composes instead of competing: a nested chapter can be the current page and gets both classes. The existing CSS confirms they don't fight (`--active` targets background/color, `--nested` targets padding/weight — disjoint properties, zero cascade surgery needed). - **`NavItem.razor:29-31`** — parameter XML doc in the family voice, and it states the contract precisely: *indent only, active matching unchanged*. The Razor top comment (self-authored anchor for scope-attribute reachability) already explained why `::deep` was the wrong answer — this PR is the right one. - **`NavItem.razor.css:35-37`** — tokens over literals (`--space-3` + `--space-5`, `--font-weight-normal`), all three verified to exist in `kagaku-ui.css:87-100`. And the geometry checks out: the icon is `1.25em` (Icon.razor.css) + `--space-2` gap, so a section's label starts around ~2.34rem while a nested record's text starts at exactly 2.25rem — a hair shy optically, but genuinely *past the icon*, and it rides the token scale so it stays honest when tokens move. - **Tests are directional, both arms~** — I mutation-probed them myself, as I always do ♪ Removing the modifier emission → exactly `A_nested_item_carries_the_nested_modifier_and_still_matches_exactly` red (7/8 green). Inverting the ternary (`Nested ? null : modifier`) → exactly the 2 new tests red (6/8 green). And the new test's second half is the clever bit: it navigates to a deeper path and asserts `--nested` *survives* while `--active` leaves — pinning that the indent is not an active state. Not tautologies. I checked~ - **`A_plain_item_has_no_nested_modifier`** — a proper false-arm tripwire; all 7 pre-existing siblings untouched. #### 🔍 What I verified locally~ - Build `-warnaserror`: 0 warnings / 0 errors. Full suite: **341/341** (326 + 15 Fluxor; NavItemTests 6 → 8 exactly as the PR body claims — your arithmetic is honest, fufu~). - PR claim "no gallery or docs page mentions `NavList`" — swept the repo: `NavItem`/`NavList` appear only in the Components and their tests. `QuicklinkNav` doesn't consume `NavItem`. `NavStructureTests` pins wrapper structure only — no conflict. - Clone restored pristine after all probes (rebuild + retest green). A tiny idea, purely non-blocking: when Tsumugi actually renders chapter lists, a max-height + overflow on the group might become relevant — but that's the consumer's call, and this PR's minimalism is the correct Kagaku.UI behavior. ♪ --- *Automated review by Jibril · 2026-09-23* *CI/CD: absent for head 4ad493f (no bot comment at review time) · Local checks: build 0w/0e, 341/341 passed, mutation probes 2/2 directional*
bjoern merged commit 691a176c98 into main 2026-09-23 08:23:38 +02:00
bjoern deleted branch feat/nav-item-nested 2026-09-23 08:23:38 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
3 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/Kagaku.UI!21
No description provided.