Skip to content

feat(ui): room list loading/migrating state (#397)#401

Draft
sanity wants to merge 1 commit into
mainfrom
fix/room-list-migrating-state
Draft

feat(ui): room list loading/migrating state (#397)#401
sanity wants to merge 1 commit into
mainfrom
fix/room-list-migrating-state

Conversation

@sanity

@sanity sanity commented Jul 12, 2026

Copy link
Copy Markdown
Contributor

Problem

During startup load — and especially during a delegate / room-contract migration — the River room list rendered blank with no loading or "migrating" indicator, which reads to users as "all my rooms are gone." Reported by Ivvor after the stdlib-0.8 River republish (delegate + room-contract re-key).

The ROOMS map populates one room at a time as each per-room slot GET returns, and on a re-keyed (empty) current delegate the rooms only arrive later via legacy migration. So an empty map does not mean "no rooms" — it usually means "still loading." Today the empty map fell straight through to a blank list (or, worse, could read as the empty state), with no way to tell "loading" from "you have no rooms."

Closes #397.

Approach

Give the room list a tri-state (ui/src/components/room_list.rs):

  • loading / migrating → a quiet inline spinner + Loading your rooms… (initial load) or Restoring your rooms… (migration, driven by the existing is_legacy_migration_in_progress() localStorage flag);
  • load resolved AND genuinely empty → the existing No rooms yet create/join call-to-action;
  • populated → the list (unchanged).

The genuinely-empty CTA now only shows once the initial load has actually resolved, tracked by a new INITIAL_ROOMS_LOADED signal (ui/src/components/app.rs) set at the terminal points of load_rooms_per_room (response_handler.rs). Because the legacy-migration probe is fire-and-forget (a fresh user with no legacy delegate gets no response at all), the empty-delegate branch waits a bounded settle (LEGACY_PROBE_SETTLE_MS = 3s, legacy delegates are node-local) before declaring "loaded & empty" — so a migrating user never flashes the empty state during the probe gap, while a fresh user gets the CTA after a short, one-time wait.

The signal only ever transitions false → true, so a WebSocket reconnect never flashes the list back to "loading."

The tri-state decision lives in a pure room_list_display(has_rooms, migrating, initial_loaded) fn (unit-tested), consumed inline in RoomList().

UI-only — no contract/delegate WASM change, no re-key. No republish in this PR; publishing is a deliberate follow-up after the look is approved.

Aesthetics — please review the look

Restrained, near-monochrome, theme-aware; the spinner reuses the existing per-room border-text-muted style (no accent, no banner). Screenshots (light + dark) of the migrating and empty states:

  • Migrating (light): quiet spinner + "Restoring your rooms…"
  • Empty (light): muted comments icon + "No rooms yet" / "Create a room or join one with an invite code."

(Screenshots attached in the PR thread / provided to the reviewer. The red WebSocket pill visible at the bottom of the dev screenshots is the pre-existing ConnectionStatusIndicator — this is a no-sync dev build with no node; it is unrelated to this change.)

Testing

  • New pure-fn unit tests (room_list_display) pin the crux: an empty map during migration or before load resolves is never the empty CTA; only a resolved-and-empty map is (cargo test -p river-ui --bins, 393 passed).
  • cargo clippy clean (wasm, no-sync and example-data,no-sync); cargo fmt clean.
  • Verified visually via a dev-only ?room_list_state=migrating|loading|empty|populated URL override (compiled out of production — example-data/wasm-gated), screenshotted in light + dark.

[AI-assisted - Claude]

🤖 Generated with Claude Code

https://claude.ai/code/session_01CD8VBzb8qsDvdcPdF6TNQG

During startup load — and especially during a delegate/room-contract
migration — the room list rendered blank with no indicator, which reads
to users as "all my rooms are gone" (Ivvor's report after the stdlib-0.8
republish). The `ROOMS` map populates one room at a time and, on a
re-keyed delegate, only after legacy migration, so an empty map does not
mean "no rooms."

Give the room list a tri-state:
- loading / migrating -> a quiet inline spinner + "Loading your rooms…" /
  "Restoring your rooms…" (the latter driven by the existing
  `is_legacy_migration_in_progress()` flag);
- load resolved AND genuinely empty -> the "No rooms yet" create/join CTA;
- populated -> the list.

The genuinely-empty CTA now only shows once the initial load has actually
resolved, tracked by a new `INITIAL_ROOMS_LOADED` signal set at the
terminal points of `load_rooms_per_room` (with a bounded settle after the
fire-and-forget legacy probe so a migrating user never flashes the empty
state). UI-only — no contract/delegate WASM change, no re-key.

The tri-state decision is a pure `room_list_display()` fn with unit tests.
A dev-only `?room_list_state=` URL override (example-data builds only)
lets each state be screenshotted without a live node.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CD8VBzb8qsDvdcPdF6TNQG
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.

River UI: room list shows blank (no loading/migrating state) during startup load & delegate migration — reads as "all my rooms are gone"

1 participant