Store interface preferences per account (#55) - #85
Conversation
The sidebar arrangement and display settings were kept only in localStorage, so they stayed in one browser. They are now also stored against the signed-in player's account through GET/PUT /api/session/preferences and applied wherever that player next signs in. localStorage remains the working copy: the account copy seeds it on load and receives every change afterwards, so an unreachable backend costs nothing beyond the arrangement not travelling, and an arrangement made before signing in is uploaded rather than discarded. The reconciliation rule is a pure function (mergePreferences) covered by the Node test suite. The stored payload is opaque JSON, capped at 8192 characters, so a preference added to the game page needs no backend change while an account still cannot be used as unbounded storage. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A panel opened or closed while the account copy is applied raises its toggle event asynchronously, after the guard against echoing that back up had already been cleared. The guard now ends on the next task instead. Adds the cookie-forwarding tests the other per-user BackendService calls have, for the preference read and write. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Self-review rubric (performed against the diff and the CI run on head
Findings from the adversarial pass, two of which were fixed in
One further gap is restated from the PR body rather than scored: a green CI run does not verify the inline page wiring, since neither job executes it. A manual smoke test — change a setting and the panel arrangement, sign in from a second browser, confirm the arrangement is there — is recommended before this is relied on. This review was performed and posted during a Gardener session (https://github.com/Stephenson-Software/gardener). |
checkSession sets the username before the account copy is requested, so a preference written in the window between the two - by the load-time panel restore echoing a toggle, or by the player changing a setting - scheduled an upload 500ms later that could beat the response. That put this browser's copy over the account's and permanently lost the arrangement the request was about to return. Uploads are now gated until the merge has happened, and a change made while the gate is shut is remembered and sent once it opens, so it is delayed rather than dropped. A failed load opens the gate too, leaving the player able to save from the browser they are on. Corrects the player guide and changelog alongside: both said an offline change is sent up on the next load, which contradicted the stated rule that the account copy wins - it holds only for a preference the account has never held. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Verification review (head
|
* Cover the preference upload gate with the Node test suite (#86) Move the state machine deciding when a signed-in player's preferences may be sent to their account out of game.html's inline script and into game-logic.js as createPreferenceSync, taking the reads, writes and requests as callbacks so the orderings that carry the risk can be driven without a browser. The reads, writes, requests and the DOM work stay inline as those callbacks. The defect found by hand during the review of #85 - a preference changed while the account copy was still in flight scheduling an upload that could beat the response and put this browser's stale copy over the account's - now fails a test rather than only a reviewer. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Cover createPreferenceSync's browser-default timers and error reporter The suite drives an injected clock everywhere else, leaving the setTimeout / clearTimeout the browser is actually given, and the no-op onError a caller may rely on, exercised by nothing. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Summary
GET/PUT /api/session/preferencespair on the backend holds each signed-in player's displaysettings and sidebar arrangement (order, which panels are shown, which are open), so signing in
on another browser or device brings the arrangement along instead of starting from the defaults.
localStorageis kept as the working copy: it is seeded from the account copy on load andreceives every change afterwards. An unreachable backend therefore costs nothing beyond the
arrangement not travelling. Where the two copies differ the account's wins; a preference the
account has never held — chosen before signing in, or while the backend was down — is uploaded
rather than discarded.
mergePreferencesingame-logic.js), so it is covered by the Node test suite rather than living untested in thepage's inline script.
between sign-in and the account copy arriving would schedule an upload that could beat the
response and put this browser's stale copy over the account's, losing the arrangement the request
was about to return. A change made while the gate is shut is remembered and sent once it opens,
so it is delayed rather than dropped.
user_preferencestable, keyed by username), capped at 8192characters: a preference added to the game page needs no backend change, while an account cannot
be used as unbounded storage. An oversized or unreadable payload is answered with
400ratherthan a
500, and preferences that can no longer be parsed are reported as unset so the pagefalls back to its own defaults.
coalesced into a single request.
The show/hide, reorder and remembered-collapsed-state half of #55 was shipped in earlier cycles;
what remained was the per-account persistence, which this completes.
Modules touched
Both. Backend gains the model, repository, service and endpoints; the web client gains the matching
proxy routes, the pure merge rule and the page wiring; the documentation sources of truth are
updated alongside.
Scope note
Eighteen files are touched, above the loop's ~10-file soft ceiling. Six of them are test files and
the rest are the dependency-coupled minimum for one endpoint pair (entity → repository → service →
controller, then the web-client caller that the
ProxyRouteCoverageTestcontract requires, plus thethree documentation sources of truth). Net non-test change is roughly 360 lines, inside the ~400-line
ceiling.
Coverage note
The page wiring in
game.htmlis inline script, which neither the Maven build nor the Node suiteexecutes; a green CI run does not verify it. The parts that could be extracted (the merge rule) were,
and the proxy routes are held by
ProxyRouteCoverageTest, but a manual smoke test of the game page —change a setting and the panel arrangement, sign in from a second browser, confirm the arrangement is
there — is recommended before this is relied on.
Test plan
mvn -f backend/pom.xml test— green (277 tests; newPreferencesServiceTest,GameControllerPreferencesTest)mvn -f web-client/pom.xml test— green (26 tests; new proxy cases inWebControllerTest;ProxyRouteCoverageTestconfirms both new calls the page makes have routes)node --test web-client/src/test/js/— run by the Web Client CI job, which exercises the newmergePreferencescasesCloses #55
This PR description was drafted during a Gardener session (https://github.com/Stephenson-Software/gardener).