# Persisted settings across production updates ## Result No Phase 1–5 persistence regression was reproduced. No runtime fix or breaking commit is justified. The old b77938a shell (verified tag `1175f1a1d2c1`) upgraded through its own banner/Refresh UI flow at the same origin to current main `cf27d4f15a29747123437862ab4e461fd78178e6`. All 24 existing setting keys and all 14 checked controls were preserved on Chromium and WebKit. The original worktree base/live deployment snapshot `0b8e398` was also tested separately: both engines preserve the same 24/24 keys, controls, profile/import policy; Chromium again saves and plays the complete clip before/after. This avoids conflating concurrent main changes. Both servers are actual archived Bun implementations, with isolated DB/media directories. A local proxy supplies deliberately older profile copies and blocks profile writes; no credentials or real online profiles are used. Settings values were set using real controls/change handlers, and EQ using its actual preset button. The installed page was reloaded before recording its exact `_ytpdata` JSON; old/new raw snapshots are in the results file. Refresh UI and import use the existing button handlers via `.click()` on the DOM element: physical Playwright hit-testing was obstructed by the bottom nav at large font size. This tests persistence/update handlers, not modal pointer geometry. ## Every-key comparison Values below are identical on both engines; no key was removed or reset. | Key | Old (after reload) | Upgraded | Status | | --- | --- | --- | --- | | quality | `"360p"` | `"360p"` | same | | volume | `1` | `1` | same | | audioOnly | `false` | `false` | same | | autoPreload | `true` | `true` | same | | p2pShare | `true` | `true` | same | | p2pReceive | `true` | `true` | same | | saveBeforePlay | `true` | `true` | same | | repeatMode | `"all"` | `"all"` | same | | loopOne | `true` | `true` | same | | shuffle | `false` | `false` | same | | theme | `"light"` | `"light"` | same | | fontScale | `"large"` | `"large"` | same | | levelVolume | `false` | `false` | same | | liveAudioIOS | `false` | `false` | same | | parallelSaves | `2` | `2` | same | | density | `"compact"` | `"compact"` | same | | perfMode | `true` | `true` | same | | reduceMotion | `true` | `true` | same | | layout | `"f7-swipe"` | `"f7-swipe"` | same | | autoBackupEnabled | `false` | `false` | same | | autoBackupIntervalDays | `7` | `7` | same | | serviceVideo | `false` | `false` | same | | foldedCards | `{}` | `{}` | same | | eq | `{"preset":"bass","g":[5,7,5,3,1,0,0,0,0,0]}` | `{"preset":"bass","g":[5,7,5,3,1,0,0,0,0,0]}` | same | Save before playing and Auto-save playlist videos stayed ON; audio-only OFF. Quality stayed 360p, layout f7-swipe, loop/repeat ON, light theme, large font, compact density, performance/reduced motion ON and two parallel saves. The tested b77938a build already has ten-band EQ, so its Bass boost curve was unchanged. The five-band compatibility migration in app.js was not triggered. ## Behaviour and alternative overwrite paths Chromium saved the complete 30,290-byte H.264/AAC clip before and after the upgrade, set cachedIds, used an OPFS blob source, reached readyState ≥3 and advanced playback time. The existing copy was deleted before the second open to force a genuinely new post-upgrade download. Linux WebKit reports native OPFS unsupported on both builds; preservation/UI checks pass but successful native storage/playback there is not claimed or shimmed. Profile tests exercise the launch path with `syncedAt:200`: | Server updatedAt | Server Save before playing | Device result, both engines | | ---: | --- | --- | | 100 | false | Local true and every local setting retained | | 300 | false | Server false adopted; layout classic and defaults/remote values applied | A copy with old *values* but a newer timestamp can therefore disable autosave. This is existing last-write-wins behavior, not an update migration regression: `pullProfileIfNewer()` and `applyProfileData()` at b77938a already implement the same timestamp comparison and whole-settings replacement. Equal timestamps also retain local settings (unit test). A genuine older timestamp cannot reset local settings in these tests. `API.loadData()` reads localStorage in web mode; boot overlays loaded.settings on DEFAULT_SETTINGS. Phase 4 extraction does not create a second data object. The Phase 3 head loader only reads `_ytpdata` and sets appearance attributes; it never writes settings. `syncOnLaunch()` invokes profile reconciliation after the first render. `/api/user/data` was monitored/mocked with false autosave settings and received zero requests: fingerprint sync is not a settings pull at launch. `saveDataToStorage()` pushes metadata to `/api/user/sync`, without a settings field. Importing a backup containing opposite settings through the actual file input and Import button preserved the current settings. `importBackup()` merges playlists/history/positions but does not apply `imp.settings`. Intentional profile adoption/share-link loading uses `applyProfileData()` and *does* replace settings; this is already documented on the existing confirmation UI. ## Verification ```sh node perf/settings-persist.mjs --browser all --target main --allow-unsupported-webkit node perf/settings-persist.mjs --browser all --target 0b8e398 --allow-unsupported-webkit --out perf/results/settings-persist-live-base.json node --test frontend/*.test.js (cd server && bun install && bun run test) ``` Without `--allow-unsupported-webkit`, unverified native WebKit storage exits 2. Any assertion failure exits 1 even with that allowance. The server-side proxy is essential: WebKit worker network requests bypass page-route mocks. The two shared Chromium Settings errors (`null.addEventListener`) are recorded in the results; they already occur on the old build. They did not stop the playback/autosave handlers from persisting or saving. This is not an assertion that every Settings interaction is error-free. Only tests, harness and documentation changed; no runtime implementation or save behavior changed. A phase bisect and failing-then-fixed runtime test cannot be supplied honestly because no setting loss was reproduced on either target. ## Phone checks On the installed PWA, inspect Settings → Playback → Save before playing, ensure Audio-only is OFF and no explicit streaming override is selected. Auto-save playlist videos is a separate policy. Check the linked profile and whether another device/imported profile pushed a newer copy with the toggle OFF. Compare settings before/after an online launch. Re-check origin/PWA identity, iOS Clear Website Data/storage eviction/private mode, free quota and Downloads failure details. Native iPhone OPFS, suspension, quota sharing and storage reset remain unverified by Linux WebKit. A phone-side failing sequence/settings export is needed before changing the sync policy or save logic.