Skip to content

fix: make thread auto-settling opt-in

MacroscopeApp / Macroscope - UI Consistency failed Aug 26, 2026 in 3m 1s

UI Consistency: 2 issues found

Findings in apps/web/src/components/settings/SettingsPanels.tsx:

  • Thread settling reset drops the inactivity window (~line 1961): the retired "Auto-settle inactive threads" row owned the reset for sidebarAutoSettleAfterDays. The new mode row resets only sidebarAutoSettleMode, so a customized window survives the reset, the dependent days row is hidden, and the value can no longer be reset from the panel while still applying whenever inactivity is re-selected. Suggested fix: reset both keys and treat either as dirty, matching the new-threads row pattern.
  • Restore-defaults label hides a non-default window (lines 499–503): the days comparison is gated on sidebarAutoSettleMode === "inactivity", but restoreDefaults resets sidebarAutoSettleAfterDays unconditionally, so with mode never and a non-default window the confirmation dialog resets a setting it never listed (or the restore is disabled if nothing else is dirty). Suggested fix: drop the mode condition.

Both were posted as inline review comments with suggested diffs.

Details

Note

Your check run agent prompt is: .macroscope/check-run-agents/ui-consistency.md
More information about how Check Run Agents work can be found in our Help Center.

Scope reviewed (include patterns apps/web/src/**/*.{ts,tsx,css}): components/settings/SettingsPanels.tsx, components/settings/settingsSearch.ts, components/Sidebar.tsx, components/ChatView.tsx, hooks/useThreadActionMenu.ts, components/ThreadStatusIndicators.test.ts. Mobile, contracts, client-runtime and docs changes were used only as context.

No issues found in:

  • Shared primitives: the new mode control uses the core Select composition already used by every other settings row (SelectTrigger with aria-label, SelectValue, SelectPopup align="end" alignItemWithTrigger={false}, SelectItem hideIndicator). No raw control reconstruction, no call-site overrides of primitive geometry, focus ring or hit target; only a contextual width (w-full sm:w-64, wider than the sm:w-40 rows because the longest option label is "When PR merges or closes").
  • Accessibility/interaction: trigger keeps an explicit aria-label; the value guard narrows the union before updateSettings, and selecting inactivity seeds sidebarAutoSettleAfterDays when it is null, so the dependent AutoSettleDaysInput (unchanged primitive, Input with min/max and draft commit) cannot render a value the policy ignores in the normal flow.
  • Settings search: the two retired ids are replaced by a single thread-settling entry that matches the row's searchableSetting(...) id; no dangling references to auto-settle-merged-threads / auto-settle-inactive-threads remain in the repo.
  • Mode threading: autoSettleMode is read from useClientSettings and passed to effectiveSettled / the change-request gate consistently in Sidebar.tsx (row prop + partition memo, with the new value in the dependency list), ChatView.tsx (woke banner + settled memo) and useThreadActionMenu.ts. No environment-scoped action, CSS/Tailwind ownership, theme variant, or scroll/mask behavior is touched by this PR.

Verification suggestion for the author: the typing/interaction contract change is covered by the updated threadSettled / settings tests; no production-CSS or visual-regression gate is needed for this change.


πŸ” Agent activity β€” 13 tool calls

πŸ’­ I'm looking at the in-scope files, particularly focusing on the SettingsPanels.tsx change where a Switch component was replaced by a Select, and checking how it should align with other Select usages elsewhere in the settings UI.

1. git_tools β€” SelectTrigger|SelectPopup|SelectValue|SelectItem|<Select$|<Select\b

2. browse_code β€” apps/web/src/components/settings/SettingsPanels.tsx

πŸ’­ The Select usage looks fine. Now I'm checking whether the inactivity threshold row still has its reset action and searchable id after the changes, comparing against the original file to see if the nested "Days of inactivity before auto-settle" row ever had one. I'm also spotting a potential inconsistency: if sidebarAutoSettleAfterDays is null but mode is "inactivity" (say from a synced or restored setting), the UI would display a default day count while the actual effective behavior never auto-settles anything, since a null value with inactivity mode means settling never triggers. I want to trace how autoSettleAfterDays flows into the effective settled logic in Sidebar to confirm this mismatch. I also want to check whether the searchable settings list still references any removed setting IDs like the old auto-settle-merged or auto-settle-inactive threads entries, so I'll search the codebase for those strings and related keywords.

3. git_tools β€” auto-settle-inactive-threads|auto-settle-merged-threads|thread-settling|sidebarAutoSettleOnMerge|sidebarAutoSettleAfterDays

4. browse_code β€” apps/web/src/components/settings/settingsSearch.ts

πŸ’­ I want to check useSettingsRestore around lines 495-580 and 655-665 to verify consistency between the restore/reset logic and the sidebarAutoSettleAfterDays condition tied to inactivity mode.

5. browse_code β€” apps/web/src/components/settings/SettingsPanels.tsx

6. browse_code β€” apps/web/src/components/settings/SettingsPanels.tsx

πŸ’­ I'm noticing the "Days of inactivity before auto-settle" row seems to have lost its reset button since the old row tied to sidebarAutoSettleAfterDays was removed and replaced with a Select. I need to check the merge base to see if that nested days row originally had a reset action at all.

