Skip to content

Render research CTA via @lobbyside/react when widget is online#884

Merged
Dhravya merged 4 commits intosupermemoryai:mainfrom
sarupbanskota:lobbyside-headless-integration
Apr 24, 2026
Merged

Render research CTA via @lobbyside/react when widget is online#884
Dhravya merged 4 commits intosupermemoryai:mainfrom
sarupbanskota:lobbyside-headless-integration

Conversation

@sarupbanskota
Copy link
Copy Markdown
Contributor

@sarupbanskota sarupbanskota commented Apr 24, 2026

Summary

Adds the headless @lobbyside/react hook so the research CTA can be rendered by our own component against live widget state, while keeping the existing book-a-call card as the fallback.

Behavior

  • Online and queue open — host avatar, ctaText, name + title, buttonText; the entire card is a click target that calls joinCall({ visitor }) with the logged-in user's name / email / company (org) / github when available, and opens the returned entryUrl in a new tab.
  • Offline / error / queue full — continues to render the existing "Be part of the next supermemory app" card with the Book a call link to cal.com/supermemory/growth.
  • Loading — renders nothing, so the fallback card doesn't flash before the widget resolves.
  • Dismiss X preserved (localStorage sm_next_app_research_cta_dismissed_v1).

Analytics

  • Adds next_app_research_cta_lobbyside_call_clicked.
  • Existing next_app_research_cta_book_call_clicked is retained for the book-a-call path.

How it looks

When online:

image

(You control the name, avatar, text, etc. from within the Lobbyside dashboard).

When offline:

image

(We'll soon add support for fallback data. Once that's live, you can also manage the offline strings on Lobbyside directly - more convenient to edit!).

Pre-filled visitor info

When a guest opens the join call link, their email, name etc is pre-filled from the auth context. I wasn't able to test this locally since I didn't have the proper local setup!

🤖 Generated with Claude Code

Adds the headless @lobbyside/react hook so the research CTA can be
rendered by our own component against live widget state, while keeping
the existing book-a-call card as the fallback.

Behavior:
- Online and queue open: host avatar, ctaText, name + title, buttonText;
  the entire card is a click target that calls joinCall({ visitor })
  with the logged-in user's name / email / company (org) / github when
  available, and opens the returned entryUrl in a new tab.
- Offline / error / queue full: continues to render the existing
  "Be part of the next supermemory app" card with the Book a call link
  to cal.com/supermemory/growth.
- Loading: renders nothing, so we don't flash the fallback card before
  the widget resolves.
- Dismiss X preserved (localStorage sm_next_app_research_cta_dismissed_v1).

Analytics: adds next_app_research_cta_lobbyside_call_clicked; the
existing next_app_research_cta_book_call_clicked is retained for the
book-a-call path.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@vorflux vorflux Bot left a comment

Choose a reason for hiding this comment

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

Reviewed — found 2 issues.


Review with Vorflux

const joinArgs =
Object.keys(visitor).length > 0 ? { visitor } : undefined
const { entryUrl } = await widget.joinCall(joinArgs)
window.open(entryUrl, "_blank", "noopener,noreferrer")
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Popup blocker will silently drop the new tab on Safari/iOS and stricter browsers.

window.open(entryUrl, ...) is called after await widget.joinCall(), which means the user-activation gesture has already been consumed by the time the window opens. Browsers that enforce user-activation for window.open (Safari, Firefox with strict settings) will block the popup — but the queue entry has already been created, so the host sees a queued caller who never arrives.

Fix: open a blank window synchronously on click, then redirect it after joinCall() resolves:

const win = window.open('', '_blank', 'noopener,noreferrer')
const { entryUrl } = await widget.joinCall(joinArgs)
if (win) win.location.href = entryUrl

Or navigate the current tab (window.location.href = entryUrl) if opening a new tab is not strictly required.

const { entryUrl } = await widget.joinCall(joinArgs)
window.open(entryUrl, "_blank", "noopener,noreferrer")
} catch (err) {
console.error("[Lobbyside] joinCall failed", err)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

joinCall() failures are silently swallowed — the user gets no feedback and the card stays in its "online" state.

The Lobbyside SDK explicitly throws QUEUE_FULL and INACTIVE errors, both of which can happen as a race after the card rendered as clickable (e.g. the queue fills or the host goes offline between render and click). Right now those cases log to the console and do nothing visible.

Consider branching on err.code and falling back to the book-a-call card (or showing an inline error) so the user isn't left with a broken click:

} catch (err: unknown) {
  console.error('[Lobbyside] joinCall failed', err)
  const code = (err as { code?: string })?.code
  if (code === 'QUEUE_FULL' || code === 'INACTIVE') {
    // force fallback to the book-a-call card
    setWidgetError(true)
  }
}

- Size the host avatar at 36x36 so the face reads from the corner of
  the screen.
- Add an 8px green online dot in the bottom-right corner of the avatar,
  ringed in the card background color.
- Wrap Phone and Users icons in matching 36x36 flex containers so the
  center "×" glyph stays on the decorative cross lines in both the
  online (avatar) and offline (Users) layouts.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@socket-security
Copy link
Copy Markdown

socket-security Bot commented Apr 24, 2026

No dependency changes detected. Learn more about Socket for GitHub.

👍 No dependency changes detected in pull request

sarupbanskota and others added 2 commits April 24, 2026 01:30
Two issues flagged on the CTA's click handler:

- window.open(entryUrl) was called after await joinCall(), so
  Safari/iOS and other browsers that enforce user-activation for popups
  silently blocked the new tab while the queue entry had already been
  created — the host saw a caller who never arrived. Open a blank tab
  synchronously on click and redirect it once joinCall resolves.

- joinCall() errors (QUEUE_FULL and INACTIVE can race after the card
  rendered as clickable, NETWORK is possible anytime) were only logged,
  leaving the user with a dead click. Redirect the pending tab to the
  cal.com book-a-call URL in the catch so the CTA always lands
  somewhere useful, matching the behavior of the offline / queue-full
  fallback card.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Pinned to 0.2.0. The only behavior change we rely on is still covered
by the existing status/isQueueFull/joinCall surface; no consumer-side
code change is needed.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@Dhravya Dhravya merged commit 210fceb into supermemoryai:main Apr 24, 2026
2 checks passed
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.

2 participants