Add the storage and snapshot-update half of the external-change-detection
mechanism. RefreshHelper now keeps a mutex-protected snapshot string and exposes
accessors for it; Refresh captures a fresh snapshot at the start of any refresh
whose scope set includes COMMITS or BRANCHES.
We capture before reading the git state, not after. Capturing after would let an
external change that lands between the git state read and the snapshot (say, the
next step of a rebase running in another terminal) leave the stored snapshot
newer than what we actually rendered; the poller would then see no difference
and never refresh again, stranding the UI on the intermediate state. Capturing
first keeps the snapshot from running ahead of the render, so if disk moves
during the refresh the next poll catches it.
No reader of the snapshot exists yet — the polling goroutine that consumes it
comes in a later commit. Keeping the snapshot hook in its own commit isolates
the invariant that the snapshot stays in sync with what the UI has observed,
which is what makes the poller's change-detection predicate work across in-app
commands and focus-in refreshes.