The credentials list marked every row with the same key glyph, so the only
thing distinguishing them was the path. `IconedResourceType` renders the
provider's own mark from the resource type already on the row, falling back
to a generic box for types with no icon.
The data table explanation said "a data table is a database this workspace
owns" directly under a label reading "Data tables to set up", and "this
project ships with one it expects to find" directly next to the count that
says so. Both halves went; what a data table is *for* and what to do next
are what a first-time reader needs.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The setup step drove `editDataTableConfig` itself, which meant it could
name a table and record migrations but could not create the database
behind it — the case a brand-new workspace is always in. It now opens
`AddDataTableWizard`, which owns that whole path.
Four additive props carry what the import flow needs and nothing else,
so `DataTableSettings` is unchanged:
- `initialName` — the migrations only apply to a table of the name they
target, so the wizard opens on it. Still editable.
- `modalTarget` — `#content` is the `(logged)` shell's scroll container,
and the import page reparents out of it, so the portal would find
nothing and the dialog never appear.
- `finishAlso` / `onFinishAlso` — running the migrations was invisible
until it had already happened. It is now named on the final button
("Create data table and run migrations") and reported as the last row
of the wizard's own checklist, failing there rather than silently.
Rows are marked done rather than removed, so the list still says what
was set up. Resources keep their card and swap "Fill in" for "Saved".
`Finish` is the primary and stays disabled until nothing is outstanding;
`Skip for now` sits beside it, and the info alert explaining the skip
turns into a success one when everything is configured.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A project's data tables and credentials cannot travel with it: a data table is a
named database connection the workspace owns, and resource values are secrets the
hub never publishes — `importResourceStub` creates every one of them empty. The
wizard used to state that as a dead end. Mid-import it asked the user to cancel,
create the data tables by hand and start over, which for a *new* workspace was
every single time, since a new workspace has no data tables at all.
Step 4 replaces that. It appears only when the run leaves something undone, lists
what that is, and does it in place: a Postgres resource per missing data table
(one merged `editDataTableConfig` write, then the migrations), and the existing
resource editor for each credential. Skipping is allowed and says plainly which
parts of the project will not run.
It is self-sufficient from `workspace` + `slug` — it re-fetches the export rather
than reading the executor — so reloading on it works and the plan in the URL stays
the whole state. Rows are marked done rather than removed, with SaveButton's
confirmation flash, because a checklist line that vanishes when completed reads as
something going wrong.
Two things the step needed from elsewhere:
- `ResourceEditorDrawer` gained `onSaved`. `onRestored` fires only when an old
version is restored, so a caller showing state derived from the resource had no
way to know a save had happened — the row kept saying "missing token" after the
token was filled in.
- The run now loads the destination's membership into `userStore`. The wizard's
page is reparented out of `(logged)` and never gets that layout's `getUserExt`,
so anything asking what the user may do reads "no user" and refuses.
`applyOneMigration` is exported for the same reason the step exists: the import
skips a migration whose data table is not configured, and this is where it is not
skipped any more.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The post-await guard compared only the path, and the path is built from the
review step's fields -- so picking an existing resource stops the wizard minting
one without changing it. A check already in flight then answered for a branch
nobody was on, and a `true` disabled Finish over a path the run no longer
writes. The cleanup cannot help: it cancels a pending timer, not a live request.
Both sides of the await now ask the same question.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`removeRow` reports `kept` when the undo cannot reach the server, so the row
this run wrote stays in the workspace config and comes back in `existingNames`.
The client-side name check then refused the retry on the run's own name, with
no way forward but a rename. The instance database name has carried the same
exemption since it was written; this is the data table name catching up.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* feat: rework the add-resource drawer list
The resource type picker in the "Add a resource" drawer showed 273 types as
bordered chips in three columns, labelled by their raw type name with the
description hidden in search text only.
Rows now carry the product name, the type name, and its description, and the
list is searchable and keyboard-drivable.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: address local review nits on the resource list
- keep DOM focus on the highlighted row when arrow keys move it from a
focused row, so Enter never activates a different row than the lit one
- ignore the `mouseenter` the browser fires when rows scroll under a
stationary pointer, which dragged the highlight back mid-navigation
- namespace the OAuth rows' aiId: a provider is listed in both sections,
and triggerableByAI keys a single map by id
- seed the custom-type set from the names call, so the section survives
the full resource-type list 403ing on a public app domain
- drop resourceTypeLabel, whose last caller now uses the display name
- read a leading acronym as letters when picking a/an ("an S3 resource")
- test resourceTypeDisplayName directly
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* feat: scroll the resource list on its own, and report search results
- the drawer no longer scrolls: step 1 is a full-height column with the
search field and the sync button fixed, and only the rows scrolling.
This drops the sticky search bar and the scroll-margin the rows needed
to clear it
- searching shows a per-section count, hides the sections it empties,
and states plainly when nothing matched at all
- section spacing moved onto the column's gap, so a section a search
empties takes its spacing with it instead of leaving a hole
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: address review-round nits on the resource list
- one definition of "a search is active": a whitespace-only query kept
the browse ordering but still ranked, dropped the database grouping
and highlighted row 0
- "a NATS resource": the acronym rule reads initials as letters, which
is wrong for an all-caps name said as a word
- give the lightweight picker's wrapper a height, so the step-1 list
fills it the way it fills the drawer
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: drop the article from the add-resource title
Whether a label takes "a" or "an" follows how it is said, and the
spelling does not carry that: "an S3" but "a NATS", "an MCP" but "a
REST", "a URL" but "an hour". Three review rounds each found another
name the rule got wrong, so the title now names the type without an
article.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Denial, a token error and a malformed callback all sent the user to
/resources whether or not a run was parked. Nothing else consumes the park, so
the run stayed in sessionStorage and sprang the wizard open on an unrelated
later visit instead. A parked run now lands on the data tables tab, where the
wizard resumes on the setup step and can authorize again.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The two messages differ in what they ask the user to do, and the condition
choosing between them — whether the lowercased name is one the resource keeps —
is not visible from either call site. `Connect_Timeout` is the case that keeps
them honest: miscased *and* unstorable, so respelling it would not help and the
message must not suggest it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Two blockers and the findings around them.
- The wizard pushed history entries for navigations the user did not ask for.
`folder` initialises to the project slug while the plan holds none, so the
mirroring effect fired on mount and pushed a URL differing only by `&folder=`;
pressing Back returned to the entry without it, which re-fired and re-pushed.
Back could never leave step 3. `go` now takes `{ replace: true }`, used by that
effect and by the step guard — the two navigations the page decides on its own.
The comment claiming `go` replaced was written without checking that `goto`
forwards to SvelteKit, which defaults `replaceState` to false.
- Undoing a run left the app pointing at the workspace it had just deleted:
`#ensureWorkspace` switches in, `deleteCreatedWorkspace` deleted without
switching out. The dead id was persisted on the next navigation, `getUserExt`
then returned undefined, and the following reload logged the user out. The
executor now remembers where the app pointed before it started and puts it back.
- `FORBID_TAGS: ['style', 'image']` on the hub SVGs. The profile allows both; an
inline `<svg><style>` is document-scoped, so a hostile hub could restyle this
page — including moving the wizard's own Import and Delete controls — and
`<image href>` is a beacon. The doc comment asserted a guarantee the config did
not deliver.
- The existing-workspace id is validated like the new one and encoded where it is
interpolated into `/api/w/<ws>/...`; it arrives from the URL exactly as the new
one does and ends up in `workspaceStore`.
- `AppConnectInner`'s two RadioCards get a `role="radiogroup"` wrapper, since they
now carry `role="radio"` and a screen reader cannot place a radio without one.
- `FolderPicker` records a created folder against the membership it is reading, and
before reloading, so a non-admin can re-pick the folder they just made in another
workspace instead of finding it `(read-only)`.
- Step 3 shows trigger and data table migration counts once the export is fetched.
The page this replaced showed them, and the warning underneath talks about
triggers the user was never told about.
- First tests for the two pure modules: the workspace-id contract the wizard and
the create form must not drift on, and the plan/URL round trip the whole wizard
rests on.
- Doc fixes: the retry claim (the granularity is the task, not the item), the bench
header, a fractional `?step=`, an empty name in the destination card, and the
three copies of one rationale AGENTS.md asks to state once.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Review fixes, then the parts of the wizard that were hand-built where the
design system already had an answer.
Correctness:
- Hub SVGs are sanitised with DOMPurify before `{@html}`. The earlier comment
claimed the markup came from the hub's own icon package rather than user
input, which the custom-URL feature makes false: the hub is whatever address
the user typed.
- The run owns navigation while it is in flight. The stepper refuses to move,
`beforeNavigate` cancels browser back/forward, and unmounting resolves a
pending migration review so the executor cannot hang waiting on a component
that is gone.
- The folder edited on the last step reaches the executor, so a retry after
changing it imports where the field now says.
- `validateWorkspaceId` and the workspace-entry pair (`listUserWorkspaces` then
`switchWorkspace`) are extracted, so the wizard and the real create form
cannot drift on what an id is or on what entering a workspace means.
Design system:
- The destination tiles are `RadioCard`, which gains `showRadio` and a snippet
`description`; the wizard turns the glyph off because the border and tint
already say which one is picked. `RadioCard` now also carries `role="radio"`
and `aria-checked`, which it had neither of, and marks its selection with
`surface-accent-selected` — the token `FileExplorer`, `TriggersTable` and
`RunnableRow` all use for the chosen row.
- Form labels follow `brand-guidelines.md` — sentence case, real `<label>`
elements so the text focuses the field, Caption-styled errors — rather than
one-off 11px uppercase tertiary text. They use the lighter secondary weight,
since the fields arrive prefilled and the value carries the meaning.
Folder choice, restored and merged:
- Picking an existing folder came back for an existing-workspace destination.
`FolderPicker` takes a `workspace` prop so it can list a workspace without
switching to it, and resolves `whoami` there — its write flags came from
`$userStore`, i.e. the wrong workspace, which rendered every real folder
read-only and unselectable. A new workspace has no folders to choose between,
so it is not asked.
- The progress list and the imported paths are one component: the paths hang
off the import task that produces them instead of forming a second list.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: check direct-deployment lock and superadmin in the deploy preflight
`checkDeployPermission` mirrors the server's `check_deploy_rules` so the deploy
UI can disable an action with a reason instead of letting the click come back
403. It modelled only `RestrictDeployToDeployers`, leaving two terms out:
- `DisableDirectDeployment` was never evaluated. In a workspace carrying only
that rule the preflight allowed the deploy and the request 403'd.
- The server bypasses on `ApiAuthed.is_admin`, which is `usr.is_admin ||
super_admin`, while `whoami` reports the two separately. A superadmin who is
a plain member of the workspace was refused a deploy the server allows.
Evaluate `DisableDirectDeployment` first, as the server does, so the same
message wins when both rules block, and add the superadmin term to the shared
ruleset bypass helper. `wm_deployers` membership is an implicit pass on
`RestrictDeployToDeployers` alone, so it no longer short-circuits the rules
fetch the way admin does — a deployer is still bound by a direct-deployment
lock, and a test pins that.
The operator refusal stays above the admin/superadmin short-circuit: the server
refuses operators in the item handlers whatever their global role, so a
superadmin who is an operator in the workspace is still refused. Its doc no
longer presents that term as part of the `check_deploy_rules` mirror, since the
rule carries no operator term and refusing every kind here is deliberately
stricter than the server.
Callers no longer name which rules the preflight covers. That list rots at every
site that repeats it, so it lives only at the preflight itself.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: apply the direct-deployment refusal only to the kinds the server gates
`check_deploy_rules` runs from the item handlers, and only scripts, flows, apps,
resources, resource types, variables and folders reach it. Schedules and
triggers hit no gate at all: in a `DisableDirectDeployment` workspace the server
returns 200 for a schedule and 403 for a script.
The preflight answers per workspace, and that one answer disabled the deploy
action for every kind, so adding the direct-deployment term would have blocked
schedule and trigger deploys the server accepts. Tag each refusal with the term
that produced it and let callers narrow a direct-deployment refusal to the kinds
the server actually gates; a selection still blocks as soon as one gated kind is
in it.
The deployers-only term keeps applying to every kind. It over-reaches the same
way, but narrowing it would loosen the UI beyond mirroring the new rule, so it
stays as it is and no existing behaviour changes.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: mirror the superadmin bypass in the per-item deploy checks too
`checkPathWritePermission` and `canPreserveOnBehalfOf` still tested `is_admin`
alone. The server reads the merged `ApiAuthed.is_admin` in both places —
`is_owner` for path ownership and `can_preserve_on_behalf_of` for the deploy
identity — so a superadmin who is a plain member was refused a write the server
accepts: creating a script in a folder owned by someone else returns 201 for
them.
Also drop the rule enumeration from the session deploy guard's comment, which
named the operator and deployer rules for a preflight that now covers the
direct-deployment lock and answers per kind.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: keep the deploy refusal on an empty selection and match the advice to the fork lock
* fix: mirror the superadmin bypass in the compare page's on-behalf-of gate
* docs: name the variable that tracks the deploy direction
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Falling back to the first organization named one the seeded project is not in,
since `supabaseSummary` prefers `intent.org` over the project's own. Unset, it
falls through to the project's organization identifier — the right one, spelled
as a slug rather than a name.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Making parameter names case-sensitive gave `unsupportedConnectionParam` two
reasons to refuse, and the single message explained only one. `?SslMode=` was
answered with "Windmill cannot store SslMode on a Postgres resource", which is
false twice over: sslmode is exactly what the resource stores, and the string
asks for nothing because Postgres rejects the URI. It now names the spelling
when the parameter is one we keep, and the storage limit otherwise.
The folder-list guard also still read the `resume` prop that `open(parked)` was
changed to stop trusting, so the resumed path now comes from whatever `reset`
was handed.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`format('%I', NULL)` raises rather than returning NULL, so a role whose
search_path names no valid schema failed the whole privilege query and was
reported as an unreachable database. That is the one case `fix_search_path`
exists to name, and it never reached the user. Verified against a local server
with `SET search_path = ''`.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The loader took `orgs[0]` independently of the project it seeded, so an account
whose first project sits outside its first organization had the review step name
an organization the database does not belong to. Picking a project by hand
already derives it; the seeding now does the same.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
libpq does not fold them: `?SslMode=disable` is rejected as an invalid URI
query parameter rather than read as `sslmode`, which a local server confirms.
Folding made Windmill accept and honour a string Postgres itself refuses;
naming the parameter instead tells the user why it cannot be stored.
The last-value-wins rule for a repeated parameter is unchanged, and matches
what the same server does.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
One reader for both the parser and the allowlist, since they disagreed about
what a string says in two ways that both ended in a weaker connection than was
pasted:
- `URLSearchParams.get` takes the first of a repeated parameter and libpq takes
the last, so `?sslmode=disable&sslmode=require` was read as `disable`.
- The allowlist folded the parameter name and the parser did not, so
`?SslMode=verify-full` was refused by neither and honoured by neither, and
saved as the `require` default.
The parked Supabase run is now handed to `open()` rather than read back off the
`resume` prop it was just assigned to, so restoring it does not depend on when
that prop reaches the component.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: route legacy AI entry points to sessions instead of the unmounted chat
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: keep createSession's workspace choice and revert pipeline hand-off
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: guard in-session step generation and restore AI action labels
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: keep AI Fix usable in-session and stop silent no-op hand-offs
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: neutral AI form assistant heading to match both branches
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* docs: state the AI form assistant branch rationale once
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* feat: auto-send AI hand-offs and keep in-session step generation in global mode
* fix: name the AI session in the entry point labels
* fix: claim auto-send reactively and queue programmatic sends mid-turn
* test: pin the auto-send claim going stale
* fix: stop the script drawer hand-off from abandoning its unsaved script
* fix: keep a stale hand-off prompt and close the pre-loading send window
* fix: only blank the composer for an intent this wrapper can claim
* fix: report composer edits only, never the mount-time draft
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: Ruben Fiszel <ruben@windmill.dev>
* feat(frontend): record the outcome of every AI chat tool call
The `ai_chat`/`tool` counter fired before execution, so nothing recorded
whether a tool call succeeded, and the three paths that refuse a call before
it runs recorded nothing at all.
Log once per call on whichever path ends it, keyed `<tool_name>:<status>`
over ok, error, declined, rejected and blocked_plan_mode. Per-tool totals now
need `split_part(key, ':', 1)` downstream; rows keyed by the bare tool name
coexist for up to 60 days.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* docs(frontend): state what the tool-call telemetry statuses do not cover
`ok` means the tool function resolved, which includes tools that report failure
by returning an error string, and a call abandoned mid-execution logs nothing.
Also pin that a hallucinated tool name reaches telemetry nowhere.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
* feat: audit the icon library against brand guidelines
Every icon component checked against its brand's own published guidelines for
correct artwork, current colours, and readability on both app surfaces.
- 127 marks now carry a per-theme pair (text-[#light] dark:text-[#dark]), applied
only where the brand publishes a reversed or dark variant. twMerge where the
component exposes a class prop, so callers can still pass sizing.
- 296 of 304 brand icons record their source in a comment above the <svg>,
including the rule where the brand imposes one (Google forbids recolouring,
Cal.com is deliberately greyscale, Oracle reserves the MySQL dolphin).
- BRAND_COLORS.md is generated from the components, so the table cannot drift
from the code.
- Marks that were unreadable on a surface: 13 -> 1 on dark, 9 -> 4 on light.
The remainder are blocked by trademark terms, not unfixed.
- Wrong artwork replaced where a first-party or CC0 source existed: PayPal is
the real three-colour monogram, Stripe is the bare S rather than an app tile,
gcloud resolves to Google's mark instead of a generic hexagon.
- Concept icons (CACertificate, DbIcon, Webdav, Asset*, Bcrypt) inherit
currentColor instead of hardcoding a colour.
Fixes a cross-component CSS bug: ten icons embedded <style> inside their <svg>.
Svelte only scopes a component's top-level style block, so those were injected as
document-global rules under names like .st0 and .cls-2, which four icons each
defined differently. WindmillIcon renders from the root logged-in layout, putting
.st0 { fill:#ffffff } on every page. Class names are now namespaced per icon.
Adds /kitchen_sink/icons, a gallery rendering every icon on both surfaces at once.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* feat: render brand icons in the surrounding text colour in control strips
A trigger picker mixing lucide glyphs (Webhook, Route, Database) with brand marks
(Kafka, GCP, AWS) read as two sets of controls once the marks became coloured.
Adds an .icon-mono utility that redirects descendant fills to currentColor, applied
by the container rather than passed to the icon. That is what makes it work on every
icon: GoogleCloudIcon has four hardcoded fills, no currentColor and no class prop, so
nothing passed to it could change its colour, and gradient-based marks cannot express
a monochrome variant at all without being redrawn.
- ToggleButton takes a monochromeIcon prop, opt-in since it is used app-wide.
- TriggersBadge, SidebarContent and QuickMenuItem (which backs GlobalSearchModal)
apply it unconditionally: these are uniform lists where one coloured entry among
grey ones reads as an error.
- DropdownV2 gains menuClass, because it portals its menu and a wrapper around the
component cannot style it. CaptureButton passes icon-mono through it.
!important is required because a handful of icons paint through style="fill:…", which
no selector outranks. Stroke is redirected only where one is declared, so shapes
carrying stroke="none" do not sprout outlines. Wrappers use display:contents, so no
layout box is added.
RowIcon is deliberately untouched — table rows keep showing brand colour.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* docs: close out the icon provenance gaps
Sources the 8 icons that had none and settles the 54 records whose author rated
itself below "high" and which no verifier ever reached — the earlier run's
verification stage was killed by a session limit.
46 confirmed as already correct, 11 citations corrected, 3 colours corrected.
Two changes were refuted and reverted by the adversarial pass:
- Mysql: the comment had the colour-to-shape mapping inverted. Rasterising the
first-party asset shows #00758F paints the dolphin and "My" while #F29111 paints
"SQL", not the reverse. The mark renders monochrome here, so nothing on screen
was ever wrong — only the note. Also rescoped the trademark sentence to what the
page literally says.
- AdobeAcrobatSignIcon: a "corrected" citation was rejected on evidence. The agent
claimed the original URL 404s; three fetches returned HTTP 200 with a genuine
Adobe SVG whose stylesheet is .a{fill:#584ccc}. Reverted to the original comment,
which also resolves the one unverified colour change on this branch — #584CCC is
current and first-party confirmed.
AmqpIcon is deliberately left with no brand colour: AMQP is an OASIS protocol, not
a vendor, and amqp.org publishes no palette.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* feat: add icons for 11 resource types that had none
19 hub resource types fell back to a generic Boxes glyph. One agent per brand went
looking for a square vector logomark from a first-party source, with an adversarial
check on everything it produced; 11 landed and 8 correctly came back empty.
Added: beamer, campayn, codat, comapeo_server, klaviyo, matteroom, mollie, motimate,
paychex, terra, vectara. Each records its source, and the components follow the
library's conventions — no <style> block (Svelte does not scope those, which is what
made .st0 leak document-wide), gradient ids prefixed with the component name.
The other 8 keep the fallback, which is the right outcome rather than a failure:
- actimo, adrapid, aero_workflow, matteroom-adjacent niche products publish their mark
only as raster. Upscaled PNGs would look soft beside 300+ vector marks.
- gfw redirects to Global Nature Watch and publishes a wordmark, not a mark.
- leonardoai, localcontexts, weatherapi, webscrapingai serve nothing usable.
No hand-tracing: approximating a mark from a screenshot is invention, not sourcing,
and a wrong logo is worse than the tidy fallback glyph.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* feat: lettermark fallback for reserved marks, and fix the resources table rows
Icons
- Brands that reserve their logo for licensees no longer ship it. BrandLetterIcon draws
the initial in the brand's own colour instead: recognisable, not their mark, and not
invented artwork. Adobe Acrobat Sign and MySQL use it, plus the eight resource types
whose brands publish no vector mark at all.
Adobe: "does not allow the use of its product icons by third parties in their products
or related materials of any kind, except through an Adobe partnership agreement".
On dark the letter inverts to a filled rounded square, because a mid-tone brand colour
chosen to read on white goes dim as a foreground on #2e3441. Where white-on-tile is
also dim, the tile takes a near-black letter instead; light-mode letters are darkened
along their own hue until they clear 3:1. Every pair was measured, not eyeballed.
- Google Docs was drawing a generic monochrome document glyph while carrying a comment
claiming Google's colours. Replaced with Google's own 192px product icon.
- Azure was drawn monochrome, justified by a comment citing Microsoft's rule against
distorting the mark — which drawing it monochrome is. Replaced with Microsoft's own
logo_azure.svg. Their terms say to use the icons "as they would appear within Azure";
permitted use is diagrams, training and documentation, which is recorded in the file.
- Adobe Acrobat Sign's artwork was a geometric "A" plus a squiggle, not Adobe's ribbon
swirl. Moot now that it is a lettermark, but the mark was wrong.
- Gradient, mask and clip ids in the new artwork are namespaced per icon; ids are
document-global and collide the same way the .st0 class names did.
Resources tables
- Description cells are a fixed two lines: min-h floors short ones, line-clamp ceilings
long ones, so every row is the same height. Full text on hover via title.
- Widened to 30rem (84 chars/line) and vertically centred. The clamp needs
display:-webkit-box, which stacks lines from the top, so the span sits in a
flex items-center wrapper rather than carrying the height itself.
- w-full min-w-0 max-w-[30rem] instead of a fixed w-96, so a narrow viewport shrinks the
column and truncates rather than forcing the page to scroll sideways.
- The actions column loses its border-l separator and right-aligns the "Shared globally"
badge, matching the rows that show buttons.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: icon-mono filled lucide outlines and missed currentColor brand marks
Two bugs in the monochrome utility, both from the fill rule being too blunt.
- Lucide icons are outlines: fill="none" with stroke="currentColor" and no fills on
their children. Forcing fill on every descendant overrode that none and turned each
glyph into a solid blob. The filled case is now scoped to svgs that do not declare
fill="none", and svgs that do only get children redirected if they declare a real
fill of their own — so a brand mark drawn as an outline still works.
- Brand marks that paint with currentColor carry their own text-[#hex] class, so
redirecting fills left them branded: MQTT stayed #660066, NATS #375C93. The svg now
inherits the container's colour, which is what actually makes them monochrome.
Also wires the sidebar's trigger section, which was never covered: those links render
through MenuLink, not the sub-item block that had the class.
Verified in the browser across all five shapes an icon can take — lucide outline,
hardcoded fill, currentColor plus brand class, outline root with filled children, and
inline style="fill:#..". Lucide keeps fill:none and a grey stroke; the rest follow the
container.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* refactor: dedicated monochrome trigger icons instead of a CSS override
Reverts the trigger surfaces to the icons that were there before the brand-colour
audit, as ./icons/triggers/ variants. A trigger picker lists brand marks beside lucide
glyphs (Webhook, Route, Database), so a coloured mark reads as a different kind of
thing rather than a peer.
Ten variants, restored from main where they were already monochrome: Kafka, NATS, MQTT,
AMQP, AWS, Azure, Nextcloud, Google, GitHub. Google Cloud is the exception — main's copy
is a greyscale rendition rather than currentColor, so it is rebuilt from the current
four-colour artwork with the fills dropped.
Separate files rather than the CSS override that was there, because coercion cannot work
in general: forcing fills to currentColor breaks lucide's outline icons, which are
fill="none" with a stroke, and marks that set their own text-[#hex] class ignore a fill
rule entirely. Both bugs were live. The .icon-mono utility, ToggleButton's monochromeIcon
prop and the DropdownV2 menuClass pass-through are gone with it.
index.ts documents which folder to use where: ./triggers/ for trigger surfaces, the
full-colour mark for the resource picker, AppConnect and docs, and keep the two in sync.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: trigger pages and global search still used the colour brand marks
The ToggleButtonGroup on each trigger page pairs a brand icon with a lucide Code
glyph, so GCP Pub/Sub rendered Google's four-colour mark next to a monochrome one.
Kafka, NATS, MQTT and the rest had the same wiring; they were just less obvious
because their marks are near-monochrome already.
Repoints all seven trigger pages and the global search nav entries at the
./icons/triggers/ variants. RowIcon is left on the full-colour marks: table rows
show brand colour by design.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: restore the greyscale GCP trigger icon, and show variants in the gallery
The trigger variant had been flattened to currentColor, which collapses Google's cloud
into one flat silhouette and loses the tonal steps that give it shape. The pre-audit
icon was greyscale, not monochrome — #B0B0B0 / #D0D0D0 / #E0E0E0 / #FFFFFF — so it is
restored verbatim from main.
Also globs icons/**/*.svelte in /kitchen_sink/icons so trigger variants render next to
the full-colour marks they shadow, labelled by folder. Comparing the two is the thing
this page was missing.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: flow trigger dropdown rows use the desaturated marks too
The flow-graph badge menu still rendered the full-colour brand marks next to
lucide glyphs. Route both dropdowns through triggerIconMapMono: the badge
itself keeps the colour mark, only the rows it opens change.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* feat: rank resource-type search results by best match
Searching the description is what makes `gdrive` findable as "google", but it
also means "google" matches a dozen types that only mention the product in
passing. Rank a match on the type's own name above any description match, and
break ties on where the match starts, so `googleai` leads and a description
opening with "Google OAuth token..." beats one mentioning Google halfway
through.
Applied to all three resource-type searches: the Resource Types tab (whose bare
term also only searched the name until now), the add-resource drawer, and the
schema-narrowing picker.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: trigger pages and global search show the full-colour marks
The desaturated variants belong to the two dense lists that sit beside lucide
glyphs -- the sidebar trigger list and the capture dropdown. Everywhere else a
brand mark stands on its own and should be the real one: the per-kind trigger
pages, the command palette, the capture table and the chat tool cards. Records
the rule in icons/index.ts so the next caller picks the right folder.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: address review round on the icon and resource-type work
- AppConnectInner went back to listResourceTypeNames for the list: /resources/type/list
is not on the public app domain's route allow-list, so a published app's resource
picker 403'd and, because the throw left connectsManual unset, stayed empty on every
retry. Descriptions now load best-effort behind it.
- Dropped DropdownV2's menuClass: nothing passes it; the flow-graph badge menu styles
melt's Menu, which has its own.
- icons/index.ts named two surfaces for the desaturated variants; there are four, and
the flow-graph badge and the menu it opens differ. Dropped the stale GCloudIcon note.
- GoogleCloudIcon takes width/height again: generic call sites resolve it through
APP_TO_ICON_COMPONENT and pass no size, so gcloud rendered at 16px after the remap.
- The path explainer is one ResourcePathHint component instead of the same copy twice.
- BRAND_COLORS.md recorded Ansible, Datadog, Deno, DeepL and Toggl as fixed; each
publishes a second artwork swapped in by class, so their dark hex and ratio were
wrong. Header no longer claims a generator that isn't in the repo.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: drop the duplicate gcloud icon and unblock the connect list
GCloudIcon.svelte was rewritten into the same four-colour mark as
GoogleCloudIcon.svelte and nothing pointed at it any more, so it was two files
drawing one logo waiting to drift apart.
The description fetch also sat on the critical path: the "Others" list showed
skeletons until a request for every type's full schema returned -- one that a
published app is guaranteed to get a 403 on. It now runs unawaited, and search
re-ranks when the descriptions land.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: AwsIcon and GoogleIcon take size again
The audit narrowed both to width/height with a 24px default, but every dynamic
call site passes size — RowIcon, the flow trigger badges, ToggleButton, global
search, the chat tool cards, the native-trigger page — so the SQS and Google
marks rendered at 24px wherever a smaller size was asked for. Both take size
again, keep width/height for the call sites that use those, and accept a class
so RowIcon's grey still applies.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: row-strip brand marks keep their colour
RowIcon greyed five of its seven brand marks with text-gray-400 while gcp and
azure rendered in colour. Now that AwsIcon accepts a class, the grey took its
wordmark but not its hardcoded #FF9900 smile, so the SQS row came out half
grey, half orange.
The rule this branch settled on is that only the four trigger menus desaturate;
a table is not one of them. Dropping the class from all five makes the strip
agree with the gcp and azure rows beside them, and with the lucide glyphs
staying grey since they carry no brand colour to keep.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
- `sslmode` was found by searching the query text, so it also matched inside
another parameter's value: `?application_name=sslmode=disable` passed the
allowlist on the parameter name and then parsed as a request to turn TLS off,
which both the wizard and the resource form saved and probed. Parsed with
`URLSearchParams` by exact name, with a test.
- `secretMark` read the variable with `decryptSecret` defaulted to true, so
every write decrypted a secret nothing reads and recorded the decryption --
including someone else's on the retry about to refuse it. It wants only
`edited_at`, which is returned either way.
- The probe gave up at 15s while the worker allows its Postgres connect 20s, so
a host that accepts the connection and never answers was cancelled and
reported as a missing worker rather than a failed connection.
- The create-mode region and project name did not report an intent change, so
renaming a project after a name collision left the failure naming the old one.
- Two comments described the code as it was before the claim mark became a
revision, and a doc comment outlived the field it documented.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`text-blue-500` is the marketing blue `#3B82F6`, which brand-guidelines.md
rules out in the app interface.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The wider side padding and lighter dialog heading were changing all 17 Modal2
dialogs to suit this one flow. They move behind an opt-in `formStyling`, taken
by the three dialogs this branch owns; every other Modal2 renders as it did.
Also drops two comments that cited a design approval rather than a constraint,
and shortens the blocks that had grown past the four lines AGENTS.md asks for.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: show runtime-detected assets in a run's Assets tab
* fix: address review nits on run assets tab
* fix: cap the run assets list and report when it is cut
* fix: cap run assets by asset, not by row
- useSupabaseOauth reports success as onAuthed, alongside the failures it
already reported. SupabaseResourceConnect was watching `authed` to find out;
it takes the callback instead, keeping the guard that stops an authorization
started elsewhere on the page from opening its dialog.
- SupabaseProjectStep loads its orgs and projects through a resource keyed on
the token, so the `loaded` latch goes and re-authorizing reloads rather than
keeping the lists from the expired session.
- SetupChecklist records what the user toggled and derives the open state from
it, a failed step defaulting to open. Recording the open state instead needed
an effect to force it, and that effect re-ran on every progress update, so a
description closed while anything was still ticking reopened. A close now
holds for the life of the checklist, including across Try again.
Leaves the message listener, which subscribes to another window.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Each was doing work a derived, a load callback or a real entry point does
better.
- The name conflict is kept with the name it was raised for and derived from
it. As an effect it was correct only because it never read what it wrote:
the pre-flight sets the message and the effect does not re-trigger, so adding
a read would have cleared it the instant it appeared. The message now also
comes back if the taken name is retyped, which is what the server will say.
- The default resource selection is seeded inside the fetcher that loads the
list, where "has the fetch settled" cannot be asked wrong.
- Reset-on-open becomes an exported open(), called by the settings page, so a
fresh run is set up by the act of opening rather than by a flag emulating
mount.
- The OAuth connects and the folder list become resources; supabaseAvailable
and folders are derived from them. defaultFolder takes the list rather than
reading it, so the fetch can seed off its own result.
Leaves the debounced path check, which is async with an out-of-order guard.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The wizard's connection check ran on the API server through two endpoints added
for it. That server is a different machine with a different identity, so the
answer was about the API server rather than about the worker that will run the
queries: a host reachable from one is not necessarily reachable from the other,
and IAM RDS and Azure workload identity authenticate as whichever process opens
the connection.
Run the privilege query as a preview job instead. A job goes through the
worker's Postgres executor, which is where `PgAuthMode::of` already picks the
authentication mode, and it takes either a resource value or a `$res:` path
exactly as a Postgres step does. Postgres composes the suggested GRANT
statements through `format('%I')`, so identifier quoting stays where it is
already implemented.
Removes `test_datatable_resource_connection` and
`test_datatable_connection_value`, and `connect_as_the_worker_would` with them.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: default an omitted app policy execution_mode to publisher
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* docs: drop stale comments claiming execution_mode is required
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: derive a raw app's policy on deploy instead of trusting the caller's
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* chore: pin the ee ref to the companion branch
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: vendor the raw-app policy derivation into the bundle job
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* docs: note the vendored raw-app policy bundle
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: derive the policy on a value-only raw-source update too
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: reject raw-app runnables whose shape yields an unusable grant
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: cache the new policy query and tighten raw-app runnable validation
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: let the policy bundle drift guard survive a CRLF checkout
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref to 23431f5cf1d627051ded89111bbf2e301e9db456
This commit updates the EE repository reference after PR #729 was merged in windmill-ee-private.
Previous ee-repo-ref: 0bdf8818fa115ad6b0d14f3117a18e8a580cce4d
New ee-repo-ref: 23431f5cf1d627051ded89111bbf2e301e9db456
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>
* fix(copilot): validate the version an approval stamps
* feat(copilot): let plan mode write artifacts, but never the plan
* feat(copilot): tell plan mode it may keep notes, not rewrite the plan
* feat: add empty state cards to list pages
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: animate trigger drawers on first open
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: distinguish filtered-empty schedules, reuse the rAF helper
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: hide the header create button while the empty state offers it
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Revert "fix: hide the header create button while the empty state offers it"
This reverts commit 98c57eede3.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: use the default variant for the empty state button
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* refactor: share hasActiveFilters from the filter searchbar module
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>