await handleSaveFlow(detail)}
{loading}
@@ -1875,9 +1882,7 @@
{forceTestTab}
{highlightArg}
aiChatOpen={aiChatManager.open}
- showFlowAiButton={!disableAi &&
- customUi?.topBar?.aiBuilder != false &&
- $roleCaps.flows !== 'compose'}
+ {showFlowAiButton}
toggleAiChat={() => aiChatManager.toggleOpen()}
{sessionOpen}
onOpenPreview={flowPreviewButtons?.openPreview}
diff --git a/frontend/src/lib/components/PageHeaderBar.svelte b/frontend/src/lib/components/PageHeaderBar.svelte
index 1ad2602d8e..e7e32df604 100644
--- a/frontend/src/lib/components/PageHeaderBar.svelte
+++ b/frontend/src/lib/components/PageHeaderBar.svelte
@@ -33,6 +33,7 @@ The row's height matches the sidebar's own header row, so the two read as one ba
const content = $derived(pageHeader.content)
const actions = $derived(pageHeader.actions)
+ const hasActions = $derived(actions.length > 0)
const item = $derived(content?.item)
const section = $derived(content?.section)
@@ -82,8 +83,8 @@ The row's height matches the sidebar's own header row, so the two read as one ba
trail: trailMeasures.trail,
summary: BAR_STAGES.map(() => (hasSummary ? GAP + summaryFloorWidth : 0)),
actions:
- actions.length === 0 ? [0] : actionsRigid ? actionWidths : [GAP + FLEXIBLE_ACTIONS_RESERVE],
- folds: actions.length > 0 && actionsRigid ? (content?.actionFolds ?? 0) : 0,
+ !hasActions ? [0] : actionsRigid ? actionWidths : [GAP + FLEXIBLE_ACTIONS_RESERVE],
+ folds: hasActions && actionsRigid ? (content?.actionFolds ?? 0) : 0,
earlyFolds: content?.earlyActionFolds,
summaryNamesItem,
natural: {
@@ -314,7 +315,7 @@ The row's height matches the sidebar's own header row, so the two read as one ba
{/if}
- {#if actions.length > 0}
+ {#if hasActions}
- = 2 || hosted ? 'hidden' : 'contents'}>
- RUNS_PATH + window.location.search,
- workspaceId: $operatingWorkspace ?? undefined
- }}
- btnProps={{ unifiedSize: 'sm' }}
- />
-
+ {#if !hosted}
+
+
+ {/if}
{/snippet}
{/if}
diff --git a/frontend/src/lib/components/ScriptBuilder.svelte b/frontend/src/lib/components/ScriptBuilder.svelte
index 247382d6d4..3ee365be89 100644
--- a/frontend/src/lib/components/ScriptBuilder.svelte
+++ b/frontend/src/lib/components/ScriptBuilder.svelte
@@ -22,6 +22,10 @@
} from '$lib/script_helpers'
import { isWorkflowAsCode } from './graph/wacToFlow'
import AIFormSettings from './copilot/AIFormSettings.svelte'
+ import { SUPPORTED_CHAT_SCRIPT_LANGUAGES } from './copilot/chat/script/core'
+ import OpenInSessionButton, {
+ type OpenInSessionSource
+ } from './sessions/OpenInSessionButton.svelte'
import {
defaultScripts,
enterpriseLicense,
@@ -212,9 +216,13 @@
const compactTopbar = $derived(
ownsPageHeader ? pageHeader.actionFold >= 1 : topbarWidth > 0 && topbarWidth < 900
)
- // The second fold holds the trail and one button. Diff and Settings join the menu that compact
+ // In the page header no fold drops a button's label: what does not fit goes into the menu. The
+ // second fold holds the trail and one button. Diff and Settings join the menu that compact
// already shows, and Deploy stays out: it is what this page is for.
const phoneTopbar = $derived(ownsPageHeader && pageHeader.actionFold >= 2)
+ // The third fold leaves the autosave indicator as its icon, so the AI session button still fits
+ // right before Deploy on a phone.
+ const narrowTopbar = $derived(ownsPageHeader && pageHeader.actionFold >= 3)
// The workspace this editor operates on: deploy, save-draft, trigger loading
// and the AutosaveIndicator all target it. Falls back to the full-page
@@ -915,6 +923,24 @@
}
}
+ const sessionOpen: OpenInSessionSource | undefined = $derived(
+ userDraftPath
+ ? {
+ // URL draft path the editor loads/saves by, not the friendly
+ // `script.path` (a new script's has no row → "not found").
+ target: { kind: 'script', path: userDraftPath },
+ workspaceId: opWorkspace ?? undefined,
+ beforeOpen: persistDraftForSession
+ }
+ : undefined
+ )
+ // The header's button, gated like the code editor's AI entry point it stands in for.
+ function headerSessionOpen(): OpenInSessionSource | undefined {
+ if (disableAi || customUi?.editorBar?.aiGen == false) return undefined
+ if (!SUPPORTED_CHAT_SCRIPT_LANGUAGES.includes(script?.language ?? '')) return undefined
+ return sessionOpen
+ }
+
// Inside an AI session pane (which injects an aiChatManager via context) the
// extra deploy-dropdown options — Deploy & Stay here, Fork, Edit in workspace
// fork, Export — don't make sense: the session always stays put and is already
@@ -2323,7 +2349,8 @@
}}
afterName={scriptMarks}
actions={scriptHeaderActions}
- actionFolds={2}
+ sessionSource={headerSessionOpen()}
+ actionFolds={3}
separator="none"
/>
{:else}
@@ -2481,6 +2508,7 @@
{loadedFromDraft}
{othersDraftsCount}
{onOpenOthersDrafts}
+ compact={narrowTopbar}
/>
{/if}
{/snippet}
@@ -2510,7 +2538,7 @@
unifiedSize={headerBtnSize}
on:click={() => (metadataOpen = true)}
startIcon={{ icon: Settings }}
- iconOnly={compactTopbar}
+ iconOnly={compactTopbar && !ownsPageHeader}
title="Settings"
>
Settings
@@ -2534,7 +2562,7 @@
on:click={() => openDiffDrawer()}
disabled={diffDisabled}
btnClasses={diffDisabled ? 'pointer-events-none' : undefined}
- iconOnly={compactTopbar}
+ iconOnly={compactTopbar && !ownsPageHeader}
title={diffTitle}
startIcon={{ icon: DiffIcon }}
>
@@ -2580,6 +2608,9 @@
{@render settingsButton()}
{/if}
+ {#if ownsPageHeader}
+
+ {/if}
+
{#snippet fallback()}
-
+
{#snippet fallback()}