7. git_tools β€” a3a8cbd:apps/web/src/components/settings/SettingsPanels.tsx

πŸ’­ I'm checking whether the nested "days of inactivity" row's styling and value binding match the pre-existing base structure or if something was newly introduced in this change. I'm tracing whether a null autoSettleAfterDays combined with "inactivity" mode could create a UI mismatch β€” displaying a default day count while inactivity settling actually never triggers. I'm checking whether the default value logic and restore-defaults path guard against this, or whether some legacy persisted state could leave the days field null while inactivity mode is selected. I also notice a related dirty-state bug: the row's reset button only resets the mode, not the days value, so if a user changes both mode to "inactivity" and days to a non-default number, then the reset resets mode back to "never" but leaves days unchanged and untracked β€” orphaning that changed value with no way to reset it or even see it listed as dirty. This is a UI consistency issue too β€” other nested rows like start-from-origin have their own reset control, and the new-threads reset already covers both keys, so the settling row breaking that pattern stands out. I'm also noting the nesting style class on the days row is pre-existing and not something introduced here, so that's out of scope, but the removed "auto-settle-inactive-threads" search item means searching "inactive" no longer surfaces this row at all. I'm weighing whether the wider select trigger (sm:w-64) is a real inconsistency, but given the longer label text it seems justified rather than a bug. Now I'm shifting to Sidebar.tsx to check how autoSettleMode is typed and consumed, particularly around line 830, to see if any inactivity-mode logic got dropped there.

8. browse_code β€” apps/web/src/components/Sidebar.tsx

9. browse_code β€” apps/web/src/components/ChatView.tsx

πŸ’­ I notice the same mode-gate guard duplicated across three call sites (ChatView, Sidebar row, and effectiveSettled), and since effectiveSettled now takes a mode parameter, a shared helper might be cleanerβ€”though this feels more like a duplication concern than a UI consistency issue, so it's borderline whether to flag it. Now I'm checking useThreadActionMenu to see whether it correctly passes autoSettleMode through to effectiveSettled or canSettle.

10. browse_code β€” apps/web/src/hooks/useThreadActionMenu.ts

11. browse_code β€” packages/client-runtime/src/state/threadSettled.ts

πŸ’­ I'm checking whether effectiveSettled's inactivity branch requires autoSettleAfterDays to be non-null.

12. browse_code β€” packages/client-runtime/src/state/threadSettled.ts

πŸ’­ With inactivity mode and days null, no settling occurs, but the UI still shows a fallback default of 3 which misrepresents actual behavior. I've confirmed the row's onCommit always produces a valid number so null can't originate there, and defaults are set to 3 rather than nullβ€”so the real risk is users who previously disabled the switch having a persisted null value stored. Now I'm tracing whether the reset action truly fails to restore days properly: with mode "inactivity" and days at a non-default value like 30, clicking reset sets mode to "never" but leaves days untouched at 30 while hiding the days row, which matters for how changedSettingLabels evaluates whether the setting differs from default. Since that comparison only checks days when mode equals "inactivity", switching back to inactivity later silently restores 30 rather than the default 3, and neither the reset button nor the changed-labels list ever flags this. The real fix is to stop gating the days comparison and reset on the mode condition so the actual persisted value is always accounted for β€” I'll note this as one concrete comment covering both the reset action and the changedSettingLabels logic. Good, both target lines fall within the diff hunks, so comments there should be valid. I'm also scanning for smaller inconsistencies like label wording differences between web and mobile ("Never" vs "Never (manual only)"), but that's a mobile concern and out of scope here. Checking the days row too for a missing searchable-setting id, though it looks like a minor, non-blocking detail. Now I'm verifying the sidebar's memoization of autoSettleMode and confirming autoSettleAfterDays flows correctly into effectiveSettled logic β€” that seems fine. I'm drafting the inline suggestion for the reset button tied to sidebarAutoSettleMode, matching the existing pattern used for other settings resets. I need to double-check that this suggestion range falls within the actual diff hunk boundaries, since the surrounding context lines need to be part of the visible diff for the suggestion to apply correctly. Given the risk, I'm leaning toward commenting on the specific changed line with a fenced diff snippet instead of a suggestion block, and doing the same for the second comment on the settings comparison logic. I also realize restoreDefaults resets the sidebar auto-settle days unconditionally, but the confirmation dialog only surfaces "Thread settling" when the mode is inactivity β€” so a state with mode "never" and non-default days could silently reset without being listed, or leave the restore button disabled if nothing else is dirty. I'll write this as a second, brief comment since it targets a different location in the same file, keeping both comments in scope.

13. modify_pr β€” Reviewed the web UI changes for the exclusive auto-settle policy (SettingsPanels.tsx, settingsSearch.ts, Sidebar.tsx, ChatView.tsx, useThreadActionMenu.ts). The new Select follows the existing settings-row Select composition exactly (trigger aria-label, SelectValue, SelectPopup align="end" alignItemWithTrigger={false}, hideIndicator items), and the mode gate is threaded consistently through the sidebar row, chat banner and action menu. Two findings, both about the reset ownership of the retired inactivity row: the standalone sidebarAutoSettleAfterDays row (and its reset button) is gone, so a non-default inactivity window can no longer be reset from the panel and is partly invisible to Restore defaults.


Agent Credits: 143 credits