Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/api-docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/api-lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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: |
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/flowdrop-bundle.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/flowdrop-lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/flowdrop-storybook.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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

Expand Down
9 changes: 9 additions & 0 deletions libs/flowdrop/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions libs/flowdrop/api/v1/bundled.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -6716,6 +6717,7 @@ components:
binding.
properties:
chat:
type: object
allOf:
- $ref: '#/components/schemas/PlaygroundChatBinding'
nullable: true
Expand Down
2 changes: 2 additions & 0 deletions libs/flowdrop/api/v1/components/schemas/workflow.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -365,6 +365,7 @@ PlaygroundChatBinding:
nullable: true
description: Interface input that receives the person's message.
history:
type: object
allOf:
- $ref: '#/PlaygroundChatHistory'
nullable: true
Expand Down Expand Up @@ -398,6 +399,7 @@ WorkflowPlayground:
binding.
properties:
chat:
type: object
allOf:
- $ref: '#/PlaygroundChatBinding'
nullable: true
Expand Down
7 changes: 7 additions & 0 deletions libs/flowdrop/src/lib/components/App.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -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) */
Expand Down Expand Up @@ -283,6 +288,7 @@
navbarTitle,
navbarActions = [],
branding,
navbarEnd,
showSettings = true,
showStatus = true,
apiBaseUrl,
Expand Down Expand Up @@ -2124,6 +2130,7 @@
{showStatus}
{showSettings}
{branding}
end={navbarEnd}
saveShortcut
onWorkflowSettings={toggleWorkflowSettings}
status={doctorStatus}
Expand Down
9 changes: 9 additions & 0 deletions libs/flowdrop/src/lib/svelte-app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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) */
Expand Down Expand Up @@ -741,6 +748,7 @@ export async function mountFlowDropApp(
navbarTitle,
navbarActions,
branding,
navbarEnd,
showSettings,
showStatus,
authProvider,
Expand Down Expand Up @@ -838,6 +846,7 @@ export async function mountFlowDropApp(
navbarTitle,
navbarActions,
branding,
navbarEnd,
showSettings,
showStatus,
endpointConfig: config,
Expand Down
7 changes: 7 additions & 0 deletions libs/flowdrop/src/routes/test/editor/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -1653,8 +1656,12 @@
{contextMenu}
{branding}
{navbarActions}
navbarEnd={showNavbarEnd ? navbarEndContent : undefined}
{settingsCategories}
/>
{#snippet navbarEndContent()}
<span data-testid="navbar-end-test">Ada Lovelace</span>
{/snippet}
</div>

<style>
Expand Down
13 changes: 13 additions & 0 deletions libs/flowdrop/tests/e2e/editor-branding.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,3 +63,16 @@ test.describe('Navbar branding', () => {
expect(navBox!.height).toBeLessThanOrEqual(48);
});
});

test.describe('Navbar end content', () => {
test('renders the host snippet in the navbar end, before the settings gear', async ({ page }) => {
await gotoEditor(page, 'workflow=simple&navbarEnd=1');
const end = navbar(page).locator('.flowdrop-navbar__end');
await expect(end.getByTestId('navbar-end-test')).toHaveText('Ada Lovelace');
});

test('renders nothing without the snippet', async ({ page }) => {
await gotoEditor(page, 'workflow=simple');
await expect(page.getByTestId('navbar-end-test')).toHaveCount(0);
});
});
60 changes: 60 additions & 0 deletions libs/flowdrop/tests/unit/mount/navbarEnd.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/**
* `mountFlowDropApp` passes `navbarEnd` on to the editor. App itself is replaced
* by a stub that records its props: what is under test is the mount function's
* forwarding, not the navbar (the e2e test covers that).
*/

import { describe, it, expect, afterEach, vi } from 'vitest';
import { createRawSnippet } from 'svelte';
import type { Workflow } from '../../../src/lib/types/index.js';
import { DEFAULT_PORT_CONFIG } from '../../../src/lib/config/defaultPortConfig.js';

const received: Record<string, unknown>[] = [];

vi.mock('../../../src/lib/components/App.svelte', () => ({
default: (_anchor: unknown, props: Record<string, unknown>) => {
received.push(props);
}
}));

const { mountFlowDropApp } = await import('../../../src/lib/svelte-app.js');

const workflow: Workflow = {
id: 'wf-navbar-end',
name: 'Navbar end',
nodes: [],
edges: [],
metadata: { schemaVersion: '1.0.0', createdAt: '', updatedAt: '' }
};

window.matchMedia = ((query: string) =>
({
matches: false,
media: query,
addEventListener() {},
removeEventListener() {}
}) as unknown as MediaQueryList) as typeof window.matchMedia;

afterEach(() => {
received.length = 0;
document.body.innerHTML = '';
});

describe('mountFlowDropApp({ navbarEnd })', () => {
it('passes the snippet to the editor', async () => {
const navbarEnd = createRawSnippet(() => ({ render: () => '<span>Ada</span>' }));
const el = document.createElement('div');
document.body.appendChild(el);
const app = await mountFlowDropApp(el, {
workflow,
portConfig: DEFAULT_PORT_CONFIG,
categories: [],
features: { showToasts: false, autoSaveDraft: false },
showNavbar: true,
navbarEnd
});

expect(received[0].navbarEnd).toBe(navbarEnd);
app.destroy();
});
});
Loading