Skip to content

Add native Workflow export to daydream.live#636

Merged
thomshutt merged 2 commits intomainfrom
daydream-export-2
Mar 10, 2026
Merged

Add native Workflow export to daydream.live#636
thomshutt merged 2 commits intomainfrom
daydream-export-2

Conversation

@thomshutt
Copy link
Copy Markdown
Contributor

@thomshutt thomshutt commented Mar 9, 2026

Summary by CodeRabbit

  • New Features
    • Added Daydream export option in the export dialog for uploading workflows directly to daydream.live
    • Integrated authentication flow requiring user login for Daydream exports
    • Displays loading state during the export process to indicate progress
    • Successfully exported workflows automatically open in a Daydream import session for further customization and refinement

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 9, 2026

📝 Walkthrough

Walkthrough

The changes introduce Daydream export functionality across the frontend by adding authentication and export state props to multiple components, creating a new Daydream API utility module, and implementing the core export logic with state management in StreamPage.

Changes

Cohort / File(s) Summary
Dialog & Component Props
frontend/src/components/ExportDialog.tsx, frontend/src/components/PromptInputWithTimeline.tsx, frontend/src/components/PromptTimeline.tsx
Added three new optional props (onExportToDaydream, isAuthenticated, isExportingToDaydream) and propagated them through the component hierarchy. ExportDialog also adds a new "Export to daydream.live" button with conditional icon and text based on authentication state.
Daydream API Utility
frontend/src/lib/daydreamExport.ts
New module providing createDaydreamImportSession() function to handle Daydream API communication, building workflow data and handling bearer token authentication and error responses.
Stream Page Implementation
frontend/src/pages/StreamPage.tsx
Implements core Daydream export logic with authentication state management, event listeners for auth changes, and handleExportToDaydream() callback that constructs workflow data, creates import sessions, and manages loading/error states.

Sequence Diagram

sequenceDiagram
    participant User as User
    participant StreamPage
    participant AuthService as Auth Service
    participant DaydreamAPI as Daydream API
    participant ExternalBrowser as External Browser

    User->>StreamPage: Click "Export to daydream.live"
    
    alt User Not Authenticated
        StreamPage->>AuthService: Check authentication
        AuthService-->>StreamPage: Not authenticated
        StreamPage->>AuthService: Redirect to sign-in
        AuthService->>ExternalBrowser: Open sign-in page
    else User Authenticated
        StreamPage->>AuthService: Check authentication
        AuthService-->>StreamPage: Authenticated
        StreamPage->>StreamPage: Build ScopeWorkflow with settings<br/>prompts, plugins, LoRAs
        StreamPage->>StreamPage: Retrieve Daydream API key
        StreamPage->>DaydreamAPI: createDaydreamImportSession(apiKey, workflowData)
        DaydreamAPI->>DaydreamAPI: POST /v1/workflows/import-sessions
        DaydreamAPI-->>StreamPage: ImportSessionResponse {token, createUrl}
        StreamPage->>ExternalBrowser: Open createUrl
        StreamPage->>User: Show success toast
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 A daydream export, oh what delight!
Props dance through components, prop-forwarding right,
Auth checks and tokens make workflows take flight,
To daydream.live we send your creative might! ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title directly and clearly describes the main change: adding native Workflow export functionality to daydream.live. The title accurately summarizes the primary purpose of the changeset across all modified components.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch daydream-export-2

Comment @coderabbitai help to get the list of available commands and usage tips.

@thomshutt thomshutt force-pushed the daydream-export-2 branch from a2cfa7d to b071f41 Compare March 9, 2026 17:26
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 9, 2026

🚀 fal.ai Preview Deployment

App ID daydream/scope-pr-636--preview
WebSocket wss://fal.run/daydream/scope-pr-636--preview/ws
Commit daf938c

Testing

Connect to this preview deployment by running this on your branch:

uv run build && SCOPE_CLOUD_APP_ID="daydream/scope-pr-636--preview/ws" uv run daydream-scope

🧪 E2E tests will run automatically against this deployment.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 9, 2026

✅ E2E Tests passed

Status passed
fal App daydream/scope-pr-636--preview
Run View logs

Test Artifacts

Check the workflow run for screenshots.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
frontend/src/components/PromptTimeline.tsx (1)

155-157: Don’t mask a missing Daydream export binding with a no-op.

These props are optional upstream, but this wrapper still gives ExportDialog a callable handler. A caller can omit the feature wiring and still render a Daydream CTA that silently does nothing. Make the export props required end-to-end or omit the CTA when no real handler is present.

