The menu takes the place the close button held at the end of the row, and
migrations and permissions grey out while the row has unsaved edits — they
act on the saved data table, which an unsaved one is not.
A permissions drawer on a schema row: who owns it, and what each role may do
in it. Ownership moves the schema and everything already in it, and writes
the default privileges that keep the owner in reach of what the other roles
create later — an object belongs to whoever creates it, so that is the only
way to cover what does not exist yet.
Grants are built as statements — privileges, scope, role — and read back from
the catalog, per object and including default privileges, so what the page
shows is what the database has. Every change is confirmed against its own SQL
and runs in one transaction.
The editor takes a target rather than a schema: a table is the same call with
one more identifier, for when the table and role sections want it.
The badge is now a button with a chevron, opening the select dropdown over
the roles the caller may use — the dropdown alone, not Select's own input.
Picking one reconnects the manager as that role, switching data table first
when the badge belongs to another one.
The menu now replaces a row's own icon on hover rather than its chevron, and
a table row keeps its icon with the favourite star moved to the far right.
Table menus are no longer limited to the data table the manager is connected
to: alter and delete route through the same switch the create rows use, so
they run against the connection of the data table the row belongs to.
* fix: force HTTP router rebuild on trigger-change notification
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix: coalesce http trigger change events into one forced rebuild
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix: retry the coalesced http router rebuild when it fails
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix: mark http routers stale when a forced rebuild fails
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix: keep the router invalidation across an in-flight rebuild
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
* fix: require admin on workspace tarball settings export
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix: name the refused flag in the settings export error
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
* fix(frontend): operator menu opens on hover, pins on click
The operator hamburger synthesized a trigger click on every mouseenter, so
melt toggled the menu: re-entering an open menu closed it, and a real click
after a hover-open closed it too.
Hover now opens the menu only when closed and closes it 150ms after the
pointer leaves; the portaled content carries the same handlers so moving
between button and list keeps it open. A click is intercepted in the capture
phase: when hover already opened the menu the click is swallowed (melt would
otherwise toggle it shut) and pins it instead, so it stays open until a click
outside or on the trigger.
Opening and closing both go through a synthetic click on the trigger because
melt's menubar renders content only when rootActiveTrigger is set, which only
the trigger's own click handler does.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FwLAoY7s9iPkYPGcmnw6UC
* refactor(frontend): move hover-open/pin into Menu's openOnHover prop
The hover machinery duplicated what meltComponents/Popover.svelte already
offers as openOnHover. Menu.svelte owns both the trigger wrapper and the
content div, so the grace timeout, the pin flag and the synthetic trigger
click belong there rather than in the consumer.
OperatorMenu is back to its original markup plus `openOnHover`, and the other
Menubar users can opt in. Popover keeps its own implementation: it is built on
createPopover, not the menubar, and does not need the trigger-click detour.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FwLAoY7s9iPkYPGcmnw6UC
* fix(frontend): show the keyboard highlight on operator menu rows
sidebarClasses.hoverBg only reacts to the pointer, so rows styled with it
alone stayed transparent while melt moved data-highlighted through them:
arrow keys walked the menu invisibly. Affected Home, Runs, Schedules and
Tutorials (MenuLink), plus Account settings, Switch theme and All workspaces.
MenuLink adds the highlight only when it is rendered as a menu item; the
sidebar and settings-menu call sites pass no `item`, so nothing changes there.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FwLAoY7s9iPkYPGcmnw6UC
* fix(frontend): one highlight state per menu row, accent for the selected one
Menu rows carried a hover rule and a data-highlighted rule at once. Melt moves
data-highlighted with the pointer as well as the keyboard, so the hover rule
was a second, independent state: the row under the pointer and the row the
arrow keys had reached both lit up. Menu rows now style data-highlighted only.
"More triggers" keeps its hover rule — it is a plain div, not a melt item, so
it never receives data-highlighted.
The selected row also painted bg-surface-hover, making the current page
indistinguishable from a highlight. It now uses the accent pair the rest of
the app uses for selection, bg-surface-accent-selected + text-accent.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FwLAoY7s9iPkYPGcmnw6UC
* fix(frontend): make "More triggers" a real menu item
It was a hand-rolled <div role="button" tabindex="0">. Melt collects the rows
arrow keys walk with querySelectorAll('[data-melt-menu-id="<menuId>"]'), an
attribute only the item builder stamps on, so the row was skipped — and its
tabindex was no help either, since Tab inside an open menu is intercepted to
close it.
It is now a MenuItem. Melt closes the menu on item click unless the click is
defaultPrevented, and Svelte delegates onclick to the root, which runs after
melt's own listener, so the toggle sits in a capture handler on a wrapper
where it reaches the event first.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FwLAoY7s9iPkYPGcmnw6UC
* Revert "selected sidebar row on the accent tokens"
sidebarClasses drives the whole sidebar and SessionPicker, not just the
operator menu; restore selectedBg/selectedText to their previous values.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FwLAoY7s9iPkYPGcmnw6UC
* feat(frontend): open the operator menu below the hamburger
Menu defaults to right-start, which put the operator menu alongside the
trigger and over the page header. bottom-start drops it under the hamburger,
left-aligned. Set on this menu only; the shared default is unchanged.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FwLAoY7s9iPkYPGcmnw6UC
* feat(frontend): show the operator menu trigger as selected while pinned
Nothing distinguished a pinned menu from one that is merely following the
pointer, so a click gave no feedback. Menu hands `pinned` to the triggr
snippet, and the operator hamburger keeps sidebarClasses.selectedBg while it
holds — the tint that hover gives it, now persisting after the pointer leaves.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FwLAoY7s9iPkYPGcmnw6UC
* refactor(frontend): address Codex/Claude review on the menu hover changes
Reuse debounce from $lib/utils for the hover grace period instead of a
hand-rolled timer, matching how Popover implements the same delay.
Give the "More triggers" capture wrapper role="none" so it doesn't sit
between role="menu" and role="menuitem" as an unlabelled node, and spell out
in the comment why the listener has to be on an ancestor rather than on the
item itself.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FwLAoY7s9iPkYPGcmnw6UC
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
* feat(frontend): flag the fork-compare datatable schema diff as legacy
* fix(frontend): make the legacy datatable diff alert copy match the opt-in flag
* fix(frontend): key the GitHub App installation selector on installation_id
The GitHub Account ID dropdown used `account_id` as both the option value
and the lookup key. A workspace can hold several installations for the same
org (re-installed, or added from another workspace), so `.find()` resolved to
whichever came first: picking the live installation could hand back a stale,
token-errored one whose `repositories` are empty, leaving the repository
dropdown blank. `RepositorySelector`'s pagination matched the same way and
appended the wrong installation's page.
Both now key on `installation_id`, and the dropdown appends the installation
id to the label only for orgs that appear more than once. Switching
installation remounts `RepositorySelector` and clears the selected
repository, so its loaded pages no longer carry over.
Fixes WIN-2448
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix(frontend): shorten the duplicate-org installation label
Drop the "installation" word from the disambiguating suffix: the id alone
already tells the two entries apart, and it keeps the errored variant short
enough to read at a glance.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
* fix(frontend): follow the operating workspace in step input forms
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(frontend): rethrow auth errors and wire remaining variable pickers
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* refactor(frontend): use runed watch for picker workspace reloads
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: migrate slack resource-connect oauth to v2
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix: keep slack scopes one per entry, as every other provider does
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
* fix(debugger): parse bun 1.4's UUID inspector token
Bun 1.4 changed the inspector URL's token to a hyphenated UUID. The stderr
scraper matched `[a-z0-9]+`, so it stopped at the first hyphen and connected to
a truncated path, which the inspector answers with 404. Every TypeScript debug
session has failed to attach since the 1.4.0 bump, taking the windmill-extra
integration tests with it.
Match the whole path, and only once its line is newline-terminated: a stderr
chunk can end mid-URL and would otherwise be read as a complete, truncated URL.
A close before the handshake completes is now reported as the connection
failure it is, rather than as a finished script, and the debuggee is reaped -
--inspect-wait blocks until a debugger attaches, so a failed attach leaked a bun
process per session.
On the test client, queue events that arrive before their waiter registers: the
server sends 'initialized' immediately behind the 'initialize' response, which
the client could drop and then time out waiting for.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XMizaQRcnWRd79t5wWhjBN
* fix(debugger): keep the first terminated event's result on launch failure
A socket that drops after the handshake opens but mid-command-sequence reports
the termination from onclose, carrying the script result, and then fails the
launch. Sending a second terminated from the failure path overwrote that result
with an error-only event. Guard the send the way every other emit site in the
file does, leaving the reaping unconditional.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XMizaQRcnWRd79t5wWhjBN
* fix(debugger): report an inspector drop during setup as the failure it is
The setup commands run over an open socket and none of them reject when it
drops - sendInspectorCommand only has its own timer - so a drop between the
upgrade and Inspector.initialized was reported as a clean termination, and the
error surfaced up to 10s later or, once the duplicate was guarded, not at all.
Draw the line at execution actually starting rather than at the socket opening,
so those failures terminate with the connection error, immediately and once.
Pair the "Failed to start Bun" output with the terminated event it explains,
so a run that already reported its result cannot also be told it failed to
launch.
Prove the inspector URL complete with whitespace rather than an end-of-line:
trailing text on the banner line would otherwise stall the parse for 10s.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XMizaQRcnWRd79t5wWhjBN
* fix(debugger): mark execution started only once the start command is answered
Inspector.initialized is what starts the script, so setting the flag before
awaiting its reply left a drop during that round trip looking like a clean
termination - the same silent failure, narrowed to one command. Its reply
precedes any close on the socket, so the continuation still runs before onclose
and a real run is not misread as a failure.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XMizaQRcnWRd79t5wWhjBN
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Serve the caller's usable roles and the default one alongside the tree, so
the badge costs no extra round trip per data table. Shown only where naming
the role says something: a data table without permissions, or one whose only
usable role is the implicit admin, gets none.
The left-pane filter also matches schema names now.
The New table / New schema rows now show under every data table, not only
the one the manager is pointed at. Creating outside it switches to that data
table first, and the request rides through the parent because the switch
re-mounts the manager.
Tree rows also read uniformly now — the background alone marks the current
one — with the create rows set apart in secondary.
Picking a table now also makes it the current one, previewed on the right —
across data tables too, which re-mounts the manager, so the picked
schema/table travel through the input the drawer keeps.
The table picker for raw apps had its own checkbox tree next to the
manager's navigator. Now that the navigator is itself a data table ->
schema -> table tree, the picker is the same tree with a checkbox in
place of the chevron and row menu, so a pick can span data tables.
Also aligns the indent step between tree levels at 16px.