mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-10-18 16:03:24 +00:00
datatable-external-instance-ui
9069
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
effdcd9915 |
fix: recover from a refused mcp read assertion, drop stale discovery (#10710)
* fix: recover from a refused mcp read assertion, drop stale discovery Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: drop the stale listing from the raw error, not the bounded payload Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
3468cb68b1 |
fix: drop sampling params on Claude models that reject them (#10708)
* fix: drop sampling params on Claude models that reject them Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs: scope the sampling-param claim to what was probed and split the bedrock test Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * test: build the disable body through the resolver instead of asserting a rejected shape Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: use the Gemini 3.1 Pro id that actually resolves Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: Bedrock Sonnet 5 cannot disable thinking, unlike the native API Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
3f07a1a803 |
feat: let the global AI chat call connected MCP servers as the user (#10656)
* feat: let the global AI chat call connected MCP servers as the user Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: address review findings on the chat MCP tools Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat: connect MCP servers from a predefined list in chat and agent steps Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat: show the OAuth redirect URL in the instance connect settings Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: clarify the OAuth redirect URL copy in instance settings Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: match the instance settings warning style and drop the redirect tooltip Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: use the standard warning alert for the redirect url mismatch Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: correct the GitHub token guidance in the MCP registry Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat: warn when an OAuth connect lacks the scopes an MCP server needs Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: request the connect's scopes when the oauth popup is opened directly Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat: connect an oauth-app MCP server without leaving the panel Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: seed connect scopes from the instance config only Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat: make the chat use only the MCP servers you turn on Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * refactor: align the MCP connect UI with the design system * feat: make a pasted url the default way to connect an mcp server * feat: show provider icons on the suggested mcp servers * fix: make both mcp sign-in paths behave the same and stop reloading on toggle * fix: clarify the mcp tool step's server field and drop its info alert * fix: name the mcp resource in the tool step and move the transport note into the connect box * fix: drop the redundant description on the mcp resource field * fix: make the mcp connections trigger icon-only * fix: scope enabled mcp servers to the account and address review nits * fix: wait for connect scopes and create session connections in the operating workspace * feat: move mcp connections into the chat's plus menu and fix review findings * fix: show mcp servers as checkboxes so off reads as a state * feat: give menu rows an on/off switch and use it for mcp servers * fix: lead the mcp menu rows with the switch * feat: keep the menu open while toggling and simplify the connect card * fix: ask for the server before the credential in the connect card * fix: show one credential path at a time in the connect card * fix: label the path field and move token guidance into its tooltip * fix: open straight into connect and keep the server menu scannable * feat: warn when an mcp connection lands outside your own space * refactor: require the workspace on the mcp connect components and rename the oauth child * fix: replace the oauth variable on reconnect and bound every mcp result * feat: show a connected server's provider icon in the connections list * feat: resolve mcp provider icons from the url and clarify the path field * style: align the mcp connect card with the design system surfaces * style: drop the redundant oauth support line and name the scopes oauth scopes * feat: keep the mcp connect card open in the connections drawer * feat: preopen the mcp connect card under the agent step resource picker * feat: resolve a typed mcp url to its registry entry and describe the token field * style: name both mcp connect actions connect * style: name the mcp oauth actions connect with the provider * style: say in the path description what the connect action will save * style: name the resource type in the mcp connect path description * feat: cache mcp provider icons and confirm disconnect in a modal * fix: keep the mcp menu switches live and the disconnect modal above the drawer * style: fall back to the plug icon in the mcp menu rows * fix: never destroy a foreign variable or resource when connecting an mcp server * fix: prove a token variable is ours before writing it and bound mcp search failures * fix: pin an mcp oauth popup to the target it was opened for * fix: bind an mcp credential to the server and popup it was requested for * fix: bound mcp tool calls with a deadline and drop stale server listings * fix: keep the disconnect confirmation handler returning void * fix: tie the mcp tool cache to the resource revision and the grant to its scopes * fix: verify mcp read-only server-side, keep oauth connector mounted Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
53eb94659b |
feat(telemetry): extend feature-usage tracking beyond AI features (#10681)
* feat(telemetry): extend feature-usage tracking to long-tail features Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs: describe telemetry as product feature usage rather than AI usage Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * refactor(telemetry): trim disclosure copy and drop unused pick origin Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(telemetry): count trigger fires per run and key hub picks from hub data Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(telemetry): slugify hub keys and order both writers' upserts Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(telemetry): key native trigger adoption by service so it matches fires Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * chore: update ee-repo-ref for native trigger adoption fix Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * refactor(telemetry): move feature-usage collection into the ee crate * docs: point feature-telemetry at the moved registry and rust writer * docs: correct the trigger-fire gate comment to match measured step counts * docs: put the private-build caveat on the verification step * chore: update ee-repo-ref to f079db9e7962a413b349c4ff8036080894f30771 This commit updates the EE repository reference after PR #725 was merged in windmill-ee-private. Previous ee-repo-ref: 055adb80416f9339c9a28ae7fbaeadad30d74959 New ee-repo-ref: f079db9e7962a413b349c4ff8036080894f30771 Automated by sync-ee-ref workflow. --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> Co-authored-by: Ruben Fiszel <ruben@windmill.dev> Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com> |
||
|
|
1a606b1856 |
fix flaky sessionState IndexedDB hydration race (#10692)
* test: fix flaky sessionState IndexedDB hydration race * test: fold logout into the login barrier helper |
||
|
|
68fc7825bb |
fix: refresh AI provider model defaults and capability metadata (#10690)
* fix: refresh AI provider model defaults and capability metadata Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: send explicit thinking disable for Claude and cap Opus 4.1 output Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: resolve mistral-medium-latest window and OpenRouter Claude 5 off Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: cover au. bedrock geo and Fable 5 caching, revert unverified mistral ladder Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: scope the Anthropic explicit disable to models that think by default Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: translate the reasoning off sentinel on the backend Anthropic path Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: translate the reasoning off sentinel on the Bedrock Converse path Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * refactor: share the reasoning off sentinel and make its translation testable Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
850b028778 |
feat: advertise the pinned artifact version in get_preview_status (#10691)
* test: let global evals seed the session's preview tabs Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat: advertise the pinned artifact version in get_preview_status Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * test: reject ambiguous preview-tab and artifact eval fixtures Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
60c5ad252a |
fix: keep a resource's linked secret reference in sync while renaming (#10693)
* fix: keep a resource's linked secret reference in sync while renaming Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: guard null resource args when renaming Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
633d7bcb2e |
feat: add trigger_history table with source tracking (#10696)
* feat: add trigger_history table with source tracking Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: gate trigger history reads on scopes and harden its writers Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: filter trigger history scopes in SQL and match the cleared-handler diff Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: record a trigger restore from the trashbin in its history Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: record bulk http trigger creates and document the recording boundary Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: lock the trigger row when capturing its history preimage Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: only record an auto-disable that actually flipped the schedule Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * chore: state the auto-disable invariant once instead of at four call sites Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat: render trigger history changes as a structured field diff Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: make a server-initiated disable atomic with its history row Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs: note that the auto-disable savepoint takes no pool connection Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs: note the flow fallback is the last chance to disable Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: never leave a trigger enabled because its history row failed Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: retry the disable history row instead of dropping it on first failure Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: use the design-system Button for the change-value expander Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: hold the trigger row lock across its disable history row Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: keep the history-loss alert out of the listener cancellation race Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: read the history workspace through the trigger-workspace seam Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
caa189868c |
feat(ai-sessions): add plan mode (#10057)
* feat(sessions): let an opener name the artifact version to show A tab already remembers the version a reader pinned, and re-pointing it keeps that pin. Plan mode needs the two intents that leaves out: a plan card scrolled up the transcript wants the version it proposed, and a plan going up for approval wants the current text with no pin at all. `ArtifactVersionTarget` is those two alongside the existing one: a number, `'latest'`, or omitted. Omitted still cannot double as `'latest'` — every artifact tool re-opens the document it just wrote, so taking that as a request to move would yank a reader out of the version they chose on every edit the agent makes. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat(copilot): add the plan-mode gate and tag plan-mode-safe tools Plan mode is a read-only posture, so something has to decide which tools it may still run. `Tool.planModeSafe` is that tag, and processToolCall fails closed on it: untagged means mutating means blocked. Deriving it from `requiresConfirmation` was not an option — unconfirmed mutating tools exist, and a posture that leaks one is not a posture. The gate runs twice per call. Before `validateBeforeConfirmation`, so a validator cannot reach out while planning; and again after the confirmation wait, because plan mode can be entered while a mutating tool's card is already pending, and that approval must not carry it through. Arguments are read one field at a time rather than through a parse of the whole call. `change_note` is optional and cosmetic, and a model that sends it as `null` would otherwise fail the object parse and take the plan down with it — the user being told there was no plan to approve, which is false. Also here, because refusing a call well needs them: a validator may now return the row the user reads and the result the model gets separately, a tool may word its own cancellation, and a tool may start work when its card appears rather than when it is approved. The gate is consulted before any of them. `shouldAutoAcceptToolConfirmations` is asked about the tool by name, because skipping the confirmation wait is itself an answer on the user's behalf and one tool must not be answered for. Deciding that without the name would put the exception out of reach of the only path that needs it. The gate stays inert until a chat supplies `isPlanModeActive`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat(copilot): give a session one versioned plan document The plan the user agrees to has to survive `/clear`, so it belongs to the session rather than the conversation, and a session holds exactly one. Its id is the session's, so the primary key is the constraint — there is no second row to mint, no index to maintain and no schema change at all. Every write reads the row it is about to replace inside the transaction that replaces it. Read outside, two tabs both see version N, both stamp N+1, and the later write silently drops the earlier one's text and its snapshot; IndexedDB serialises readwrite transactions over a store, so read and write together cannot interleave. Approval takes the same route but patches only the pointer: an approval computed while another tab was revising must not carry this tab's older content back over the newer text. Approval is `approvedVersion`, a pointer at a version, never a flag. Below the current version means the newest text is a proposal the user has not agreed to; absent means nothing here was ever approved. Only exit_plan_mode can leave the pointer behind, since every write outside plan mode carries it forward — an amendment the user's posture already trusts is still the agreed plan. Declining writes nothing at all: the refused proposal stands as the newest version, with the agreed one still in history. Nor can create_artifact confer approval. It asks for no confirmation, so the model writing a plan document is not the user agreeing to one; a plan written there holds the session's slot as a draft until a decision lands on it. That is also why the approved version is exempt from pruning. A plan approved at v1 and then planned against for twenty more rounds would otherwise lose the very version that stands as agreed, and with it the card that opens it, the banner offering it back, and read_artifact at that version. It is excluded from the pruning candidates rather than added on top, so the budget is unchanged and what survives simply stops being contiguous. The write reports whether the database took it. Most callers still degrade like the reads do, but a plan cannot: returning one the database refused would let the user approve and execute against a document that disappears on reload — a refused plan write raises instead. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat(copilot): add plan mode — the posture and its two tools enter_plan_mode asks to hold work; exit_plan_mode hands over a plan and, on approval, gives the posture back to whatever preceded it. Both carry `planModeSafe`, since a posture with no exit is a trap. Only the transition the current posture allows is offered, so there is no tool for leaving a posture the chat is not in. A planning round runs from entering plan mode to the proposal the user decides on. It remembers only the write it made, because nothing it does is undone — and that write is shared between the card's confirmation hook and the tool's `fn`, so the plan is on screen while the user is deciding whether to approve it rather than after. The round is identified by an epoch bumped on *entering*, not by the conversation. A chat rotation mid-approval must still let that approval hand the posture back; a round the user has since left and re-entered must not, or approving the old plan would drop them out of a read-only posture they just chose. Saving a proposal revises the session's plan document and creates one only when there is none — both halves in a single transaction, so a second tab proposing at the same moment revises the row this one wrote rather than racing it. Persistence failures hold the posture. Approval is reported only once both the proposal and the approval pointer are durable, so a plan the database refused cannot unblock mutating tools. The failure is reported from `fn` and no earlier: the write settles while the card is still waiting to be confirmed, and clearing that card from underneath the wait would take away the only control that resolves it. An auto-accepting posture answers for the user through one predicate, asked by every path that answers: the pending-card sweep, the confirmation itself, and the decision to skip the wait at all. enter_plan_mode never qualifies: YOLO means "stop asking and run it", and a call from a tool set snapshotted before the switch must not answer that with a read-only posture — whether its card is already pending or has yet to be registered. Plan mode lives in its own controller with a narrow view of the chat it runs in: it reads that autonomy state and asks for the two changes it can cause, rather than owning any of it. Plan mode is offered only in a session chat, and a session chat is GLOBAL for its whole life. The gate reads that mode, so `changeMode` refuses to move one out of GLOBAL rather than resting the invariant on a picker being hidden. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat(copilot): surface plan mode in the chat and the artifact list Plan mode is the only posture that refuses work, so the composer says so before the user types the request it is about to turn down: the mode pill is tinted whole rather than by its icon, and the empty placeholder carries the constraint in words. Teal, not the house green — green is the transcript's success colour a few rows up, and a mode signal in it would read as "this worked" rather than "this is held". A blocked tool renders as its own lean row naming the tool, not as an error: the call did what plan mode says it should, and "why can't it edit" is answered where it is asked. A plan card names the decision — proposed, approved, or not approved — and never the button, since a Stop and a posture switch resolve it too. Its button opens the version that card proposed, so a card far up the transcript still shows the plan it put forward rather than whatever the document has become since. The artifact list and the preview header both label the plan through one badge helper, so the two cannot disagree about what counts as one: a plan the user never approved keeps the plan icon and takes the neutral badge, leaving the teal to mean exactly one thing. In the viewer, an unapproved revision says so in a bar that cannot be scrolled past, with the version the user did agree to one click away. The autonomy picker became a table with one row per posture, so adding one touches a single place instead of four parallel switch statements. A version of a plan is read against the one the user approved, not against the newest: latest is only where the model happened to stop. So the approved version is never stale — its bar is teal and points forward to the draft rather than warning about it — the version in front of it is the draft, and anything behind it is history that is neither and takes no pill at all. The list opens a plan at the approved version for the same reason, which is what lets its pill say `plan` while an unapproved draft sits at the head. One helper answers all of it, so the list and the preview header cannot drift apart on what counts as the plan. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * test(ai-evals): exercise plan mode end to end A case a unit test cannot stand in for: it starts in plan mode against the real gate and the real exit_plan_mode, and grades whether the model researches and hands over a usable plan instead of guessing at one. The checklist does not grade what the harness does for the model — exit_plan_mode writes the plan document itself, so "saves the plan as an artifact" would pass on any run where the tool is called at all. The eval store seeds artifacts with history and mirrors the store's own approval rules, so a rename cannot promote a proposal the user turned down. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(ai-evals): import the plan-mode messages from the module that owns them `PLAN_MODE_MESSAGES` moved to `planModeMessages.ts`; `planMode.ts` imports it without re-exporting. Under vitest, which runs the frontend adapters, the stale import resolved to `undefined` rather than failing to link, so `global-planmode1-hands-over-a-plan` threw on the approval message after the posture had already been dropped and the tool withdrawn. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(copilot): state plan mode's constraint in neutral text The composer's two-tone placeholder becomes a plain "Read-only" beside the autonomy picker, next to where YOLO puts its own warning, and a blocked call's row drops the mode colour. Teal is left marking what the posture is — the badge, the version bars, the pill — rather than every call it refuses. ContextTextarea goes back to main with the accent: `placeholderAccent` had no other consumer, and the aria-label existed only because the accent blanked the native placeholder. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(copilot): hold the plan header's verdict until the snapshot lands Opening a plan at the version its reader approved pins a version behind the head, and until that read resolves `shownVersion` is still the head — so the header wore the draft's badge and its orange "not approved" bar over the very case the pin exists to serve, then flipped. The header now says nothing while `restoringPin`, as the body already does. Judging `pinned` instead would print the approved signal over text that is still the draft, trading a true transient signal for a false one. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(copilot): refuse a hand-over once plan mode has ended A response can carry two exit_plan_mode calls, and the tool list they run against is snapshotted before the first one restores the posture. The second then found the tool with plan mode already over: under YOLO every confirmation is answered for the user, so it wrote its own summary and stamped the user's approval on a plan no card had shown them. Refused in `validateBeforeConfirmation` rather than in `fn`, since `onConfirmationRequested` writes the document too. The maintenance path is untouched — a plan still gets revised outside the posture with update_artifact, which is what the tool's own description already tells the model to use. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
22eadab67d |
perf: resolve the worker external IP in the background (#10697)
* perf: resolve the worker external IP in the background `run_workers` awaited `external_ip::get_ip()` — an HTTPS GET to hub.windmill.dev — before spawning any worker, so every worker process paid that round trip before its first job pull. Measured on a CE debug build it was 120-450 ms of a ~200-500 ms startup, and behind a firewall the call does not fail fast: it burns its whole 5 s connect timeout, on every process start. That cost is per-job under EXIT_AFTER_N_JOBS. The value is informational (it is only written to `worker_ping.ip`, which the workers list displays so users can whitelist the address), so nothing needs to wait on it. It now resolves into a process-wide cache off the startup path, and `WORKER_EXTERNAL_IP` supplies it explicitly for deployments that know their egress address or have no egress at all. Until it resolves the ping carries no IP, which `insert_ping_query` now COALESCEs so a reclaimed row keeps the address the previous process wrote instead of being blanked. The main loop reports the IP as soon as it lands rather than on the next periodic tick, so a short-lived process still records it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: keep unknown worker IPs out of the whitelist alert Review follow-ups: - `WhitelistIp` filtered only the `'unretrievable IP'` sentinel, so the `'NO IP'` one a pending or failed lookup now leaves in the row would be offered as an address to whitelist. It filters both. - Register `WORKER_EXTERNAL_IP` in `ENV_SETTINGS` so operators can confirm from the instance settings view that it took effect. - The worker tracked whether it had reported the IP by re-reading the cache after each ping rather than remembering what the ping carried, so a lookup landing mid-ping marked it reported without it reaching the row. The value is read once and threaded through `insert_ping` / `update_worker_ping_full`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: report a sentinel IP once the lookup has definitively failed Keeping the previous process's address on a reclaimed `worker_ping` row is right while the lookup is still in flight, but not once it has failed: the row would advertise an address nothing has confirmed, and the whitelist alert would offer it. A failed lookup now reports `UNKNOWN_IP`, leaving NULL to mean "in flight". `WORKER_EXTERNAL_IP` is rejected when longer than the `varchar(50)` column rather than panicking the worker on its initial ping, which is a hard failure. Adds the regression guard for the `ON CONFLICT` semantics: reverting to `ip = EXCLUDED.ip` would compile and blank every reclaimed row. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: keep the agent initial ping acceptable to older servers An agent worker routinely runs against a server of a different version, and one predating the background lookup rejects an initial ping carrying no IP — which `run_worker` turns into a panic, so a newly upgraded agent would crash-loop against it. The not-resolved-yet case goes over the wire as the sentinel instead, and the server maps it back so a reclaimed row still keeps its address while resolution is pending. Also documents `ip` as the one conditional exception to `insert_ping_query`'s "only `started_at` and `jobs_executed` survive a restart", and adds `WORKER_EXTERNAL_IP` to the README env-var table. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: deliver the resolved IP to servers that only take it at registration A server predating the background lookup applies `ip` from the initial ping only, and ignores it on the periodic ones. An agent registering before its lookup resolves would therefore keep the sentinel forever on such a server, where it used to report its real address. It registers a second time once the address is known, skipping that when the address is still unknown, when the server is reached over SQL and needs no second registration, or once a job has run, since registering clears the row's current job. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: re-register the resolved IP even after a job has run Gating the second registration on "this process has not run a job yet" meant an agent that pulled queued work before its lookup resolved never delivered the address to a server that only takes one at registration. No job of the worker is in flight where that runs, so the gate bought nothing beyond the last job's id, which the next job refills. Documents the two cases where WORKER_EXTERNAL_IP stops being an optimisation and becomes the only way to report an address: an agent against such a server, and a process shorter-lived than the lookup. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * revert: drop the WORKER_EXTERNAL_IP escape hatch Supplying the address by hand skips the hub lookup, which is not something to make easy. Resolving it in the background is what keeps it off the startup path; opting out of it is a separate decision this does not need to take. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: distinguish an IP never established from one that could not be retrieved `NO IP` was doing double duty: the column default for a row whose lookup has not resolved, and the marker for one that failed. An operator reading the workers list could not tell "not resolved yet" from "this instance cannot reach the hub", and the latter is the actionable one. A failed lookup now reports `unretrievable IP`, which is also what it reported before the lookup moved off the startup path. That leaves `NO IP` meaning only "no address established", which is what an agent sends while its lookup is in flight and what the server maps back to "unresolved" — so the wire sentinel no longer collides with the failure marker, and an agent delivers the failure to a server that only reads an IP at registration. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
9334727d99 |
feat: stream audit logs in batches when a page is slow to load (#10695)
* feat: stream audit logs in batches when a page is slow to load Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: bound streamed page size and clear stale rows on stop Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: keep the runs batch cap and drop rows of a replaced query on failure Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: ignore stop once a load has settled and reset paging when one fails Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * chore: update ee-repo-ref to aab7da6e1f8b1fadacc2208913a5d6596f06f922 This commit updates the EE repository reference after PR #727 was merged in windmill-ee-private. Previous ee-repo-ref: 59ba8d7ce9ce1de0814b159b3813c2ac2a49239a New ee-repo-ref: aab7da6e1f8b1fadacc2208913a5d6596f06f922 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> |
||
|
|
80a18ec284 |
chore(main): release 1.789.0 (#10670)
* chore(main): release 1.789.0 * Apply automatic changes --------- Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com> |
||
|
|
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> |
||
|
|
c3b2275864 |
docs(agents): rework agent context, fix dev-env docs, vendor skills (#10667)
* docs(agents): scope agent guidance to where it loads AGENTS.md loads in every session. Three of its sections only ever applied to one directory, and docs/autonomous-mode.md was unreferenced by anything in the repo, so none of its content was in effect. - Move "Verifying Backend Changes" to backend/CLAUDE.md, "Verifying Frontend Changes" and "Banned Patterns" to frontend/CLAUDE.md. They now load when working under those directories, which is when they apply. - Update the two cross-references that pointed at the moved sections (pr and svelte-frontend skills). - Delete docs/autonomous-mode.md. Its "don't stop early" half is already in .webmux.yaml's oneshot system prompt, which actually loads; its trigger was bypassPermissions, which does not imply an absent user; and it restated AGENTS.md and the pr skill with copies that had drifted (hardcoded ports, relative screenshot paths). Salvaged the UI traps it uniquely documented into frontend/CLAUDE.md and dropped the three stale profile references. AGENTS.md drops ~3.6k characters with no guidance lost. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs(agents): guidance for building a feature — reuse, telemetry, live verification Three recurring gaps, all cases where a pointer existed but nothing triggered on it. Component reuse. The svelte-frontend skill documented three components with props, which reads as the whole catalog; the barrel exports 23 and common/ has 34 subdirectories against those 23. So "never use raw HTML elements" was an instruction agents could not follow. Added a mandatory discovery step: read the barrel, grep the tree, and treat the documented three as examples. Brand guidelines. frontend/brand-guidelines.md is 34k characters referenced by bare path, which nothing opens speculatively. Added a table mapping what you are building to the section that governs it, entered with grep rather than a full read. Product telemetry. feature_usage has 14 registered actions across three features, and an unregistered (feature, kind) pair is dropped by valid_feature_usage_event with a bare continue — no error, still a 204 — so frontend-only instrumentation silently records nothing. New docs/feature-telemetry.md carries the criteria for when to instrument, the four-step recipe including the allowlist and the InstanceSettings disclosure, and the privacy rules. Raised in the plan for user-facing work, not as a separate question, and not at all for bugfixes or refactors. Also: validation now ends at exercising the change on the running instance, with standing permission to spin up whatever that takes. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(dev): correct the worktree dev-environment guidance Several things agents were told to do did not match what the machine does. - Env discovery pointed at .env / .env.local / backend/.env. In a webmux worktree the real values are in $(git rev-parse --git-dir)/webmux/runtime.env (BACKEND_PORT, FRONTEND_PORT, DATABASE_URL, CARGO_FEATURES, WM_DB_NAME), sourced by every pane and undocumented. Reading it is also not blocked by the Read(**/.env) deny rules, which the old instruction walked straight into. - The database name rule said branch-with-underscores. worktree-common.sh uses the worktree directory basename, and Postgres truncates at 63 characters, so branch hugo/win-2340-… resolves to windmill_win_2340_…_and_eval with no hugo_ prefix and the tail chopped. A wrong DATABASE_URL guts the sqlx cache. - The restart procedure said "tmux pane 1" and sent keys to an undefined <pane1>. Pane 1 is the backend under the full profile and the frontend under frontendOnly. Replaced with finding the pane by pane_current_command, recovering the live feature set from the running process (CARGO_FEATURES in runtime.env only records what the pane started with), and restarting in place. - Added recovery for an orphaned backend holding the port: it reparents to systemd when its shell dies, so it survives anything that looks like cleanup. Three checks before killing a single pid, because pkill -f windmill takes out every sibling worktree. - Agents spawned their own servers because AGENTS.md opened by telling them to. Now it checks for the existing panes first; the spawn commands are scoped to a plain checkout. - New EE worktrees branched from the EE repo's local main, which nothing fast-forwards, so they started behind the commit pinned in backend/ee-repo-ref.txt — the one CI builds against. They now base on the pin, falling back to main only when it is unreadable. - Enabled webmux autoPull so local main stays current; new worktrees are branched from it. Documented what WM_CLONE_DB does, including that it terminates every connection to the base windmill database. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat(skills): vendor grilling/architecture skills; tighten PR ready and review rounds Vendors five skills from https://github.com/mattpocock/skills (MIT, pinned at 84fdeffd12f2ee307994d1eb6feb48173b6e0502). They are one dependency closure: grill-me is a stub that runs grilling, and improve-codebase-architecture draws its vocabulary from codebase-design and its CONTEXT.md upkeep from domain-modeling. .agents/skills/UPSTREAM.md records the license, the pin, and the four local deltas so a refresh stays a diff: - flattened the upstream engineering/ and productivity/ split - rewrote bundled-file links to repo-root paths, since relative links break when read through the .claude/skills symlink - dropped the upstream agents/openai.yaml packaging metadata - removed every ADR path. This repo has not adopted ADRs, and a skill that offers to create them is how the practice arrives by side effect rather than by decision. PR workflow changes, all in the pr skill: - A round that never starts is usually a conflict with main, not a CI outage. Resolve by merging, not rebasing — a rebase rewrites the head SHA that round verdicts and the clean-round marker are keyed to. If the merge advances backend/ee-repo-ref.txt, the EE worktree has to follow or cargo check --features private compiles a tree neither the author nor CI intends. - A clean round no longer means an automatic flip to ready. Wide blast radius (*_ee.rs, migrations, OpenAPI or the generated client, auth paths, shared worker infrastructure, a new public surface) asks first; self-contained changes flip. Unattended, the judgement holds and the action degrades: flip the small ones, leave the rest at a clean draft with the reason in the PR body. - Rounds that never converge are usually structural. After three without convergence, stop, name the module the findings cluster around, and suggest improve-codebase-architecture rather than burning more CI. AGENTS.local.md (gitignored, with CLAUDE.local.md importing it) holds the ready/ask calibration, recorded as dated observations rather than a rule. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs(dev): state that each worktree gets its own fresh database The per-worktree section warned which DATABASE_URL to use but never said where the database comes from: the post-create hook creates and migrates a new one per worktree, so it starts with none of the main instance's workspaces, scripts or flows. WM_CLONE_DB was documented only as a comment in .webmux.yaml, which reads as how things work rather than as a per-project opt-in. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * chore(sqlx): script the cache backup/restore instead of documenting it The update-sqlx skill spelled out a cp/comm/rm dance around `cargo sqlx prepare`, which empties backend/.sqlx before regenerating — a failed run leaves the cache gutted (observed: 2350 -> 142 entries), and a --all-targets run in a CE checkout fails that way every time. Three problems with documenting it: - The backup path was the literal /tmp/sqlx_backup, shared by every worktree. Two concurrent runs overwrite each other's backup, which is the only thing standing between a failed prepare and a gutted cache. - The restore was a copy-pasted `rm -rf .sqlx && cp -r ... && cp ...` chain. - Skipping the backup is what turns a routine failure into a lost cache, and a convention is easier to skip than a command. sqlx-cache.sh has backup / newq / restore, keeps state in a per-worktree directory, and leaves the judgement call where it belongs: `newq` prints each added entry's query field for review, and only `restore` writes them in. Also adds the general rule that scratch files belong outside the checkout — anything written into the tree has to be deleted again, and rm prompts each time. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs(agents): state why a routine cleanup prompts, and where scratch goes The guard hook already auto-allows a plain rm whose operands are under /tmp or inside a git checkout in $HOME, so deleting a temp dir or a stale .sqlx entry costs nothing. What prompts is the command shape: the hook's tokenizer defers on &&, ;, redirects, quotes and $VAR, so a chained cleanup falls through to the Bash(rm:*) ask rule. That was recorded only inside a paragraph about screenshot file paths in frontend/CLAUDE.md, where nobody looking for it would find it. Stated in Core Principles instead, alongside the rule that scratch belongs outside the tree — for the reason that actually applies, which is not committing junk rather than avoiding prompts. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * chore(security): deny agent edits to the permission hooks and project settings .claude/hooks/guard-rm-outside-tmp.sh and guard-main-branch.sh are the enforcement points for everything the permission rules are meant to catch, and nothing stopped an agent editing them. One sed -i disables the guard for every later command, silently, and the deny list in .claude/settings.json has the same exposure. Defence in depth rather than a boundary: an agent with arbitrary bash can still delete, and this may only close the Edit-tool path if Bash writes are not covered by Edit deny rules. It costs nothing and removes the cheapest way to turn the guards off. Changing them now means editing the files by hand, which is the intent. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: address review round findings on head |
||
|
|
a91d55769d | chore: pin git-sync scripts to hub 28903/28904 (cli 1.787.0) (#10682) | ||
|
|
6fbc3fccb6 | fix(flow): pass the flow's worker tag when testing a loop iteration (#10680) | ||
|
|
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> |
||
|
|
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> |
||
|
|
435fbaece0 |
stop websockets resurrecting a reclaimed dev server (#10676)
* fix: stop websockets resurrecting a reclaimed dev server * docs: condense the websocket invariant comment * test: stub fetch suite-wide so waking cannot hit a real dev server * fix: let websockets join an in-flight start |
||
|
|
2ff8681715 |
chore: add dev server supervisor to cut idle vite dev memory (#10672)
* chore: add dev server supervisor and dev-only polling dormancy * fix: address review findings in dev supervisor * fix: support https mode and bound the idle reaper in dev supervisor * fix: persist dormancy install guard and hold the reaper during startup * chore: run worktree frontends under the dev supervisor * fix: keep app websockets working and reap children on sighup |
||
|
|
b39860235c |
chore(main): release 1.788.0 (#10664)
* chore(main): release 1.788.0 * Apply automatic changes --------- Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com> |
||
|
|
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> |
||
|
|
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> |
||
|
|
2ac3e64fe2 |
chore(main): release 1.787.0 (#10657)
* chore(main): release 1.787.0 * Apply automatic changes --------- Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com> |
||
|
|
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> |
||
|
|
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> |
||
|
|
20953a0c67 |
chore(main): release 1.786.1 (#10652)
* chore(main): release 1.786.1 * Apply automatic changes --------- Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com> |
||
|
|
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> |
||
|
|
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 |
||
|
|
45a6e4932a |
chore(main): release 1.786.0 (#10649)
* chore(main): release 1.786.0 * Apply automatic changes --------- Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com> |
||
|
|
66c0d1251d |
fix(copilot): read an artifact inside the transaction that revises it (#10647)
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
a65a184a80 |
chore(main): release 1.785.0 (#10626)
* chore(main): release 1.785.0 * Apply automatic changes --------- Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com> |
||
|
|
442bbcc1fc | align variable value font size with the rest of the table (#10643) | ||
|
|
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> |
||
|
|
a8816b896d |
feat(ata): prefer the npm proxy when the instance configures a registry (#10632)
* feat(ata): prefer the npm proxy when the instance configures a registry * fix(npm-proxy): cap tarball extraction and stop pinning a failed config probe * fix(npm-proxy): keep large packages cacheable by using a single shard * fix(npm-proxy): cache the archive so a large package is served, not refused * fix(npm-proxy): read archives off the runtime, keeping only what types need * fix(npm-proxy): charge a retained entry for what it allocates, not its bytes * fix(npm-proxy): size retention for real packages and read the manifest back * fix(npm-proxy): charge path bytes and pin the manifest read-back * fix(npm-proxy): stop retaining past the budget instead of refusing the package |
||
|
|
a02a97ce3b |
fix(parser-py): keep first param when def main( line has trailing comment (#10586)
* fix(parser-py): keep first param when def main( line has trailing comment Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * chore: bump windmill-parser-wasm-py to 1.782.0 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
e54b6a914c |
fix(ata): fall back to the npm proxy when the CDN request fails outright (#10630)
* fix(ata): fall back to the npm proxy when the CDN request fails outright * fix(ata): surface proxy failures and guard the body read too * docs(ata): state the proxy catch's constraint, not its history * fix(ata): log a failed proxy d.ts fetch, which callers discard |
||
|
|
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 |
||
|
|
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>
|
||
|
|
cf3ddaa3cc |
chore(main): release 1.784.0 (#10603)
* chore(main): release 1.784.0 * Apply automatic changes --------- Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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>
|
||
|
|
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> |
||
|
|
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> |
||
|
|
4a69cd616e | keep the logins state declaration from narrowing to undefined (#10618) | ||
|
|
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> |
||
|
|
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 |
||
|
|
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> |