* refactor: make the app policy's principal the authority for its identity
* fix: align the app backfill with the sibling migration and audit the uncached address
* chore: refresh the sqlx cache after rebasing onto the merged base
* fix: resolve the app execution address uncached, it decides the job's authorization
* chore: cache the EE queries at the ref this branch pins
* chore: cache the EE queries at the ref this branch pins
* fix: derive the app draft's on-behalf-of address on read
* chore: cache the query the draft derivation test added
* fix: derive the app identity on the draft-table and version reads too
* docs: state the draft resolver's authorization contract
* fix: resolve a draft's principal against workspace membership only
* chore: cache the membership lookup the draft resolver added
* fix: drop an unresolvable draft's address instead of leaving it stale
* perf: evict the address cache on change so app dispatch can read it
* fix: evict on superadmin role changes, not only address changes
* refactor: make the app policy's address optional instead of derived on read
* fix: follow an external superadmin's rename into the apps that name them
* docs: state the removal gate once, and correctly
* refactor: drop the app-policy version constant that gated nothing
* docs: drop the last reference to the removed constant
* perf: read the address cache everywhere now that eviction reaches every replica
* fix: keep persisted addresses off the cache the poller evicts asynchronously
* docs: state where the cached address is accepted and where it is not
* docs: keep the cache rule in one place and drop the stale premise
* docs: sort the two lookups by how long a wrong answer lives
* fix: resolve the schedule address uncached where it is written to the row
* docs: name the release this actually ships in
* perf: evict a superadmin's key per workspace instead of the whole cache
* fix: evict every alias a superadmin principal can be spelled as
* docs: describe the trigger as it is
* docs: cover the round-tripped read in the cache rule
* docs: record why a stale dispatch address cannot escalate
* fix: validate a dispatch address against the principal's live binding
* fix: carry the validated address through to the job row and token
* fix: record the validated address on the job row, not the one handed in
* test: run the substep tag check as the non-superadmin it means to test
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JY4bBCR1q2c5XB8s2r7Ysc
* fix: rewrite a stored app address that disagrees with its principal
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JY4bBCR1q2c5XB8s2r7Ysc
* docs: record the accepted staleness window of the cached dispatch address
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JY4bBCR1q2c5XB8s2r7Ysc
* fix: record the validated address on the job's audit row
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JY4bBCR1q2c5XB8s2r7Ysc
* docs: record the accepted rename race of pre-transaction identity resolution
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JY4bBCR1q2c5XB8s2r7Ysc
* docs: separate the app's stored address from the derived one in the resolver doc
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JY4bBCR1q2c5XB8s2r7Ysc
* docs: describe the job identity fast path the push comments skipped
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JY4bBCR1q2c5XB8s2r7Ysc
* fix: backfill a legacy group-prefixed username as the group it names
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JY4bBCR1q2c5XB8s2r7Ysc
* fix: resolve a schedule edit's identity before opening its transaction
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JY4bBCR1q2c5XB8s2r7Ysc
* fix: never resolve a disabled member to a same-named superadmin
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JY4bBCR1q2c5XB8s2r7Ysc
* docs: state what the email-change notify buys, and rewrap two comment lines
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JY4bBCR1q2c5XB8s2r7Ysc
* fix: keep a group's runnables when offboarding a legacy group-prefixed member
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JY4bBCR1q2c5XB8s2r7Ysc
* fix: read the app author from the stored address, as execution does
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JY4bBCR1q2c5XB8s2r7Ysc
* docs: record the rename race's full consequence as a known, accepted limitation
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JY4bBCR1q2c5XB8s2r7Ysc
* docs: record the keep-target group address case as a known, accepted limitation
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JY4bBCR1q2c5XB8s2r7Ysc
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: show when the last git auto-pull status was recorded
* chore: bump ee-repo-ref for the auto-pull status fix
* fix: show the git auto-pull status age with TimeAgo instead of a year-less date
* test: pin that a stale auto-pull recovery cannot overwrite a newer state
* chore: bump ee-repo-ref for the conditional auto-pull recovery
* fix: keep TimeAgo counting past the first hour in noSeconds mode
* chore: bump ee-repo-ref for the clear_auto_pull_failure contract note
* fix: guard TimeAgo's boundary scheduler against invalid dates and pin same-head newer failures
* chore: bump ee-repo-ref for the timestamp-guarded auto-pull recovery
* test: cover a same-second newer failure surviving a stale auto-pull recovery
* chore: bump ee-repo-ref for the whole-failure recovery match
* test: name the recovery helper after its input, not its staleness
* chore: update ee-repo-ref to c6df9fdd9826efb40d3586a9f97d17dee98ac6ef
This commit updates the EE repository reference after PR #793 was merged in windmill-ee-private.
Previous ee-repo-ref: 6aff80b80cae4944a4a78a6b9244019bc37f368b
New ee-repo-ref: c6df9fdd9826efb40d3586a9f97d17dee98ac6ef
Automated by sync-ee-ref workflow.
---------
Co-authored-by: Ruben Fiszel <ruben@windmill.dev>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
* fix: bring back Publish to Hub for scripts
Publishing to the Hub moved to the folder-level flow, which publishes a
whole project and needs a workspace admin. That left no way to share a
single script, which is what private hubs mostly use the Hub for.
Restore the "Publish to Hub" item on the script detail page and in the
script list row menu. Both open the Hub's script submission form prefilled
with the script, on whichever Hub the instance is configured to use, and
are hidden when the instance disables the Hub. Flows and apps still reach
the Hub only inside a project.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: open the Hub tab before fetching, and hide Publish to Hub from operators
The script list row has to fetch the script before it can build the Hub
URL, and Safari refuses window.open after an await, so the tab never
opened there. Claim it inside the click with claimTab(), point it at the
Hub once the script loads, and close it with a toast if the fetch fails.
A blocked popup falls back to a late window.open, and says so if that is
blocked too.
Operators can't write scripts, so the row menu now hides the item from
them, as the script page's menu already does. The script page opens the
Hub with noopener, and scriptToHubUrl takes the script instead of eight
positional arguments.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: keep each dev server's session when worktrees share a host
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: keep dev auth cookies host-only on non-localhost hosts
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: keep the shared dev auth cookie with ISOLATE_DEV_AUTH=0
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
* feat(ai-sessions): turn skills on by default, and group them by folder
A skill is instructions the workspace wrote for the assistant to use, so what
carrying one costs is context rather than access. Selecting each one before it
applied made publishing a skill a two-step affair, and left most of them unused.
Skills now default to on. No storage is rewritten to get there: the preference
keeps its key and holds a decision per path, so the older array of enabled paths
still reads as "these were on" and only the paths nobody decided about move. MCP
servers stay opt-in through the same factory — their tools reach an external
system, which is a different question from context.
The Skills settings list groups into a tree once skills span more than one
folder, with a switch per folder acting on everything beneath it, and the list
answers the keyboard: Up/Down walk it, Left/Right fold, Space flips the switch
under the highlight, Enter opens the skill.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JWUQ867ZJCZJmkWUxqHija
* fix: give a modal the option to stand only as tall as the window
`AIPromptsModal` asks for 1000px of height, which is taller than a laptop
window: the dialog then scrolled inside the overlay while its list scrolled
inside the dialog — two scrollbars, one of them moving the modal itself. The
cap `Modal2` appeared to have, `max-h-screen-80`, is defined nowhere in the
tailwind config, so it never applied to anything.
`fixedHeight="viewport"` is a new value that stands as tall as the window
allows. Deliberately a definite height rather than a max-height: bodies here
size against the box with `h-full` / `grow min-h-0` and scroll inside it, and a
max-height leaves them nothing to resolve against — they grow past the surface
instead. Every existing size keeps the height it has today, so no other modal
moves. The two classes that resolved to nothing are removed.
The prompts modal and the assistant settings modal take the new value; both
already scroll inside themselves.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JWUQ867ZJCZJmkWUxqHija
* fix: pin read_skill's gate in its test, and say who a delete affects
The refusal `read_skill` gives for a path that is not a skill changed shape —
it checks the workspace listing now, not just the off-switch — and its test was
still asserting the old wording against an unmocked listing.
The delete confirmation said everyone "who selected it" loses the skill, which
stopped being true when skills started defaulting to on.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JWUQ867ZJCZJmkWUxqHija
* fix: keep the keyboard walk when the list scrolls under the pointer
The mouse takes the skills list back on a real movement over it, not on
`mouseenter`. The browser fires that one whenever rows arrive under a
stationary pointer — every scroll the keyboard itself causes, and every folder
collapse — so walking Down past the bottom of the list handed control back to a
mouse nobody had touched, and the next press restarted at the top.
Also from the review round: the "+" menu sorted skills on-first, a key that is
constant now that they start on, and pushed the one row it did move — a skill
just turned off there — out of the shortcut that turns it back on. It orders by
path. The remaining "selection" wording follows the vocabulary the rest of this
change moved to.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JWUQ867ZJCZJmkWUxqHija
* fix: carry the keyboard walk on from the row the mouse left it on
Handing the list to the mouse dropped the highlight, so the next arrow press
started again at the top. It moves to the row under the pointer instead —
invisible while the mouse leads, since drawing and acting both wait on the
keyboard being in charge, and exactly where someone would expect the walk to
carry on from.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JWUQ867ZJCZJmkWUxqHija
* fix: cap the AI prompts modal from its own call site
Reverts `Modal2` and the assistant settings modal to what they were. The
prompts modal asks for `xxl`, 1000px, which is taller than a laptop window, so
the dialog scrolled inside the overlay while its list scrolled inside the
dialog. It now passes `max-h-[80vh]` through the `css.popup` the component
already forwards.
The height stays definite underneath, which is what lets the list bound its own
scroller, and nothing outside this one modal changes.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JWUQ867ZJCZJmkWUxqHija
* refactor: drive the skills list highlight with useListHighlight
The Tools section next door already had this: `useListHighlight` owns the
highlighted index, wrapping, `scrollIntoView`, and the rule that a scroll under
a resting pointer must not hand the list back to the mouse — the bug this
section rediscovered the hard way. Reusing it drops the parallel implementation.
What stays local is what is actually a tree: Left and Right fold a folder or
step into it, Space flips the switch under the highlight, and Enter opens the
lit skill. `restingIndex` is what keeps the highlight on a folder through a
fold, where a search would instead send it back to its top hit.
The keys are answered at the window rather than on the list: leaving the editor
parks focus elsewhere, and a container-scoped handler goes silent when it does.
`move` is now returned by the composable, for the step into a folder's children.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JWUQ867ZJCZJmkWUxqHija
* fix: stop the fold's sticky row resetting the keyboard walk
`stickyKey` is read through `restingIndex`, which `useListHighlight` calls
inside the effect that reacts to the row count. As `$state` it was also a
dependency of that effect, so clearing it on the next arrow re-ran the effect
and wrote the highlight back to nothing: after collapsing a folder, one Down
lit nothing and the one after it started again at the top.
It is a plain variable now, read when the effect runs and invalidating nothing.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JWUQ867ZJCZJmkWUxqHija
* fix: keep one lit row, and keep the fold's sticky row to its fold
Three from the review of the `useListHighlight` swap:
The sticky row a fold takes is now given up as soon as that fold has rendered.
Held until the next arrow, it pulled the highlight back to that folder on any
later change — another fold, a save, a delete, a workspace switch.
Space and Enter on a focused control bring the highlight to that control's row
before the control answers them. A switch keeps focus after a plain click, and
the row drawn as highlighted was then a different one from the row that flipped.
Up and Down carry on from a row reached with Tab. `useListHighlight` cannot see
that by itself: `ListRow` puts the row's id on its outer div while focus sits on
the button inside it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JWUQ867ZJCZJmkWUxqHija
* fix: land the highlight on a named row rather than stepping to it
`move` counts steps from wherever the highlight is, and from nothing lit it can
only reach an end of the list — so the three places that meant "put it on this
row" (a row reached with Tab, the row of a focused control, a folder's parent)
sent it to the first row whenever nothing was lit yet. `useListHighlight` grows
a `moveTo` for naming the row outright, and those three use it.
The handler's own doc still said the keys are answered on the list; they went
back to the window when the editor's page transition proved able to take focus
away from it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JWUQ867ZJCZJmkWUxqHija
* fix: fold from the header click the way every other fold does
The header's own click wrote `collapsed` directly instead of going through
`fold`, so the row count changed with no row named to keep: the highlight reset,
and since the highlight is the header's only hover feedback, it went flat under
a pointer that had not moved and stayed flat.
Also from the round: a duplicated `svelte-ignore`, the missing one on the header
wrapper that takes `onmouseenter`, and a trailing comma prettier wanted gone.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JWUQ867ZJCZJmkWUxqHija
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(frontend): keep crawlers off the login page
Every page on the hub links to /user/login with itself in `rd`, so a crawler
sees one login URL per hub page — 4,311 of them in Search Console, all
rendering this same form and flagged as duplicates without a canonical. Nothing
about a login page belongs in an index, on any instance.
Mark the page noindex, as public_run already is, and ship a robots.txt that
keeps crawlers out of /user/ and /api/. The frontend is embedded as static
assets with an index.html fallback, which is why /robots.txt answered with the
app shell until now; a real file in static/ is served as itself.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix(frontend): let crawlers fetch the login page so the noindex is seen
robots.txt disallowed /user/, which stopped a crawler fetching /user/login at
all — and a page that is never fetched never shows its noindex. The two halves
cancelled: the URLs would have moved from "duplicate" to "blocked" rather than
out of the index.
Drop the disallow, keeping /api/. And since the app is client-rendered, the
meta tag only exists after a render pass; send X-Robots-Tag on /user/* from
serve_path as well, which a crawler sees on the first fetch.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>