Skip to content

feat(panels): drag UX improvements and collapsible live panels (#2217)#2500

Closed
rayanhabib07 wants to merge 4 commits intokoala73:mainfrom
rayanhabib07:feat/drag-system-2217
Closed

feat(panels): drag UX improvements and collapsible live panels (#2217)#2500
rayanhabib07 wants to merge 4 commits intokoala73:mainfrom
rayanhabib07:feat/drag-system-2217

Conversation

@rayanhabib07
Copy link
Copy Markdown
Contributor

Summary

  • Improve drag UX for panels (ghost preview, smoother interactions)
  • Add close buttons to LiveNewsPanel and LiveWebcamsPanel with Ctrl/Cmd+Z undo to restore
  • Add collapsible arrow button to live panels — shrinks panel to header bar only, pauses stream on collapse and resumes on expand
  • Fix aria-label not updating on collapse toggle (screen reader bug)
  • Fix isContentEditable missing optional chaining in undo keyboard handler

Notes

Test plan

  • Drag panels — ghost follows cursor, snaps on drop
  • Close LiveNewsPanel / LiveWebcamsPanel — Ctrl+Z restores it
  • Click collapse arrow — panel shrinks to header bar, stream pauses
  • Click again — panel expands, stream resumes
  • Collapse button announces correct label to screen readers on each toggle

rayanhabib07 and others added 4 commits March 29, 2026 13:34
- Add collapsible option to Panel base class with collapse/expand button
- Enable collapsible on LiveNewsPanel and LiveWebcamsPanel
- Add CSS to shrink panel to header height when collapsed
- Clean up debug console.logs from undo handler

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Sync aria-label on collapse button toggle (was only updating btn.title)
- Use optional chaining on isContentEditable to match null-safety of tagName guard
- Add onCollapse hook to Panel base; LiveNewsPanel pauses/resumes player on collapse; LiveWebcamsPanel nulls/restores iframe srcs to stop stream bandwidth

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@vercel
Copy link
Copy Markdown

vercel bot commented Mar 29, 2026

@rayanhabib07 is attempting to deploy a commit to the Elie Team on Vercel.

A member of the Team first needs to authorize it.

@github-actions github-actions bot added the trust:caution Brin: contributor trust score caution label Mar 29, 2026
@greptile-apps
Copy link
Copy Markdown

greptile-apps bot commented Mar 29, 2026

Greptile Summary

This PR adds a collapsible header button to LiveNewsPanel and LiveWebcamsPanel that pauses streams on collapse and resumes them on expand, using a clean onCollapse template-method hook in the base Panel class. It also adds close buttons with Ctrl/Cmd+Z undo support and fixes an aria-label screen-reader regression.

The approach is architecturally sound but LiveWebcamsPanel.onCollapse has two P1 defects that need fixing before merge:

  • iframe.src = '' navigates iframes to the current page — every other cleanup site in the same file uses 'about:blank'; this should too.
  • Restored-collapsed state doesn't suppress streams — the base-class constructor calls onCollapse(true) before LiveWebcamsPanel.render() runs, so this.iframes is empty at that moment and all iframes are subsequently created with live-stream srcs while hidden.

LiveNewsPanel.onCollapse is correct because the player is lazily initialised. The isContentEditable coercion in event-handlers.ts is a safe, type-only improvement.

Confidence Score: 4/5

Two P1 defects in LiveWebcamsPanel.onCollapse should be resolved before merging: incorrect iframe clearing and the restore-collapsed race condition.

The template-method design and LiveNewsPanel implementation are solid. However, the two confirmed bugs in LiveWebcamsPanel — wrong iframe.src = '' (loads app in iframes) and the init-order issue that leaves streams running when restoring a collapsed panel — are real runtime defects on the changed path, warranting a 4/5.

src/components/LiveWebcamsPanel.ts requires attention for both the iframe.src = '' bug and the collapsed-restore stream-leak.

Important Files Changed

Filename Overview
src/components/LiveWebcamsPanel.ts Adds onCollapse to pause/resume iframe streams — but uses iframe.src = '' (navigates to app page) instead of 'about:blank', and the collapsed-state restore path fires before iframes are created, leaving streams running in the background.
src/components/LiveNewsPanel.ts Adds onCollapse to pause/resume YouTube player and native video element via optional chaining — logic is correct and safe because the player is lazily initialized, so null-state calls are benign.
src/components/Panel.ts Wires onCollapse hook into _applyCollapsed and adds a no-op base implementation — clean template method pattern; aria-label fix for screen reader bug is correct.
src/app/event-handlers.ts Adds !! to coerce isContentEditable from `boolean

Sequence Diagram

sequenceDiagram
    participant User
    participant CollapseBtn
    participant Panel
    participant LiveWebcamsPanel
    participant LiveNewsPanel

    User->>CollapseBtn: click
    CollapseBtn->>Panel: _applyCollapsed(btn, !collapsed)
    Panel->>Panel: content.style.display = 'none'
    Panel->>Panel: update aria-label / aria-expanded
    Panel->>LiveWebcamsPanel: onCollapse(true)
    LiveWebcamsPanel->>LiveWebcamsPanel: save iframe.src → dataset.src
    LiveWebcamsPanel->>LiveWebcamsPanel: iframe.src = '' ⚠️ (should be 'about:blank')
    Panel->>LiveNewsPanel: onCollapse(true)
    LiveNewsPanel->>LiveNewsPanel: player?.pauseVideo()
    LiveNewsPanel->>LiveNewsPanel: nativeVideoElement?.pause()
    Panel->>Panel: savePanelCollapsed(panelId, true)

    User->>CollapseBtn: click again
    CollapseBtn->>Panel: _applyCollapsed(btn, false)
    Panel->>Panel: content.style.display = ''
    Panel->>LiveWebcamsPanel: onCollapse(false)
    LiveWebcamsPanel->>LiveWebcamsPanel: iframe.src = dataset.src (restore)
    Panel->>LiveNewsPanel: onCollapse(false)
    LiveNewsPanel->>LiveNewsPanel: player?.playVideo()
    LiveNewsPanel->>LiveNewsPanel: nativeVideoElement?.play()
Loading

Reviews (1): Last reviewed commit: "fix: apply greptile feedback on collapse..." | Re-trigger Greptile

for (const iframe of this.iframes) {
if (collapsed) {
iframe.dataset.src = iframe.src;
iframe.src = '';
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 iframe.src = '' navigates to the current page, not a blank frame

Setting iframe.src = '' resolves the empty string relative to the document's base URL, which makes the browser navigate the iframe to the current application page — not clear it. This means each webcam iframe will begin loading the WorldMonitor SPA itself when collapsed, wasting bandwidth and CPU.

Every other cleanup path in this file explicitly uses 'about:blank' (see retryIframe at line 383 and destroyIframes at lines 595 and 601). This should follow the same pattern:

Suggested change
iframe.src = '';
iframe.src = 'about:blank';

Comment on lines +204 to +213
protected override onCollapse(collapsed: boolean): void {
for (const iframe of this.iframes) {
if (collapsed) {
iframe.dataset.src = iframe.src;
iframe.src = '';
} else if (iframe.dataset.src) {
iframe.src = iframe.dataset.src;
}
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Streams not paused when panel is restored as collapsed from storage

Panel._applyCollapsed(btn, true) is called in the Panel base-class constructor (line 320–321 of Panel.ts) to restore the previously-saved collapsed state. At that point this.iframes is still the freshly-initialised empty array [], so the for loop in onCollapse is a no-op.

LiveWebcamsPanel.render() is only called after super() returns (constructor line 146), so all iframes are created — with live stream src values — after onCollapse(true) already fired. The panel is visually hidden but every YouTube embed continues loading in the background, consuming bandwidth and connections.

A straightforward fix is to check this._collapsed when creating each iframe and skip setting src if the panel is currently collapsed:

// in createIframe (or where the iframe is pushed to this.iframes)
const src = this._collapsed ? 'about:blank' : this.buildEmbedUrl(feed.fallbackVideoId);
iframe.src = src;
if (this._collapsed) iframe.dataset.src = this.buildEmbedUrl(feed.fallbackVideoId);

Alternatively, expose get collapsed(): boolean from Panel and read it here.

Comment on lines +209 to +211
} else if (iframe.dataset.src) {
iframe.src = iframe.dataset.src;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 dataset.src is never cleared after restoring the iframe

After expanding, iframe.src is correctly restored from iframe.dataset.src, but dataset.src is never deleted. The stale attribute persists on the element for the rest of its lifetime. While not a runtime bug, it leaves confusing hidden state on the DOM node and could mislead future code that inspects dataset.src to determine whether the iframe is "paused".

Suggested change
} else if (iframe.dataset.src) {
iframe.src = iframe.dataset.src;
}
iframe.src = iframe.dataset.src;
delete iframe.dataset.src;

@koala73
Copy link
Copy Markdown
Owner

koala73 commented Mar 29, 2026

Same, you sent this yesterday #2444

You should check Github to see your PRs

@koala73 koala73 closed this Mar 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

trust:caution Brin: contributor trust score caution

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants