uttrflow / uttrflow/uttrflow-swift
[P2] Settings edits overwrite newer choices made in the menu bar or main window
- Dominant language
- Swift
- Stars
- 4
- Forks
- 17
- Avg merge
- 3h 32m
- Merged PRs (30d)
- 277
Description
The Settings window retains a settings snapshot for its entire lifetime. Changes made through another screen are saved separately, so a later unrelated edit in Settings writes the old snapshot back and silently reverses those choices.
### Reproduction
1. With Suggestions enabled, open Settings once, then close it.
2. Disable Suggestions through the menu bar.
3. Reopen Settings and change Appearance to Dark.
4. Suggestions is enabled again, although the user only changed appearance.
The same stale-value mechanism applies to other settings changed through the main window while this controller remains alive.
### Evidence
Reviewed main at `533e6cbef5046f8bd681246d99ebf9ef9553fa40`:
- `Sources/Uttrflow/Settings/SettingsViewModel.swift`: initialization loads `store.load()` once; `apply` edits `session.settings`, and `persist` saves the entire resulting Settings value.
- `Sources/Uttrflow/Settings/SettingsWindowController.swift`: the controller retains one model; `show` refreshes identity, tab, personalisation and capabilities but never reloads the saved settings.
- `Sources/Uttrflow/AppDelegate.swift:1280`: the menu Suggestions switch calls `apply(.toggle(.suggestionsEnabled, isOn: isOn))`. The shared `apply` at line 1632 edits AppDelegate's settings, saves the result, and calls `settingsChanged`. That method updates app services and main-window presentation but does not synchronize the Settings model. Main-window `.change` actions use the same path at line 1552.
An isolated Swift test compiled the unmodified production SettingsViewModel, SettingsEditor and UserDefaultsSettingsStore, with an in-memory KeyValueStore. After creating the model with Suggestions enabled, it saved the menu-bar change through SettingsEditor and verified the stored value was false. Calling `model.apply(.appearance(.dark))` then produced `suggestions=true, appearance=dark`: the preservation assertion failed. A control that creates a fresh model after the external change passes.
This validates the production state/persistence path; the native window and menu clicks were not driven in this test.
### Expected fix and verification
Keep the Settings model synchronized with authoritative settings changes, including changes made while its window is open. Ensure a Settings edit cannot persist an obsolete value for unrelated fields. Add regression coverage for an external change followed by a Settings edit, both with the window reopened and still open, and verify existing settings callbacks remain functional.
Contributor guide
Research direction
Start with Sources/Uttrflow/Settings/SettingsViewModel.swift and SettingsWindowController.swift, then trace AppDelegate.swift at lines 1280, 1552, and 1632 to understand the shared settings path. Reproduce the stale-value failure with the isolated Swift test and add regression coverage for external changes while Settings is reopened and still open. Done means unrelated settings are preserved and existing callbacks still work.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- swift
- Domain
- desktop, testing
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 65/100