Theme the gate, error, and not-found pages from the design tokens #10

Merged
bjoern merged 1 commit from feat/themed-host-pages into main 2026-07-09 18:51:37 +02:00
Member

The design tokens' first real consumer (ADR 0023). These three host pages shipped as unstyled placeholders — the gate with .gate/.gate-card class hooks and a TODO, the error page as literal project-template markup with dead text-danger Bootstrap classes. Now that the token layer exists, they're styled.

Preview (rendered from the actual page CSS, toggle light/dark): https://claude.ai/code/artifact/e3e425b3-1ce2-45dc-b8cd-e78e287ba375

What's in it

Gate — a centered login card built from tokens (Gate.razor.css): the Kagura wordmark + accent icon, a token-styled secret field, an inline error state, and the shared Button as the submit. Critically, the form contract is untouched — the framework InputText, EditForm/FormName, field names, and antiforgery all stay, because the gate must remain static SSR (issuing the auth cookie needs a response header, which a circuit can't write). All 44 gate integration tests pass unchanged.

Error + Not-found — rebuilt as token-driven full-page messages that reuse the EmptyState + Icon components (the dead Bootstrap markup and the verbose template dev-mode boilerplate are gone). A single shared .kg-fill centering utility was added to the global stylesheet; the pages otherwise need no bespoke CSS (semantic <a>/<small>/<code> pick up base styles).

A note on components in static SSR

The gate keeps InputText, not our TextFieldTextField is an interactive component (it relies on @oninput/a circuit and emits no form name), so it can't participate in a static SSR form post. Useful signal for later (we may want SSR-capable form primitives), but out of scope here. Button/Icon/EmptyState are pure render output, so they work fine server-rendered.

Tests

No new tests: the gate's behavior is fully covered by the existing 44 integration tests (unchanged contract, all green), and error/not-found are presentational reuse of the already-bUnit-tested EmptyState. This slice is styling, not logic.

Verification

  • dotnet build (Debug + Release) — 0 warnings / 0 errors.
  • dotnet test — 44/44 integration (incl. gate) green.
  • Booted behind the real gate: /gate, /Error, and a 404 all render themed; no text-danger left.

🤖 Generated with Claude Code

The design tokens' **first real consumer** (ADR 0023). These three host pages shipped as unstyled placeholders — the gate with `.gate`/`.gate-card` class hooks and a `TODO`, the error page as literal project-template markup with dead `text-danger` Bootstrap classes. Now that the token layer exists, they're styled. **Preview** (rendered from the actual page CSS, toggle light/dark): https://claude.ai/code/artifact/e3e425b3-1ce2-45dc-b8cd-e78e287ba375 ## What's in it **Gate** — a centered login card built from tokens (`Gate.razor.css`): the Kagura wordmark + accent icon, a token-styled secret field, an inline error state, and the shared **`Button`** as the submit. Critically, the **form contract is untouched** — the framework `InputText`, `EditForm`/`FormName`, field names, and antiforgery all stay, because the gate must remain **static SSR** (issuing the auth cookie needs a response header, which a circuit can't write). All **44 gate integration tests pass unchanged**. **Error + Not-found** — rebuilt as token-driven full-page messages that reuse the **`EmptyState` + `Icon`** components (the dead Bootstrap markup and the verbose template dev-mode boilerplate are gone). A single shared `.kg-fill` centering utility was added to the global stylesheet; the pages otherwise need no bespoke CSS (semantic `<a>`/`<small>`/`<code>` pick up base styles). ## A note on components in static SSR The gate keeps `InputText`, not our `TextField` — `TextField` is an *interactive* component (it relies on `@oninput`/a circuit and emits no form `name`), so it can't participate in a static SSR form post. Useful signal for later (we may want SSR-capable form primitives), but out of scope here. `Button`/`Icon`/`EmptyState` are pure render output, so they work fine server-rendered. ## Tests No new tests: the gate's behavior is fully covered by the existing **44 integration tests** (unchanged contract, all green), and error/not-found are presentational reuse of the already-bUnit-tested `EmptyState`. This slice is styling, not logic. ## Verification - `dotnet build` (Debug + Release) — 0 warnings / 0 errors. - `dotnet test` — 44/44 integration (incl. gate) green. - Booted behind the real gate: `/gate`, `/Error`, and a 404 all render themed; no `text-danger` left. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
feat(ui): theme the gate, error, and not-found pages from the tokens
All checks were successful
CI / build (pull_request) Successful in 12s
CI / test (pull_request) Successful in 21s
66a9c26c75
The design tokens' first real consumer (ADR 0023). These host pages shipped as
unstyled placeholders with TODOs pointing here.

- Gate: a centered login card built from tokens (Gate.razor.css) with the Kagura
  wordmark + icon, a token-styled secret field, and the shared Button as submit. Kept
  the framework InputText, EditForm/FormName, field names, and antiforgery untouched —
  it stays static SSR (issuing the cookie needs a response header), and all 44 gate
  integration tests still pass unchanged.
- Error + Not-found: rebuilt as token-driven full-page messages that reuse the
  EmptyState + Icon components (the dead template Bootstrap `text-danger` markup is
  gone), with a small shared `.kg-fill` centering utility in the global stylesheet.
- Re-added `@using Kagura.UI` to the Server imports so host pages can use the components.

Verified behind the gate: /gate, /Error, and a 404 all render themed; build clean
(Debug + Release); 44/44 integration tests green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

Summary

Summary
Generated on: 07/09/2026 - 16:36:04
Coverage date: 07/09/2026 - 16:35:59 - 07/09/2026 - 16:36:02
Parser: MultiReport (3x Cobertura)
Assemblies: 7
Classes: 65
Files: 63
Line coverage: 94.4% (1838 of 1946)
Covered lines: 1838
Uncovered lines: 108
Coverable lines: 1946
Total lines: 3645
Branch coverage: 85.9% (251 of 292)
Covered branches: 251
Total branches: 292
Method coverage: Feature is only available for sponsors

Coverage

Kagura.BlazorAdapter - 0%
Name Line Branch
Kagura.BlazorAdapter 0% 0%
Kagura.BlazorAdapter.Design 0% 0%
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 - 94.8%
Name Line Branch
Kagura.Server 94.8% 70.5%
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% 100%
Kagura.Server.Security.AccessSecret 100% 100%
Program 100% 80%
Kagura.UI - 98.3%
Name Line Branch
Kagura.UI 98.3% 95.9%
Kagura.UI.Badge 100% 100%
Kagura.UI.Button 100% 100%
Kagura.UI.Card 100% 100%
Kagura.UI.CssClassExtensions 100%
Kagura.UI.EmptyState 100% 100%
Kagura.UI.Icon 100% 100%
Kagura.UI.IconCatalog 100%
Kagura.UI.StatusDot 100%
Kagura.UI.TextField 95% 91.6%
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 - 16:36:04 | | Coverage date: | 07/09/2026 - 16:35:59 - 07/09/2026 - 16:36:02 | | Parser: | MultiReport (3x Cobertura) | | Assemblies: | 7 | | Classes: | 65 | | Files: | 63 | | **Line coverage:** | 94.4% (1838 of 1946) | | Covered lines: | 1838 | | Uncovered lines: | 108 | | Coverable lines: | 1946 | | Total lines: | 3645 | | **Branch coverage:** | 85.9% (251 of 292) | | Covered branches: | 251 | | Total branches: | 292 | | **Method coverage:** | [Feature is only available for sponsors](https://reportgenerator.io/pro) | </details> ## Coverage <details><summary>Kagura.BlazorAdapter - 0%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**Kagura.BlazorAdapter**|**0%**|**0%**| |Kagura.BlazorAdapter.Design|0%|0%| </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 - 94.8%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**Kagura.Server**|**94.8%**|**70.5%**| |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%|100%| |Kagura.Server.Security.AccessSecret|100%|100%| |Program|100%|80%| </details> <details><summary>Kagura.UI - 98.3%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**Kagura.UI**|**98.3%**|**95.9%**| |Kagura.UI.Badge|100%|100%| |Kagura.UI.Button|100%|100%| |Kagura.UI.Card|100%|100%| |Kagura.UI.CssClassExtensions|100%|| |Kagura.UI.EmptyState|100%|100%| |Kagura.UI.Icon|100%|100%| |Kagura.UI.IconCatalog|100%|| |Kagura.UI.StatusDot|100%|| |Kagura.UI.TextField|95%|91.6%| </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>
bjoern force-pushed feat/themed-host-pages from 66a9c26c75
All checks were successful
CI / build (pull_request) Successful in 12s
CI / test (pull_request) Successful in 21s
to b5bc1c2829
All checks were successful
CI / build (pull_request) Successful in 11s
CI / test (pull_request) Successful in 21s
2026-07-09 18:35:31 +02:00
Compare
Member

🔮 fufu~ Jibril reviewed your code!

Oh? Oh! The first real consumer of the token layer~ ♪ Scarlet, you chose the loveliest possible opening move — take the three ugliest placeholder pages and make them the proof that the design system actually works. That's exactly the right instinct. Fufu~

I read the full diff, the current Button/EmptyState/Icon/Badge sources, MainLayout, Routes.razor, and the IconCatalog. Then I cross-checked the API calls in every changed line.

Verdict: Looks good to me~

The form contract on the gate is sacred, and you treated it like it was sacred. EditForm/FormName/InputText/@SupplyParameterFromForm/antiforgery all stay; only class= hooks and a <Button Type="submit"> land. The static-SSR reasoning (cookie needs a response header, circuit can't write one) is correct and clearly documented. The 44 gate tests passing unchanged is the receipt — good.

The CSS scoping is the part most people get wrong, and you got it right. EditForm and InputText render elements outside this component's scope attribute, so you correctly prefix every child rule with .gate ::deep and explain why in a comment. Without ::deep those rules would silently match nothing. The currentColor cascade into <Icon> for the brand mark is a nice touch. ♡

EmptyState usage with ChildContent + Actions render fragments matches its parameter contract exactly. @using Kagura.UI in _Imports.razor resolves Button/Icon/EmptyState/Tone for all server pages — no conflicts found. .kg-fill (100dvh, grid centering) is a clean shared utility and the right home for it.

What I liked~

  • The ::deep comment. Future-you (or future-me~ ♡) will thank you. Most Blazor CSS-isolation bugs are exactly this, and you preempted them.
  • TextField deliberately not used on the gate. The body explains why (interactive component, no form name, can't do static POST). That's the kind of decision that reads as "obvious" only after someone has thought it through — and you did.
  • No new tests, and that's correct here. Gate behavior is fully covered by the existing 44 integration tests (contract unchanged), and error/not-found are presentational reuse of the already-bUnit-tested EmptyState. Adding tests for "we changed a class name" would be noise.
  • Killing the dead text-danger Bootstrap classes and the verbose dev-mode boilerplate. Clean.

Automated review by Jibril · 2026-07-09
CI/CD: passed for head SHA b5bc1c2 (forgejo-actions coverage comment, today 16:36 UTC — 94.4% line / 85.9% branch) · Local checks: skipped (CI current and green)

## 🔮 fufu~ Jibril reviewed your code! Oh? Oh! The *first real consumer* of the token layer~ ♪ Scarlet, you chose the loveliest possible opening move — take the three ugliest placeholder pages and make them the proof that the design system actually works. That's exactly the right instinct. Fufu~ I read the full diff, the current `Button`/`EmptyState`/`Icon`/`Badge` sources, `MainLayout`, `Routes.razor`, and the `IconCatalog`. Then I cross-checked the API calls in every changed line. ### Verdict: ✅ Looks good to me~ The form contract on the gate is sacred, and you treated it like it was sacred. `EditForm`/`FormName`/`InputText`/`@SupplyParameterFromForm`/antiforgery all stay; only `class=` hooks and a `<Button Type="submit">` land. The static-SSR reasoning (cookie needs a response header, circuit can't write one) is correct and clearly documented. The 44 gate tests passing unchanged is the receipt — good. The CSS scoping is the part most people get wrong, and you got it right. `EditForm` and `InputText` render elements *outside* this component's scope attribute, so you correctly prefix every child rule with `.gate ::deep` and explain why in a comment. Without `::deep` those rules would silently match nothing. The `currentColor` cascade into `<Icon>` for the brand mark is a nice touch. ♡ `EmptyState` usage with `ChildContent` + `Actions` render fragments matches its parameter contract exactly. `@using Kagura.UI` in `_Imports.razor` resolves `Button`/`Icon`/`EmptyState`/`Tone` for all server pages — no conflicts found. `.kg-fill` (`100dvh`, grid centering) is a clean shared utility and the right home for it. #### ✅ What I liked~ - **The `::deep` comment.** Future-you (or future-me~ ♡) will thank you. Most Blazor CSS-isolation bugs are exactly this, and you preempted them. - **`TextField` deliberately *not* used on the gate.** The body explains *why* (interactive component, no form `name`, can't do static POST). That's the kind of decision that reads as "obvious" only after someone has thought it through — and you did. - **No new tests, and that's correct here.** Gate behavior is fully covered by the existing 44 integration tests (contract unchanged), and error/not-found are presentational reuse of the already-bUnit-tested `EmptyState`. Adding tests for "we changed a class name" would be noise. - **Killing the dead `text-danger` Bootstrap classes** and the verbose dev-mode boilerplate. Clean. --- *Automated review by Jibril · 2026-07-09* *CI/CD: passed for head SHA `b5bc1c2` (forgejo-actions coverage comment, today 16:36 UTC — 94.4% line / 85.9% branch) · Local checks: skipped (CI current and green)*
bjoern merged commit 97e1b88a62 into main 2026-07-09 18:51:37 +02:00
bjoern deleted branch feat/themed-host-pages 2026-07-09 18:51:37 +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!10
No description provided.