mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-10-27 00:02:35 +00:00
fix: keep a staged flow path across reloads and follow moves in session tabs (#11967)
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
72c6b10821
commit
6b56656bd6
@@ -135,8 +135,8 @@
|
||||
let {
|
||||
initialPath = $bindable(''),
|
||||
/** The draft row's own path (the URL path). Unlike `initialPath`, which the
|
||||
* route re-seeds from `draft_path` so the topbar shows the pending name,
|
||||
* this stays where the item actually is. */
|
||||
* route re-seeds from a draft-only flow's `draft_path` so the topbar shows
|
||||
* the pending name, this stays where the item actually is. */
|
||||
userDraftPath = '',
|
||||
onTakeLatest = undefined,
|
||||
pathStoreInit = undefined,
|
||||
@@ -1436,18 +1436,17 @@
|
||||
readFieldsRecursively(flowStore.val)
|
||||
}
|
||||
})
|
||||
// Sync `$pathStore` from `flowStore.val.path` (which `initFlow` populates
|
||||
// from the loaded flow — including the draft's rename, when there is one).
|
||||
// This effect only tracks `flowStore.val.path`, so popover edits that go
|
||||
// straight to `$pathStore` don't trigger it and aren't overwritten.
|
||||
// Replaces the previous `$pathStore = initialPath` push (added in #2536 for
|
||||
// the VSCode extension), which silently dropped any draft-renamed path
|
||||
// because `initialPath` is the URL, not the loaded path.
|
||||
// Sync `$pathStore` from the loaded flow: its staged `draft_path` when the
|
||||
// draft has one, else its `path`. Seeding from `path` alone would make the
|
||||
// effect below drop a staged move on every reload, and deploy would then
|
||||
// create at a taken path. Only `flowStore.val.path` is tracked, so popover
|
||||
// edits that go straight to `$pathStore` aren't overwritten.
|
||||
$effect.pre(() => {
|
||||
// `flowStore.val` is typed `OpenFlow` here but `initFlow` actually puts a
|
||||
// `Flow` (with `path`) in it.
|
||||
const p = (flowStore.val as Flow | undefined)?.path
|
||||
if (p) untrack(() => ($pathStore = p))
|
||||
const flow = flowStore.val as (Flow & { draft_path?: string }) | undefined
|
||||
const p = flow?.path
|
||||
if (p) untrack(() => ($pathStore = flow?.draft_path || p))
|
||||
})
|
||||
|
||||
// Persist the user-typed path into the draft JSON as `draft_path`
|
||||
|
||||
@@ -10,9 +10,9 @@ import type { WorkspaceItem } from './workspacePicker'
|
||||
|
||||
export type FlowBuilderProps = {
|
||||
initialPath?: string
|
||||
/** The draft row's own path (the URL path). `initialPath` is re-seeded from the
|
||||
* draft's `draft_path` so the topbar shows the pending name, so it can't be used
|
||||
* to resolve what is actually deployed. */
|
||||
/** The draft row's own path (the URL path). For a draft-only flow `initialPath` is
|
||||
* re-seeded from the draft's `draft_path` so the topbar shows the pending name, so
|
||||
* it can't be used to resolve what is actually deployed. */
|
||||
userDraftPath?: string
|
||||
/** Moves the draft's base to the head and keeps its content; offered in the diff
|
||||
* drawer while the draft is behind, and called with the version the drawer shows
|
||||
|
||||
@@ -131,10 +131,10 @@
|
||||
onDetails={onSeeDetails}
|
||||
condensedHeader={true}
|
||||
customUi={{ topBar: { aiBuilder: false } }}
|
||||
onDeploy={() => {
|
||||
onDeploy={(e) => {
|
||||
// FlowBuilder has no deploy toast and the session stays put, so toast here.
|
||||
sendUserToast('Deployed')
|
||||
runtime.itemDeployed(workspaceId, 'flow', path)
|
||||
runtime.itemDeployed(workspaceId, 'flow', path, e.path)
|
||||
}}
|
||||
/>
|
||||
{/snippet}
|
||||
|
||||
@@ -148,7 +148,15 @@
|
||||
if (dp !== undefined) {
|
||||
surfacedDraftPath = true
|
||||
if (val.draft_path !== dp) val.draft_path = dp
|
||||
} else if (surfacedDraftPath && val.draft_path !== undefined) {
|
||||
} else if (
|
||||
surfacedDraftPath &&
|
||||
val.draft_path !== undefined &&
|
||||
// Equal to the saved path, it is not stale: a deploy just moved the app
|
||||
// there. Clearing it would schedule a save of this cell at the old path
|
||||
// that lands after the deploy discarded it, and the moved draft row
|
||||
// would come back on the new path.
|
||||
val.draft_path !== cell.saved.val?.path
|
||||
) {
|
||||
val.draft_path = undefined
|
||||
}
|
||||
})
|
||||
|
||||
@@ -124,7 +124,7 @@
|
||||
// Fires on every deploy (primary, "Deploy & Stay here", and lib — we
|
||||
// ignore e.stay since the session always stays).
|
||||
sendUserToast('Deployed')
|
||||
runtime.itemDeployed(workspaceId, 'script', e.path)
|
||||
runtime.itemDeployed(workspaceId, 'script', path, e.path)
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
@@ -339,7 +339,6 @@
|
||||
v = undefined
|
||||
}
|
||||
if (tok !== loadFlowToken) return
|
||||
version = v
|
||||
|
||||
const backendFlow = await FlowService.getFlowByPath({
|
||||
workspace: $workspaceStore!,
|
||||
@@ -376,6 +375,9 @@
|
||||
// Layer the draft (`.draft`, if any) over the deployed payload at the field
|
||||
// level. See /scripts/edit's loader for the rationale.
|
||||
const { draft: draftFromBackend, ...deployedFlow } = backendFlow as any
|
||||
// Set together with the base below: across the await above, a new head beside
|
||||
// the previous load's base reads as stale, and the stale prompt latches open.
|
||||
version = v
|
||||
// The flow_version the draft forked from; undefined when editing the deployed
|
||||
// flow directly (no draft) or for a draft never forked from a deploy.
|
||||
draftBaseVersion = backendFlow.draft_base
|
||||
@@ -395,13 +397,16 @@
|
||||
deployedBaseline = backendFlow.no_deployed
|
||||
? undefined
|
||||
: (structuredClone($state.snapshot(deployedFlow)) as Flow)
|
||||
// Surface the saved `draft_path` to the Path widget so the topbar shows the
|
||||
// pending name, not the `draft_{uuid}` URL. Else the widget seeds from the
|
||||
// URL, the first edit clobbers `draft_path`, and the friendly name is lost.
|
||||
// Otherwise the URL, set on every load: this route instance is reused when the
|
||||
// editor follows its draft to a moved item, and the path it opened on is where
|
||||
// deploy would look for the flow.
|
||||
const renderedDraftPath = (effectiveFlow as any).draft_path as string | undefined
|
||||
// A draft-only flow surfaces its saved `draft_path` to the Path widget so the
|
||||
// topbar shows the pending name, not the `draft_{uuid}` URL. Else the widget
|
||||
// seeds from the URL, the first edit clobbers `draft_path`, and the friendly
|
||||
// name is lost. A deployed flow keeps the URL even with a staged move: it is
|
||||
// where deploy updates the flow and where its triggers live (FlowBuilder shows
|
||||
// the staged path). Set on every load: this route instance is reused when the
|
||||
// editor follows its draft to a moved item.
|
||||
const renderedDraftPath = backendFlow.no_deployed
|
||||
? ((effectiveFlow as any).draft_path as string | undefined)
|
||||
: undefined
|
||||
flowInitialPath = renderedDraftPath || (page.params.path ?? '')
|
||||
|
||||
// "Load another user's draft" handoff: render their value over the deployed
|
||||
|
||||
Reference in New Issue
Block a user