diff --git a/.github/workflows/api-docs.yml b/.github/workflows/api-docs.yml
index dafdae4f..ee705397 100644
--- a/.github/workflows/api-docs.yml
+++ b/.github/workflows/api-docs.yml
@@ -29,7 +29,7 @@ jobs:
- name: Setup Node.js
uses: actions/setup-node@v4
with:
- node-version: '20'
+ node-version: '22'
cache: 'pnpm'
cache-dependency-path: apps/api-docs/pnpm-lock.yaml
diff --git a/.github/workflows/api-lint.yml b/.github/workflows/api-lint.yml
index 20b39c37..9913d425 100644
--- a/.github/workflows/api-lint.yml
+++ b/.github/workflows/api-lint.yml
@@ -35,7 +35,7 @@ jobs:
- name: Setup Node.js
uses: actions/setup-node@v4
with:
- node-version: '20'
+ node-version: '22'
cache: 'pnpm'
cache-dependency-path: apps/api-docs/pnpm-lock.yaml
@@ -72,7 +72,7 @@ jobs:
- name: Install oasdiff
run: |
curl -sSL https://raw.githubusercontent.com/Tufin/oasdiff/main/install.sh | sh
- sudo mv oasdiff /usr/local/bin/
+ oasdiff --version
- name: Check for breaking changes
run: |
diff --git a/.github/workflows/flowdrop-bundle.yml b/.github/workflows/flowdrop-bundle.yml
index 48b7f80a..f8c7b76d 100644
--- a/.github/workflows/flowdrop-bundle.yml
+++ b/.github/workflows/flowdrop-bundle.yml
@@ -39,7 +39,7 @@ jobs:
- name: Setup Node.js
uses: actions/setup-node@v4
with:
- node-version: '20'
+ node-version: '22'
cache: 'pnpm'
cache-dependency-path: libs/flowdrop/pnpm-lock.yaml
diff --git a/.github/workflows/flowdrop-lint.yml b/.github/workflows/flowdrop-lint.yml
index 38fd364c..ad0056aa 100644
--- a/.github/workflows/flowdrop-lint.yml
+++ b/.github/workflows/flowdrop-lint.yml
@@ -35,7 +35,7 @@ jobs:
- name: Setup Node.js
uses: actions/setup-node@v4
with:
- node-version: '20'
+ node-version: '22'
cache: 'pnpm'
cache-dependency-path: libs/flowdrop/pnpm-lock.yaml
diff --git a/.github/workflows/flowdrop-storybook.yml b/.github/workflows/flowdrop-storybook.yml
index 49b777aa..7b5ac2ae 100644
--- a/.github/workflows/flowdrop-storybook.yml
+++ b/.github/workflows/flowdrop-storybook.yml
@@ -21,7 +21,7 @@ on:
jobs:
storybook:
name: Stories pass as tests, Storybook builds
- runs-on: ubuntu-latest
+ runs-on: ubuntu-24.04 # Playwright cannot install Chromium on 26.04
defaults:
run:
working-directory: libs/flowdrop
@@ -37,7 +37,7 @@ jobs:
- name: Setup Node.js
uses: actions/setup-node@v4
with:
- node-version: '20'
+ node-version: '22'
cache: 'pnpm'
cache-dependency-path: libs/flowdrop/pnpm-lock.yaml
diff --git a/libs/flowdrop/CHANGELOG.md b/libs/flowdrop/CHANGELOG.md
index f1f31fe8..eea20482 100644
--- a/libs/flowdrop/CHANGELOG.md
+++ b/libs/flowdrop/CHANGELOG.md
@@ -5,6 +5,15 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+## [Unreleased]
+
+### Added
+
+- **`App` takes a `navbarEnd` snippet.** It renders at the navbar's trailing end, before the settings
+ gear, so a host can put its own controls there, such as a user menu. `Navbar` already had this as
+ its `end` slot; `App` now forwards it. It shows only when the navbar does. `mountFlowDropApp` takes
+ the same `navbarEnd` option.
+
## [2.15.2] - 2026-10-18
### Fixed
diff --git a/libs/flowdrop/api/v1/bundled.yaml b/libs/flowdrop/api/v1/bundled.yaml
index 55ab6ad6..1e22f7a4 100644
--- a/libs/flowdrop/api/v1/bundled.yaml
+++ b/libs/flowdrop/api/v1/bundled.yaml
@@ -6684,6 +6684,7 @@ components:
nullable: true
description: Interface input that receives the person's message.
history:
+ type: object
allOf:
- $ref: '#/components/schemas/PlaygroundChatHistory'
nullable: true
@@ -6716,6 +6717,7 @@ components:
binding.
properties:
chat:
+ type: object
allOf:
- $ref: '#/components/schemas/PlaygroundChatBinding'
nullable: true
diff --git a/libs/flowdrop/api/v1/components/schemas/workflow.yaml b/libs/flowdrop/api/v1/components/schemas/workflow.yaml
index c512993c..f89a3a9a 100644
--- a/libs/flowdrop/api/v1/components/schemas/workflow.yaml
+++ b/libs/flowdrop/api/v1/components/schemas/workflow.yaml
@@ -365,6 +365,7 @@ PlaygroundChatBinding:
nullable: true
description: Interface input that receives the person's message.
history:
+ type: object
allOf:
- $ref: '#/PlaygroundChatHistory'
nullable: true
@@ -398,6 +399,7 @@ WorkflowPlayground:
binding.
properties:
chat:
+ type: object
allOf:
- $ref: '#/PlaygroundChatBinding'
nullable: true
diff --git a/libs/flowdrop/src/lib/components/App.svelte b/libs/flowdrop/src/lib/components/App.svelte
index 23f3d022..6df3c557 100644
--- a/libs/flowdrop/src/lib/components/App.svelte
+++ b/libs/flowdrop/src/lib/components/App.svelte
@@ -195,6 +195,11 @@
navbarActions?: NavbarAction[];
/** White-label logo for the navbar (replaces the FlowDrop wordmark) */
branding?: NavbarBranding;
+ /**
+ * Host content at the navbar's trailing end, before the settings gear (the
+ * navbar's `end` slot), e.g. a user menu. Only shown with the navbar.
+ */
+ navbarEnd?: Snippet;
/** Show settings gear icon in navbar */
showSettings?: boolean;
/** Show the "Connected" status indicator in the navbar (default: true) */
@@ -283,6 +288,7 @@
navbarTitle,
navbarActions = [],
branding,
+ navbarEnd,
showSettings = true,
showStatus = true,
apiBaseUrl,
@@ -2124,6 +2130,7 @@
{showStatus}
{showSettings}
{branding}
+ end={navbarEnd}
saveShortcut
onWorkflowSettings={toggleWorkflowSettings}
status={doctorStatus}
diff --git a/libs/flowdrop/src/lib/svelte-app.ts b/libs/flowdrop/src/lib/svelte-app.ts
index 021feb5b..618664d2 100644
--- a/libs/flowdrop/src/lib/svelte-app.ts
+++ b/libs/flowdrop/src/lib/svelte-app.ts
@@ -8,6 +8,7 @@
*/
import { mount, unmount } from 'svelte';
+import type { Snippet } from 'svelte';
import WorkflowEditor from './components/WorkflowEditor.svelte';
import App from './components/App.svelte';
import type {
@@ -167,6 +168,12 @@ export interface FlowDropMountOptions {
* logo's accessible name, `logoAlt` overrides the logo's accessible name, `href` an optional link around it.
*/
branding?: NavbarBranding;
+ /**
+ * Host content at the navbar's trailing end, before the settings gear, e.g. a
+ * user menu. Only shown with the navbar. A Svelte host passes a snippet from its
+ * template; other hosts can build one with `createRawSnippet`.
+ */
+ navbarEnd?: Snippet;
/** Show settings gear icon in navbar */
showSettings?: boolean;
/** Show the "Connected" status indicator in the navbar (default: true) */
@@ -741,6 +748,7 @@ export async function mountFlowDropApp(
navbarTitle,
navbarActions,
branding,
+ navbarEnd,
showSettings,
showStatus,
authProvider,
@@ -838,6 +846,7 @@ export async function mountFlowDropApp(
navbarTitle,
navbarActions,
branding,
+ navbarEnd,
showSettings,
showStatus,
endpointConfig: config,
diff --git a/libs/flowdrop/src/routes/test/editor/+page.svelte b/libs/flowdrop/src/routes/test/editor/+page.svelte
index 934aefb5..b00588e4 100644
--- a/libs/flowdrop/src/routes/test/editor/+page.svelte
+++ b/libs/flowdrop/src/routes/test/editor/+page.svelte
@@ -246,6 +246,9 @@
: undefined
);
+ // --- Host content at the navbar's end (?navbarEnd=1), e.g. a user menu ---
+ let showNavbarEnd = $derived($page.url.searchParams.get('navbarEnd') === '1');
+
// --- White-label logo (?branding=1 name + image; =alt adds logoAlt; =name name only; =wide a logo far wider than the start column) ---
const brandingSvg = (width: number) =>
`data:image/svg+xml;utf8,${encodeURIComponent(
@@ -1653,8 +1656,12 @@
{contextMenu}
{branding}
{navbarActions}
+ navbarEnd={showNavbarEnd ? navbarEndContent : undefined}
{settingsCategories}
/>
+ {#snippet navbarEndContent()}
+ Ada Lovelace
+ {/snippet}