@@ -139,6 +139,13 @@ function App(): React.JSX.Element {
139139 const rightSidebarOpen = useAppStore ( ( s ) => s . rightSidebarOpen )
140140 const isFullScreen = useAppStore ( ( s ) => s . isFullScreen )
141141 const settings = useAppStore ( ( s ) => s . settings )
142+ // Why: render-level gate for the experimental agent dashboard retention
143+ // sync. Reading the flag here (rather than only inside useDashboardData /
144+ // useRetainedAgentsSync) lets us skip mounting RetainedAgentsSyncGate
145+ // entirely for non-toggled users, which drops all feature-tied
146+ // subscriptions (agentStatusByPaneKey, agentStatusEpoch, etc.) instead of
147+ // keeping them alive behind an early-return inside the hook bodies.
148+ const agentDashboardEnabled = useAppStore ( ( s ) => s . settings ?. experimentalAgentDashboard === true )
142149 const canGoBackWorktree = useAppStore ( canGoBackWorktreeHistory )
143150 const canGoForwardWorktree = useAppStore ( canGoForwardWorktreeHistory )
144151 const titlebarLeftControlsRef = useRef < HTMLDivElement | null > ( null )
@@ -147,10 +154,10 @@ function App(): React.JSX.Element {
147154
148155 // Subscribe to IPC push events
149156 useIpcEvents ( )
150- // Why: retention must run at App level (not inside AgentDashboard) because
151- // the sidebar hovercard also reads retained entries. If retention only ran
152- // when the dashboard is mounted , "done" agents would vanish from the hover
153- // any time the user collapses the dashboard panel .
157+ // Why: retention must run at App level so the inline per-card agents list
158+ // always sees retained entries. If retention ran inside the sidebar-card
159+ // subtree , "done" agents would vanish any time the user collapsed a card's
160+ // inline agents section .
154161 //
155162 // The retention hooks are hosted inside <RetainedAgentsSyncGate /> (a leaf
156163 // component that renders null) rather than being called inline here.
@@ -159,10 +166,17 @@ function App(): React.JSX.Element {
159166 // event frequency), re-rendering the entire app tree on every agent status
160167 // update. Hosting the subscriptions in a leaf isolates that churn.
161168 //
162- // The experimentalAgentDashboard gate still lives inside the hooks
163- // themselves (useDashboardData early-returns [] from its memo;
164- // useRetainedAgentsSync early-returns from its effect), so the gate
165- // component is cheap when the setting is off.
169+ // The render-level gate on <RetainedAgentsSyncGate /> (see
170+ // agentDashboardEnabled above) keeps the experimental feature fully dark
171+ // for non-toggled users: without the gate mounted, none of its feature-tied
172+ // zustand selectors (agentStatusByPaneKey / agentStatusEpoch / etc.) are
173+ // ever subscribed, so PTY agent-status events cause zero work for them.
174+ //
175+ // The inner hook guards (useDashboardData early-returns [] from its memo;
176+ // useRetainedAgentsSync early-returns from its effect) remain as
177+ // defense-in-depth: they keep both hooks safe to call from any future
178+ // callsite, and they handle the in-session off→on toggle transition
179+ // cleanly without relying on a remount race when the setting flips.
166180 // Why: git conflict-operation state also drives the worktree cards. Polling
167181 // cannot live under RightSidebar because App unmounts that subtree when the
168182 // sidebar is closed, which leaves stale "Rebasing"/"Merging" badges behind
@@ -926,10 +940,16 @@ function App(): React.JSX.Element {
926940 }
927941 >
928942 < TooltipProvider delayDuration = { 400 } >
929- { /* Why: leaf-mounted retention sync. Hosts useDashboardData() +
930- useRetainedAgentsSync() so their high-churn store subscriptions
931- re-render a null component rather than the entire App tree. */ }
932- < RetainedAgentsSyncGate />
943+ { /* Why: leaf-mounted retention sync, gated at the render level by
944+ agentDashboardEnabled. Hosting useDashboardData() +
945+ useRetainedAgentsSync() inside a null-rendering leaf keeps their
946+ high-churn store subscriptions from re-rendering the App tree;
947+ the outer conditional drops those subscriptions entirely for
948+ users who have not toggled the experimental agent dashboard on,
949+ so PTY agent-status events do no feature-tied work for them.
950+ The hooks' internal early-returns remain as defense-in-depth
951+ (see the comment above useIpcEvents()). */ }
952+ { agentDashboardEnabled ? < RetainedAgentsSyncGate /> : null }
933953 { /* Why: in workspace view (split groups always enabled), the full-width
934954 titlebar is removed so tab groups + terminal extend to the top of
935955 the window. Left titlebar controls move to a header above the sidebar.
0 commit comments