Also applies to: 615-617

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/components/PromptTimeline.tsx` around lines 155 - 157, The
optional Daydream export props (onExportToDaydream, isAuthenticated,
isExportingToDaydream) on the PromptTimeline wrapper are masking a missing
export handler and can render an ExportDialog CTA that silently no-ops; update
PromptTimeline (and the other instance at the referenced lines) to either make
these props required end-to-end (remove the ? from
onExportToDaydream/isAuthenticated/isExportingToDaydream) or, preferably, gate
rendering of the ExportDialog/CTA behind a truthy onExportToDaydream check so
the CTA is omitted when no real handler is supplied; adjust PropTypes/TS types
and any parent callers to pass the handler or rely on the conditional render to
avoid a silent no-op.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@frontend/src/pages/StreamPage.tsx`:
- Around line 302-340: The browser popup is blocked because
openExternalUrl(result.createUrl) runs after an await; to fix, open a blank
window synchronously before the async call and then navigate it when the URL is
available: call window.open("", "_blank") (store the returned Window reference)
immediately before awaiting createDaydreamImportSession in the export flow in
StreamPage (where createDaydreamImportSession and openExternalUrl are used),
then after the await set the window.location.href (or call openExternalUrl with
the stored ref) to result.createUrl; keep existing toasts and
setIsExportingToDaydream logic intact.

---

Nitpick comments:
In `@frontend/src/components/PromptTimeline.tsx`:
- Around line 155-157: The optional Daydream export props (onExportToDaydream,
isAuthenticated, isExportingToDaydream) on the PromptTimeline wrapper are
masking a missing export handler and can render an ExportDialog CTA that
silently no-ops; update PromptTimeline (and the other instance at the referenced
lines) to either make these props required end-to-end (remove the ? from
onExportToDaydream/isAuthenticated/isExportingToDaydream) or, preferably, gate
rendering of the ExportDialog/CTA behind a truthy onExportToDaydream check so
the CTA is omitted when no real handler is supplied; adjust PropTypes/TS types
and any parent callers to pass the handler or rely on the conditional render to
avoid a silent no-op.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 399168f1-b9f7-42ea-9e6b-96d201ad2b14

📥 Commits

Reviewing files that changed from the base of the PR and between 7f6a8df and b071f41.

📒 Files selected for processing (5)
  • frontend/src/components/ExportDialog.tsx
  • frontend/src/components/PromptInputWithTimeline.tsx
  • frontend/src/components/PromptTimeline.tsx
  • frontend/src/lib/daydreamExport.ts
  • frontend/src/pages/StreamPage.tsx

Comment on lines +302 to +340
const apiKey = getDaydreamAPIKey();
if (!apiKey) {
toast.error("Not authenticated with Daydream");
return;
}

