s&box/field-guide
the symptom, in your words

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

✓ verified on engine 26.07lane: Building UIposted
▸ 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 nests for >8 values if needed
}

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 — seen in a real project.
s&box moves fast; an undated fix is a liability. Spot a stale detail?