* feat(editors): responsive top-bars + script test-pane pixel-min + flow graph overlay
Editor top bars now collapse on narrow widths (measured via container
clientWidth, not viewport — they live inside drawers / session panes
where the viewport stays wide):
- FlowBuilder: Diff + Save draft fold into the ellipsis menu when
the top bar narrows below 720px (Save draft keeps its ⌘S / Ctrl+S
shortcut indicator). Test-flow button moves out of the top bar
and into a graph-pane overlay matching the dev page; the overlay
position flips from top-2 right-2 to top-14 left-1/2 when the
graph pane itself is narrower than 800px. FlowEditor exposes a
graphOverlay snippet prop for that.
- ScriptBuilder: Settings + Draft labels collapse to icon-only;
a new DropdownV2 ellipsis surfaces Tag / Settings / Save draft
when even icons don't fit. The ellipsis itself uses variant=subtle.
- AppEditorHeader / RawAppEditorHeader: fullscreen / dark-mode /
breakpoint toggle group + Debug-runs / Jobs buttons hide; Save
draft moves into the Deploy dropdown.
- EditorBar: a "Helpers" DropdownV2 collapse for Context var /
Variable / S3 / Resource / Git repo / Resource type / Database /
Ducklake / Data table / Reset when the bar narrows below 800px
(EDITOR_BAR_HELPERS_COMPACT_THRESHOLD). Above that, the existing
icon-only mode (1420px threshold) still applies.
- ScriptEditor's test pane gets a pixel-based minimum width (400px)
derived from the splitpane's clientWidth. The Pane uses Svelte 5
function-binding so the splitter writes to a raw $state while the
splitpane reads the clamped derived value — no $effect, no
release-time bounce, drag stops at the boundary. Cap raised to
80% so the test pane can take most of the editor on very narrow
layouts while leaving a sliver of code visible.
- VS Code button on ScriptEditor: collapses to icon-only below the
EDITOR_BAR_WIDTH_THRESHOLD (1420px) instead of being hidden
entirely by viewport `lg:` breakpoint; hidden completely when the
editor is rendered inside a session pane.
- AI wand button on ScriptEditor + RawAppEditorHeader: hidden inside
a session pane (detected via `getContext('aiChatManager')`) — the
session owns its own AI chat.
- DeployButton: drops the unused `newFlow` gate (callers updated).
- FlowDiffViewer / FlowGraphDiffViewer: inlineDiff prop forwarding
+ onHeight callback on FlowGraphV2 so diff viewers can equalize
side-by-side graph heights.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* ui: unify Debug / wand / test-toggle button sizes + HideButton defaults
Two small consistency passes on shared button components:
- ScriptEditor's Debug, AI wand and Test-panel-toggle buttons all
use unifiedSize="sm" so they line up in the toolbar; Test toggle
switches from custom marine btnClasses to variant="accent-secondary".
HideButton gains a passthrough unifiedSize prop so the wand and
test toggle can match Debug without overriding btnClasses.
- HideButton's own defaults shift to variant="subtle" + sm
unifiedSize, dropping the legacy color="light" / variant="contained"
+ tailwind-merge background overlay; the selected (hidden) state
is now a tinted wrapper div instead of overriding btnClasses.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* ui(script): editor toolbar polish
Small consistency tweaks on the script editor's top-right overlay:
- Lowercase "test" / "Exit debug" panel labels.
- `bg-surface` on the overlay container so the absolute-positioned
buttons read as a single panel over the graph rather than disjoint
pills.
- Debug button picks up `destructive={debugMode}` so the active
state reads as "you're in debug mode" instead of accent.
- Console and "Delegating to git repo" buttons drop the custom
`btnClasses` border-on-surface treatment and switch from
`size="xs"` to `unifiedSize="sm"` so they match the other buttons
in the cluster.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(raw-app): collapsible file sidebar, default-collapsed in session preview
The raw-app editor's left sidebar (file tree, runnables, history) ate a
lot of horizontal space — fine in the standalone editor, painful in the
session preview pane where the chat is already taking half the screen.
Add a small collapse / expand toggle. Persist the user's preference in
localStorage so it sticks across opens.
Two independent localStorage keys via the new `sidebarStorageKey` prop:
- standalone editor: `raw-app-sidebar-collapsed` (default expanded)
- session preview: `raw-app-sidebar-collapsed-preview` (default collapsed)
Otherwise the two contexts would race for the same key — whichever
opens first would dictate the other's default. Splitting the keys lets
each have its own remembered preference.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fixup(editors): use untyped getContext for AI-chat-manager session detection
The cherry-picks landed `getContext<AIChatManager>('aiChatManager')` to
hide per-editor AI/VSCode buttons when rendered inside a session pane.
The `AIChatManager` class is exported only on the sessions branch (used
for typing session-provided manager overrides). On `main` the manager
file exports only the singleton instance, so importing the class fails
the type-check.
The session-pane detection just needs a truthy/falsy probe — drop the
type parameter and the class import. `inSessionPane` ends up as
`getContext('aiChatManager')` (returns `unknown`, coerced to boolean
via `!!`). Same runtime behaviour, no class-export dependency.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* ui(editors): flow test in top bar; ellipsis folds draft/jobs/tutorials
* refactor(app-editor): drop dead AppEditorTutorial button path
* ui(editors): wire compactHelpers in flow-step + raw-app inline editors
* ui(raw-app): sidebar Cmd/Ctrl+B toggle + uppercase section titles
* ui(editors): keep Diff/Settings inline as icon-only when narrow
* fix(editors): address review nits on test-pane/Helpers/thresholds
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(chat): waiting-for-user indicator and arrow polish
- Show "Waiting for your input" (text-accent + flipping Hourglass) instead
of the typing dots when the latest tool is staged for confirmation
(Run/Cancel) or has an active askUserQuestion. The dots imply the AI
is working, which is misleading when the loop is paused on the user.
- Scroll-to-latest arrow:
- Move up to bottom-12 when the flow Accept/Reject row is visible so
they no longer overlap.
- Wrap in a solid bg-surface + shadow + border badge so the icon
doesn't bleed into messages behind it.
- Bump unifiedSize xs → sm for a slightly larger target.
- Hourglass uses a custom CSS keyframe (:global so the rule reaches the
Lucide SVG root) with 4 s period and cubic-bezier(0.65, 0, 0.35, 1)
easing — feels like flipping the hourglass rather than spinning.
* fix(chat): raise waiting indicator above accept/reject row
* fix(chat): solid background behind reject all button
* feat(chat): @ picker in controls row, badges above input, polish
* [ee] fix(saml): preserve deep links from /a/[...path] across SAML round-trip
Fixes WIN-1962.
PR #9225 only covered users who pass through /user/login on their way to
the IdP — that's where `redirectSaml()` runs and where the deep link gets
stuffed into `RelayState`. The reported flow doesn't go through that
page: it hits `/a/[...path]` (the public-app custom-path route, outside
the `(logged)` layout) where `PublicApp.svelte` renders its own `<Login>`
and was passing `page.url.toString()` as `rd` — the full URL.
Three problems compounded:
1. `redirectSaml()` only set `RelayState` when `rd.startsWith('/')`,
so a full URL silently fell through and the deep link was lost.
The IdP echoed back the SP-library default (BASE_URL), which the
ACS validator correctly rejected as a potential open-redirect.
2. `persistRd()` stored the full URL in `localStorage.rd`. On the
fallback landing at `/user/login`, the post-login redirect saw
an `http://...` value, hit the cross-origin branch, and bounced
to `/` — which from a logged-in but workspace-less state shows
the "Loading user…" modal forever (bug 2).
3. The EE `safe_relay_state_redirect` validator rejected any full
URL, including same-origin ones, so even IdPs that prepend the
origin or that pass a configured absolute deep link via
IdP-initiated SSO got dropped on the floor.
The fix is a single concept applied at every layer: reduce a redirect
target to a safe same-origin relative path, or refuse it.
Frontend:
- `logoutRedirect.ts`: new `toSameOriginRelativePath(rd)` helper that
accepts both `/foo` and `https://current-origin/foo`, with the same
open-redirect guards as the backend (length cap, control chars, no
protocol-relative or back-slash tricks). Returns `null` for
cross-origin or malformed input.
- `PublicApp.svelte`: pass `pathname + search + hash` to `<Login>`
instead of the full URL — this alone fixes the happy path.
- `Login.svelte`: `redirectSaml()`, `persistRd()`, and `redirectUser()`
all route through the helper, so full URLs from `/a/[...path]` are
reduced before being put in `RelayState`/`localStorage`/`goto()`.
- `/user/login/+page.svelte`: the same reduction is applied to the
resolved `rd` so any stale full-URL value in `localStorage.rd` still
navigates to the intended page instead of falling into the
cross-origin branch.
Backend (EE companion: windmill-ee-private#TBD):
- `safe_relay_state_redirect` now reduces a `RelayState` whose origin
matches `BASE_URL` to its path before applying the same-origin path
safety rules. Bare BASE_URL with no path still falls back to
`/user/login` (no useful deep link to honor).
- New `same_origin_relative_path` helper + expanded unit tests.
Test plan:
- [x] Frontend: `vitest run src/lib/logoutRedirect.test.ts` — 9 passed
- [x] Backend: `cargo test -p windmill-api ... saml_ee::tests` — 3 passed
(`honors_same_origin_relative_path`, `reduces_same_origin_full_url_to_path`,
`falls_back_on_open_redirect_attempts`)
- [ ] Manual e2e (needs configured SAML IdP — not on local CE):
- Unauthenticated visit to `/a/<path>` → click SSO → SAML → land on
`/a/<path>` (RelayState now carries the relative path).
- IdP that echoes BASE_URL as default → ACS still falls back to
`/user/login` (no useful path to honor), but the page no longer
hangs: the stale full-URL `localStorage.rd` is reduced to its path
and the post-login redirect navigates to it.
- Tampered `RelayState` (`//evil.com`, `https://evil.com/x`) → ACS
rejects, lands on `/user/login`.
* chore: update ee-repo-ref to 3489c243b0e5a8eb0dbc86e90917fbe72843573b
This commit updates the EE repository reference after PR #584 was merged in windmill-ee-private.
Previous ee-repo-ref: 635ff3eeb8e47bb84d5686942605f67f8f6224b4
New ee-repo-ref: 3489c243b0e5a8eb0dbc86e90917fbe72843573b
Automated by sync-ee-ref workflow.
---------
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
* feat(ai-chat): link workspace paths and show tool item references
Detect Windmill paths (u/..., f/...) in assistant messages and render
them as clickable pills with the right icon, resolved against a per-
workspace cache. Tool execution headers now list the script/flow/app
paths referenced in tool parameters as external links.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(ai-chat): linkify inline-code paths, refine pill styling
- Inline-code spans whose value is exactly a Windmill path now render
as a link pill (paths inside larger inline code or fenced blocks
stay as code).
- Tool-header chips moved to their own row to avoid overflow clipping
when the title wraps.
- Borderless pills, no default background (hover only), kind icons
use the home-page palette (script blue, flow teal, app orange),
and the external-link indicator only appears on hover.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(ai-chat): linkify variables/resources/triggers + inline drawer
- Workspace item registry now also lists variables, resources, schedules,
and all 10 trigger kinds; resource wins over variable on path collisions
(Windmill auto-creates a companion variable for every resource).
- Pill icons delegated to the canonical RowIcon component so each kind
matches the home-page styling (script blue, flow teal, app orange,
resource boxes, schedule calendar, etc.).
- Pill href includes the hash fragment each list page already consumes
(#/resource/<path>, #<path> for variables/schedules/triggers), so
opening the link puts the user on the list page with the matching
editor drawer already open.
- For variable and resource pills, a hover-revealed side-panel button
opens (or toggles closed) the editor drawer inline next to the chat,
without navigating away. VariableEditor and ResourceEditorDrawer gain
a closeDrawer() export and forward their close event so the host can
drive toggling.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor: simplify ai chat workspace item links
* refactor: keep ai chat path linkification only
* perf: avoid eager ai chat path cache loads
* refactor: simplify ai chat path linking
* feat: open ai chat path links in drawers
* refactor: homogenize workspace item kinds
* fix: toggle ai chat item drawer
* refactor: trim ai chat path cache
* fix: cancel ai chat drawer reopen
---------
Co-authored-by: Guilhem Lemouel <guilhemlemouel@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: Ruben Fiszel <ruben@windmill.dev>
Two FlowProgressBar bugs:
1. stop_after_if (without 'label as skipped') ends the flow with
step < modules.length, leaving the bar at <100% with a spinner.
2. failure_module execution drives step past modules.length, so the bar
overflows past 100% and never reflects the error.
The fix clamps progress to the failed module when the error handler
runs, and forces 100% Done when the flow completed successfully but
stopped early.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(chat): visual redesign — input, streaming indicator, scroll polish
Visual refresh of the AI chat surface used in both the global right-side
panel (Cmd+L) and inline editor panels. No new features, no system-prompt
or tool changes, no sessions code.
Input redesign
- Default textarea to `rows={1}` and autosize as the user types.
- Drop the separate Send button row in favour of a single
`<Button variant="subtle" iconOnly>` overlaid bottom-right of the
textarea — `ArrowUp` when idle (disabled until text is typed),
`Square` when loading (cancels via `aiChatManager.cancel()`).
- Padding `!pl-3 !pr-10 !py-2` keeps text clear of the floating button.
- Top spacing `mt-1` on the outer wrapper restores breathing room
above the input (lost when the old @-button row was removed).
- Context chip row renders only when something is selected.
- `ContextTextarea` `min-height: 2.25rem` so the empty textarea
collapses to a tight single line.
Streaming indicator
- Replace the old floating "Stop" button with a sticky-bottom badge
showing three animated typing dots and a formatted wall-clock
(`Xs`, `Xm Ys`, `Xh Ym`) — driven by `aiChatManager.loading`.
- CSS keyframes `chat-typing` with staggered animation-delay for the
wave effect.
Scroll behaviour
- Replace `onwheel`-based stick-to-bottom detection with `onscroll`
position check (8px threshold). Auto-scroll re-engages when the
user scrolls back near the tail.
- Smooth scroll → `behavior: 'auto'` so token-append doesn't race
the animation.
- New `enableAutomaticScroll` method on `AIChatManager`, complement to
the existing `disableAutomaticScroll`.
- Floating "scroll to latest" arrow (`ArrowDown` design-system Button,
`transition:fade`, `unifiedSize="xs"`, `iconOnly`) appears once the
user scrolls >200px above the tail; click re-enables auto-scroll
and jumps to bottom. Centered horizontally over the scroll viewport.
Message rendering
- Assistant markdown tuned: `prose-headings:font-medium`, h1 `text-sm`,
h2+ `text-xs`, plus `prose-p:text-xs prose-li:text-xs
prose-code:text-xs prose-pre:text-xs`. Stops AI replies blasting
oversized titles.
- Fenced code blocks shrink to `!text-xs` on the `not-prose` wrapper
so fenced code matches inline code at 12px.
- User-message wrapper switches to symmetric spacing (`mt-4 mb-6`)
with a new `isLast` prop that adds `!mb-12` to the latest message
— breathing room between the last bubble and the input without
affecting siblings.
Layout / padding
- Wide-layout messages tightened to `px-7` (was `px-8`); input outer
to `px-6`. The input box sits a touch left of the message text;
textarea's own `!pl-3` brings the typed text back into alignment
with the messages above.
Other
- `AIChatManager` class is now exported (was private). Allows callers
to type a `getContext<AIChatManager>('aiChatManager')` provider
override. No behaviour change for the global singleton.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* refactor(chat): restore @ picker, extract typing indicator and shared helpers
* feat(chat): cap non-wide chat at max-w-2xl, add side padding, drop input top border
* feat(chat): esc cancels active generation, tone down snapshot row
* fix(chat): only draw tool-content fade when content actually overflows
* style(chat): tighten non-wide side padding (px-4/px-3 -> px-3/px-2)
* fix(chat): inline ⌘K shows dots + stop button, swallow programmatic scroll events
* fix(chat): keep scroll-to-latest fresh during cooldown; ResizeObserver for tool-content fade
* fix(chat): contain wide content - propagate showFade, table scroll, bubble + inline code wrapping
---------
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
* feat(vault): add optional KV secret path prefix setting (WIN-1960)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref to 0189ba6504fd70eb4929e4881d624d48efd14aee
This commit updates the EE repository reference after PR #581 was merged in windmill-ee-private.
Previous ee-repo-ref: e32e8d6483550c67897e09b6f900dff1034bdae8
New ee-repo-ref: 0189ba6504fd70eb4929e4881d624d48efd14aee
Automated by sync-ee-ref workflow.
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
* [ee] feat(indexer): observability for unavailable search index
A user hit `Not found: There is no index reader to search from` when
searching service logs and could not tell whether it was a config
error or a bug, and asked for visibility into the indexer status
(WIN-1956).
Backend (EE companion PR):
- Replace the opaque error with an actionable message explaining the
likely causes (indexer disabled, still starting, or blocked
acquiring the indexer lock) and pointing to the status panel.
- Add a coarse `state` (running | stale | never_started) to
`/indexer/status`, derived from the lock row, distinguishing a
never-configured indexer from a stale/blocked one.
Frontend:
- Instance Settings > Indexer now shows Running / Stale / Not started
with a tooltip explaining what to check for each.
- Service logs search now catches failures and shows an inline,
actionable Alert instead of an unhandled rejection.
Fixes WIN-1956
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref to 017d36418a65ce5c840c502e3174df0c393612ba
This commit updates the EE repository reference after PR #580 was merged in windmill-ee-private.
Previous ee-repo-ref: 18b7e1b30a1ff582c4a072580bbb8aec34e22cdc
New ee-repo-ref: 017d36418a65ce5c840c502e3174df0c393612ba
Automated by sync-ee-ref workflow.
* fix(indexer): address review nits
- IndexerMemorySettings: older backends without `state` reporting
`is_alive: false` now show "Stopped" (red) again instead of
falling through to "Unknown" (codex/cubic P2).
- ServiceLogsInner: clear stale logs/counts on a failed search so the
error isn't shown alongside results from a previous query (codex P2).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
* fix(frontend): open customer portal in popup synchronously to bypass Safari blocker
Safari blocks window.open() called after an await because it loses the
user-gesture context. Open a blank tab synchronously on click, then
assign location.href once the portal URL resolves.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(backend): wire dev_override feature flag in backend crate
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat: export audit logs to dedicated object store folder
* fix: gap-free audit export via snapshot-xmin gate and stable object keys
* test: add integration test for audit log object store exporter
* fix: cursor audit export on snapshot xmin to prevent id-leapfrog loss
* fix: protect audit s3 checkpoint from config sync and bound export interval
* fix: anchor audit s3 checkpoint at enable time to not skip first-window rows
* fix: anchor first audit export at the enable transaction's xid
* fix: use epoch timestamp floor on first audit export run to not drop old backlog
* fix: anchor audit export at startup for env-var enable path
* fix: anchor audit export via enabling-txn snapshot xmin trigger
* fix: bound the bootstrap audit export to MAX_XID_INTERVAL per tick
* refactor: store audit export cursor in background_task_state, add status endpoint
* docs: align store_audit_logs_s3 setting text with the actual enable-boundary contract
* [ee] refactor: move audit s3 export core logic to EE, gate on Enterprise license
* chore: update ee-repo-ref to ec3cd353245e1cdf6a290528dbd7f2ac2498386c
This commit updates the EE repository reference after PR #579 was merged in windmill-ee-private.
Previous ee-repo-ref: 4ffc6d5f874e64d7dc4a147b4e73baa6c44867a5
New ee-repo-ref: ec3cd353245e1cdf6a290528dbd7f2ac2498386c
Automated by sync-ee-ref workflow.
---------
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
* fix: scope SimpleEditor webview paste to focused editor instance
* fix: scope webview clipboard paste to focused editor instance
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix: bail on missing selection instead of pasting at document start
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix: hide SimpleEditor paste sink input from a11y tree and tab order
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix: honor SAML RelayState to redirect to deep link after SSO login
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore: bump ee-repo-ref for SAML RelayState validator test
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref to a3fefe85f5f2f52bb473fa47acc9efa8fd0b2206
This commit updates the EE repository reference after PR #577 was merged in windmill-ee-private.
Previous ee-repo-ref: 445a22536b1a6c342cde0baa6fbca9e25092f94b
New ee-repo-ref: a3fefe85f5f2f52bb473fa47acc9efa8fd0b2206
Automated by sync-ee-ref workflow.
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
* feat: show job status in favicon on the run page
* test: cover getJobStatusKind favicon status mapping
* chore: remove favicon unit tests
Co-authored-by: Diego Imbert <diegoimbert@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
---------
Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Diego Imbert <diegoimbert@users.noreply.github.com>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
* fix: resolve absolute-path imports in monaco ts editor
* fix: dispose absolute-path extra libs on editor teardown and reset
* fix: skip late ata local-file callbacks after editor teardown
* fix: sidebar menu rendering expanded-but-empty near 768px width
The desktop sidebar branch is gated by JS (`innerWidth < 768`), but its
width was set only via Tailwind `md:` classes (`@media (min-width:768px)`).
`window.innerWidth` rounds fractional viewport widths, so at e.g. 767.8px
JS rounds to 768 and renders the desktop sidebar, while the CSS media
query does not match and no width class applies — leaving the sidebar
shell expanded with no width/content. Drop the now-redundant `md:`
prefix so width tracks the JS branch decision.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix: align content offset breakpoint with sidebar JS gate
The sidebar width now follows the JS innerWidth gate, but the main
content left-offset in AiChatLayout still used the `md:` CSS media
query, leaving the two breakpoints out of sync in the same sub-pixel
band. Pass an `isMobile` flag from the layout (mirroring the sidebar's
`innerWidth < 768` condition) and gate the content padding on it with
unprefixed classes so sidebar width and content offset always flip
together.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat: include service accounts in instance settings users list
Service accounts (workspace-scoped, no password row) now appear in the
superadmin users list with a Bot icon, workspace badge, and a link to
manage them in the workspace settings. Role is locked to Operator.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore: update sqlx offline cache
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix: use composite key for users each block
Service accounts can share emails across workspaces, so key by email + workspace_id to avoid Svelte each_key_duplicate.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat: read-only flag on API tokens, orthogonal to scopes
Add a per-token `read_only` boolean set at creation time. When true, the
token can only call HTTP methods classified as Read (GET/HEAD/OPTIONS).
Mutating methods and job-run actions are rejected with 403, regardless of
which scopes are attached. Surfaced as a prominent toggle in the standard
token-creation flow and a discreet `2xs` toggle in MCP mode (where users
often want write access, so we don't bias them toward enabling it).
MCP enforcement: read-only tokens hide all script/flow/hub tools from
`list_tools` and only see endpoint tools whose method is GET, and the
runner rejects `call_tool` on anything mutating.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix: review fixes for read-only token flag
- Exempt /api/mcp/* and /mcp/* paths from the read-only middleware check.
MCP transport runs over POST (streamable HTTP / SSE), so otherwise the
middleware would 403 every MCP request before the runner could enforce
read-only at the tool-call level.
- Tighten is_endpoint_read_only to GET only, matching the read_only_hint
that create_endpoint_annotations actually emits.
- Add unit test for check_read_only_for_route covering GET/HEAD/OPTIONS,
mutating methods, and run paths.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore: bump ee-repo-ref to read-only-trigger-toggle
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(frontend): make read-only toggle discreet in both modes
Match the MCP-mode treatment in standard mode: text-tertiary, 2xs, shared
"Read-only" label. The tooltip switches per mode so the explanation still
fits the context.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(frontend): gate read-only toggle behind Limit token permissions
The read-only toggle now only shows when the user has limited the token's
scopes (standard mode) or in MCP mode (which always picks an MCP scope).
Turning the limit off also resets read-only so it doesn't silently stick.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(frontend): hide incompatible MCP tools when read-only is on
When the read-only toggle is on in MCP mode:
- Endpoint badges and the custom-mode endpoint MultiSelect filter to GET.
- Already-selected non-GET endpoints are pruned from the scope.
- The scripts/flows preview is replaced with a note explaining they're
hidden (the runner already rejects script/flow runs for read-only).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(frontend): place read-only toggle at top of limited scope area
The previous gate required at least one scope to be picked before the
read-only toggle appeared, which made it look missing while the user was
still building their scope list. Move the toggle inside ScopesPicker:
- Standard mode: sits directly under the "Limit token permissions" toggle
whenever Limit is on, before the scope selector.
- MCP mode: sits at the top of the MCP scope block.
readOnly is now $bindable on ScopesPicker so CreateToken still owns the
value. The auto-reset on un-limit moves into ScopesPicker too.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(frontend): nest read-only toggle inside the scope list card
Place the read-only toggle at the top of the scope list (between the
Selected Scopes summary and the bordered domain list) via a new optional
topSlot snippet on ScopeSelector. Keeps ScopeSelector decoupled from
read-only specifics; ScopesPicker fills the slot.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref to 9bc8160be50b3e57a60daf4e1b71c389a6e02b8a
This commit updates the EE repository reference after PR #571 was merged in windmill-ee-private.
Previous ee-repo-ref: f53d26e6685dfd60bfa67686fbd7358169cfd130
New ee-repo-ref: 9bc8160be50b3e57a60daf4e1b71c389a6e02b8a
Automated by sync-ee-ref workflow.
* fix: address CI review for read-only token flag
- P1 (Codex): narrow the MCP middleware exemption from "any /api/mcp/*"
to just the streamable HTTP transport endpoints
(/api/mcp/gateway, /api/mcp/w/{ws}/{mcp,sse,list_tools}). Without this,
a read-only token could POST /api/mcp/gateway/oauth/server/approve and
mint a follow-on non-read-only MCP token via the OAuth code/token
exchange.
- P2 (Claude/cubic): fix test comment/assertion mismatch — the run-path
assertion now exercises GET (which is what the RUN_PATH_ACTIONS
elevation comment describes) in addition to POST. Add a regression
assertion for /api/mcp/gateway/oauth/server/approve.
- P2 (cubic): short-circuit script/flow/hub-script/resource fetches in
MCP list_tools when read_only is on — they would only be discarded
below, so skipping the DB and resource fan-out is pure win.
- P2 (cubic): when scopes are pre-supplied via the CreateToken prop, the
ScopesPicker isn't rendered, which previously hid the read-only
toggle entirely. Render it next to the pre-supplied scopes display.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
- exists_script_by_path now filters archived = false, matching the
conflict check in create_script_internal. Previously the frontend
blocked creating a new script at a path occupied only by archived
scripts, even though renaming to that same path was allowed.
- Hide the Delete entry in the script details "..." menu unless the
user is admin. The backend delete_script_by_hash already requires
admin, so non-admins would always see an error after clicking.
* perf(dynselect): only retrigger when helper-script args actually change
Parse the inline helper's signature with the existing WASM parser and
restrict the form-arg diff to keys the helper actually consumes. Typing
into unrelated fields no longer queues a dynselect job every second.
Falls back to the previous full-args comparison when the helper is
deployed or parsing fails.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(dynselect): avoid double helper-script fetch on mount
usePromise defaults to loadInit=true, so refresh() ran before the
JobLoader child was bound (firing a no-op pending promise) and the
$effect then fired a second refresh once the bind:this resolved.
Disable loadInit so the effect owns the single first call.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor(dynselect): use parser directly instead of inferArgs
inferArgs mutates a Schema object we never use and goes through a
shared cache; when fed an empty schema for non-main entrypoints the
caller cannot reliably read back the resulting properties. Add
parseEntrypointArgs that just runs the parser and returns the
parameter name Set (or undefined when unknown / unsupported / has
rest args / function not found). DynamicInput uses that and keeps
the previous params in flight while the next parse is computing.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(dynselect): support deployed helpers in smart retrigger
Add getHelperEntrypointArgs which dispatches on HelperScript.source:
inline parses immediately; deployed fetches the script (or the flow's
inline dyn-select code) once and caches per (workspace, kind, path,
entrypoint). Without this the /scripts/get/* run view fell back to
the full-args comparison and still retriggered on unrelated fields.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(dynselect): zero-arg helpers report empty deps, not unknown
Codex review flagged that a valid zero-parameter entrypoint was being
treated as "couldn't determine signature" and falling back to the
full-args comparison. Distinguish "function found with no params" from
"function not found" via the parser's auto_kind field — only the
latter sets it, so empty args + auto_kind=null means a real zero-arg
helper and we return an empty Set (no retrigger on unrelated fields).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat: add EditableInput component
* feat: add EditorHeader for flow editor with file picker entry point
* feat: WorkspaceItemPicker for editor header navigation
* feat: clickable breadcrumb in EditorHeader, scoped picker keyboard nav
* fix: reload flow on URL change and reset highlight in search mode
* feat: editor header layout polish and trigger removal
* feat: apply EditorHeader to script, app, and raw app editors
* fix: show generated initial path for new apps in EditorHeader
* fix: align EditorHeader new-app path with draft drawer's Path component
* fix: read page.params.path in loadApp to reload on URL change
* fix: remount AppEditor when navigating between apps
* fix: clear app/files on URL change so editor remounts with fresh data
* fix: route picker selections to /apps/edit or /apps_raw/edit based on raw_app
* fix: drop Save button from path popover; defer rename to deploy
* refactor: drop pathPopoverContent snippet, bind newEditedPath directly
* refactor: dedupe editor header plumbing (editPathFor, userPathPrefix, breadcrumb snippet)
* fix: freeze breadcrumb during path edit so popover doesn't drift
* fix: drop spinner from path dependency-check; render nothing when no usages
* fix: swallow 404 in checkFlowOnBehalfOf so renaming a flow doesn't toast
* Revert "fix: swallow 404 in checkFlowOnBehalfOf so renaming a flow doesn't toast"
This reverts commit 82dec462ae.
* refactor: drop moveRenameManager dep from EditorHeader; pass onBehalfOfEmail as prop
* refactor: replace breadcrumb-snapshot effect with open/close setter
* refactor: drop unused dirtyPath state from EditorHeader
* fix: surface Path validation error in pen popover
* fix: decouple Path validation error from hideFullPath toggle
* refactor: use InputError for path validation message (slide transition)
* fix: re-derive Path meta from external path changes (sibling sync)
* docs: note Path's meta could be replaced with function-form bindings
* fix: 'Exit & see details' uses deployed path, not live store
* fix: undo/redo shortcut uses shiftKey instead of fragile case-match
* fix: type errors and keep edit pen visible while popover is open
* chore: remove unused meltComponents/Accordion wrapper
* fix: focus search input on picker open via popover openFocus selector
* fix: refocus picker search input on every popover open
* fix: pre-seed picker loaded state from cache so accordion opens at the right place
* fix: breadcrumb and picker track savedPath, not draft-renamed live path
* fix: inject current draft item into picker so breadcrumb scope isn't empty
* docs: add component-level and prop-level docs to EditableInput
* feat: warn that a deploy is needed when path is edited on a saved item
* feat: show same path-change-needs-deploy message in flow/script settings
* fix(flows): persist draft-renamed path through reload and dirty check
* fix: include path in unsaved-changes diff so renames trigger the modal
* feat: nested folders in picker tree and breadcrumb
* fix: per-segment popover state so switching breadcrumbs closes the previous one
* refactor: replace accordion picker with drill-through picker
* refactor(picker): review fixes, drill polish, and breadcrumb collapse
* fix(picker): review fixes — banned bindable, drop sibling-sync, load races, mouse highlight
* fix(picker): smooth-scroll highlighted row into view on open
* fix(picker): second-pass review fixes — load races, cache invalidation, breadcrumb/picker a11y, raw_app routing
* fix(RowIcon): apply size prop to resource_type and fallback divs
* feat(picker): add cross-kind 'All' root; deeper breadcrumb segments open there
* fix(picker,editor): third-pass review — search loading state, pen autofocus, allow empty summary, a11y
* fix(picker,editor): fourth-pass review — own check, flowbuilder arrows, editableinput double-save, customui.path gate, invalidate races
* fix(editor,picker): codex review — fresh URL state per load, granular whitelabel topBar gates
* fix(flows): clear localStorage in auto-reload to break URL-state loop
* fix(frontend): branch download UI on shouldDownloadViaClient instead of intercepting in onclick
When OpenAPI.TOKEN is set, several download links rendered an `<a href>`
to the API and relied on an `onclick` handler to call `e.preventDefault()`
and route the request through `downloadViaClient`. This is fragile in
embedded contexts (e.g. the whitelabel React SDK) where Svelte's hydrated
event listener may not intercept the click in time, so the browser
follows the unauthenticated `href` straight to the API.
Mirror the drawer pattern already used in `LogViewer` and
`FlowStatusViewerInner`: render a `<button>` calling `downloadViaClient`
when `shouldDownloadViaClient()` is true, and fall back to the plain
`<a href download>` otherwise. Affects the LogViewer top bar, the large-
result download in DisplayResult, the inline S3 link in ObjectViewer,
the CSV link in ParqetCsvTableRenderer, and FileDownload.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(frontend): treat custom HEADERS / basic auth as token equivalents in shouldDownloadViaClient
Customers wiring the SDK with cookie-bypass auth via custom request
headers (e.g. `OpenAPI.HEADERS = getAuthHeaders()` returning a Bearer
header) had `shouldDownloadViaClient()` return false because it only
checked `OpenAPI.TOKEN`. The plain `<a href download>` branch then
followed the link without those headers, so authenticated downloads
silently degraded to the cookie path (or failed when there is no cookie).
Widen the check to any non-cookie auth: TOKEN, HEADERS, or USERNAME
(basic auth). Route `downloadViaClient` through the generated client's
`getHeaders` so all configured auth schemes are applied consistently
instead of hand-building an Authorization header for TOKEN only.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs: add global ai mode plan
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* feat: add global ai draft mode
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: scope global ai mode to scripts and flows
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* refactor: simplify global ai workspace item shape
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* refactor: split global ai write tool into per-type tools
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* feat: add global ai schedule and trigger workspace item tools
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* feat: add dev-only /global_drafts route to inspect ai draft store
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* feat: add edit_script and patch_flow_json global ai tools
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* feat: add deploy_workspace_item global ai tool with confirmation
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* feat: emit open-resource action card after deploy_workspace_item
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* feat: add delete_workspace_item global ai tool with confirmation
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* chore(system_prompts): emit RESOURCES_BASE and resource/variable zod schemas
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* feat: add global ai resource and variable workspace item tools
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: search_resource_types uses listResourceType to avoid embedding feature dep
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Revert "fix: search_resource_types uses listResourceType to avoid embedding feature dep"
This reverts commit 6d1d19514a.
* feat: emit open-resource action card for variable and resource deploys
* feat: add global ai raw app workspace item tools
* feat: split raw-app prompt into chat-only authoring and cli prefix
* feat: add init_app global ai tool to scaffold raw apps from templates
* fix: pass write_flow value as JSON string for gemini compat
* refactor: hoist countExactMatches and applyExactReplace to chat/shared
* refactor: extract editableFlowJson module shared with global mode
* fix(global): preserve flow schema and groups across draft and deploy
* feat: extract inline scripts from flow reads and patches in global mode
* refactor: add findAndReplace helper for match-validated text patches
* refactor: extract getInlineRunnableContent helper for app file tools
* refactor: extract assertNotGeneratedAppFile guard for /wmill.d.ts
* feat: gate global ai mode behind localStorage flag for dev rollout
* chore: bump svelte to ^5.55.5 in raw app template (sync with main)
* fix: isolate global ai draft rollout
* fix: preserve global ai deploy metadata
* fix: harden global ai draft tools
* chore: remove global ai plan doc
* fix: align raw app prompt guidance
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* [ee] feat(license): offline (URL-bound) license keys
Offline keys are a 4-segment variant for air-gapped customers — no
phone-home, embedded seat/CU caps, locked to the instance's base_url.
Existing 3-segment online keys are unchanged.
Companion PRs:
- windmill-labs/windmill-ee-private (full design + EE impl)
- windmill-labs/windmill-customer-service (issuance + portal)
- windmill-labs/windmill-cf-worker-keygen (signing)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* [ee] refactor(license): bind offline keys via instance hash; simpler CU enforcement
- /settings/license_status now surfaces an `instance_hash` superadmins share
with support when requesting an offline key
- OfflineMetadata: `hash` replaces `base_url`; OfflineCapStatus reports
`current_cu` (last 2min) and drops the grace-period fields
- verify_license_key now takes a db so EE can recheck the hash
- InstanceSetting.svelte: hash copy-block + simpler status panel
- Bump ee-repo-ref
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* [ee] chore(license): bump ee-repo-ref
Pulls in the current_cu clamp + prod public key restoration.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* [ee] refactor(license): split instance_hash endpoint; minimal cap UI; restore workers expiry toast
- `instance_hash` is no longer part of /settings/license_status responses; it
lives at GET /settings/instance_hash (super-admin only) so it isn't re-emitted
on every status poll. The UI doesn't show it — admins fetch it explicitly when
requesting a key from support.
- InstanceSetting offline cap UI is now two compact green/red status lines
(Seats X.X/Y and CUs X.X/Y) placed above the action buttons, matching the
existing "Latest key renewal" badge style. The block-panel is gone.
- "Latest key renewal" line and the "Renew key" button are now hidden when an
offline key is loaded (renewal is server-disabled for offline keys).
- Restore parseLicenseKey + checkLicenseExpiration toast on /workers
(works for both 3- and 4-segment keys).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* [ee] chore(license): bump ee-repo-ref
Pulls in the plain-SHA256 instance hash + stats_ee revert.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* [ee] chore(license): bump ee-repo-ref
Picks up the alert wording change.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* [ee] chore(license): bump ee-repo-ref
Picks up the instance_uid cache so the periodic verify_license_key cycle
no longer hits global_settings.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* [ee] refactor(license): rename /settings/license_status → /offline_license_status
The endpoint was only used by the offline-license UI; the other fields it
returned (license_key_id, license_key_valid, kind, offline metadata) were
unused. Rename to clarify scope and flatten the response — it now returns
just the OfflineCapStatus (or null when no offline license is loaded).
Frontend uses `offlineCapStatus != null` as the "is offline" check.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* [ee] fix(ci): regenerate sqlx cache for the inline worker_ping query
After reverting unused stats_ee helpers (fetch_worker_pings*), the
inline `sqlx::query_as!(WorkerPingRecord, ...)` in get_stats_payload
lost its cache entry — CI's check_ee_full + cargo_test were failing
under SQLX_OFFLINE=true with E0282 type-inference errors.
Re-running update_sqlx.sh regenerates the cache file under its
current hash and prunes a couple of stale entries.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* [ee] fix(license): address cubic-bot review
- get_offline_license_status: propagate enforce_offline_caps errors as 500
instead of swallowing into a "no offline license" (Option::None) response
- canonical_base_url: rewrite the doc to match the actual fallback behavior
(lowercase + trailing-slash strip on URL parse failure); the original
cross-service contract is gone since the customer-service no longer
canonicalizes (treats the instance hash as opaque)
- check_seat_cap_for_new_user: take an email and short-circuit when the
email is already in `usr ∪ workspace_invite` so net-zero invite upserts
and invite→user transitions aren't spuriously blocked at cap. Mirrors
the dedup rule the count itself uses.
- Bump ee-repo-ref to pull in the EE-side change
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* [ee] chore(license): bump ee-repo-ref
Picks up the exact-delta seat-cap check (replaces the simple existence
short-circuit). Regenerates the new sqlx cache for the bool_and query.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* [ee] fix(license): propagate get_instance_hash errors; bump ee-repo-ref
- get_instance_hash: replace `.ok().flatten()` with map_err+? so DB errors
during instance_uid lookup surface as 500 instead of silently returning
`{"instance_hash": null}` (same pattern get_offline_license_status already uses)
- Bump ee-repo-ref to pull in the enforce_offline_caps cached-state preservation
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref to c6cd1afe2d9e04809b30751cd1687b28a65e62b1
This commit updates the EE repository reference after PR #566 was merged in windmill-ee-private.
Previous ee-repo-ref: a6d91016ae0d43c46604313aecae3aa9c778c8e0
New ee-repo-ref: c6cd1afe2d9e04809b30751cd1687b28a65e62b1
Automated by sync-ee-ref workflow.
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: Ruben Fiszel <ruben@windmill.dev>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>