The CLI and hub buttons take the shape the hero gave them before the bar did —
`xs`, hint-coloured, the hub keeping its outbound mark — so a side trip does not
read as loudly as the thing the page is for.
The ellipsis is gone while the hero is on the page: the hero's cross already
puts it away, so a menu whose only item repeats that is a button that does
nothing new. It comes back when it has something of its own to carry, which is
the side trips on a narrow bar and the way back to a hero that has been put
away. That entry is now "Pin session hero to homepage", and "Remove session
chat" survives only as the cross's own words, where one action keeps one name.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A fork that had drifted stopped saying anything about its drafts: the banner
printed the draft count only while the fork was in sync, and the drift badges
replaced it the moment there was anything to deploy. A fork can be 86 ahead, 39
behind and holding 128 drafts; it now says so.
All four counts open the page that holds them — ahead the deploy direction,
behind `?dir=update`, conflicts the side they are resolved from before
deploying, drafts the drafts half. The button beside them still chooses a
direction on the reader's behalf; a badge means the one thing it names.
The colours and the icons are the compare page's, so the counts in the bar and
the rows behind them say the same thing the same way: ahead green, behind blue,
conflicts orange. Drafts share blue with behind and are told apart by a pencil
and by naming what they count, rather than by an arrow that would read as a
third direction. Both arrows are straight, here and on the compare page.
"Review changes" replaces "Review & Deploy Changes", and it renders in every
state. What the comparison has to say — still counting, or failed — is a mark
beside the button rather than a branch that replaces it: a fork whose count is
unfinished is exactly when a reader wants the page that counts properly.
The drafts banner outside a fork drops "This workspace has", so the count reads
the same wherever it appears.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A menu is where a thing goes when the bar cannot hold it, not where it belongs.
Above 1024px of bar, "CLI / MCP" and "Hub" stand as their own buttons; below it
they fold back into the ellipsis, which is still where they live on a phone.
The threshold reads `pageHeader.barWidth`, not the viewport, so it tracks what
the sidebar actually leaves behind. Home's trail is ~250px and its other
registered actions ~400, so the two labelled buttons fit from about 850 up;
1024 leaves margin before the trail would start truncating, and it does not
truncate at any width between 760 and 1600.
The ellipsis hides itself when it would open an empty menu, which happens only
once both side trips are in the bar and the composer is already on the page.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`check frontend build` has been red on every head for a day, through four review
rounds that each came back clean — none of the reviewers type-check, and they
say so in their own coverage sections. Ten errors:
The dev badge's `label` and `color` were typed `string | undefined`, but the
workspace rows carry `string | null`, so all seven of its call sites failed.
`forkAccentStyle` took the same widening.
A Svelte transition is called with the node it animates, and `stripArrive` and
`stripLeave` were declared with no parameters at all.
And `DevWorkspaceSetting` still imported `Badge` after its badge moved into the
shared component.
`svelte-check` now reports 0 errors. Note that it only agrees with CI once
`src/lib/gen` has been regenerated — the gitignored client goes stale against
main and invents errors CI never sees.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The refusal panel was hand-built from a div and two paragraphs with literal
colors, copied from the Runs page's own operator refusal. The frontend's rules
say to use the design-system component, and `Alert` already draws exactly this.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A fork does not carry the role. The server writes the forker's membership row in
the new workspace from the parent's `is_admin` and lets `operator` fall to its
`false` default, so an operator who forks lands in a full-rights copy of the
workspace they could only run things in.
`canCreateFork` refuses an operator, which takes the entry out of the workspace
picker, the session family picker and the session changes bar;
`editInForkAllowed` refuses one too, so no editor offers "Edit in fork". The
fork page refuses on its own, since a URL reaches it whatever the pickers show.
And a workspace the user is an operator in is no longer a base the fork form
will accept, so the base picker cannot route around the gate.
This hides the affordance; it does not enforce the rule. `create_fork` asks only
for membership — `require_admin` covers the dev-workspace case alone — so the
API still answers an operator who calls it directly. The server-side fix is to
carry `operator` into that membership row, the way the member-copy path already
does, and it belongs in a backend change rather than here.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Three nits from the review round.
The contrast bisection measured the hue at full float precision, but a browser
paints 8-bit channels: rounding them moved the result up to 0.025 off the target,
which took #993388 to 4.478 against white — under the AA floor the comment
claims. Quantizing the channels inside `paintedLuminance` puts the worst case
across 2880 hue and saturation combinations at 4.500, and your own fork's badge
measures 4.53 as painted.
An agent nobody can write still offered the rename behind its path segment: an
editor with two read-only fields, in place of the click that copies the path.
It is gated on `readOnly` now, the way a detail page is gated on `onSaved`. The
script and flow editors take the same gate from the `customUi` flags that
already decide whether their summary offers one — unreachable today, since the
editors own the band only on routes that pass no `customUi`, but the four
editors now answer the question the same way.
And the registry's doc named an `onEditName` field that has never existed.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Four things the local reviews caught in the rename work:
A detail page handed the band a `pathTrigger` whether or not it could save one.
Without `onSaved` — every script, flow or agent page the reader cannot write —
`SummaryPathDisplay` fell through to its read-only branch, which ignores the
`label` it was given and draws its own block: the trail lost its path and the
summary appeared twice, two lines deep, in a 44px bar. The page now offers the
trigger only where a rename can be saved, and the component renders what it was
handed rather than its own row.
The band gives up its own segment the moment a page offers a `pathTrigger`, so a
trigger whose own gate is closed left the trail with no path at all — for the
whole of an agent draft's load, not just a flush. Each gated snippet now draws
the path itself in that case.
`openFocus` named the path field as `#path`, which melt resolves with a
document-wide query; `Path` hardcodes that id at every call site, and the
popover is portalled to the body, so it lost document order to any other `Path`
on the page. It is scoped to the popover's own field now.
And `open` was an exported `$bindable(false)` on an optional prop, which the
frontend's own rules ban. No caller bound it, so it is local state.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The pen beside a name is gone. The name itself is the trigger, with a hover fill
behind it where the click opens an editor rather than going anywhere — the path
segment that only copies keeps no fill, which is what tells the two apart. A bar
with a summary field of its own (EditorHeader's condensed pane) still gets the
pen, since it has no name to hang the trigger on.
Clicking either half of a name opens the editor under that half with the cursor
in the matching field. The band draws the path and the page draws the summary, so
one popover cannot anchor under both: a page hands the band a `pathTrigger`
snippet and the band renders its path segment inside it, passing the classes its
own segment wears. Each editor supplies a second PathEditPopover for it, and
`focusField` names the field to focus — `#path` explicitly, since melt otherwise
lands on the row's first button.
Those snippets render from the band's tree, which outlives the values they read
by a flush, so each is gated on the value it reads. Without that an agent page
whose draft had not loaded threw inside the band and left the breadcrumb empty.
The trail now carries two weights: the segment that ends it is what the page is
about and takes `font-medium text-emphasis`, everything before it is a step
quieter. Which segment that is follows the route — the workspace on home (the
fork alone inside a fork), a list page's own name, an item's summary.
And it truncates instead of switching to icons at a breakpoint. The workspace
part yields first, the family name before the fork, then the path's folders, and
the item's own name last. The bar no longer wraps on a phone: a wrapping row
never squeezes anything, it hands the trail a line of its own and leaves it
whole.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Every surface that names a dev workspace drew the same blue badge, which said
nothing about which workspace it belonged to. One component, DevWorkspaceBadge,
now draws it: a fork with a colour fills the badge with that hue and writes the
label in white, and a fork without one keeps whatever its row had before.
The hue cannot carry white at a fixed lightness — HSL lightness is not perceived
brightness, so yellow and purple at the same L differ by several stops — so
forkAccentStyle solves the badge's lightness by bisection instead, taking the
lightest shade of the hue that still holds white at WCAG AA. Measured across the
wheel the contrast lands between 4.52 and 4.64.
The session picker opens with every family collapsed, since the fork a session
runs in is the session's own and not something that picker should offer to
change.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: strip NUL from ai agent output and job args before jsonb writes
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix: also strip NUL from tool call ids and citations
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* feat: count OIDC signatures and reuse server-side tokens
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* chore: update ee ref
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* chore: update ee ref
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* chore: update ee ref
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref to cbda28f632d773fd44e9bef8bea36dcb5e35b55d
This commit updates the EE repository reference after PR #847 was merged in windmill-ee-private.
Previous ee-repo-ref: cec12fc52eb1bb5d3f4f4ae489d86a64e96c6c0c
New ee-repo-ref: cbda28f632d773fd44e9bef8bea36dcb5e35b55d
Automated by sync-ee-ref workflow.
---------
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
* feat: refuse unscoped tokens of restricted jobs, add OIDC scopes claim
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix: keep restricted job_perms rows by a stamp that survives retention
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* chore: log refused unscoped job tokens at warn
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix: key the unrestricted cache by token, keep restricted rows on bulk delete
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix: clear a sweep stamp raced onto a queued job
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix: stamp restricted job_perms rows under a row lock
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* test: give the memo test's job token scopes
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* refactor: keep only the OIDC claim, drop the server-side refusal
A relying party can already tell a restricted job from the claim, which is
absent when the presented token carries no scopes. The auth-layer refusal,
its lookup cache and the job_perms retention it needed are removed.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref to 065c2c64c3904d5c0b2766b424210d34f9e54d2d
This commit updates the EE repository reference after PR #845 was merged in windmill-ee-private.
Previous ee-repo-ref: ea5354477cdf281598a816a5e16ad1f476d42afb
New ee-repo-ref: 065c2c64c3904d5c0b2766b424210d34f9e54d2d
Automated by sync-ee-ref workflow.
---------
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
* feat: choose build with AI or code when creating a raw app
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix: carry the picked data table into a new-app session and seed only on 404
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix: wait for data tables before build with AI and stop re-registering on edits
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix: create apps without an unusable data table and name session-seeded apps
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* feat: rename new-app choices and hint where to switch to AI later
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* feat: run the weekly ai evals on global and cli across sonnet, opus, gpt-6 and deepseek
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix: keep claude- prefixed and capitalised aliases in the cli model filter
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* feat: add drive.readonly and drive to gdocs oauth scope options
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix: describe what the gdocs drive scope grants in its label
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* feat: open a run as a pushed page on narrow runs page
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix: keep modifier-click multi-select and arrow keys on the narrow runs list
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix: leave the narrow re-run page once the re-run is sent, and clear the opened run on reset
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix: close the re-run options once a table re-run completes
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix: serve the raw-app preview shell sandboxed, load a blob copy in the editor
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix: give the preview shell the deployed app wrapper's sandbox flags
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* feat: confine jobs:read to the runnable paths it names
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix: filter job lists by read scopes alone and keep a job's own flow run readable
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix: drop the add a prompt for AI button from the run page
* fix: open the run page in the session preview from a plain chat button
* fix: rename additional prompt for AI to AI prompt
* fix: put the run page chat button beside advanced when no AI prompt
* fix: honor custom timeout in setup phases and name the limit hit
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix: label a shared-deadline phase timeout as the job's remaining budget
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix: register dbt deploy jobs so their phase timeout is not called custom
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
It claimed a click holds the sidebar card open. Nothing distinguishes a click
from a hover there — both call the same opener, and the pointer leaving
schedules the close either way — so on a desktop the card follows the pointer
and goes when it goes. Measured: hovering slides the rail from -213 to 5, the
click moves nothing, and the pointer leaving puts it back at -213.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Whether the sidebar starts detached depends on the role: an operator gets it out
of the way, a developer gets it docked. That default held only until the control
was pressed once, because the choice went to a single key read before the role.
From then on both roles wore whichever was pressed last, and the one that had
not asked for it could never get its own default back — switch a workspace where
you are an operator for one where you are a developer, and the sidebar stayed
detached.
Each role now keeps its own choice, and falls back to its own default until it
has one.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: search slow runs filters by time windows instead of timing out
* fix: cancel a pending runs page when its listing is reset
* fix: keep runs scan windows disjoint at microsecond precision
* fix: race the single runs request against the window scan
* fix: list the cursor window when a runs scan starts at the oldest job
* fix: show the runs history scan as an info alert
* fix: show runs scan progress as a bar and keep the session button on the searchbar row
* fix: stop a runs scan whose windows are no faster when narrower
* fix: right-align the runs searchbar row when the header wraps
* fix: keep the runs scan stop button inside a narrow list pane
The pen on an agent's page was handed the summary and the path but not the
labels, so the popover offered an empty list over a resource that had some. It
saves every field it shows in one write, and the resource endpoint sets the
column it is given, so adding a label there replaced the agent's own: an agent
labelled `production` and `scheduled` came back from one edit labelled only with
the label just added. A flow's page and a script's page both pass theirs; this
one now does too.
Verified against the running instance: an agent created with
`["production","scheduled"]`, one label added through the pen, reads back
`["production","scheduled","new"]` — and `["new"]` with the prop removed again.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A team plan canceled mid-month now keeps premium until the 1st, capped at the
executions its final invoice paid for. That reuses the past-due cap, so the job
refusal and the UI toast said "your last invoice is unpaid". Word both for a
canceled plan, and show it on the plan settings page.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
The panel was remembered as a width, sampled while it stood still. A window
resized during full screen left that width describing a layout that no longer
existed, and the way out pinned the band to it for the length of the move. It
is now remembered as the share of the row the panel takes, which the new row
width turns back into the right inset: leaving full screen at 1200px after
entering it at 1600px, the band starts at 628 rather than at 909.
The pane transition's duration is read from the property the timer is set from,
so the class cannot outlive the movement it describes, and two comments that
described earlier drafts are rewritten: the band's name in full screen comes
from the session's own header once it has mounted, and the tab strip's wrapper
carries a fade, not a measured move.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Full screen used to drop the session's name from the breadcrumb and leave the
tab strip inside the panel. The band now carries both: the session's name where
"Sessions" stood, and from the breadcrumb to the right edge one filled surface
holding the tabs and the preview's controls, the bar's full height.
The switch between the two layouts is the panel expanding to the left. The chat
column is resized to nothing rather than unmounted, so it can be seen taking its
place -- and so a toggle no longer tears down every mounted chat and rebuilds it
on the way back. Panes transition their width only while that switch is in
flight, since a splitter drag sets the same inline width and would trail the
pointer.
The band floats over the content in full screen as it already did beside a
panel, so only its right edge moves and the page below never shifts by the
bar's height. Leaving full screen, the panel travels back across ground the
band has already claimed, so it passes one layer above it for the length of the
move: pinned to the width the panel will end at, the bar would otherwise show
through as a white patch over the panel's left edge.
The breadcrumb also gains the acting fork, the way every other page shows it,
which replaces the "Acting on" control the session header carried.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A page in a session's preview panel loads in a frame that sets `nomenubar`, so the
layout renders the band with `navHidden`. That flag hid the whole breadcrumb —
which is what carries the page's name — while the actions beside it had no such
guard, leaving a row of controls belonging to nothing.
Hiding the trail is still right: it leads to the host's workspace and offers a
picker that would take the reader out of the session. So the breadcrumb gains a
`nameOnly` mode that drops the workspace part and its picker and keeps the page's
own name, and the band asks for it instead of rendering nothing. The summary line
loses the same guard, so an item in there shows its path and summary.
Only the pages whose own title the band took over are affected. Variables,
Resources, Schedules and Triggers still draw their own `PageHeader` and render no
band when embedded, so nothing is doubled.
The bar's left padding is there to sit a button against — the sidebar handle, or
the workspace disc, both of which carry their own margin. Embedded there is
neither, and the name started as bare text 8px from the edge, so that case takes
the same 16px the bar already keeps on its right.
Workspace settings drops the copy-id button from the band: the General tab names
the workspace ID with a field of its own, which is where someone looks for it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* feat(ai-chat): show per-file diffs for raw app edits
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix: keep reference runnable writes on the generic tool card
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix: extend diff row highlights across horizontal overflow
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix: collapse queued and running chat tool calls into a waiting row
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix: correct the waiting-row comment in groupToolRuns
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix: fold queued calls only behind a call in flight, after edit groups
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix: stop a waiting row before queued calls that form an edit or explore group
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>