feat(realtime): live own-profile refresh in the sidebar self block (LC-173 phases 1-2) #218

Merged
nrupard merged 1 commit from feat/lc-173-live-own-profile into main 2026-05-26 18:23:13 +02:00
Owner

Closes the in-scope phases of LC-173 (audit gap C1, deferred by LC-170). POST /settings (post_profile) changed avatar / display_name / bio and broadcast nothing, so editing your profile did not update any open tab until reload.

Scope (phases 1-2)

The editor's own sidebar self block (avatar + display label + @username + custom status) now refreshes live in every one of their tabs. The sidebar is on every authed page, so this also live-updates the settings page's own chrome (phase 1) and the self block everywhere else (phase 2).

Phase 3 deferred: how OTHER users see this profile in message author rows / DM headers across surfaces. That is the expensive cross-surface refresh (analogous to UserStatusChanged's multi-instance update, but for avatar src + names) and stays a follow-up.

Changes

  • New UserProfileChanged { user_id } event. post_profile broadcasts it via broadcast_to_user after persisting all fields, fanning to every connection of the editor (same per-user fan-out as LC-161 invitations - no topic needed since the sidebar is ubiquitous).
  • The sidebar self block is extracted into partials/sidebar_self.html (shared by the sidebar and the OOB fragment) and wrapped in <div id="sidebar-self">. The WS send task gates UserProfileChanged on the recipient being the editor and swaps #sidebar-self via ws/own_profile_live.html, re-fetching the user so fresh values render.
  • The #status-picker slot and the room/peer lists sit outside the block, so an open status picker and the current enclave's room list survive the swap - unlike a full render_sidebar, which renders the Home (DM-only) variant and would clobber both.

Deliberately out of scope

Not live-swapping the settings <form> fields: that would clobber edits in progress in another tab. The editing tab already reflects via the existing redirect to /settings.

Verification

just test and just test-saas both green (106 test result: ok each, zero failures); cargo clippy / cargo fmt clean. Regression test (routes_drafts::sidebar_self_block_is_present_and_swappable) pins the #sidebar-self region and the own-avatar wrapper inside it so the partial extraction cannot silently regress.

Closes the in-scope phases of LC-173 (audit gap C1, deferred by LC-170). `POST /settings` (`post_profile`) changed avatar / display_name / bio and broadcast nothing, so editing your profile did not update any open tab until reload. ## Scope (phases 1-2) The editor's own **sidebar self block** (avatar + display label + `@username` + custom status) now refreshes live in every one of their tabs. The sidebar is on every authed page, so this also live-updates the settings page's own chrome (phase 1) and the self block everywhere else (phase 2). **Phase 3 deferred**: how OTHER users see this profile in message author rows / DM headers across surfaces. That is the expensive cross-surface refresh (analogous to `UserStatusChanged`'s multi-instance update, but for avatar `src` + names) and stays a follow-up. ## Changes - New `UserProfileChanged { user_id }` event. `post_profile` broadcasts it via `broadcast_to_user` after persisting all fields, fanning to every connection of the editor (same per-user fan-out as LC-161 invitations - no topic needed since the sidebar is ubiquitous). - The sidebar self block is extracted into `partials/sidebar_self.html` (shared by the sidebar and the OOB fragment) and wrapped in `<div id="sidebar-self">`. The WS send task gates `UserProfileChanged` on the recipient being the editor and swaps `#sidebar-self` via `ws/own_profile_live.html`, re-fetching the user so fresh values render. - The `#status-picker` slot and the room/peer lists sit outside the block, so an open status picker and the current enclave's room list survive the swap - unlike a full `render_sidebar`, which renders the Home (DM-only) variant and would clobber both. ## Deliberately out of scope Not live-swapping the settings `<form>` fields: that would clobber edits in progress in another tab. The editing tab already reflects via the existing redirect to `/settings`. ## Verification `just test` and `just test-saas` both green (106 `test result: ok` each, zero failures); `cargo clippy` / `cargo fmt` clean. Regression test (`routes_drafts::sidebar_self_block_is_present_and_swappable`) pins the `#sidebar-self` region and the own-avatar wrapper inside it so the partial extraction cannot silently regress.
feat(realtime): live own-profile refresh in the sidebar self block (LC-173 phases 1-2)
All checks were successful
check-secrets / Nosey parker (push) Successful in 4s
check-secrets / TruffleHog (push) Successful in 7s
check-secrets / Kingfisher (push) Successful in 8s
check-secrets / Nosey parker (pull_request) Successful in 3s
check-secrets / TruffleHog (pull_request) Successful in 4s
check-secrets / Kingfisher (pull_request) Successful in 5s
Check / clippy + fmt + tests (pull_request) Successful in 1m34s
Create release / Create release from merged PR (pull_request) Has been skipped
3c52b03117
From audit gap C1, deferred by LC-170. POST /settings (post_profile) changed avatar / display_name / bio and broadcast nothing, so editing your profile did not update any open tab until reload.

Scope (phases 1-2 of the issue): the editor's own sidebar self block (avatar + display label + @username + custom status) now refreshes live in every one of their tabs. The sidebar is on every authed page, so this also covers the settings page's own chrome. Phase 3 (how OTHER users see this profile in message author rows / DM headers across surfaces) is deferred as the expensive cross-surface refresh.

- New UserProfileChanged { user_id } event. post_profile broadcasts it via broadcast_to_user after persisting all fields, so it fans to every connection of the editor (same per-user fan-out as LC-161 invitations; no topic needed since the sidebar is ubiquitous).
- The sidebar self block is extracted into partials/sidebar_self.html (shared by the sidebar and the OOB fragment) and wrapped in <div id="sidebar-self">. The WS send task gates UserProfileChanged on the recipient being the editor and swaps #sidebar-self via ws/own_profile_live.html, re-fetching the user so fresh values render. The #status-picker slot and the room/peer lists are outside the block and untouched, so an open picker and the current enclave's room list survive the swap (unlike a full render_sidebar, which would clobber both).

Deliberately NOT live-swapping the settings <form> fields: that would clobber edits in progress in another tab. The editing tab already reflects via the existing redirect-to-/settings.

Regression test pins the #sidebar-self region + the own-avatar wrapper inside it so the partial extraction cannot silently regress.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
nrupard deleted branch feat/lc-173-live-own-profile 2026-05-26 18:23:13 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
psa-systems/lets-chat!218
No description provided.