Skip to content

The what's-new dialog read the store before it loaded (GRYT-1101) - #517

Merged
sivert-io merged 1 commit into
mainfrom
claude/GRYT-1101-whats-new-race
Sep 9, 2026
Merged

sivert-io merged 1 commit into
mainfrom
claude/GRYT-1101-whats-new-race

Conversation

@sivert-io

Copy link
Copy Markdown
Member

Reported as "I installed 1.11.1 and I can't see the what's new". It isn't that install — the dialog has never shown for anybody and never would.

What happens

WhatsNew reads getUserValue("whatsNewSeenVersion", null) in an effect that runs on mount. The per-user store is filled by loadForUser, which useSettings calls from its own effect and awaits. The read always wins that race, against an empty module-level cache.

So every launch:

  1. seen is null → the component treats it as a fresh install → records the version, says nothing.
  2. The recording goes nowhere either. setUserValue returns early when there's no cachedUserId, having written only to the in-memory cache — which loadForUser then replaces wholesale.
  3. Next launch is in exactly the same state. Forever.

Confirmed on a running client, not reasoned about. Before the fix localStorage held user:<id>:nickname, :servers and :hasSeenTour — and no whatsNewSeenVersion at all. After it, the key is written.

The fix

userStorage says when a user's data is actually in the cache, and the dialog waits for that before deciding anything.

Why the check didn't catch it

check-whats-new.mjs handed the effect a fake getUserValue that was ready from the first call. It tested the decision and never the timing — which is exactly the thing that was broken.

It now runs the effect against a store that hasn't loaded, asserts the subscription and the effect's dependency, and executes markLoaded itself rather than only asserting loadForUser mentions it. Six mutations, six caught, two of which only touch userStorage.

This is one of three reasons

The dialog stayed shut for three independent reasons, and all three had to go:

The store race this PR
changelog.json is served with no Access-Control-Allow-Origin, so the fetch is refused from 127.0.0.1 (desktop) and from app.gryt.chat Gryt-chat/site#121
1.11.1 had no line written yet fixed in site#120, deploying

It needs all three plus a release after this one before anyone sees it.

🤖 Generated with Claude Code

Reported as "I installed 1.11.1 and cannot see it". It was not that install.
The dialog had never shown for anybody and never would.

WhatsNew read getUserValue("whatsNewSeenVersion") in an effect that runs on
mount. The per-user store is filled by loadForUser, which useSettings calls
from its own effect and awaits, so the read always won the race against an
empty module-level cache. Every launch: seen is null, which the component
treats as a fresh install, so it records the version and says nothing. And the
recording went nowhere either — setUserValue returns early with no cachedUserId,
writing only to the in-memory cache that loadForUser then replaces.

Confirmed on a running client before the fix: localStorage held
user:<id>:nickname, :servers and :hasSeenTour, and no whatsNewSeenVersion at
all. After it, the key is written.

userStorage says when a user is actually loaded, and the dialog waits for that
before deciding.

check-whats-new.mjs missed this because it handed the effect a fake
getUserValue that was ready from the first call — it tested the decision and
never the timing. It now runs the effect against a store that has not loaded,
asserts the subscription and the dependency, and executes markLoaded itself
rather than only asserting that loadForUser names it. Six mutations, six
caught, including two that only touch userStorage.

This is one of three reasons the dialog stayed shut. The second is that
changelog.json is served with no Access-Control-Allow-Origin, which is
Gryt-chat/site. The third is that 1.11.1's line had not been written yet.

Co-Authored-By: Claude Opus 5 <[email protected]>
@sivert-io
sivert-io merged commit 47d4ff5 into main Sep 9, 2026
3 checks passed
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