Skip to content

Stop writing one profile's state into another - #268

Merged
ramonski merged 1 commit into
masterfrom
test/frontend-more-utils
Sep 15, 2026
Merged

ramonski merged 1 commit into
masterfrom
test/frontend-more-utils

Conversation

@ramonski

Copy link
Copy Markdown
Member

profileStorage prefixes every localStorage key with the active profile name, and gets that name from a fetch on module load. These helpers are synchronous and run during render, so there is a real window on every page load where the name is not yet known. The old code assumed "default" in that window.

Two things went wrong with that, and the tests that found them fail against the old code:

A write landed in the wrong profile. profileSet during the window wrote default:filter while org-mode was active.

A read moved a legacy value into the wrong profile, and deleted the original. profileGet migrates an unprefixed key under the current prefix and removes it. During the window that prefix is default:, so the value the org-mode profile owned was filed under default and the original unlinked. That one does not undo itself.

Both are reachable: this install has two profiles, and isDeadlineAcked is called while task cards render, which is well inside the window.

The fix. The resolved name is cached in localStorage and read back synchronously on the next load, which closes the window entirely — except on the first load after a switch, where the cache is one behind. For that load, writes are buffered and flushed once the name arrives, and the legacy migration is held back rather than guessing. useSwitchProfile now records the new name before its caller reloads, which closes that last case too.

What is left is that a read in the window can return the previous profile's value: wrong on screen for a few milliseconds, and unlike the other two it corrects itself.

Five tests on the module, twenty in the frontend suite. tsc clean, build clean.

profileStorage prefixes every localStorage key with the
active profile name and gets that name from a fetch on
module load. These helpers are synchronous and run during
render, so there is a real window on every page load where
the name is not known. The old code assumed "default".

A write in that window landed under default: while
org-mode was active.

Worse, a read moved a legacy value into the wrong profile
and deleted the original: profileGet migrates an
unprefixed key under the current prefix and removes it,
and during the window that prefix is default:. The value
the active profile owned was filed elsewhere and the
original unlinked. That one does not undo itself.

Both are reachable. This install has two profiles, and
isDeadlineAcked runs while task cards render, well inside
the window.

The resolved name is now cached in localStorage and read
back synchronously on the next load, which closes the
window except on the first load after a switch. For that
load writes are buffered and flushed once the name
arrives, and the migration waits rather than guessing.
useSwitchProfile records the new name before its caller
reloads, closing that case too.

What is left is that a read in the window can return the
previous profile's value: wrong on screen for a few
milliseconds, and unlike the other two it corrects itself.
@ramonski
ramonski merged commit d954d11 into master Sep 15, 2026
@ramonski
ramonski deleted the test/frontend-more-utils branch September 15, 2026 07:11
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.

1 participant