Skip to content

fix(settings): prevent null localStorage values from overriding state defaults#815

Open
TimeToBuildBob wants to merge 2 commits intoActivityWatch:masterfrom
TimeToBuildBob:fix/null-localstorage-settings-override
Open

fix(settings): prevent null localStorage values from overriding state defaults#815
TimeToBuildBob wants to merge 2 commits intoActivityWatch:masterfrom
TimeToBuildBob:fix/null-localstorage-settings-override

Conversation

@TimeToBuildBob
Copy link
Copy Markdown
Contributor

Fixes #813

When localStorage contains keys with null values (keys present but not set by this app), localStorage.getItem(key) returns the string "null" which then overrides the sane defaults defined in the Pinia state() function.

Root cause: The old code merged all localStorage keys (including garbage/null ones) with server keys, then applied all of them with ``. A key present in localStorage with a null value would overwrite the default.

Fix: Two-part change:

  1. Separated the merge loop: server keys first (authoritative), then localStorage fills gaps only for keys not already set.
  2. Skip null return values from localStorage.getItem — if a key produces null, it genuinely doesn't exist and the state() default should be preserved.

Verification: The views/settings-load flow that triggers the TypeError on Activity page now starts from correct defaults instead of receiving null. Manual testing on a fresh AW instance with empty/no settings should show the Activity page loading without errors.

@codecov
Copy link
Copy Markdown

codecov Bot commented May 8, 2026

Codecov Report

❌ Patch coverage is 0% with 20 lines in your changes missing coverage. Please review.
✅ Project coverage is 30.71%. Comparing base (f4b9379) to head (81d9ba8).

Files with missing lines Patch % Lines
src/stores/settings.ts 0.00% 19 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master     #815      +/-   ##
==========================================
- Coverage   30.76%   30.71%   -0.05%     
==========================================
  Files          33       33              
  Lines        1973     1976       +3     
  Branches      368      366       -2     
==========================================
  Hits          607      607              
+ Misses       1345     1290      -55     
- Partials       21       79      +58     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented May 8, 2026

Greptile Summary

This PR fixes a regression where localStorage keys holding the string "null" (produced by localStorage.setItem(key, null)) would override the sane Pinia state() defaults after being passed through JSON.parse, which evaluates to JS null.

  • The load loop is split into two passes: server settings are inserted first (authoritative), then localStorage iterates over its own keys and fills only gaps that the server didn't cover, skipping entries whose raw value is JS null or the literal string "null".
  • The isJsonKey parsing block and cleanCategory transform are preserved in the localStorage pass exactly as before, so no behavioral changes exist for valid stored data.
  • The used Set tracks server-sourced keys to prevent any duplicate processing in the second pass.

Confidence Score: 5/5

Safe to merge — the change is a well-scoped fix to the settings load path with no new external dependencies and no changes to save/persist logic.

The two-pass merge correctly prioritises server settings, then guards against both JS-null and string-"null" localStorage values before applying existing JSON parsing and type coercion. Behaviour for all valid stored values is unchanged, and the cleanCategory transform and isJsonKey list are preserved without alteration.

No files require special attention.

Important Files Changed

Filename Overview
src/stores/settings.ts Rewrites the settings load logic from a single merged-key loop into a two-pass approach: server values first (authoritative), then localStorage fills gaps while skipping JS-null and string-"null" values to protect Pinia state defaults.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[load settings] --> B[get_settings from server]
    B --> C{for each server key}
    C -->|starts with underscore| D[skip]
    C -->|valid key| E[store server value, mark as used]
    E --> F{for each localStorage key}
    F -->|starts with underscore or already used| G[skip]
    F -->|valid key| H[raw = localStorage.getItem]
    H --> I{raw is null or string null}
    I -->|yes| J[skip, keep state default]
    I -->|no| K{isJsonKey?}
    K -->|yes| L[JSON.parse, cleanCategory if classes]
    K -->|boolean string| M[parse as boolean]
    K -->|other| N[store raw string]
    L --> O[patch store with merged storage]
    M --> O
    N --> O
Loading

Reviews (2): Last reviewed commit: "fix(settings): guard against string "nul..." | Re-trigger Greptile

Comment thread src/stores/settings.ts Outdated
The previous null guard (raw === null) only catches the case where
localStorage.getItem returns JS null (key not present). Since the loop
iterates Object.keys(localStorage), every key already exists — raw is
never JS null here.

The actual root cause: an older AW version (or third-party code) calling
localStorage.setItem('views', null) stores the string "null". For JSON
keys, JSON.parse("null") evaluates to JS null, which then overrides the
defaultViews state() default with null.

Fix: also skip raw === 'null' to preserve state() defaults for any key
whose localStorage value is the null sentinel string.
@TimeToBuildBob
Copy link
Copy Markdown
Contributor Author

@greptileai review

@TimeToBuildBob
Copy link
Copy Markdown
Contributor Author

Ready to merge — all CI green, Greptile 5/5 (safe to merge), review thread resolved.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

activity: valid activity route throws TypeError 'reading 0' and stays loading

1 participant