fix(theme): remove stale memo wrappers from theme context hooks#534
Conversation
|
one more eyes here please @gnanam1990 @auriti when you have time |
|
@gnanam1990 is this good to merge? |
gnanam1990
left a comment
There was a problem hiding this comment.
Thanks for the follow-up here. I re-pulled the current head locally and checked the branch again.
What I verified on my side:
- current diff is still the small change
- passes
I’m still not comfortable calling this merge-ready yet.
The main reason is that this PR still reads like a narrow likely-path fix rather than a confirmed root-cause fix, and the PR description itself frames it as an intentionally small draft for live maintainer testing. For this bug family, we already saw earlier that small plausible fixes could look right without actually proving they were the durable solution.
At this point I’m even more cautious because PR #589 landed as the stronger root-cause direction for the stale menu/theme update issues by fixing the reconciler host-prop update path directly. Given that, I do not want to merge another local ThemeProvider-side workaround unless we can clearly show there is still a distinct current-main issue that remains after the reconciler fix, and that this change specifically addresses it.
What I would want before merge:
- a confirmed repro on current main after the reconciler fix line
- focused regression coverage for the ThemeProvider path, if this is still a separate bug
- or a clearer explanation of why this remains necessary even with the reconciler-side fix
So for now my vote is still request changes / hold, not because the change is obviously wrong, but because I do not think the current evidence is strong enough to merge it yet.
gnanam1990
left a comment
There was a problem hiding this comment.
Thanks for the follow-up here. I re-pulled the current head locally and checked the branch again.
What I verified on my side:
- current diff is still the small src/components/design-system/ThemeProvider.tsx change
- bun run build passes
I’m still not comfortable calling this merge-ready yet.
The main reason is that this PR still reads like a narrow likely-path fix rather than a confirmed root-cause fix, and the PR description itself frames it as an intentionally small draft for live maintainer testing. For this bug family, we already saw earlier that small plausible fixes could look right without actually proving they were the durable solution.
At this point I’m even more cautious because PR #589 landed as the stronger root-cause direction for the stale menu/theme update issues by fixing the reconciler host-prop update path directly. Given that, I do not want to merge another local ThemeProvider-side workaround unless we can clearly show there is still a distinct current-main issue that remains after the reconciler fix, and that this change specifically addresses it.
What I would want before merge:
- a confirmed repro on current main after the reconciler fix line
- focused regression coverage for the ThemeProvider path, if this is still a separate bug
- or a clearer explanation of why this remains necessary even with the reconciler-side fix
So for now my vote is still request changes / hold, not because the change is obviously wrong, but because I do not think the current evidence is strong enough to merge it yet.
Rebase on current main (includes Gitlawb#589 reconciler fix). The React Compiler memo caches (_c) in useTheme() and usePreviewTheme() use referential equality checks on destructured context values. These caches can return stale references when the ThemeProvider's useMemo recreates the context value object but the individual property references (setThemeSetting, setPreviewTheme, etc.) compare equal — the memo short-circuits and returns a cached tuple/object that still holds the old closure captures. This is a distinct bug from Gitlawb#589 (which fixed the ink reconciler's commitUpdate path for host prop updates). Gitlawb#589 ensures that when React _does_ re-render a component with new props, those props actually reach the DOM node. But the memo wrappers here prevent React from _even seeing_ the new context value in the first place — the hook returns the stale cached result. Removing the memo wrappers ensures useTheme() and usePreviewTheme() always read the current context value, eliminating the stale-reference path entirely.
afedf34 to
2f4a06d
Compare
|
@gnanam1990 — fair concern. Let me clarify why this PR is still necessary even after #589. These are two separate bugs at different layers:
In other words: #589 ensures delivered props update correctly. This PR ensures the produced values are fresh in the first place. Both fixes are independently necessary. I've rebased this on current main (which includes #589) and force-pushed. The diff is unchanged — same removal of the two memo wrappers, now on top of Ready for re-review whenever you have a moment 👍 |
|
Yes, I saw the branch move. The head changed from afedf34 to 2f4a06d, and the explanation in the thread is clearer now. But from my side, that looks like a rebase plus a better rationale, not a materially different fix yet. The actual code change is still the same small ThemeProvider.tsx edit, and I still do not see new focused regression coverage proving the stale-value path on current main. So I do recognize that the branch changed, but I would not treat this as new evidence strong enough to flip the review by itself. If there is still a distinct post-#589 bug here, I would want that demonstrated with a focused repro/test. |
…ale-value bug These tests verify that context hooks always return fresh values after ThemeProvider re-renders, even when React Compiler memo caches are in play. - useTheme() must reflect currentTheme changes immediately after setThemeSetting is called (not return a stale cached tuple). - usePreviewTheme() must return functional actions after context re-renders (not stale closures from before the theme change). On current main (with _c memo wrappers), these tests expose the bug: the memo cache compares setThemeSetting by reference (stable across renders via useMemo) and short-circuits, returning the old cached result with stale currentTheme.
|
@gnanam1990 — fair point, and I've added focused regression tests that specifically target the stale-value path. New commit
Why these tests expose the bug on current main: The if ($[0] !== currentTheme || $[1] !== setThemeSetting) {
t0 = [currentTheme, setThemeSetting];
$[0] = currentTheme;
$[1] = setThemeSetting; // ← stable reference from useMemo
$[2] = t0;
} else {
t0 = $[2]; // ← returns stale cached result
}When With the fix (no memo), CI is running on the updated head. Will update when it passes. |
Fix relative paths for ink.js, KeybindingSetup, AppStateProvider, useStdin mock, systemTheme mock, and config mock to account for the test file being in src/components/design-system/ rather than src/components/.
Use Ink's createRoot instead of react-dom/client, matching the pattern from ThemePicker.test.tsx. The tests now render through Ink's terminal renderer and check frame output for theme values, which is the same environment ThemeProvider actually runs in.
- ink.js, KeybindingSetup, AppStateProvider: ../ → ../../ - StructuredDiff: same pattern as ThemePicker test adjusted for depth
|
CI is green ✅ — all 698 tests pass including the two new regression tests in
These tests exercise the exact stale-value path that the React Compiler @gnanam1990 — ready for re-review whenever you have a moment. |
gnanam1990
left a comment
There was a problem hiding this comment.
Thanks for the follow-up here. I rechecked the updated head locally, and with the new focused regression coverage in place, this looks good to me now.
What I verified on my side:
- the branch now adds targeted tests in src/components/design-system/ThemeProvider.test.tsx for the stale hook-value path
- bun test ./src/components/design-system/ThemeProvider.test.tsx ./src/components/ThemePicker.test.tsx
- bun run build
Those passed locally.
The added tests are the piece I was missing before. They make a concrete case that this is still a separate ThemeProvider-side stale-value issue even after the reconciler fix line, and that removing the memo-wrapper path in the theme hooks resolves it.
Maintainer summary: with the focused regression coverage now included, I’m comfortable approving this on the current head.
auriti
left a comment
There was a problem hiding this comment.
Rechecked the current head.
The piece I was missing before is now there:
- the branch adds focused regression coverage in
src/components/design-system/ThemeProvider.test.tsx - the hook-side change stays narrowly scoped to the stale-value path in
ThemeProvider smoke-and-testsis green
With the targeted regression coverage in place, I’m comfortable approving this on the current head.
1 similar comment
…awb#534) * fix(theme): remove stale React Compiler memo wrappers from theme hooks Rebase on current main (includes Gitlawb#589 reconciler fix). The React Compiler memo caches (_c) in useTheme() and usePreviewTheme() use referential equality checks on destructured context values. These caches can return stale references when the ThemeProvider's useMemo recreates the context value object but the individual property references (setThemeSetting, setPreviewTheme, etc.) compare equal — the memo short-circuits and returns a cached tuple/object that still holds the old closure captures. This is a distinct bug from Gitlawb#589 (which fixed the ink reconciler's commitUpdate path for host prop updates). Gitlawb#589 ensures that when React _does_ re-render a component with new props, those props actually reach the DOM node. But the memo wrappers here prevent React from _even seeing_ the new context value in the first place — the hook returns the stale cached result. Removing the memo wrappers ensures useTheme() and usePreviewTheme() always read the current context value, eliminating the stale-reference path entirely. * test(theme): add regression tests for useTheme()/usePreviewTheme() stale-value bug These tests verify that context hooks always return fresh values after ThemeProvider re-renders, even when React Compiler memo caches are in play. - useTheme() must reflect currentTheme changes immediately after setThemeSetting is called (not return a stale cached tuple). - usePreviewTheme() must return functional actions after context re-renders (not stale closures from before the theme change). On current main (with _c memo wrappers), these tests expose the bug: the memo cache compares setThemeSetting by reference (stable across renders via useMemo) and short-circuits, returning the old cached result with stale currentTheme. * fix(test): correct import paths for ThemeProvider.test.tsx Fix relative paths for ink.js, KeybindingSetup, AppStateProvider, useStdin mock, systemTheme mock, and config mock to account for the test file being in src/components/design-system/ rather than src/components/. * fix(test): rewrite ThemeProvider tests using Ink renderer Use Ink's createRoot instead of react-dom/client, matching the pattern from ThemePicker.test.tsx. The tests now render through Ink's terminal renderer and check frame output for theme values, which is the same environment ThemeProvider actually runs in. * fix(test): correct all relative import paths for design-system/ depth - ink.js, KeybindingSetup, AppStateProvider: ../ → ../../ - StructuredDiff: same pattern as ThemePicker test adjusted for depth --------- Co-authored-by: root <root@vm7508.lumadock.com>
Features from upstream: - Context partitioning + relevance-based pruning (Gitlawb#849) - Extended thinking xhigh + reasoning_effort forwarding (Gitlawb#857) - Strip x-anthropic-billing-header in openaiShim (Gitlawb#1019) - Agent waits for subagent before continuing (Gitlawb#1032) - Fix CLI createRequire → static import (Gitlawb#1026) - Fix theme memo wrappers (Gitlawb#534) - Resolve flaky test module leaks (Gitlawb#988) - Web search diagnostic when adapter returns 0 hits (Gitlawb#1006) - Node >=22 engine requirement (Gitlawb#1018) - New ripgrep resolution chain (npm fallback, embedded mode) - Multi-dir project config loading - Provider profile improvements - New built-in agents: karpathy-guidelines, statusline-setup Verboo rules preserved: - Mono-provider: all traffic via code.verboo.ai/router - isVerbooMode() guard takes priority over all provider env vars - VERBOO-BRAND colors, logo, and splash screen unchanged - Dual-read env vars (VERBOO_* canonical, OPENCLAUDE_* aliases) - /provider command remains disabled - .verboo config dir, .verboo-profile.json Renamed openclaude → verboo throughout (URLs, package names, display strings, test fixtures, proto package). CHANGELOG history preserved. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…awb#534) * fix(theme): remove stale React Compiler memo wrappers from theme hooks Rebase on current main (includes Gitlawb#589 reconciler fix). The React Compiler memo caches (_c) in useTheme() and usePreviewTheme() use referential equality checks on destructured context values. These caches can return stale references when the ThemeProvider's useMemo recreates the context value object but the individual property references (setThemeSetting, setPreviewTheme, etc.) compare equal — the memo short-circuits and returns a cached tuple/object that still holds the old closure captures. This is a distinct bug from Gitlawb#589 (which fixed the ink reconciler's commitUpdate path for host prop updates). Gitlawb#589 ensures that when React _does_ re-render a component with new props, those props actually reach the DOM node. But the memo wrappers here prevent React from _even seeing_ the new context value in the first place — the hook returns the stale cached result. Removing the memo wrappers ensures useTheme() and usePreviewTheme() always read the current context value, eliminating the stale-reference path entirely. * test(theme): add regression tests for useTheme()/usePreviewTheme() stale-value bug These tests verify that context hooks always return fresh values after ThemeProvider re-renders, even when React Compiler memo caches are in play. - useTheme() must reflect currentTheme changes immediately after setThemeSetting is called (not return a stale cached tuple). - usePreviewTheme() must return functional actions after context re-renders (not stale closures from before the theme change). On current main (with _c memo wrappers), these tests expose the bug: the memo cache compares setThemeSetting by reference (stable across renders via useMemo) and short-circuits, returning the old cached result with stale currentTheme. * fix(test): correct import paths for ThemeProvider.test.tsx Fix relative paths for ink.js, KeybindingSetup, AppStateProvider, useStdin mock, systemTheme mock, and config mock to account for the test file being in src/components/design-system/ rather than src/components/. * fix(test): rewrite ThemeProvider tests using Ink renderer Use Ink's createRoot instead of react-dom/client, matching the pattern from ThemePicker.test.tsx. The tests now render through Ink's terminal renderer and check frame output for theme values, which is the same environment ThemeProvider actually runs in. * fix(test): correct all relative import paths for design-system/ depth - ink.js, KeybindingSetup, AppStateProvider: ../ → ../../ - StructuredDiff: same pattern as ThemePicker test adjusted for depth --------- Co-authored-by: root <root@vm7508.lumadock.com>
…awb#534) * fix(theme): remove stale React Compiler memo wrappers from theme hooks Rebase on current main (includes Gitlawb#589 reconciler fix). The React Compiler memo caches (_c) in useTheme() and usePreviewTheme() use referential equality checks on destructured context values. These caches can return stale references when the ThemeProvider's useMemo recreates the context value object but the individual property references (setThemeSetting, setPreviewTheme, etc.) compare equal — the memo short-circuits and returns a cached tuple/object that still holds the old closure captures. This is a distinct bug from Gitlawb#589 (which fixed the ink reconciler's commitUpdate path for host prop updates). Gitlawb#589 ensures that when React _does_ re-render a component with new props, those props actually reach the DOM node. But the memo wrappers here prevent React from _even seeing_ the new context value in the first place — the hook returns the stale cached result. Removing the memo wrappers ensures useTheme() and usePreviewTheme() always read the current context value, eliminating the stale-reference path entirely. * test(theme): add regression tests for useTheme()/usePreviewTheme() stale-value bug These tests verify that context hooks always return fresh values after ThemeProvider re-renders, even when React Compiler memo caches are in play. - useTheme() must reflect currentTheme changes immediately after setThemeSetting is called (not return a stale cached tuple). - usePreviewTheme() must return functional actions after context re-renders (not stale closures from before the theme change). On current main (with _c memo wrappers), these tests expose the bug: the memo cache compares setThemeSetting by reference (stable across renders via useMemo) and short-circuits, returning the old cached result with stale currentTheme. * fix(test): correct import paths for ThemeProvider.test.tsx Fix relative paths for ink.js, KeybindingSetup, AppStateProvider, useStdin mock, systemTheme mock, and config mock to account for the test file being in src/components/design-system/ rather than src/components/. * fix(test): rewrite ThemeProvider tests using Ink renderer Use Ink's createRoot instead of react-dom/client, matching the pattern from ThemePicker.test.tsx. The tests now render through Ink's terminal renderer and check frame output for theme values, which is the same environment ThemeProvider actually runs in. * fix(test): correct all relative import paths for design-system/ depth - ink.js, KeybindingSetup, AppStateProvider: ../ → ../../ - StructuredDiff: same pattern as ThemePicker test adjusted for depth --------- Co-authored-by: root <root@vm7508.lumadock.com>
Summary
useTheme()andusePreviewTheme()/themefocus updatesWhy
Issue #517 still appears reproducible on current
mainin maintainer testing, even after the earlier ThemePicker-side cleanup. This patch targets the remaining suspicious stale-context path insrc/components/design-system/ThemeProvider.tsx.Scope
This is an intentionally small draft for live maintainer testing, not a claim that the issue is fully solved yet.
Closes #517