view shell live code: repoint already-bound symbol pointers, so a patch changes what draws
Leo (2026-11-04): "Couldn't we architect stuff to make everything more amenable to hot swappage?"
The view shell's live code never changed what it drew. In the headless measurement (scripts/measure/live-code-shell.sh), every generation's badge was the same picture, grey with padding 8. Each patch loaded and replaced its functions (functions=4), but the drawing still ran the build's code. The measurement's "second patch redrew: yes" came from cmp on PNG bytes (2,036 vs 2,044 bytes for the same pixels).
Why. The executable is linked -interposable -no_fixup_chains, so its calls to its own Swift functions go through classic symbol pointers. One of them is __la_symbol_ptr for PreviewBadge.body (dyld_info -fixups: lazy-bind PreviewBadge.body). Generation 0 draws before any patch, so by then dyld has bound that pointer to the build's body, and dyld_dynamic_interpose does not rewrite a pointer that is already bound.
Fix. After the existing dyld_dynamic_interpose call, Interposer.rebind writes the patch's address into every __la_symbol_ptr and __got slot of the executable that names a replaced function, as fishhook does. __DATA_CONST is made writable for the write. The existing interpose call stays as it is.
Measured on a fleet Mac at this fix plus #35705. inject.sh was the only other change, to tar the PNGs out. The badge's rendered width, at 2× scale:
| generation | edit | before this fix | with it |
|---|---|---|---|
| 0 | none | 135 px, grey | 135 px, grey |
| 1 | padding 8 → 24, red | 135 px, grey | 199 px, red (+64 = 2 × 16 pt × 2) |
| 2 | padding 24 → 40 | 135 px, grey | 263 px, red (+64) |
| 3 | a new file's green dot (#35705) | 135 px, grey | 283 px, red, with the green dot (+20 = (6 + 4) pt × 2) |
| 4 | a new @State | refused | refused |
I checked the PNGs by eye as well. Edit to pixels: 1.45 s for the first patch and 1.14 s for the next.
Follow-up: the measurement should compare decoded pixels or widths, not bytes. It is a separate PR because the fleet runs only the reviewed bytes of a measurement script.
Part of manaflow-ai/cmuxterm-hq#7066.
🤖 Generated with Claude Code
Agent session: agent session
![Autofix with [code]smith](https://pr-comments-assets.blacksmith.sh/codesmith/autofix-with-codesmith-dark-3x.png)
Open in [code]smith · Need help on this PR? Tag @codesmith-bot with what you need.
Summary by cubic
Fixes the view shell's live code so a patch actually changes what draws: previously every generation of the headless measurement drew the same badge, because dyld_dynamic_interpose does not rewrite a symbol pointer dyld has already bound (the build's view had been drawn before any patch).
- Adds
Interposer.rebind, which after the interpose call writes the patch's address into every__la_symbol_ptrand__gotslot that names a replaced function, as fishhook does. - Makes
__DATA_CONSTwritable for the write viamprotect. - Measured result: the badge's padding and color changes from a patch now show in the rendered output, and a new file's drawing appears.
Migration
- None. The executable's existing
-interposable -no_fixup_chainslinking is required for the rebind to be possible; that is already in place.