the symptom, in your words

"My s&box UI won't update / is frozen"

✓ verified on 26.07.08e
lane Building UIposted updated

▸ SYMPTOM

You change game state, but the panel on screen never reflects it. A counter stays stale, a modal won't close, a loading bar sits at 0% even though work is progressing. No error, no crash: the UI is simply not re-rendering.

▸ CAUSE

BuildHash() is the only re-render trigger in s&box Razor. The panel recomputes the hash each frame and re-renders only when that integer changes. Any value your markup reads but the hash omits is silently frozen.

Hashing a collection reference or object identity stays constant even as contents mutate, so the UI looks dead while data moves underneath.

▸ FIX

Hash everything the markup reads, collection contents, derived display strings (they're cheap), day/date, and every open/closed flag you branch on:

snippet
@inherits PanelComponent

@code {
    protected override int BuildHash() => HashCode.Combine(
        Counts.Values.Sum(),    // collection CONTENTS, not the reference
        Game?.Day ?? 0,
        Progress,               // loading bar / time-sliced work
        UIState.ThisPanelOpen,
        UIState.AnyModalOpen
    );
    // HashCode.Combine has NO 9-arg overload - a flat 9th
    // argument is CS1501. Nest instead:
    // HashCode.Combine(HashCode.Combine(a..h), i)
}

Skeleton reminder:

snippet
@using Sandbox
@using Sandbox.UI
@namespace YourGame
@inherits PanelComponent

<root>
    @if (Game is not null && UIState.ThisPanelOpen)
    {
        <div class="panel">@Counts.Values.Sum()</div>
    }
</root>

If the panel type itself can't be found from C#, fix @namespace first: razor-namespace-trap.

▸ WHY IT WORKS

Razor does not diff the full tree every frame. It treats BuildHash() as a dirty bit: same int → skip rebuild. Content-sensitive hashes (sums, counts, flags, progress) change when the player-visible state changes, so the markup runs again and the screen catches up.

Verified on engine 26.07.08e: seen in a real project.
s&box moves fast; an undated fix is a liability. Spot a stale detail?

Want to know when new guides or fixes drop? Join the community to help build this out. Report gotchas, flag outdated fixes, or just lurk.

Join the Discord