Commit Graph
6140 Commits
Author SHA1 Message Date
Guilhem Lemouel 3cc0d93acf Merge remote-tracking branch 'origin/main' into glm/quick-datatable-onboarding
# Conflicts:
#	backend/ee-repo-ref.txt
2026-08-13 18:34:09 +02:00
Guilhem Lemouel ad79a6eb9e fix(frontend): refuse a resource path the wizard run does not own 2026-08-13 18:32:12 +02:00
GuilhemandClaude Opus 5 adc7947579 feat: open an AI session from runs, jobs and trigger pages (#10608)
* feat: open an AI session from the runs and trigger pages

* feat: tell the chat which page the session preview shows

* fix: observe shallow url writes and keep page tabs deduped by path

* feat: open an AI session from the resource and variable drawers

* fix: re-point page tabs on hash change and follow the drawer's workspace

* fix: force a load when a page tab is re-pointed within one document

* fix: report a re-pointed preview tab as retargeted, not opened

* fix: reload a preview tab re-pointed at the url the frame drifted from

* fix: canonicalize runs previews and read drawer anchors per page

* fix: dedupe page tabs on the path so self-written filters don't duplicate

* perf: carry the active-preview rule only in chats that have a side panel

* fix: read a preview tab's hash as a row only where the page deep-links one

* fix: focus the preview tab showing the exact location before retargeting by path

* refactor: give preview locations one module that says what they mean

* fix: report the active preview from what is on screen, not the selected tab

* fix: read a preview location's view from the params a request can set

* fix: count every filter a request can set, and flush drawer drafts before routing

* docs: state each preview-routing constraint once, within four lines

* fix: take a page's view params from the filter schema it already declares

* docs: describe the filter contract the url builders now follow

* fix: describe a preview to the model from addressing fields only

* fix: keep a filter value holding a delimiter apart from two filters

* fix: keep a preview description to one line the model can trust

* fix: materialize the resource editors before persisting the draft

* docs: bring the preview-routing constraints back within four lines

* fix: refuse to route a preview on state the drawer could not persist

* fix: read a resource drawer's validity from the editor, not from draft dirtiness

* fix: answer what the user can see from one place in both descriptions

* refactor: name each write to a preview tab's two locations, and the read

* fix: flush only editors holding a pending change

* fix: drop a list page's row anchor when its drawer closes

* fix: clear the row anchor on every list page that deep-links one

* fix: keep a closed drawer closed, and refuse to leave unparseable text

* refactor: register the resource json field in the shared unparseable set

* refactor: decide a forced load where the command changes, not in the host

* fix: navigate a preview frame only when it is not already there

* fix: boot a remounted preview frame where the user left it

* fix: carry a list page's filters and open row into the session

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix: navigate a preview frame by what it shows, not by its url

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix: read a resource's raw-editor validity from the current parse

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix: compare preview views without iterating URLSearchParams

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* refactor: drop re-exports the path leaf left without readers

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-13 11:12:52 +00:00
hugocasa 6fbc3fccb6 fix(flow): pass the flow's worker tag when testing a loop iteration (#10680) 2026-08-13 11:03:55 +00:00
hugocasaandClaude Opus 5 ef99a739dd fix(github-app): complete the self-managed setup instructions, render the page header (#10683)
* docs(github-app): state the pull-direction permissions and the App owner field

The in-product "How to create a GitHub App" panel only listed Contents and
Metadata, which covers the push direction of git sync. Webhooks, pull requests
and checks are what the git to Windmill direction needs, and a GHE Cloud
(*.ghe.com) app also needs App owner, whose field hint was the only place
saying so.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(instance-settings): render the GitHub App page header

The branch tested the pre-rename category name, so the page rendered with no
header at all. Naming the header after the category duplicates the card
below it, so the card that holds the app credentials is now labelled for what
it is, next to the webhook base url card.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-13 11:03:42 +00:00
Guilhem Lemouel b5eb60f8e9 refactor(frontend): load the supabase resource dialog only behind the flag 2026-08-13 11:47:44 +02:00
Guilhem Lemouel c3785e2b9d fix(frontend): keep the supabase resource link off the popup callback path 2026-08-13 11:09:10 +02:00
Guilhem Lemouel d5d51c9bcd feat(frontend): gate the supabase resource path behind the dev flag 2026-08-13 10:38:31 +02:00
Guilhem Lemouel 2dc1f98ee5 Merge remote-tracking branch 'origin/main' into glm/quick-datatable-onboarding
# Conflicts:
#	backend/ee-repo-ref.txt
2026-08-13 09:30:44 +02:00
Ruben FiszelandClaude Opus 5 71b9989daa feat: auto-build binaries to object storage on deployment (#10673)
* feat: auto-build binaries to object storage on deployment

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix: queue the auto-build from pre-locked deploys and off the lock slot

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix: materialize companion modules before a deploy-time build

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix: keep a build job from stamping lock_error_logs on a healthy script

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* test: de-flake test_flow_lock_all and surface the lock error it hides

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* test: trim drafting history from the flow-lock fixture comments

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix: stop a binary build from restarting dedicated workers

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix: keep the build-job marker off the agent wire and out of user args

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-13 07:50:28 +02:00
Guilhem LemouelandClaude Opus 5 1d0b51441b feat(frontend): gate the data table wizard behind a dev flag
The wizard only appears with `dataTableWizard` set in localStorage; without it the
settings page keeps the inline-row flow it had before this branch, down to the empty-state
copy and the "New Data Table" button, and the wizard component is not mounted at all. The
existing e2e suite drives that button, so the default-off flag is also what keeps it green.

Step 2 of "your own database" becomes one list rather than a segmented control: the
workspace's Postgres resources, then a New resource card that expands in place. A
connection string is not an alternative to a resource, it is how one is written, and the
old layout taught otherwise. The card holds the same connection as a string or as fields
and carries values across when you switch, so `parse` and `compose` have to be inverses --
hence the percent-encoding on both sides, which also fixes a password containing `@`
silently corrupting in the resource form. The Supabase step now uses the same shape.

Names and paths are checked as they are typed rather than at the end of a run that may
have created a billed project first: the data table name against the charset
`edit_datatable_config` enforces, the instance database name against what
`setup_custom_instance_db` will accept, and the resource path against both the resource
and variable namespaces, since the run writes to both and both writes upsert.

`test_datatable_connection_value` refuses `$var:`/`$res:` in its body. It feeds
`transform_json_value_unchecked`, which resolves references with no permission check of its
own, so an admin could otherwise have had the API server decrypt any workspace secret and
hand it to a host the same request chose -- without the audit trail a variable read leaves.
Callers testing something unsaved hold the literal value already.

Alert, SetupChecklist and postgresConnectionString change for everyone, not just behind the
flag: body-only alerts no longer reserve an empty title row, the checklist can nest the
checks a step is made of, and the connection-string parser is shared with the resource form.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-12 20:05:34 +02:00
AlexRV12andClaude Opus 5 73b71a8fac feat(sessions): persist artifact version selection in preview tabs (#10655)
* feat(sessions): persist artifact version selection in preview tabs

The artifact viewer's version pin was component-local state, so picking an
older version from the history dropdown was lost on reload. It now rides on
the preview tab's URL (`artifact:<id>?v=<n>#<name>`), which is persisted with
the tab, so a reload lands the reader back on the version they were reading.

Omitting a version means "leave the reader where they are", not "show the
latest". Every artifact tool re-opens the document it just wrote, so an
omitted version that cleared the pin would yank a reader out of the version
they chose on every single edit. That rule lives in keptVersion(), which
targetUrl() applies to every path that re-points a tab, so open() and
navigate() cannot disagree about it — the breadcrumb picker opens highlighting
the artifact the active tab already shows, and re-picking it must not double
as a reset to latest. A pin belongs to a (tab, artifact) pair, so a tab
re-pointed at a different document carries nothing over, and a new tab starts
unpinned. Moving off a pin is the reader's own action, through the version
dropdown, "Back to latest", or the new pinArtifactVersion(). Since the pin is
part of the tab model, get_preview_status now reports it, so the assistant can
tell that the reader is not looking at what it just wrote.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(sessions): bound a stamped artifact version to a safe integer

Number.isInteger(1e21) is true, but interpolating it yields `?v=1e+21` while
parseArtifactRoute matches digits only, so artifactUrl could stamp a url that
reads back as null — the one outcome the guard exists to prevent, and one that
would persist with the tab. Safe integers always interpolate in decimal.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(artifacts): tell a failed version read apart from a missing version

getArtifactVersion swallowed a rejected read and returned undefined, so a
transient IndexedDB failure was indistinguishable from a pruned snapshot. Both
its callers act on that distinction, and both acted wrongly: the artifact
viewer clears the reader's pinned version on absence — now that the pin is
persisted with the tab, clearing it destroys it — and read_artifact tells the
model the version is gone and to call list_artifact_versions.

It now rejects instead. The store still answers for the current version, which
it holds in memory and can serve without the DB; anything older propagates, the
viewer keeps the pin and leaves the document on screen, and read_artifact
reports a read it could not make rather than a version that does not exist.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-08-12 15:31:31 +00:00
GuilhemandClaude Opus 5 83bdff89d5 use the Password component on the login and reset-password forms (#10661)
* fix(frontend): use the Password component on the login and reset-password forms

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(frontend): submit auth forms once per Enter keypress

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(frontend): conceal revealed password before submitting auth forms

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-12 14:45:12 +00:00
GuilhemandClaude Opus 5 eb238e3f0b fix: stop the AI chat destroying secret variables on edit (#10616)
* fix: stop the AI chat destroying secret variables on edit

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix: clear stale staged secret values and state the draft-staging rule

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* docs: condense the pending-secret invariant to its field

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix: refuse empty and oauth-managed secret values, keep drawer-staged ones in the draft

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix: resolve a variable deploy's secret from one draft snapshot

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* refactor: make the variable draft the single source of a staged secret

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* docs: drop stale in-memory secret invariants from comments and the eval

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix: stop null account/expires_at leaking into variable drafts and diffs

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix: report when a variable deploy leaves the secret value unchanged

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* docs: scope the variable-value readability claims to the chat

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* docs: correct the secret-draft invariant in the diff masking comment

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* docs: record why a non-secret value is resent on a partial update

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix: stop "Load secret value" discarding a staged secret

The audit-logged load writes the deployed secret into the draft row the
variable drawer shares with the AI chat, so offering it while that row
already stages a value silently replaces it — and the deploy that follows
carries the old value with no sign the staged one was lost.

The gate that hid the action already existed but keyed on
`isEncryptedDraftValue`, which only holds once a draft has round-tripped
through the server. A value staged in the same tab is still plaintext, so
it slipped through. Key on "anything staged" instead; clearing stays
explicit via Reset.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix: extend the variable draft's empty-value sentinel past secrets

Two gaps in the chat's variable write path, both from treating "the draft
cannot carry this value" as meaning only "the value is secret".

`variableToDraftState` drops the value of an OAuth-managed variable so a
refreshed live token is never pinned into a draft, leaving '' behind. The
deploy body resent that '' verbatim for a non-secret one, wiping the token
the refresh flow owns. The sentinel now covers every value the draft is not
allowed to hold, which also removes the divergence from
`VariableEditor.save` and the shared deployer.

Making a variable secret when it holds no value produced a secret draft
staging '', a deploy body with no `value`, and the backend's "cannot change
is_secret without updating value too" — the sibling create path already
answers that case with guidance, so answer it here too.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix: gate the Secret toggle's secret load on the staged value too

The toggle calls `onLoadSecret` on every change so an is_secret flip has a
value to send, but that load overwrites the shared draft row — the same
discard the button gate just closed, reached by a different control.

It now loads only when the row stages nothing, which is exactly when the
flip needs a value fetched. With a value already staged there is one to
send, and it is the one the user or the chat put there.

Blocking the load costs the side effect that used to mask a worse bug: for
a deployed variable, the load replaced an `$encrypted:` marker with real
plaintext before save. Without it, un-securing a marker would store the
marker string as the value, since the deploy endpoints only decrypt it while
is_secret stays true. So the toggle is disabled outright while a marker is
staged — Reset first. That closes the marker case for draft-only variables
as well, where no load could ever have masked it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-12 13:13:41 +02:00
GuilhemandClaude Opus 5 ce58b8495c feat: expose every runs filter on the open_page chat tool (#10612)
* feat: expose every runs filter on the open_page chat tool

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix: reject runs filters the page would silently ignore

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix: normalize runs list filters and refuse combinations the page drops

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix: validate the full folder-name contract and pin evals to one call

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix: refuse queue statuses the concurrency view cannot filter on

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-12 09:34:37 +00:00
Ruben FiszelandClaude Opus 5 2808150ae4 fix: avoid content shift on home page load and in the script editor logs pane (#10654)
* fix: avoid content shift on home page load and in the script editor logs pane

The tutorial banner rendered by default and was removed once an API round-trip
resolved that it should not show, jumping everything below it up by 58px on
every home page load. It now caches the last resolved state in localStorage and
paints that first, so the first frame already matches what the sync concludes; a
device with nothing cached stays hidden until the sync answers.

The logs header spinner was an unsized lucide icon (24px) where the settled
state renders a 12px Timer, so the row grew 7px while a job was queued and
shrank back when it started, shoving the log body down and up again.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix: keep the tutorial banner hidden when dismissed mid-sync

The banner is interactive while the initial tutorial-progress request is still
in flight, so a dismiss or a skip can land before the sync resolves. The
continuation then overwrote the user's choice and brought the banner back.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix: pin the result placeholder row height across the spinner swap

Sizing the spinner to the font size still left it 6px short of the text-sm line
box it replaces, so the row contracted instead of growing. Pin the height on the
container so it holds in both states and tracks the root font size.

Also assign state before persisting it, and collapse the duplicated rationale
above the banner cache.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix: stop the test panel splitpanes resting one header too tall

The panes carried `!max-h-[calc(100%-{...}px)]`, but the arbitrary value is
built by string interpolation so Tailwind never emitted a rule for it: the
class was inert and the computed max-height was `none`. The panes then took
their 100% height, ignoring the header row above them, and overflowed the
column by exactly the header. Flex only applied the shrink transiently, so a
reflow during a run snapped the whole logs & result region up ~12px and back.

min-h-0 lets flex size the panes to the space that is actually left, which is
what the clamp was reaching for and is correct for the debug and bottom layouts
too, without their hardcoded 83/43/0 pixel guesses.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-12 09:33:22 +02:00
Ruben Fiszel 5f819cd344 fix(frontend): skip reserved ids when auto-assigning flow module ids (#10651)
* fix(frontend): skip reserved ids when auto-assigning flow module ids

* test: state the reserved-id invariant only beside the implementation
2026-08-12 08:41:30 +02:00
AlexRV12andClaude Opus 5 66c0d1251d fix(copilot): read an artifact inside the transaction that revises it (#10647)
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-12 02:13:29 +02:00
GuilhemandClaude Opus 5 583649bb23 feat(frontend): show prod and dev as sibling choices in the workspace picker (#10590)
* fix(frontend): declutter the workspace picker and workspace creation

* fix(frontend): fail open when the auto-invite domain check errors

* fix(frontend): fail closed when the auto-invite domain check errors

* feat(frontend): present prod and dev as sibling choices on the workspace card

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-11 13:01:24 +00:00
Guilhem LemouelandClaude Opus 5 be49abe6cf fix(frontend): restore the data tables description lost in the branch split
The rewritten description went into DataTableSettings.svelte shortly before that file was
restored wholesale to its pre-rebuild state, so it left with the row rework it had nothing
to do with. The tab went back to describing the plumbing -- a fully managed PostgreSQL
database, reachable from the SDK -- which never answered the question a new user actually
has: why this rather than a Postgres resource.

It leads with what a data table is, then the two things a resource cannot do -- nobody
needs the credentials to query it, and the name can be pointed at another database without
editing anything that uses it -- and closes with what Windmill runs on top. Both middle
claims are the ones every resolution path backs up: datatable:// resolves by workspace and
name, unchecked.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-11 13:32:00 +02:00
Ruben Fiszel ceacc17014 fix(raw-apps): respect the instance .npmrc in the raw app editor (#10629)
* feat(raw-apps): route in-browser npm installs through the npm proxy

* fix(npm-proxy): follow npm range semantics and cache packuments

* fix(npm-proxy): bound the packument cache by bytes and stream tarballs

* fix(npm-proxy): keep a v-prefixed pin exact and read the tarball once

* chore(raw-apps): bump the ui_builder pin to the npm-proxy installer
2026-08-11 12:30:40 +02:00
Guilhem LemouelandClaude Opus 5 394bd5acf9 fix(frontend): make the wizard stepper navigate the steps it already offers
Stepper dispatches a click and paints cursor-pointer on every reached step, but the wizard
never listened, so the breadcrumbs invited a click and did nothing.

They now reach any step already passed, in either direction: going back to check something
should not cost the progress, which means tracking the furthest step reached rather than
the current one. Forward movement still only happens through the primary action, so a step
is never reachable without having been validated -- and changing the intent revokes the
steps ahead of it, or Finish could run against a review built from something the user has
since edited. The five places that cleared the probe on an edit now do both through one
call.

During a run nothing is reachable, and the stepper says so rather than showing a pointer
over steps that will not respond.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-11 12:09:43 +02:00
Guilhem LemouelandClaude Opus 5 632b20ed9d revert(frontend): move the destructive button restyle out of the wizard PR
This reverts 3881e4d8ea. Making default and subtle destructive buttons red at rest changes
every existing caller of the prop -- the workspace integrations, AI skills, workspace
creation and the instance database drop -- so it is a design-system change, and the call
sites it fixed are the migrations list and the database manager. None of that is the setup
wizard.

Nothing on this branch passes destructive any more, so it leaves with no loose ends.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-11 11:56:19 +02:00
Guilhem LemouelandClaude Opus 5 ca717e64ee fix(frontend): stop the wizard claiming the resource folder controls who can use a data table
"Who can use this database" was wrong. Every path that resolves a datatable:// reference --
both executors and the agent-worker endpoint -- reads the resource unchecked, by workspace
and name. A resource in u/admin is usable by everyone's scripts. The folder governs who can
see and edit the connection, and who can reference the resource directly in a SQL step;
neither is who can use the data table. The wizard was contradicting the tab's own
description two screens later.

The folder select and name field become one Path picker, the same one the resource,
variable and script forms use, so the review step reads as a resource path rather than a
permission choice. Its initialPath is snapshotted when the step opens: Path seeds itself
from it, and a live value fights the typing. Finish now also gates on Path's error, so a
taken or malformed path stops the run before it writes anything.

The button that opens all this says "Add a data table" -- the data table is what you get;
the database is a detail chosen along the way.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-11 11:53:54 +02:00
Guilhem LemouelandClaude Opus 5 9e7d0a541b fix(frontend): confirm before dismissing the data table wizard mid-setup
Closing was guarded while a run was in flight and unguarded before one, which is backwards:
a run leaves a row to resume from, whereas a backdrop click on the review step threw away
the project, the pasted password and the folder with nothing to recover them from.

Backdrop, Escape and the close button now go through one path that asks first. It only asks
when there is something to lose -- no provider chosen yet, or a run that already produced a
result, closes immediately -- so the dialog does not become something to click through.
Continue in the background still leaves in one click; that exit was always the deliberate
one.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-11 11:41:46 +02:00
Guilhem LemouelandClaude Opus 5 5da093bc31 revert(frontend): keep the data tables settings table as it was
The settings table and the setup wizard are two changes that only shared a file. Splitting
them makes each reviewable: this branch keeps the wizard, and the read-only row, gear
panel, health probe and clickable databases move to their own branch.

The rows go back to the editable form with its pickers and save footer, still opening the
wizard from Add a database. DataTableSettingsPanel, dataTableHealth and dataTableOrigin
had no other consumers and go with them; the connection report stays, because the wizard
shows it too.

DataTableSettingsType keeps `origin`: the wizard writes it, and the review step reads it
back to warn when two data tables would share one database and therefore one
_wm_migrations table.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-11 11:25:36 +02:00
ec99108cf6 feat(triggers): nested filter groups and dotted paths (#10625)
* feat(triggers): nested any_of / all_of filter groups

A trigger filter entry can now be a group — `{"any_of": [...]}` or
`{"all_of": [...]}` — nesting further entries, so criteria like
`A AND B AND (C OR D)` are expressible. Existing flat `{key, value}` lists keep
their meaning, combined by the trigger's `filter_logic` as before.

Filters are compiled once per connection: the set of top-level keys the whole
tree references is collected up front, so a message is parsed in a single
streaming pass that captures only those keys, instead of one full pass per leaf
filter as before. Filters that fail to parse are now logged rather than dropped
silently, since a nested group is easier to mistype than a flat entry.

The editor gains "Add group", rendering groups recursively with their own
AND/OR selector; Kafka and WebSocket triggers share it.

Fixes WIN-2345

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(triggers): drop empty filter groups instead of evaluating them

A group with no criterion cannot evaluate to a constant: true makes an `or`
filter list accept every message, false mutes an `and` list. Two clicks in the
editor ("Add group", save) produced one. Drop it when compiling so its siblings
stay in force, and reject at save time the filters the listener would otherwise
drop silently.

Also restore the item shape of `$ref`-typed arrays in the generated agent
schemas: the extractor only resolved refs at the property level, so moving
`filters.items` to a shared schema flattened it to a bare object. Resolving them
inside `items` too also recovers the shapes `initial_messages` and the MQTT
`topics` had already lost.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(triggers): name the offending entry when a nested filter is invalid

Serde's untagged error only reports that the outermost entry matched no
variant, whatever depth is actually wrong, which defeats the point of
validating a group at save time. Walk the tree instead and report the path.

Normalize the WebSocket editor's filters to [] on load, as the Kafka editor
does, so the list component can rely on an array.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(triggers): key filter rows by node so deletion keeps values aligned

The value editor seeds itself from `code` once, so an index-keyed row reused
for a different filter kept showing the deleted row's value.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* chore: bump ee-repo-ref after merging main

The merge pulled OSS code that needs EE symbols newer than the companion
branch's base, so the companion was merged with EE main too.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* perf(triggers): keep filter short-circuiting from materializing unread fields

The single-pass scan deserialized every referenced key before the boolean tree
ran, so an AND whose first leaf rejects the message still allocated the large
objects the later leaves name — the shape this feature exists for. Borrow the
wanted keys as raw slices during the scan and parse a field only when
evaluation actually reaches it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* feat(triggers): none_of filter group

Negation of a nested group, so a trigger can exclude what it must not react to
without inverting every other criterion. A key the message does not carry
satisfies it: there is nothing there to match.

Only groups can negate — the root's operator is the trigger's filter_logic
column, which has no value for it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* feat(triggers): address a nested field with a dotted path

`{path: "a.b.c", value: v}` alongside the existing `{key, value}`, so the common
case reads the way people write it instead of nesting the shape into the value.
A separate field rather than dots in `key`, which already means the top-level
field spelled that way — overloading it would resettle what existing triggers
over flattened payloads match.

Paths address objects only for now: a path through an array does not match
rather than guessing an element, and array containment stays on the value side.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* docs(triggers): mention none_of in the filter_logic description

Plus a test for the empty-path-segment rejection, which had none.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* chore: update ee-repo-ref to 78859aab0c6e78283ec8d2b37e8c410963afdc83

This commit updates the EE repository reference after PR #722 was merged in windmill-ee-private.

Previous ee-repo-ref: 0e42ba72ccc38a6b0a380f58afe0db36d284f4c9

New ee-repo-ref: 78859aab0c6e78283ec8d2b37e8c410963afdc83

Automated by sync-ee-ref workflow.

* fix(triggers): reject a criterion naming both key and path

The untagged enum takes such an entry as a `key` criterion and drops the
`path`, which is the silent-ignore the save-time validation exists to prevent.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* refactor(triggers): drop the label next to the key/path toggle

The toggle already shows which one is selected.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(triggers): reject an entry that combines a criterion with a group

Generalizes the key+path fix: the untagged enum settles a half-and-half entry
on the first variant that fits and ignores the rest, so a criterion carrying a
group key lost the whole subtree without a word.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
2026-08-11 11:07:12 +02:00
Guilhem LemouelandClaude Opus 5 57842a10e0 feat(frontend): open the database behind a data table, and say when it cannot write
Every database in the list now opens the surface that owns its credentials. A postgres
one opens its resource in the editor drawer; a Windmill instance one opens the instance
modal, which is where its setup checks, password rotation and drop already lived. Both
are reachable from the row and from the panel's provenance list, and the provider icon
moved inside the button so the whole thing is one target.

CustomInstanceDbWizardModal targeted #content unconditionally, which put it underneath
the panel drawer that now opens it. It takes a target, and the panel portals it to the
body.

The status column gains a third state. The probe reports privileges but nothing gated
the dot on them, so a data table whose role cannot create tables showed as Connected and
only failed when someone ran a migration. It reads "Limited permissions" instead, and
opens the panel on the report carrying the GRANTs that fix it -- the settings page has
already probed, so the panel takes that report rather than asking the user to run Test
connection over work already done. fullyPrivileged is exported from the report component
so the dot and the report cannot disagree about what counts as healthy.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-11 11:04:41 +02:00
Guilhem LemouelandClaude Opus 5 979c055bae fix(frontend): fall back to a direct Supabase connection when the pooler cannot be read
Reading a project's Supavisor config needs the database_pooling_config_read scope, which
an instance's Supabase OAuth app may never have been granted. No retry recovers from
that, and the wizard treated it as fatal: the user was left with an error and no way to
finish connecting a project that was otherwise fine.

resolveSupabaseConnection replaces the bare pooler read everywhere it happened. Asking
for session pooling and failing now yields a direct connection plus the reason, which
supabaseResourceValue already knew how to write. Nothing about the fallback is silent --
direct is IPv6-only, which is the whole reason session pooling is the default -- so the
wizard warns on its review step and the resource drawer says so in its toast.

The row is recorded before credentials are saved, so an origin claiming session pooling
has to be corrected once a direct host is what gets written; the run patches it through
set_datatable_setup rather than leaving the panel to report a mode nothing uses.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-11 11:04:24 +02:00
Guilhem LemouelandClaude Opus 5 cd4ecbe52a feat(frontend): rebuild data table setup around a read-only row
The wizard gathers intent over two steps, reviews it on a third and writes nothing
until Finish, so a billable Supabase project is created only once the user has seen
what will happen. runSetup is also the retry: every step probes for its own result
before doing anything, so running it again on a half-finished data table resumes
instead of duplicating. Its steps are keyed rather than dispatched on their titles,
where rewording one changed what it did.

The settings row stops being an editable form with a dirty/save cycle. It carries the
name, where the database came from, a health dot and two actions; everything rare
moved into the gear panel, which also offers Finish setup for a data table whose
wizard never completed. Manage is ExploreAssetButton, the control the ducklake list
already uses, and the row and panel both link out to the underlying resource.

supabaseResourceValue no longer assembles the pooler host from the region.
aws-0-<region>.pooler.supabase.com is wrong for any project Supabase allocated
elsewhere, so the host, user and port come from the pooler config endpoint.

Two data tables sharing one database also share _wm_migrations, which is probed
unqualified, so the review step warns when the database being connected is already
behind another data table.

SupabaseConnect is deleted. The resource drawer uses the shared project step
restricted to existing projects: creating one is a billed action and belongs in the
wizard, which has somewhere to report what it did. The kitchen_sink checklist
playground goes with it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-11 10:15:15 +02:00
Guilhem LemouelandClaude Opus 5 3881e4d8ea fix(frontend): make destructive default and subtle buttons read red
Both variants were neutral until the pointer arrived, then filled solid red: nothing
marked the button as destructive until you were already on it. They now carry red text
at rest, with a faded red border on default and a light red wash on hover, which is
what the legacy red border style in the same file had always done.

Three call sites passed color="red" alongside a design-system variant. getStyleClass
returns before colour is read for accent, accent-secondary, default and subtle, so the
delete-migration control, its modal confirm and the import-database button had all been
rendering neutral. They pass destructive now.

The dropdown variant strips the button's own border, and matched border-border-light
literally -- a class the destructive style no longer contains.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-11 10:14:53 +02:00
hugocasaandClaude Opus 5 c09de594b6 feat: version resource values with history, diff and restore (#10596)
* feat: version resource values with history, diff and restore

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix: record resource versions in a trigger so direct writes are covered

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix: show the selected version's value and tighten history write access

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* perf: gate resource version recording in trigger WHEN clauses

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* feat: clear a resource's past versions, and address review nits

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix: restore the displayed version and keep author attribution on pooled writes

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix: scope history to the selected workspace and gate clearing on ownership

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix: gate restore on write access and clearing on the signed-in workspace

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* refactor(frontend): share the version-history row between script and resource drawers

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* perf: trim resource version history in the monitor sweep, not on write

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(frontend): match the script versions drawer shell for resource history

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* perf(frontend): highlight version values instead of mounting monaco

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(frontend): match the script drawer's code preview presentation

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix: rank version trim in one windowed pass instead of a correlated delete

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* refactor(frontend): treat the newest version as current by position

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* perf: gate the resource version trim to an hourly sweep

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix: unnest the version row action and correct the trim cadence docs

* perf: cap the history listing and use sets for reference lookup

* feat: warn when a resource is written more than 60 times a minute

* fix: lower the resource write advisory to 20 per minute

* fix: discard stale history loads and never diff against an unread value

* fix: correct the write advisory boundary and document the eviction lock

* fix: read history and the live value from one snapshot

* refactor: read the drawer's diff baseline from versions, not the live resource

* fix: open the history drawer with no version selected

* fix: disarm the clear confirmation and clear the pane when the selection moves

* fix: explain the missing diff and drop a guard that can no longer fire

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-10 21:32:18 +02:00
5b0a159a01 fix(smtp): explain why a test email failed instead of 'deadline has elapsed' (#10620)
* fix(smtp): explain why a test email failed instead of 'deadline has elapsed'

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(smtp): keep non-SMTP error codes and retire a stale test alert

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* chore: update ee-repo-ref to f0df8b82c4c089d384423ed64b8504506084820d

This commit updates the EE repository reference after PR #721 was merged in windmill-ee-private.

Previous ee-repo-ref: 1ffaf3dea81e007c6c11146c1e12e97e83f5b938

New ee-repo-ref: f0df8b82c4c089d384423ed64b8504506084820d

Automated by sync-ee-ref workflow.

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
2026-08-10 21:21:14 +02:00
bf1b2cdcf9 fix(duckdb): cast list columns in quicksearch so tables containing them can be previewed (#10614)
* fix(duckdb): cast columns in quicksearch so nested types can be previewed

DuckDB's `CONCAT` implicitly casts scalars but rejects nested types:

    D SELECT CONCAT(' ', ['a','b']);
    Binder Error: Cannot concatenate types VARCHAR and VARCHAR[] - an explicit
    cast is required

Quicksearch concatenates every visible column, so one LIST, STRUCT or MAP column
makes a table impossible to preview — both the grid and its row count fail:

    Binder Error: Cannot concatenate types VARCHAR, VARCHAR, BIGINT, ...,
    VARCHAR[], ... and TIMESTAMP WITH TIME ZONE - an explicit cast is required
    LINE 1: ... FROM "raw"."accounts" WHERE ($1 = '' OR CONCAT(' ', "id", ...

Every scalar in that list concatenates fine on its own — VARCHAR, BIGINT,
DOUBLE, BOOLEAN, DATE and TIMESTAMPTZ were each checked individually — so the
array column is the entire cause.

Cast each column in the predicate. The comparison is textual either way, so no
result changes, and the projection is untouched: casting there would change the
types the caller reads back. This follows the shape already used for MSSQL in
`mssql_needs_cast_for_eq`.

Both DuckDB quicksearch sites are covered, SELECT and COUNT. Fixing one leaves
the grid rendering while the row count still errors.

Tests include the live path: the Database Manager sends a
`-- WM_INTERNAL_DB_SELECT {...}` marker and the backend expands it, so the new
test drives that expansion with the real 27-column definition captured from a
failing job, `sync_id VARCHAR[]` included. It fails without the fix and passes
with it.

* fix(frontend): cast columns in the DuckDB quicksearch

Same defect as the Rust query builders, in the implementation that actually
runs. `make_select_query` / `make_count_query` in windmill-common have no callers
anywhere in the repo; the query the browser sends is built here.

DuckDB's CONCAT implicitly casts scalars but rejects nested types, and
quicksearch concatenates every visible column, so one LIST column makes a table
impossible to preview — both the page and its row count fail with

    Binder Error: Cannot concatenate types VARCHAR, ..., VARCHAR[], ... and
    TIMESTAMP WITH TIME ZONE - an explicit cast is required

The helper lives in select.ts and is imported by count.ts so the two cannot
drift, and both call sites are fixed: fixing only SELECT leaves the grid
rendering while the row count still errors.

* fix(duckdb): cast only list columns in quicksearch, leaving other SQL byte-identical

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* test(frontend): pin the DuckDB quicksearch column list byte-for-byte

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Ruben Fiszel <ruben@windmill.dev>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-10 20:48:19 +02:00
Ruben FiszelandClaude Opus 5 8c65511e81 fix: raw app new-app modal ignores instance-level AI settings (#10619)
* fix: stop the new raw app modal claiming AI is unconfigured before it knows

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix: require a resolved workspace before trusting the loaded AI config

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix: drop the unreachable token guard and point superadmins at instance settings

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix: show the instance settings link to superadmins who are not workspace admins

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-10 20:39:38 +02:00
AlexRV12andClaude Opus 5 77adf85ccd feat: version history for session artifacts (#10574)
* fix: never replace an in-flight indexeddb open, only a settled one

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* feat: keep a version history for session artifacts

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* feat: let the assistant browse an artifact's earlier versions

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* feat: pick an older artifact version from the preview panel

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* test(ai_evals): cover the change note the assistant writes on each edit

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix: bound every indexeddb open, not only one told it is blocked

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-10 19:51:29 +02:00
Ruben Fiszel 4a69cd616e keep the logins state declaration from narrowing to undefined (#10618) 2026-08-10 19:45:27 +02:00
Ruben FiszelandClaude Opus 5 c725d62fb0 fix(frontend): call a dev workspace a dev workspace in the merge UI (#10605)
* fix(frontend): call a dev workspace a dev workspace in the merge UI

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* refactor(frontend): drop the unreachable dev-workspace guard on the fork modals

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-10 19:30:24 +02:00
Ruben Fiszel 1a709d49f9 unblock the frontend check (type error + svelte-check OOM) (#10617)
* fix(frontend): count login options inside a closure so tsc keeps their type

* ci: give svelte-check a heap above node's 4GB default
2026-08-10 19:25:02 +02:00
GuilhemandClaude Opus 5 676256bacc feat(flow-editor): measure step panel placement (#10543)
* feat(flow-editor): measure the redesigned step panels

Instruments the flow editor's step, loop and branch panels on the existing
anonymous `feature_usage` channel, so the redesign can be judged on how the
panels are actually used rather than on nothing.

Eight event kinds under a new `flow_editor` feature: panel opens and their
dwell (bucketed, per placement), placement-preference overrides, which
settings get configured or cleared, settings that read as invalid, the
prop-picker connect lifecycle, AI input suggestions, and the step header
menu that "Save to workspace" now lives behind.

Settings changes are diffed off `describeStepSettings`, the same view the
graph badges render, so the telemetry vocabulary cannot drift from the one
on screen. Only `panel_open` and `setting` carry an entity id — one opaque
id per editor mount — since a per-entity row is only worth its cost where
the spread per editing session is the question.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(flow-editor): keep the panel telemetry honest

Review follow-ups on the instrumentation:

- The top dwell bucket was `120s+`, and `+` is outside the charset
  `is_identifier_shaped` accepts, so `log_feature_usage` skipped those
  events and still answered 204 — the longest visits vanished with no
  error on either side. Renamed to `120s_plus` and pinned every emittable
  key against the backend's charset in a test, since the producer is
  TypeScript and the validator is Rust.
- Dropped the per-session entity id from `setting`: it would pay a row per
  session per day across twenty-four keys, for a distribution its plain
  counter already largely answers.
- An armed connect that went away with its component never reported, so
  `open` did not balance against `insert` + `abandon`.
- Session preview tabs keep hidden editors mounted, which billed panel
  time nobody spent. `FlowEditorView` now publishes the visibility it
  already knows about.
- Re-picking the active placement row logged a move, which also made
  `auto:from_docked` mean two different things.
- The last dwell of a session was lost on tab close, since Svelte tears
  components down on navigation but not on `pagehide`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* refactor(flow-editor): narrow the telemetry to panel placement

The eight-kind instrumentation measured more than could be read. With nothing
recorded before the redesign there is no baseline to compare panel opens, dwell
times, settings usage or connect funnels against, so those counters answered
questions nobody could act on while costing a row per key per day in an
instance-wide table.

What remains are the three numbers the modal panel is actually judged on: how
often the 1280px breakpoint puts the panel in a modal, and how often people
override that in each direction.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(flow-editor): stop counting placement in session preview tabs

Preview tabs keep every flow editor mounted and laid out at panel width
whether or not it is the visible one, and that panel is narrower than the
breakpoint by construction. Each flow tab opened in a session therefore
emitted a `breakpoint_modal` on mount, and one drag of the session panel
across 1280px emitted one per mounted tab — with no host dimension in the
key to separate that from the crossings the counter exists to measure.

Also corrects the comment on the no-op placement guard, which justified
itself with a key vocabulary that no longer exists.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(flow-editor): make the three placement counters comparable

Sessions were excluded from the breakpoint counter but not from the two
override counters, so a pin made in a session landed in the same bucket
used to judge the breakpoint, with no crossing in the denominator to read
it against. All three are now gated together.

An override is also only counted when it moves the panel. Choosing
"Detached" on an editor the width had already put in a modal states a
preference without changing anything, and the aggregate carries no width
to separate that from the wide-screen override that is the actual signal.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* docs(flow-editor): describe the two override keys by what emits them

They documented themselves as overriding `auto`, which is no longer the
rule: pinning Attached on a wide editor overrides `auto` and emits
nothing, while going from an Attached pin to Detached below the
breakpoint emits `force_detach` even though `auto` would have produced a
modal there too. This file is what someone reads when interpreting the
numbers, and "override of auto" is the misreading the emission rule
exists to prevent.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(flow-editor): count the panel moving, not the breakpoint being armed

The tracker held "the breakpoint is responsible for this modal" rather
than "the panel is modal", so on a narrow editor pinning Detached and
releasing it back to Auto emitted a second breakpoint_modal for a panel
that never moved. It also died with the editor, which FlowBuilder rebuilds
through a `{#key}` on every reload — each rebuild re-armed it and counted
the same narrow editor again.

Both inflate the denominator that the two override counters are read
against, and both bias it the same way: toward concluding that nobody
overrides the breakpoint.

The tracker now follows the panel's placement across preference changes,
and FlowBuilder owns it from above the `{#key}`, which also puts the
session exclusion in one place instead of at each call site. The
moves-only rule moves into `forcedPlacementEvent` so both halves of it sit
in the module the tests can reach.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(flow-editor): ignore placements measured before the editor is laid out

A reload rebuilds the editor through `{#key renderCount}`, and the panel
controller is rebuilt with it: its width restarts at zero, which resolves to
`docked` because that is what is safe to render rather than because the editor
is wide. The breakpoint tracker read that transient as the panel having docked
and counted the real width landing as a fresh crossing, inflating the
denominator both override ratios are read against.

`useFlowPanelMode` now exposes `measured`, and the tracker skips anything
unmeasured instead of recording it as a placement.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* docs(flow-editor): state the placement invariants once each

The width-zero rule had accumulated at four sites, two of which forward it
without being able to break it. Keep it beside the guards that enforce it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-10 12:29:31 +02:00
Guilhem d638fab5f6 stack login options in one column below four (#10598)
* fix(frontend): stack login options in one column below four

* style(frontend): drop redundant w-full on login buttons
2026-08-08 00:07:23 +02:00
Guilhem LemouelandClaude Opus 5 53250ac782 fix(frontend): stop Supabase resources coming up modified when first opened
Resource forms fill in every unset property from the schema as soon as they render,
so a postgresql resource saved without region, root_certificate_pem and use_iam_auth
was dirty -- and had saved a draft -- the first time anyone looked at it. Write them
with the rest of the value.

SupabaseConnect also rebuilt the resource shape by hand instead of using the shared
helper, which is how the pooler host format ended up in two places.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-07 18:47:39 +02:00
Guilhem LemouelandClaude Opus 5 5cc4cd55df fix(frontend): tidy the resource drawer around the Supabase entry point
Connect Supabase was a hand-styled anchor carrying Supabase's brand hex values
rather than a Button, and it sat in a row whose other controls had settled on
unifiedSize md. Making it a Button meant SupabaseIcon had to satisfy IconType, so it
now takes `size` (deriving height/width from it) alongside the string props its other
callers pass.

The manual resource form spaced every field 32px apart and WhitelistIp added another
16px of its own, which read as a gap rather than a rhythm. One gap of 16px, with the
form itself given a little more separation from the description above it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-07 18:47:31 +02:00
Guilhem LemouelandClaude Opus 5 bc18ebec16 refactor(frontend): one setup checklist and one Supabase step for every host
The data table wizard, the instance database modal and the resource drawer each had
their own version of the same two interactions, and they had already begun to drift:
the wizard's Supabase resource shape was rebuilt by hand in the drawer, and the
instance checks rendered with no notion of a step being in flight.

SetupChecklist replaces LoggedWizardResult, whose only consumer was the instance
modal. It adds the running state that component lacked, so a list driven by an
endpoint that reports nothing until it returns still shows where it is. Both the
instance checks and the Supabase provisioning stages render through it.

SupabaseProjectStep owns picking or creating a project, and useSupabaseOauth owns
the popup leg. Each host keeps only what is genuinely its own: the wizard saves a
variable and resource then verifies the connection, the resource drawer fills in its
own form. Both trigger authorization themselves, so a host can offer it a screen
earlier than the step does.

The lists load behind a spinner because which mode to open on depends on whether the
account has projects; deciding that after rendering flipped the toggle under the user.

Adds a kitchen_sink playground for the checklist so the animation and every failure
position can be exercised without a backend, a superadmin, or a Supabase account.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-07 18:47:22 +02:00
Guilhem LemouelandClaude Opus 5 521b9b5720 fix(frontend): scope the connection check to the choice that produced it
A failed check stayed on screen when the user switched Supabase mode or picked a
different provider, so a fresh tab opened showing an error about a database it had
nothing to do with. Clear the report and the error on both switches; re-clicking the
tab already selected leaves an error the user is reading in place.

Also polish the Supabase step: project cards get the provider-card treatment (icon,
p-3, flex column) instead of a hand-rolled variant whose block layout left more
padding above the name than below; form labels settle on text-emphasis; and the
signup link sits under the primary button for anyone who does not have an account
yet.

Drop the "free" badge and the "Free on Supabase" line -- every option in the wizard
is free, so neither told the user anything -- and say what the Supabase card
actually does now that connecting an existing project is the default.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-07 11:16:10 +02:00
Guilhem LemouelandClaude Opus 5 97ffd5a72a fix(frontend): run the Supabase OAuth leg in a popup
A full-page redirect unmounts the wizard, so anything the user does on Supabase's
side -- signing in, confirming an email, browsing their dashboard -- leaves them
with nothing pointing back at Windmill, and the wizard had to park its state in
sessionStorage to survive the trip.

Open the connect endpoint in a popup instead. The modal stays on screen throughout
and the callback hands the token back through postMessage rather than navigating.
The parked-state path stays as the fallback for browsers that block the popup.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-07 10:51:25 +02:00
Guilhem LemouelandClaude Opus 5 b6a011f9e0 fix(frontend): only offer Supabase when its OAuth client is configured
The wizard offered the Supabase card unconditionally, so on an instance whose
superadmin never configured a supabase_wizard client -- or whose backend is built
without the oauth2 feature, which compiles the whole /api/oauth router out -- the
card dead-ended at a 404. Gate it on listOauthConnects, the same check
ApiConnectForm already makes, fetched on open so configuring the client mid-session
does not require a reload.

Also drop the Supabase project ref from the existing-project cards: it is an opaque
identifier that means nothing outside Supabase's own dashboard URLs. Show the region
instead, plus a status word when the project is not healthy, since a paused project
is the one case where the connection check fails for a reason unrelated to the
password.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-07 09:48:22 +02:00
Guilhem LemouelandClaude Opus 5 12b26372ed fix(frontend): simplify Alert layout and balance its vertical padding
The body was rendered by two near-duplicate branches, each wrapping the text in an
extra div only to hang a margin on it, and the margins disagreed: the collapsible
branch spaced above with mt-2, the static one below with mb-2. Since isCollapsed
defaults to true, every non-collapsible alert took the static branch, so titled
alerts read as 24px of space below the text against 16px above -- visibly
off-centre -- with the title and body flush against each other.

Collapse both branches into one and drop the margins; the container's own padding
now sets top and bottom equally, with a small gap under the title row.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-07 09:48:15 +02:00
Ruben FiszelandClaude Opus 5 0459dda1bf correct typo in NATS consumer name description (#10585)
The consumer name field in the NATS trigger config read "Required is using
JetStream" instead of "Required if using JetStream", matching the wording
already used by the sibling stream name field.

Fixes WIN-2335

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-06 23:13:43 +02:00
Guilhem LemouelandClaude Opus 5 ef8141f260 fix: address review findings on the data table wizard
- The Supabase create branch advanced on `provisioning === 4` without consulting
  the check it had just run, so a role that cannot create tables could reach
  Finish. It now blocks and offers Try again.
- Retrying no longer mints a fresh secret variable + resource each time: the
  credentials are only re-created when the password actually changed.
- The generated password is captured before the create call rather than after,
  since a throw there can still leave a project behind.
- On a failed provision the project list is refreshed, so the just-created
  project can be picked up from the other tab instead of provisioning a second.
- Finish refuses a name that already belongs to another data table, which
  previously repointed it at the new database.
- Secrets go to the acting user's namespace instead of a literal `u/admin/`.
- The progress list no longer ticks "Created on Supabase" before the request is
  sent, and does not claim the database is ready when its check failed.
- The wizard's resume state is cleared when it closes, so reopening after an
  abandoned OAuth round trip is not stuck on step 2.
- The OAuth callback shares the session-storage key rather than repeating it.
- SupabaseConnect uses the shared provisioning helpers instead of a fork.
- Restores the doc comment displaced onto TestDataTableResourceQuery.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-06 22:17:31 +02:00