feat(widgets): keep a widget on screen while you navigate away (#205) #443

Merged
jared merged 1 commits from feat/widget-always-on-screen into lotus 2026-10-20 22:21:48 -04:00
Owner

Part of #205; the owner chose popout (#441) + this.

MSC2931 "always on screen": a widget holding m.always_on_screen (already auto-granted) can send set_always_on_screen. The host now answers it. Without an answer, ClientWidgetApi replied "unknown action".

A widget that asked keeps running in a small floating window when the Widgets panel closes or you switch rooms:

  • the window has a title bar with Back to room and close;
  • Back to room opens that room with the panel showing the widget, without reloading it.

How

  • Moving an iframe in the DOM reloads it. So widget frames now live from the start in an app-level fixed layer (widgetSessions.ts, outside React). While docked, the frame is positioned over the panel's placeholder every animation frame, so it looks and behaves as before.
  • RoomWidgetView is now that placeholder. The permission prompt and the "blocked (same origin)" message read from the session.
  • Layering: docked frames sit at z-index 600 (just above their side panel, below popovers and dialogs). The floating window is zIndices.floatingWidget 9980 (below the incoming-call banner and modals).
  • A widget that didn't ask is torn down when the panel closes, exactly as before.
  • One floating widget at a time. It also goes when the widget withdraws (value: false), is removed from the room, or on logout (FloatingWidget unmount).
  • A permission request that arrives while floating grants nothing extra, since there's nobody to ask.
  • The viewed widget is now an atom per room (widgetViewingAtom), so Back to room can reopen it.

Verified (Playwright, a cross-origin https test widget speaking the widget API, counting seconds since its page loaded):

step result
open in panel widget's set_always_on_screen answered {}
close the panel floating, same frame (counter 4 → 6 s)
switch rooms still floating (9 s)
Back to room in the room, docked, panel on it, 12 s: no reload
widget withdraws, then close the panel gone (0 frames)
✕ on the floating bar gone
an ordinary widget asking to send messages permission prompt shows as before; docked and visible; panel close → torn down, nothing floating

Unit tests (widgets 15/15) pass, tsc is clean, and eslint is at 29 (the ratchet).

Remaining #205 items (Jitsi and stickerpicker widget types, account-data widgets): not built, by the owner's choice. I'll note that on #205.

🤖 Generated with Claude Code

https://claude.ai/code/session_017PCb59uY3UKqDqt7B6hzd5

Part of #205; the owner chose popout (#441) + this. **MSC2931 "always on screen":** a widget holding `m.always_on_screen` (already auto-granted) can send `set_always_on_screen`. The host now answers it. Without an answer, `ClientWidgetApi` replied "unknown action". A widget that asked **keeps running in a small floating window** when the Widgets panel closes or you switch rooms: - the window has a title bar with **Back to room** and **close**; - Back to room opens that room with the panel showing the widget, **without reloading it**. **How** - Moving an iframe in the DOM reloads it. So widget frames now live from the start in an app-level fixed layer (`widgetSessions.ts`, outside React). While docked, the frame is positioned over the panel's placeholder every animation frame, so it looks and behaves as before. - `RoomWidgetView` is now that placeholder. The permission prompt and the "blocked (same origin)" message read from the session. - **Layering:** docked frames sit at z-index 600 (just above their side panel, below popovers and dialogs). The floating window is `zIndices.floatingWidget` 9980 (below the incoming-call banner and modals). - A widget that **didn't** ask is torn down when the panel closes, exactly as before. - **One floating widget at a time.** It also goes when the widget withdraws (`value: false`), is removed from the room, or on logout (`FloatingWidget` unmount). - A permission request that arrives while floating grants nothing extra, since there's nobody to ask. - The viewed widget is now an atom per room (`widgetViewingAtom`), so Back to room can reopen it. **Verified** (Playwright, a cross-origin https test widget speaking the widget API, counting seconds since its page loaded): | step | result | |---|---| | open in panel | widget's `set_always_on_screen` answered `{}` | | close the panel | floating, **same frame** (counter 4 → 6 s) | | switch rooms | still floating (9 s) | | Back to room | in the room, docked, panel on it, **12 s: no reload** | | widget withdraws, then close the panel | gone (0 frames) | | ✕ on the floating bar | gone | | an ordinary widget asking to send messages | permission prompt shows as before; docked and visible; panel close → torn down, nothing floating | Unit tests (widgets 15/15) pass, tsc is clean, and eslint is at 29 (the ratchet). **Remaining #205 items** (Jitsi and stickerpicker widget types, account-data widgets): not built, by the owner's choice. I'll note that on #205. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_017PCb59uY3UKqDqt7B6hzd5
jared added 1 commit 2026-10-20 21:07:21 -04:00
feat(widgets): keep a widget on screen while you navigate away (#205)
CI / Build & Quality Checks (pull_request) Successful in 3m36s
CI / Trigger Desktop Build (pull_request) Skipped
CI / Docker image build & smoke test (pull_request) Skipped
CI / Secret scan (gitleaks) (pull_request) Successful in 18s
CI / Playwright smoke (e2e) (pull_request) Successful in 16m40s
0746e80941
MSC2931: a widget with m.always_on_screen (auto-granted) can send
set_always_on_screen; the host now answers it, and a widget that asked
keeps running in a small floating window (title, Back to room, close)
when the Widgets panel closes or you switch rooms. Back to room returns
to it with the panel open on that widget, without reloading it.

Moving an iframe reloads it, so widget frames now live in an app-level
layer from the start (widgetSessions.ts) and are positioned over the
panel's placeholder while docked. A widget that did not ask is torn
down as before. One floating widget at a time; it also goes when the
widget withdraws, is removed from the room, or on logout. A permission
request while floating grants nothing extra.

Refs #205

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017PCb59uY3UKqDqt7B6hzd5
jared merged commit 878a34712a into lotus 2026-10-20 22:21:48 -04:00
Sign in to join this conversation.
No Reviewers
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: LotusGuild/cinny#443