7.6 KiB
Inline-Agents Card-Property Migration
Context
The "Detailed agent activity" experience is gated by two settings that evolved separately:
-
experimentalAgentDashboard(global setting, persisted insettings) — the opt-in kill switch. When off, no inline-agent code paths run; the sidebar never reads the agent status slice and the view-options menu hides the'inline-agents'checkbox entirely. -
worktreeCardProperties(persisted UI array) — the user's per-card view mode.'inline-agents'is one entry in this array. Unchecking it hides the inline list even when the experimental toggle is on.
These landed in different releases:
- Earlier rc:
experimentalAgentDashboardshipped as a toggle driving a bottom-panel Agent Dashboard.worktreeCardPropertiesat that time had no'inline-agents'entry because inline agents did not exist. - Later rc (commit
c1620496, "replace bottom-panel dashboard with inline per-card agents list"): the bottom panel was removed. The feature migrated into per-workspace-card inline lists, and'inline-agents'was added toDEFAULT_WORKTREE_CARD_PROPERTIES.
The bug
Coworkers who upgraded to the latest rc — all of whom had
experimentalAgentDashboard: true set in the previous rc — reported
that the inline agent activity was not showing up. Investigation
showed the 'inline-agents' checkbox in the Workspaces view-options
menu was unchecked for every one of them.
Root cause is in src/main/persistence.ts's load() path. For fields
whose shape is "full array of user-selected entries", the merge rule
is:
worktreeCardProperties: parsed.ui?.worktreeCardProperties ?? [...DEFAULT]
The persisted array wins wholesale over the new default. This is the
right rule for user-editable lists — otherwise anything a user
deliberately unchecks in one release would re-appear in the next
whenever the default changes — but it means adding a new entry to
the default array has no effect on users who already have the array
persisted. Every upgrading user had this array persisted (it's
written on first launch), so the new 'inline-agents' entry never
reached them.
For toggle-off users the bug is invisible: the view-options checkbox
is hidden by SidebarHeader.tsx:64-67 (liveAgentsEnabled gates the
entry out of visiblePropertyOptions), and WorktreeCard.tsx:539
doesn't render the inline list either way. For toggle-on users the
bug is the reported symptom: the feature is enabled globally but
visually absent, with no obvious hint that a second checkbox exists
to unblock it.
Fix
Two complementary pieces. They cover disjoint populations: existing opt-ins (migration) and future opt-ins (UI handler).
1. One-shot persistence migration (existing opt-ins)
In src/main/persistence.ts load(), when loading a file:
- If
experimentalAgentDashboard === trueAND the persistedworktreeCardPropertiesarray exists and does not include'inline-agents'AND the migration flag_inlineAgentsDefaultedForExperimentis absent:- Append
'inline-agents'to the array. - Set
_inlineAgentsDefaultedForExperiment: true.
- Append
- Otherwise, leave the array untouched and still set the flag to
trueso the migration never re-fires.
The flag is essential. Without it, a user who deliberately unchecks
'inline-agents' from the view-options menu would have the entry
re-added on every subsequent launch — the persistence-load path can't
tell "array never had it" from "user removed it".
The migration is scoped to experimentalAgentDashboard === true on
purpose: users who never opted into the experimental feature have no
visible symptom, and leaving their persisted array alone keeps the
"user choices stick" invariant honest.
2. UI handler (future opt-ins)
In src/renderer/src/components/settings/ExperimentalPane.tsx, when
the user flips the toggle ON via the Settings UI:
- Read the current
worktreeCardPropertiesfrom the live Zustand store. - If
'inline-agents'is not present, calltoggleWorktreeCardProperty('inline-agents')to add it.
This covers the "will click the toggle on" case. Without it, a user who opts in after shipping this change would enable the experiment but still see an unchecked view-mode checkbox, reproducing exactly the symptom the migration fixes.
We read from the live store (useAppStore.getState()) inside the
handler instead of threading worktreeCardProperties through props:
the store is the source of truth, and a stale prop reference after a
recent toggle would be easy to miss.
Schema
One new optional field on PersistedUIState:
/** One-shot migration flag for the inline-agents view-mode rollout. */
_inlineAgentsDefaultedForExperiment?: boolean
This follows the existing _sortBySmartMigrated convention in the
same file — an underscore prefix signals "migration bookkeeping, not
a user-facing setting", and the field stays optional so older data
files load cleanly.
Behavior by user group
| Group | experimentalAgentDashboard before upgrade |
What happens on upgrade | What they see |
|---|---|---|---|
| A: toggle was ON | true |
Migration runs: 'inline-agents' appended; flag set. |
Inline agent list renders immediately on next launch. View-options checkbox is checked. Can still uncheck it, and the uncheck sticks. |
| B: toggle was OFF | false or undefined |
Migration does not run (gated on toggle). Flag is set so the migration never considers them again even if they later turn the toggle on and we ship another fix here. | No visible change. The 'inline-agents' checkbox remains hidden from the view-options menu. If they later opt in via Settings, the UI handler adds 'inline-agents' at the moment of opt-in. |
| C: fresh install (any release) | N/A | No data file → defaults used directly → 'inline-agents' is in the default array. |
Works out of the box. |
Why not a simpler approach?
A few alternatives were considered and rejected:
- Always merge
DEFAULT_WORKTREE_CARD_PROPERTIESinto persisted value. Would re-add any entry a user deliberately removed in any release. Breaks the "user choice sticks" invariant for the whole array, not just this one entry. - Unconditional migration (ignore the toggle). Slightly simpler,
harmless for toggle-off users. Rejected because scoping to
experimentalAgentDashboard === truematches intent — we're fixing a regression for users who already opted in, not mutating data for users who didn't. - Do the migration entirely on the renderer side (in
hydratePersistedUI). Would also work, but the renderer already delegates all schema-evolution logic topersistence.ts. Keeping it on the main side matches the existing_sortBySmartMigratedandterminalMacOptionAsAltMigratedprecedents and makes the write-back immediate (main persists the migrated array on the next scheduled save, rather than waiting for the renderer to callwindow.api.ui.set).
Tests
src/main/persistence.test.ts gains four cases:
- Adds
'inline-agents'to persisted cardProps when experimental toggle is on. - Does not add
'inline-agents'when the toggle is off, but still sets the flag. - Respects a deliberate uncheck after the migration flag is already set (does not re-add).
- Does not duplicate when
'inline-agents'is already present.
Files touched
src/shared/types.ts— declare_inlineAgentsDefaultedForExperiment?: booleanonPersistedUIState.src/main/persistence.ts— one-shot migration inload().src/renderer/src/components/settings/ExperimentalPane.tsx— on-toggle-on UI handler that adds'inline-agents'when absent.src/main/persistence.test.ts— four new migration tests.