fix(config): put the old value back when a setter is rejected #233
Open
logan
wants to merge 1 commits from
fix/231-setter-rollback into main
pull from: fix/231-setter-rollback
merge into: :main
:main
:fix/146-stub-etxtbsy
:fix/175-wizard-follows-the-library
:fix/231-setter-rollback
:fix/197-duplicate-column-label
:docs/225-fixtures-wav-tags
:docs/220-skill-check-scope
:test/217-fixture-names-in-queue-selection
:fix/216-riff-parse-allocation
:fix/170-queue-header-action-names
:fix/210-nav-sheet-scroll-affordance
:docs/50-readme-landing-page
:feat/65-art-prefetch-ahead
:feat/71-more-as-a-bottom-sheet
:feat/54-native-touch-feel
:feat/67-entity-links-into-menus
:test/196-visual-tier-gates
:fix/138-ui-test-storage-leak
:fix/104-wav-tags-read
:fix/207-sheet-scroll-affordance
:fix/204-ui-visual-update-filter
:pi-agent-backlog-automation
:63-touch-model-phase-2
:63-android-touch-model
:186-touch-targets-settings
:186-touch-targets-page-header
:187-seek-bar-hit-area
:189-190-explore-correctness
:135-android-underrun-instrumentation
:51-android-small-screens
:fix/171-phone-queue-scrim
:fix/137-touch-only-affordances
:fix/154-nested-css-check
:feat/58-mini-player-progress-line
:fix/66-album-page-scrolls-as-one
:60-context-menu-action-sheet
:64-android-system-volume
:59-slim-the-mini-player
:55-queue-as-a-screen
:feat/57-drop-the-android-top-bar
:feat/62-jobs-as-a-notification
:fix/53-seek-bar-never-moves
:fix/159-android-task-app-id
:fix/52-android-activity-recreation-restarts-the-process
:fix/150-expand-button-under-the-art
:feat/42-inline-volume-and-centred-transport
:fix/156-queue-selection-fixture-order
:fix/151-fuse-the-scroll-guard-and-the-write
:fix/43-queue-panel-selection
:fix/143-top-bar-fits-its-window
:feat/27-jobs-into-settings
:feat/25-configurable-sidebar-tabs
:feat/6-global-back-forward
:fix/72-active-view-broadcast
:fix/69-page-header-action-overflow
:fix/quick-wins-batch
:fix/118-in-library-clear
:fix/61-mini-player-plain-text
:fix/68-hover-affordances-pointer
:fix/119-dev-headless-port
:fix/130-issue-claim-user
:fix/131-codegen-check-scope
:feat/28-autotag-match-on-album
:feat/17-demote-version-selector
:feat/38-ownership-visibility
:ci/115-manual-release
:feat/34-icon-language
:feat/7-full-tracklist-toggle
:fix/16-tagwriter-totals
:fix/unclaim-ca-certs
:fix/unclaim-shell
:ci/unclaim-on-close
:docs/closing-keyword
:docs/retire-stale-planning-docs
:docs/issue-driven-workflow
:integration/small-fixes
:fix/small-issue-batch
:fix/queue-toggle-state
:fix/drag-count-badge
:fix/album-card-year
:fix/album-tracklist-heading
:fix/seek-bar-clock-width
:fix/explore-art-scanner-requests
:chore/workflow-guardrails
No Reviewers
Labels
Clear labels
Area/Design
Area/Downloads
Area/Explore
Area/Library-UI
Area/Metadata
Area/Packaging
Area/Player
Area/Queue
Area/Settings
Area/Shell-Nav
Compat/Breaking
Kind/Bug
Kind/Documentation
Kind/Enhancement
Kind/Feature
Kind/Security
Kind/Testing
Platform/Android
Platform/Desktop
Breaking change that won't be backward compatible
Something is not working
Documentation changes
Improve existing functionality
New functionality
This is security issue
Issue or pull request related to testing
Priority
Critical
1
The priority is critical
Priority
High
2
The priority is high
Priority
Medium
3
The priority is medium
Priority
Low
4
The priority is low
Reviewed
Confirmed
1
Issue has been confirmed
Reviewed
Duplicate
2
This issue or pull request already exists
Reviewed
Invalid
3
Invalid issue
Reviewed
Won't Fix
3
This issue won't be fixed
Status
Blocked
1
Something is blocking this issue or pull request
Status
Need More Info
2
Feedback is required to reproduce issue or to continue work
Status
Abandoned
3
Somebody has started to work on this but abandoned work
Status
In Progress
Somebody is actively working on this right now
No labels
Milestone
No items
No Milestone
Projects
Clear projects
No projects
Notifications
Due Date
No due date set.
Dependencies
No dependencies set.
Reference: yonlu/yellowjacket#233
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
The issue
Config.SetTrackListColumnsassigned the new column list to the in-memory config before validating it, and returned the validation error without putting the old list back.Config.Save()validates the whole config, so a rejected value did not merely fail its own call — it failed every later save, of every unrelated setting, silently and for the rest of the session. Theme, launch page, shortcuts, libraries: none of them would stick. Nothing reached disk, so a restart cleared it, which is exactly what makes this invisible to the user and unreportable.Scope, decided explicitly
The Findings on #231 ask whether this is one fix or one pass over the file, and the previous run declined to fix it in passing on the grounds that every setter has this shape. That turns out to be false, and checking it is what bounded the diff.
The defect is precisely assignment precedes a validation that can reject that argument. That predicate is mechanical, not a judgement, and it enumerates seven setters:
SetScanConcurrency,SetThemeAccentColor,SetThemeBackgroundShade,SetDefaultPage,SetQueueFallback,SetTrackListColumns,SetFavoritesIconStyle.Each now snapshots the field it is about to overwrite and restores it on the error path — three lines apiece.
The rest were read rather than assumed, and are unchanged:
SetShortcuts,SetShortcutshortcuts.Config.ValidatereturnsnilunconditionallySetFavoritesPlaylistID,SetPinDefaultPlaylist,SetAllowMeteredCatalogDownload,SetPopupVolumeint64and bools that noValidateinspectsSetDownloadPreferencesConfig.Validatedoes not validateDownloadsat allSetViewVisibleGeneralConfig.Validateonly normalizes the visibility map rather than rejecting itSetLibraryDirectorylibrary.NewConfigbefore assigning, so there is nothing to undoThis is not asserted from reading alone — see "planted" below: on the unfixed code the two already-correct setters passed while all seven others failed.
Deliberately not done. A
Save()failure — a disk error rather than a rejection — still leaves memory ahead of disk. That value has already passed validation, so nothing is poisoned and no later save is blocked. Whether the in-memory config should roll back to match the file is a different question needing its own argument, and it is not what #231 reports.Verification
go test ./backend/config/— green. Newbackend/config/setter_rollback_test.go; each row asserts three things in order: the setter errors, the getter still reports the old value, and an unrelated save still works — the third being the one the user actually feels.Save() failed after a rejected write: invalid config: .... The two tests covering the setters left alone passed on that same run.SetLibraryDirectory,SetViewVisible), so a later refactor that breaks their up-front validation is visible rather than silent.make lint— 0 issues across all three build configurations.make test— green, all three passes (default,indexbuild,dev).make bindings-check— current.make skill-check— 47 targets, all present.make generate(no.sql/.templ),make ui-test(no frontend source touched),make e2e— #197 removed the only UI route to an invalid value, so there is no flow left to drive; the Go tier is where this behaviour lives.One thing worth knowing
The rationale was first written as a doc comment on
Save(), andmake bindings-checkcaught thatSaveis bound — v3's generator is a static analyser that carries doc comments across, so fourteen lines of backend reasoning rendered intofrontend/bindings/yellowjacket/backend/config/config.tsfor an audience with no use for it. It is a free-floating comment above the setter section instead.Coordination
Does not collide with #232 (
fix/197-duplicate-column-label), which touchesconfig-page.ts,columns.tsand a frontend test, and no Go. The one shared file isCLAUDE.md, and this entry is on theconfigpackage bullet (~line 664) rather than in #232's block (~line 3275), so the two should merge cleanly.Closes #231
CI green — run 18472, both required jobs.
checksuccess: commit messages,make lint,make test, frontend typecheck, CSS literals,make ui-test,make bindings-check,make skill-check— every step passed.e2esuccess: both browser steps ran and passed (E2E — chromium✔,E2E — webkit✔). Checked the per-step status rather than the job conclusion, since a chromium failure is what silently skips the WebKit step and that is the one source of WebKit signal.Nothing was skipped except the two failure-only steps (
App log on failure,Upload traces and screenshots), as expected on a passing run.View command line instructions
Checkout
From your project repository, check out a new branch and test the changes.