setIsExportingToDaydream(true);
try {
const pluginInfoMap = new Map<string, PluginInfo>(
plugins.map(p => [p.name, p])
);

const workflow = buildScopeWorkflow({
name: "Untitled Workflow",
settings,
timelinePrompts,
promptState: {
promptItems,
interpolationMethod,
transitionSteps,
temporalInterpolationMethod,
},
pipelineInfoMap: pipelines ?? {},
loraFiles,
pluginInfoMap,
scopeVersion: scopeVersion ?? "unknown",
});

const result = await createDaydreamImportSession(
apiKey,
workflow,
workflow.metadata.name
);

openExternalUrl(result.createUrl);
toast.success("Opening daydream.live...", {
description:
"Your workflow has been sent to daydream.live for publishing.",
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

Do modern browsers typically block window.open() when it is invoked after an awaited fetch/promise inside a click handler, rather than synchronously in the original user gesture?

💡 Result:

Yes—in most modern browsers, window.open() is likely to be blocked if it runs after an await/promise boundary, even if the await occurs inside a click handler, because popup opening is generally gated on transient user activation and browsers often stop treating the call as “directly user-initiated” once execution is deferred. Popup blockers commonly return null in this case. [1]

Browsers expose this as the User Activation model: transient activation is short-lived and can be lost/consumed, and APIs such as window.open() require it. [2] WebKit also documents cases where user activation gets consumed such that a later window.open() fails, illustrating why “do something async, then open a popup” is fragile. [3]

Typical workaround: open a blank window synchronously during the click (while activation is definitely present), then navigate it after the async work completes. [1]

Sources:
[1] MDN — Window.open() popup/user-activation requirements and blocked return behavior (developer.mozilla.org)
[2] MDN — User activation (transient activation can expire/be consumed) (developer.mozilla.org)
[3] WebKit blog — User Activation API and activation being consumed affecting window.open() (webkit.org)

Citations:


🏁 Script executed:

# Locate and examine the file mentioned in the review
fd "StreamPage.tsx" -type f

Repository: daydreamlive/scope

Length of output: 232


🏁 Script executed:

# Look for the openExternal implementation
fd "openExternal.ts" -type f

Repository: daydreamlive/scope

Length of output: 232


🏁 Script executed:

fd "StreamPage.tsx"

Repository: daydreamlive/scope

Length of output: 95


🏁 Script executed:

fd "openExternal.ts"

Repository: daydreamlive/scope

Length of output: 94


🏁 Script executed:

# Check the length of StreamPage.tsx to see if it's safe to read
wc -l frontend/src/pages/StreamPage.tsx

Repository: daydreamlive/scope

Length of output: 100


🏁 Script executed:

# Read lines around the specified range (302-340)
sed -n '295,350p' frontend/src/pages/StreamPage.tsx

Repository: daydreamlive/scope

Length of output: 1532


🏁 Script executed:

# Read the openExternal.ts file to understand the implementation
cat frontend/src/lib/openExternal.ts

Repository: daydreamlive/scope

Length of output: 639


🏁 Script executed:

# Search for other uses of openExternalUrl to see if this is a widespread pattern
rg "openExternalUrl" frontend/src/

Repository: daydreamlive/scope

Length of output: 519


🏁 Script executed:

# Search for other uses of window.open in the codebase
rg "window\.open" frontend/src/

Repository: daydreamlive/scope

Length of output: 559


Preserve the user gesture before the async export call.

openExternalUrl() is reached only after awaiting createDaydreamImportSession(). In browser builds, window.open(url, "_blank") called after an await loses transient user activation and is blocked by popup blockers. The import session still gets created, but nothing opens and the success toast becomes a false positive.

💡 Minimal fix sketch
   const apiKey = getDaydreamAPIKey();
   if (!apiKey) {
     toast.error("Not authenticated with Daydream");
     return;
   }

+  const pendingWindow =
+    window.scope?.openExternal == null
+      ? window.open("about:blank", "_blank")
+      : null;
+  if (pendingWindow) {
+    pendingWindow.opener = null;
+  }
+
   setIsExportingToDaydream(true);
-      openExternalUrl(result.createUrl);
+      if (pendingWindow && !pendingWindow.closed) {
+        pendingWindow.location.replace(result.createUrl);
+      } else {
+        openExternalUrl(result.createUrl);
+      }
     } catch (err) {
+      pendingWindow?.close();
       console.error("Export to daydream.live failed:", err);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const apiKey = getDaydreamAPIKey();
if (!apiKey) {
toast.error("Not authenticated with Daydream");
return;
}
setIsExportingToDaydream(true);
try {
const pluginInfoMap = new Map<string, PluginInfo>(
plugins.map(p => [p.name, p])
);
const workflow = buildScopeWorkflow({
name: "Untitled Workflow",
settings,
timelinePrompts,
promptState: {
promptItems,
interpolationMethod,
transitionSteps,
temporalInterpolationMethod,
},
pipelineInfoMap: pipelines ?? {},
loraFiles,
pluginInfoMap,
scopeVersion: scopeVersion ?? "unknown",
});
const result = await createDaydreamImportSession(
apiKey,
workflow,
workflow.metadata.name
);
openExternalUrl(result.createUrl);
toast.success("Opening daydream.live...", {
description:
"Your workflow has been sent to daydream.live for publishing.",
});
const apiKey = getDaydreamAPIKey();
if (!apiKey) {
toast.error("Not authenticated with Daydream");
return;
}
const pendingWindow =
window.scope?.openExternal == null
? window.open("about:blank", "_blank")
: null;
if (pendingWindow) {
pendingWindow.opener = null;
}
setIsExportingToDaydream(true);
try {
const pluginInfoMap = new Map<string, PluginInfo>(
plugins.map(p => [p.name, p])
);
const workflow = buildScopeWorkflow({
name: "Untitled Workflow",
settings,
timelinePrompts,
promptState: {
promptItems,
interpolationMethod,
transitionSteps,
temporalInterpolationMethod,
},
pipelineInfoMap: pipelines ?? {},
loraFiles,
pluginInfoMap,
scopeVersion: scopeVersion ?? "unknown",
});
const result = await createDaydreamImportSession(
apiKey,
workflow,
workflow.metadata.name
);
if (pendingWindow && !pendingWindow.closed) {
pendingWindow.location.replace(result.createUrl);
} else {
openExternalUrl(result.createUrl);
}
toast.success("Opening daydream.live...", {
description:
"Your workflow has been sent to daydream.live for publishing.",
});
} catch (err) {
pendingWindow?.close();
console.error("Export to daydream.live failed:", err);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/pages/StreamPage.tsx` around lines 302 - 340, The browser popup
is blocked because openExternalUrl(result.createUrl) runs after an await; to
fix, open a blank window synchronously before the async call and then navigate
it when the URL is available: call window.open("", "_blank") (store the returned
Window reference) immediately before awaiting createDaydreamImportSession in the
export flow in StreamPage (where createDaydreamImportSession and openExternalUrl
are used), then after the await set the window.location.href (or call
openExternalUrl with the stored ref) to result.createUrl; keep existing toasts
and setIsExportingToDaydream logic intact.

Copy link
Copy Markdown
Collaborator

@leszko leszko left a comment

Choose a reason for hiding this comment

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

LGTM

@thomshutt thomshutt merged commit 1ac85de into main Mar 10, 2026
8 of 9 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