Skip to content

fix(theme,sidebar): drop FontAwesome/eval, fix prerender, add live↔template drift guard#14

Merged
Shewart merged 5 commits into
mainfrom
fix/sidebar-trigger-and-theme-toggle
Jun 17, 2026
Merged

fix(theme,sidebar): drop FontAwesome/eval, fix prerender, add live↔template drift guard#14
Shewart merged 5 commits into
mainfrom
fix/sidebar-trigger-and-theme-toggle

Conversation

@Shewart

@Shewart Shewart commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

Summary

Three runtime bugs plus a structural fix for the underlying root cause (live library and CLI templates drifting apart silently across releases):

  1. SidebarTrigger renders nothing in consumer projects because it references <i class="fa-solid fa-bars-staggered"> — FontAwesome isn't a dependency of ShellUI and isn't installed by shellui init. Replaced with inline SVG. Mobile users can now actually open the sidebar.
  2. ThemeToggle is unreliable because it uses JSRuntime.InvokeVoidAsync("eval", "...") (blockable by CSP, fragile string eval) and reads localStorage from OnInitializedAsync (crashes during Blazor Server prerender when IJSRuntime isn't available yet). Replaced both:
    • evalShellUI.addClassToDocument(...) / ShellUI.removeClassFromDocument(...) (already shipped via wwwroot/shellui.js and used by CopyButton/FileUpload — zero new JS surface area)
    • OnInitializedAsyncOnAfterRenderAsync(firstRender) for the storage read
  3. Live ↔ template drift detection. The previous alpha shipped a Class parameter on the live ThemeToggle.razor that was never mirrored in ThemeToggleTemplate.cs — consumers using the CLI install didn't get the parameter. Added TemplateSyncTests that compares the @code block of the live .razor to the corresponding template's Content string after normalization. Future drift fails CI with a precise line-level diff.

Changes

Component / template fixes

  • src/ShellUI.Components/Components/SidebarTrigger.razor<i> swapped for inline SVG hamburger (Fix 1)
  • src/ShellUI.Components/Components/ThemeToggle.razor — eval dropped, lifecycle moved to OnAfterRenderAsync(firstRender), OnInitialized retains only the _instances registration (no JS calls) (Fix 3)
  • src/ShellUI.Components/Services/ThemeService.cs — same eval → ShellUI.* swap (helper service was also using eval; was a latent bug)
  • src/ShellUI.Templates/Templates/SidebarTriggerTemplate.cs — mirrors the SVG
  • src/ShellUI.Templates/Templates/ThemeToggleTemplate.cs — mirrors all the above, adds previously-missing Class parameter, wires @Class into the rendered class attribute, declares shellui-js as a dependency (the new JS calls require it), and the embedded ThemeService content also gets the eval → ShellUI.* swap
  • NET9/BlazorInteractiveServer/Components/UI/{SidebarTrigger,ThemeToggle,InputOTP}.razor — stale demo-app snapshots mirrored so the demo runs

Adjacent fix that came along

  • src/ShellUI.Components/Components/InputOTP.razor + src/ShellUI.Templates/Templates/InputOTPTemplate.cs + NET9/.../InputOTP.razor — same eval pattern used for focusing OTP digits; replaced with the existing ShellUI.focusElement JS helper (drops eval security/CSP issue). The template's broader API drift from the live version (still uses old ClassName parameter, inline class composition) is out of scope here and tracked separately — see "Not in this PR" below.
    • InputOTP template full sync — old ClassName parameter, inline class composition vs live's Class + Shell.Cn(...). Exempted in AllowedDrift with a reason. Tracked as a separate follow-up chip (chore/inputotp-sync); the success criterion is "remove the AllowedDrift entry and the test stays green."

Drift guard

  • ShellUI.Tests/TemplateSyncTests.cs[Theory] over (template-name, live-razor-file) pairs:

    • sidebar-triggerSidebarTrigger.razor
    • theme-toggleThemeToggle.razor
    • input-otpInputOTP.razor

    Extracts the @code block from each via the same quote/comment-aware brace-balancing tokenizer used by TemplateCompileTests from branch 1, normalizes (strip comments, blank lines, trim whitespace), and asserts equality. On failure, prints up to 5 lines of side-by-side diff so the maintainer sees exactly where they diverged.

    Source resolution uses [CallerFilePath] so the test works regardless of cwd on CI — anchors to the test source file's location at compile time.

    Includes an AllowedDrift dictionary (component name → reason) for cases where intentional divergence is acceptable. Currently has one entry: input-otp (API drift between ClassName/Class; tracked as a follow-up).

Verification

  • dotnet test ShellUI.Tests18/18 passing (3 new sync tests + 15 from branch 1).
  • Drift-bite proof: temporarily added a phantom [Parameter] public bool DriftCanary { get; set; } to the live ThemeToggle.razor. Test failed with:
    line 6
        live:     [Parameter] public bool DriftCanary { get; set; }
        template: [Parameter(CaptureUnmatchedValues = true)]
    
    Removed the canary; back to green. Confirms the guard catches realistic drift (added parameter) with a precise diagnostic.
  • No more JSRuntime.InvokeVoidAsync("eval", …) calls in src/ShellUI.Components/**. Verified by grep.

Test plan

  • CI green (tests + template-compile + smoke build from branch 1)
  • Manual: scaffold a fresh dotnet new blazor + shellui init + shellui add theme-toggle sidebar. Build and run. Mobile sidebar trigger now shows a hamburger SVG and toggles. Theme toggle flips light/dark and persists across reload. No browser-console eval/CSP errors.
  • Confirm prerender flow on Blazor Server doesn't throw — page renders before JS executes, then OnAfterRenderAsync populates _isDark from localStorage.

Shewart added 5 commits June 17, 2026 17:47
…ependency

Updated comments in the CI workflow to enhance clarity regarding the purpose of the smoke tests for CLI scaffolding. Additionally, adjusted the handling of the Blazor-ApexCharts package to ensure it is explicitly added for smoke tests, isolating specific bug classes related to template escapes.
…ve Razor components and CLI templates

Introduced a new test class, TemplateSyncTests, to ensure that the @code blocks in live Razor components match the corresponding CLI templates. This includes normalization of content to ignore comments and whitespace differences, helping to catch discrepancies that could lead to runtime errors. Updated existing comments in TemplateCompileTests for clarity on parsing behavior.
…ization flow

Updated the ThemeToggle component to streamline theme initialization by replacing the async OnInitializedAsync method with a synchronous OnInitialized method. Introduced OnAfterRenderAsync to handle localStorage access after the first render, ensuring proper theme retrieval and state updates. Simplified theme class management by utilizing ShellUI methods for adding/removing classes, enhancing performance and readability. Updated related ThemeService to maintain consistency in theme handling across components.
…oved scalability and accessibility

Updated the SidebarTrigger component in both Blazor and ShellUI templates to replace the FontAwesome icon with an SVG representation. This change enhances scalability and accessibility, ensuring a more modern and responsive design for the sidebar toggle button.
…proved focus handling

Updated the InputOTP component in both Blazor and ShellUI templates to utilize ShellUI.focusElement instead of eval for focusing on OTP input fields. This change enhances performance and reliability by leveraging a dedicated JavaScript function. Additionally, updated the InputOTPTemplate to include a dependency on shellui-js and refined the input class handling for better styling consistency.
@Shewart Shewart merged commit ef11e20 into main Jun 17, 2026
1 check passed
@Shewart Shewart deleted the fix/sidebar-trigger-and-theme-toggle branch June 17, 2026 15:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant