refactor: the auth store holds one session union, and the admin pages one dialog state

Merged
HA
10d ago
+286-256

Last of the stacked frontend PRs, on top of #361.

The auth store kept isAuthenticated as boolean | null, with user and csrfToken nullable beside it. Nothing in the app treated null (not checked yet) differently from false, since every check was === true or a truthy test. And a signed-in store could still have user: null: startSession marked the store signed in before the profile had loaded. It now holds one session, either { status: 'signed-out' } or { status: 'signed-in', user, csrfToken }, so a signed-in session always has its user. What changes:

  • startSession signs in once the profile has loaded, with the CSRF token from the login response. If that profile check fails right after login, the user stays signed out, where before they got a user-less session.
  • Initialization tells verifyAuth what to answer when the server can't be reached. A saved session younger than five minutes still survives that.
  • Templates check authStore.session.status === 'signed-in' and take {@const user = authStore.session.user} where bits-ui content reads it. TypeScript doesn't carry the narrowing into snippet closures.

The admin users page kept four nullable variables for its dialogs, and the events page four more. Each now holds one dialog union, so only one dialog can be open, and each kind carries what it's for. The user form takes mode: { kind: 'create' } | { kind: 'edit', user } instead of a nullable editingUser. The sagas page's single dialog, and the events page's replay banner, which can show beside a dialog, keep a plain id where '' means none.

The execution result and error, CodeMirror's view and the replay banner's not-yet-loaded session are still nullable. They weren't part of this item, and changing them would mostly be churn.

Lint, stylelint, the format check, svelte-check, coverage with 959 tests, and the build pass locally. The stacked PRs above main only run a few workflows. Each one gets the full CI, Playwright included, once it's rebased onto main.


Summary by cubic

Consolidates the auth store's nullable auth fields into one session union, and each admin page's per-dialog variables into one dialog union.

  • The auth store now holds either { status: 'signed-out' } or { status: 'signed-in', user, csrfToken }, so a signed-in session always has its user.
  • startSession signs in once the profile has loaded with the CSRF token from the login response; if that check fails, the user stays signed out instead of getting a user-less session.
  • Initialization tells verifyAuth what to answer if the server can't be reached; a saved session younger than five minutes still survives that.
  • Templates check authStore.session.status === 'signed-in' and take {@const user = authStore.session.user} where snippets read the user.
  • The admin users and events pages each hold one dialog union instead of four nullable variables, so only one dialog can be open at a time.
  • The user form takes mode: { kind: 'create' } | { kind: 'edit', user } instead of a nullable editingUser.

The sagas page's single dialog and the events replay banner keep a plain id where '' means none. The execution result, CodeMirror's view, and the replay banner's not-yet-loaded session stay nullable; they weren't part of this work.