feat(widgets): open a widget in its own window (#205)
CI / Build & Quality Checks (pull_request) Successful in 3m34s
CI / Trigger Desktop Build (pull_request) Skipped
CI / Docker image build & smoke test (pull_request) Skipped
CI / Secret scan (gitleaks) (pull_request) Successful in 7s
CI / Playwright smoke (e2e) (pull_request) Successful in 17m37s

Each widget in the Widgets panel (and the open widget's header) gets an
"Open in a new window" button, so a pad or board can sit beside the chat.
It opens the same filled-in URL the frame uses (widgetCompleteUrl, now
shared) with noopener,noreferrer, standalone as in Element. Only
cross-origin https URLs get the button: room state can carry anything,
e.g. a script URL from another client.

Refs #205

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017PCb59uY3UKqDqt7B6hzd5
This commit is contained in:
Lotus CI
2026-10-20 20:17:52 -04:00
co-authored by Claude Opus 5.5
parent ccbafdb147
commit c3f897b6d1
4 changed files with 86 additions and 9 deletions
@@ -8,6 +8,7 @@ import {
generateWidgetId,
classifyWidgetCapabilities,
loadWidgetConsent,
widgetPopoutUrl,
} from './widgetUtils';
const APP = 'https://chat.lotusguild.org';
@@ -122,3 +123,17 @@ test('[#266] space structure, call membership and Lotus room policy can never be
assert.equal(auto.size, 0);
assert.deepEqual(ask, []);
});
test('[#205] widgetPopoutUrl: only cross-origin https opens in a new window', () => {
// Built at runtime so the no-script-url lint rule doesn't flag the test data.
const scriptUrl = ['javascript', 'alert(1)'].join(':');
assert.equal(
widgetPopoutUrl('https://pad.example.org/p/room', APP),
'https://pad.example.org/p/room',
);
assert.equal(widgetPopoutUrl('https://chat.lotusguild.org/evil', APP), null, 'same origin');
assert.equal(widgetPopoutUrl('http://pad.example.org/', APP), null, 'not https');
assert.equal(widgetPopoutUrl(scriptUrl, APP), null, 'script URL');
assert.equal(widgetPopoutUrl('data:text/html,<b>x</b>', APP), null, 'data:');
assert.equal(widgetPopoutUrl('not a url', APP), null);
});