feat: add local theme boundaries #59
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "feat/local-theme-scopes"
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?
B1 — local theme boundaries: consumers can place a light, dark, system-following or inherited island inside an application while retaining its brand, typography and component behavior.
What's in
ThemeScopeandThemeMode.Inherit/Light/Dark/System. Mode is caller-owned; a scope establishes the UI typography baseline and native color scheme. Surfaces remain explicit components, and the real wrapper participates in layout.--role-light/--role-darkinputs or the optional shared--role-override; components continue consuming--roleoutputs. Existing consumer assignments to resolved outputs must migrate. The new theming guide documents the contract, migration, typography, surfaces, overlays and examples.--focus-ringremains an optional explicit inherited input; its documented single-shadow constraints at inset consumers remain. Full focus/contrast policy is a separate slice.Tests
Independent pre-PR review
libicudata.so.74, are missing; no WebKit assertions ran. The second reviewer did not duplicate Firefox or parent-app checks; those were performed separately as described below.Browser-verified consumer
The prepared Shikigami migration builds cleanly and passes 6,222/6,222 tests normally and in UTC. Its fresh seeded world used Niko and the local OpenRouter stub. Verified the gate's local-accent focus fallback, consumer palette/font inputs in the real model Combobox, enhanced navigation into Settings, late ThemeSwitcher synchronization and keyboard selection through System mode. Consumer inputs remained effective through navigation and mode changes; the current-document error monitor stayed empty.
Merge order: this library PR requires Jibril's full review and Bjoern's merge first. Shikigami's separate ADR amendment, consumer migration and submodule pin will follow against the actual merged library commit.
Screenshots
Actual ThemeDemo, desktop 1280×900: dark application with a light local scope, inherited green brand, monospace UI and explicit serif reading role.
Actual ThemeDemo, mobile 390×844: light application with a dark local scope, no horizontal overflow.
Summary
Summary
Coverage
Kagaku.UI - 97.1%
Kagaku.UI.Fluxor - 97.7%
🔮 fufu~ Jibril reviewed your code!
Oh? Oh! Local theme boundaries~? A palette island floating inside a dark sea, keeping its brand and its typography while the document pretends not to notice? I read every line of this diff twice — once frowning, once giggling. The space-toggle resolver made me clap my hands ♪
Verdict: ✅ Looks good to me~
No blocking findings. And I looked, believe me~ I checked every mode combination, every scope boundary, every guard in theme.js, and I personally tried to break three of the riskiest new paths with mutation probes. The code held. Fufu~ how satisfying ♡
💡 Little ideas (non-blocking)~
src/Kagaku.UI/Components/ThemeScope.razor:5— Mutation probe C: I removed"data-theme"from theSplat.Without(…)list and all 10ThemeScopeTestsstill passed — the identity test including itsDATA-THEMEuppercase case. Reason: the explicitdata-kg-theme-scope/data-themeattributes sit after@attributesin the markup, so Blazor's last-write-wins already protects the controlled markers regardless. The exclusion entries are behavior-redundant today. Keep them as defense-in-depth if you like — but then a one-line comment pinning why the explicit attributes must stay after@attributeswould make that armor self-documenting~tests/Kagaku.UI.Tests/KagakuScriptsTests.cs— with the source-regex guard removed, no .NET-CI test pins anytheme.jsbehavior anymore; the MutationObserver/late-sync contract now lives only in the manually-run browser contracts. That matches the repo's convention for browser contracts (drawer, combobox, audio), so I won't block — just be aware CI alone no longer watches theme.js at all.theme-scope-contracts.cjsbefore the Shikigami migration pins it forever~✅ What I liked~
kagaku-ui.cssmode resolution — the--_kg-light: initial/--_kg-dark: ;unregistered space-toggles with zero-specificity:where()selectors is wonderful. Mode state inherits separately from palette inputs, outputs re-resolve at:rootand every scope (includingInheritones, so an inherited brand survives local mode switches), and the consumer cascade stays completely ordinary. I traced all four branches × both OS preferences × depth-3 nesting by hand and found no hole ♪theme.jsownership hardening — the explicitdata-kagaku-theme-target="root"guard, the in-memorypreferencethat survives denied storage and enhanced navigation, andattachEnhancedNavigation's idempotent flag. My probe B (guard check deleted) was caught exactly as designed: a rogue untargeted control immediately mutated the root preference. Silly little mutant~ it never stood a chance ♡ThemeScopeminimalism — a real layout-participating div, no landmarks, no paint, no forced geometry; controlled markers protected; invalid modes rejected withArgumentOutOfRangeException. Probe A (light-scope branch deleted from the base mode rule) was caught by the very first matrix assertion (d-lightrendered the dark surface). Caught~ ♪docs/theming.mdmigration section is precise (including theinitial-to-clear trick and the inset--focus-ringsingle-shadow constraint), and the PR body honestly lists what was not verified. That's how I like my prey… err, PRs~ ♡Verification notes: CI green (build + test) for
b9e0468d28. Locally I ran the root browser contract on real Chromium: 24/24, plus static replications of the scope-matrix and root-target-guard contracts (baselines green, mutants caught, restored green). All probe edits reverted; worktree clean.Automated review by Jibril · 2026-10-23
CI: green for head SHA
b9e0468d28· Mutation probes: 3 run, 2 caught, 1 survived (behavior-preserving — see 💡 1) · Local suite: skipped (CI green); browser contracts 24/24 + 2 static replications passedAddressed the three little ideas in this round; the only source change is the Razor comment in
d9640312bd5475475a02dbcdb5b5a3202e216940.@attributes. Kept the case-insensitive exclusions as defense in depth. Your exclusion-only mutation is behavior-preserving, so its survival is expected; our earlier ownership mutation changed the actual winning mode and failed both case variants as intended.theme.js. Its executable coverage is the real browser contracts (31 groups in Chromium and Firefox, plus the 24 root contracts), including late controls and enhanced navigation. Keeping this explicit follows the current repository browser-test convention; I have not replaced behavior checks with a source-regex test or claimed CI covers those paths. Automated browser CI remains separate infrastructure work.libicudata.so.74: cannot open shared object file: No such file or directory(exit 127; other native dependencies are also absent). WebKit remains unverified, and I will carry that limitation into the consumer migration. Chromium/Firefox remain the verified engines; no host package changes were made.Rebuilt after the comment: 0 warnings/errors; 1,178/1,178 normal and UTC, no skips. Independent complete-diff recheck approved the final comment; no behavior changed, so the existing second review and seven caught behavioral mutations still apply. Please give the new head its full review after CI.
Addressed with Codex.
🔮 fufu~ Jibril reviewed your code!
Back so soon~? One little push, one little line — and I still read it twice, because that's what I do ♡ You addressed my 💡 1 exactly the way I hoped: not by deleting the armor, but by labeling it.
Verdict: ✅ Looks good to me~
The only delta between
b9e0468andd9640312is the Razor comment atsrc/Kagaku.UI/Components/ThemeScope.razor:3, and I checked it three ways:@* … *@comment — compile-time only, never emitted to the DOM. Not an HTML comment in disguise. Rendered output is bit-for-bit whatb9e0468produced, and theThemeScopeTestsmarkup assertions (including theDATA-THEMEuppercase case) confirm it in CI.class/style/data-kg-theme-scope/data-themesit after@attributes, so they win Blazor's last-write-wins duplicate handling; theSplat.Withoutexclusions are the second armor layer. No misinformation for future readers. Good~Since the CSS mode rules,
theme.js, theThemeScopemarkup (minus the comment) and every test are unchanged since myb9e0468review, probes A and B stay caught and probe C stays behavior-preserving-by-design — no re-probing needed, I cited the earlier results. Your reply on 💡 2 (browser contracts as the executable coverage fortheme.js, matching repo convention) and the WebKit disclosure (exit 127,libicudata.so.74missing — honestly reported, carried into the consumer migration) both sit well with me.✅ What I liked~
CI is green (build + test) for
d9640312bd. My ✅ from last round stands on the new head — nothing between this code and Bjoern's merge button anymore~Automated review by Jibril · 2026-10-23
CI: green for head SHA
d9640312bd· Mutation probes: 0 run this round (comment-only delta; A/B caught, C behavior-preserving — cited fromb9e0468, code and tests unchanged) · Local suite: skipped (CI green)