The variable and schedule editors are reused for whatever is opened next, so a
drawer switched while a write is in flight leaves every post-await read
describing the item now on screen. The settle then took that for an edit made
over the write and left the saved item's draft standing, and the schedule also
installed the saved item's baseline into the new one, which the mirror published
as a draft it never had.
What was sent is still adopted; the live form is read only while the editor is
where it saved from. Re-pointed, the cell it released during the write is what
answers for it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KcyBRxE8j3ujCYkum9KrC4
The last commit moved resources and variables onto the per-cell window and left
this one on `deploymentLoading`, which serializes the component instance alone —
so two editors over the same schedule could still submit different snapshots, and
the older request landing last would overwrite the newer deployment.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KcyBRxE8j3ujCYkum9KrC4
Two structural gaps, closed at the level they exist at rather than per call site.
An item is no longer owned by one editor: duplicate tabs and warm sessions mount
several over one (workspace, kind, path) cell, so a flag kept per editor
serializes nothing. The window each save already opens is per key, so that is
what says a write is in flight — every editor's Save reads it, and the local
flags are gone.
And a chat tool told the hosts only its own arguments, so anything it learned
while running was invisible to them. A deploy lands on the draft's own path
field, which a hosted editor moves by renaming a draft-only item, and the tab was
refreshed onto the path the item had left. The result now carries where it
landed, and a move is reported as the rename it is — the same report an editor's
own save makes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KcyBRxE8j3ujCYkum9KrC4
Rendered inline there is no drawer to close over the Save button, and dirtiness
alone kept it enabled while a write was in flight: a second click started an
unserialized second create/update, and if the older request finished last its
snapshot overwrote the newer deployment.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KcyBRxE8j3ujCYkum9KrC4
A marker nothing read outlives the item it was about: the path can be deployed
again from another tab or client, whose draft never touches this tab's cell. The
next discard there reverts the item rather than removing it, and would otherwise
spend the stale marker and send that item's editor back to its list.
The released record's lifetime is the settle window's alone now — `acquireEntry`
also dropping it said there were two owners, and a live entry answers for itself
anyway, so the settle never reads the record while one exists.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KcyBRxE8j3ujCYkum9KrC4
Two saves of the same cell can be in flight at once, and consuming the record on
the first settle left the second reading the released cell as "nothing newer" —
so it deleted the edit the first had just preserved. The window that recorded it
already owns its lifetime and drops it when the last save closes, so the read
does not have to.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KcyBRxE8j3ujCYkum9KrC4
The completion listener that refreshes and closes hosted editors runs only for a
tool that returned, so a draft cleanup throwing after the entity was deployed or
deleted left the tab open on state that no longer exists. The cleanup cannot undo
the mutation either, so both tools now report it in their result instead of
throwing it as if nothing had happened.
Releasing a cell recorded its value for every resource and variable, though only
a settle waiting on a write in flight ever reads one — routine editor use
retained large resource values and decrypted secrets for the tab's lifetime. The
editors now open a window around their save, and only a release inside one is
remembered; closing it drops what it held.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KcyBRxE8j3ujCYkum9KrC4
A create's form cell is the detached handle `useMany` gives an empty path, so the
settle addressed its requests to `/drafts/update/<kind>/`: three 404s on every
resource, variable and schedule created from a blank form. It has no draft to
settle — short-circuit.
Following that create's path afterwards re-keyed the blank cell onto the item just
made, and the mirror posted it as a draft: every new variable and resource landed
with an empty draft over it, secret flag and all. Only a move is followed now.
The rename retry loop existed because the form stays editable across the delete's
request and each keystroke queued a write behind it. Suspending the old key for
the length of the delete removes the race instead of racing it: one attempt is
final, and a delete that still will not land is a conflict or a failed request,
which no retry changes and which nothing said out loud before.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KcyBRxE8j3ujCYkum9KrC4
`ResourceEditor` is an entry point of the packaged `windmill-components`, so its
`save()` is a public contract: consumers outside this repo branch on the boolean
it has always returned, and an array reads as success on every failure. The
per-workspace results move to `saveWritten`, which the drawer takes.
The released-values doc goes back on the map it describes, with the constant
carrying its own — and the kinds it excludes stated as they are: a variable's
cell is recorded, decrypted secret and all, since that is a kind whose settle
reads the record back.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KcyBRxE8j3ujCYkum9KrC4
The schedule editor's "View runs" anchor navigated the top-level document, which
from a session tab means leaving the session — and onto the navigation workspace,
not the acting one. A host that is a page of its own now takes the destination
instead and re-points its tab at the Runs page, like the way back to the list.
The released-cell record is scoped to the kinds whose settle reads it back:
elsewhere it retained a whole flow or app draft, or a variable's decrypted secret
that the release is otherwise the end of.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KcyBRxE8j3ujCYkum9KrC4
An editor stays editable while its save is in flight, so a tab left in that
window releases a cell that may carry an edit newer than the write — and a
released cell reads as no cell at all, which the settle took for a draft nothing
was watching and deleted. Keep what the last holder let go of, bounded like the
other write markers, and let the settle read that when no handle is left.
The linked-workspace selector follows the hand-off button: a session tab is keyed
to one workspace, so re-pointing the editor at a linked one from inside a tab
acted on an item the session is not about.
A removal marker now also requires the delete to be what landed, as
`flushDraftDelete` does: an upsert queued behind it displaces the delete, and
waiting for the chain then reports idle on a draft that is back.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KcyBRxE8j3ujCYkum9KrC4
Settling a save that changed nothing hands the cell a value it already holds, and
the mirror's no-op branch defused the seed guard but not the one a discard arms —
so the guard stayed up and ate the user's next keystroke, which then reached
neither the server nor the unload flush. It is defused in the same place and for
the same reason as the other.
Two more from the same helper: a released handle is no cell at all rather than a
newer edit, so a save whose drawer has closed still cleans up after itself; and
the delete is only re-sent for an item that moved, since on an unchanged path the
write queued behind it is the user's and resending would revert it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KcyBRxE8j3ujCYkum9KrC4
The settle sent its delete and returned on the first flush, but the form stays
editable across that request: an edit made then parks a write behind it, and once
the callers re-key to the new path that write lands as a draft under a path the
item no longer occupies, with no editor on it. The delete is re-sent while the
key refuses to settle on it — bounded, since a form still being typed into can
always add one more.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KcyBRxE8j3ujCYkum9KrC4
The chat's discard reports its delete as landed once nothing failed or conflicted,
but the form stays editable across it: an edit made then queues a write behind
the delete and recreates the draft. The removal marker — which sends a hosted
editor away from an item it says is gone — is only published when nothing is
queued behind the delete, so it describes an item that is actually gone.
The variable save's committed flag still followed the acting workspace after the
path it pairs with began following the shown one, and its reopen let the editor
reselect its default workspace. Both follow the workspace the form is showing.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KcyBRxE8j3ujCYkum9KrC4
`WsSpecificVersions` can point the form at a linked workspace, and the form then
followed the acting workspace's write instead — which for a rename made in the
linked one meant following nothing, leaving the form on the item it shows with a
draft handle still keyed to the path that item has left. It follows the shown
workspace's write, falling back to the acting one, which is the same thing
whenever they are the same workspace.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KcyBRxE8j3ujCYkum9KrC4
An editor adopts what it loaded as its cell's baseline the moment it mounts, and
that load can predate a write that seeded the cell before it existed. Adopting
it cleared the marker that write had left, so the session concluded the write
had reached the form and skipped the re-read, leaving the editor on the older
value. Bootstrap seeds stay out of the bookkeeping now — recording nothing and
consuming nothing — while a real write still resolves a marker either way.
`can_write` also stops snapshotting the current user when the acting workspace
is the one being browsed, so it keeps following the layout's refresh.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KcyBRxE8j3ujCYkum9KrC4
`can_write` came from the navigation workspace's membership while every read and
write went to the session's, so a fork or linked workspace could disable a user
who may edit there — or offer the controls to one who may not, until the backend
said otherwise. It resolves that workspace's user, falling back to the current
one until it lands and whenever they are the same.
Whether a save kept an edit was decided before the settle, which awaits a request
of its own with the form still editable. Anything typed across it counted as
nothing, the host remounted, and the edit went with it. Read afterwards.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KcyBRxE8j3ujCYkum9KrC4
`hasDraft` compares the form to the deployed baseline, and that baseline is a
plain value on purpose — made reactive, the sync's effects track every field and
a reset provokes a write that races its own delete. So a toggle that landed
fixed the baseline where nothing was watching, and the drawer kept offering to
discard changes it had already deployed until some other field moved.
The baseline stays plain; a counter beside it marks when it moves, and the
`deployed()` the banner reads depends on that. Bumped where the baseline is
captured, adopted after a save, corrected after a toggle, and cleared.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KcyBRxE8j3ujCYkum9KrC4
The reading sat inside the branch that captures a baseline, so a schedule opened
with a config handed in — the trigger panel of a script or flow — never took
one, and a save there settled against whatever the last open had left. Both ways
in hold the deployed state on the form at that point, which is what the reading
is about.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KcyBRxE8j3ujCYkum9KrC4
`loadScheduleCfg` applies the draft overlay as well as the deployed config, so
recording the accepted `enabled` there let a draft's own value pass for one the
server had taken: saving then settled the form back to it, and a schedule the
user had switched on came out off, clean, with nothing said. It is read where
the deployed config is the one on the form, and reset for a schedule that has
none yet.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KcyBRxE8j3ujCYkum9KrC4
The toggle sets `enabled` before its request and puts it back if that fails, so
the form's value is a claim until it lands — and a save adopting it wrote an
uncommitted toggle into its baseline, where cancelling the request left it: a
false dirty state, and a Discard offering a value the server never took. The
committed value is tracked and both the baseline and what counts as written take
it, so the settle compares like for like and drops its draft instead of leaving
a row behind.
The reload queue keys by workspace as well as page: a draft landing in one says
nothing about the same page in another, and reloading it there costs that frame
its scroll. A restore reports the path it restored rather than leaving the
caller to read a live one its own await has outlived.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KcyBRxE8j3ujCYkum9KrC4
An update sends no `enabled` — the toggle deploys that field by itself — so a
save adopting its click-time snapshot wholesale rewound a toggle that had landed
meanwhile: the form called the deployed state unsaved, and Discard put the old
one back. The baseline takes the field from the toggle, not from the snapshot.
A variable save reported each workspace as it landed, and the first report
retargets the host, whose keying unmounts the editor at the next workspace's
await — releasing the handles the workspaces after it settle against, so their
drafts survived a successful save. Reports wait for the loop to finish.
The list frames now share one debounced reload queue with the chat path, rather
than reloading once per reason for one deploy.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KcyBRxE8j3ujCYkum9KrC4
Timing the fan-out against the settle was the wrong direction. The settle now
sends its own delete and resolves when it has gone out, so the editors report —
and the schedule host remounts — on a cell that is already resolved: a stalled
delete used to let that remount load the draft the save had just replaced, and
restore it over the deployed value.
A write can still land after all of that, when an edit made during the save is
kept. A list frame reads its rows from a store of its own and nothing reloads it
again, so it now reloads on the write landing rather than on our guess about
when it will.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KcyBRxE8j3ujCYkum9KrC4
The settle that follows a write parks its draft delete on the autosave debounce,
so the list frame reloaded straight after read the row's `*` and kept it: its
markers come from a store inside the iframe, and nothing reloads it a second
time when the delete lands. The fan-out sends what is parked for the key first.
That needed the variable save to report after settling rather than before —
reporting first meant there was nothing parked yet to send, and the flush passed
over a cell about to queue a delete. Every editor now resolves its cell before
saying so.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KcyBRxE8j3ujCYkum9KrC4
This commit updates the EE repository reference after PR #782 was merged in windmill-ee-private.
Previous ee-repo-ref: 313c572c9dcbcaafd8a1594df4054f9dd26f395c
New ee-repo-ref: d33ea730c550cdbc7d050aeb6d40dcef3d134e07
Automated by sync-ee-ref workflow.
The fan-out matched only tabs holding the item, so a bare list frame — which has
no row in its location to match — was skipped, and a save, rename or removal made
in a hosted editor left it showing the old rows and draft markers. It reloads,
the way a chat mutation on that page already made it.
Restoring a version replaces the deployed value as squarely as a save does, but
only the reporting editor heard: the other tabs kept the pre-restore value and a
baseline that would write it back over the restored one. It reports too, under
the workspace the history was pointed at.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KcyBRxE8j3ujCYkum9KrC4
Toggling enable writes the form before awaiting the deploy, so the autosave
queues a draft against the pre-toggle baseline. Advancing the baseline on
success then made that draft equal to it — invisible to the banner, still a row
on the server and a `*` on the list. It is settled now, keeping an unrelated
edit made during the request the way a save does.
A draft-only removal marker was only ever voided by a chat write, so one left
unread — discarded outside a session, where nothing consumes it — could be spent
by an ordinary discard on a path since recreated, sending live editors away from
an item that exists. Every write that gives the item a draft again voids it,
which is also what `persistGlobalDraft` was doing by hand.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KcyBRxE8j3ujCYkum9KrC4
Adopting it unconditionally overwrote whatever the field held by then, so a
write landing during the request — the chat seeding the cell — was erased before
anything could notice it had, and settled away as if it were the save's own.
What was written still records `true`, since that is what the create sent; the
form follows only while it still holds the value that went with it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KcyBRxE8j3ujCYkum9KrC4
Creating a schedule sends `enabled: true` whatever the form held, so a
draft-only save left the form and its new baseline recording a state the server
does not have — visible once a kept edit skips the remount that used to hide it.
Also moves the settle comment in VariableEditor back onto its call.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KcyBRxE8j3ujCYkum9KrC4
Tab ids are unique within a session, but the seeded first tab is `session` in
every one of them — so a same-path save made from one skipped the refresh in all
the others, leaving those editors on the baseline the deploy replaced. The
exclusion carries the session it came from.
Also drops the acting-workspace explanation left above `written`, which reports
every workspace the save wrote.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KcyBRxE8j3ujCYkum9KrC4
The enable/disable request carries the enabled flag alone, but claimed to leave
nothing behind — so the host remounted, and a summary or args edit still sitting
on the autosave debounce went with it. It now reports the form's real divergence
from the baseline it just moved.
A save also left `edit` and `draftOnly` to the remount, which is exactly what a
kept edit skips: a draft-only schedule created while the user typed stayed in
draft-only mode, with its path still editable and its next save a create. They
follow the write that deployed it instead.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KcyBRxE8j3ujCYkum9KrC4
`save()` now answers with the workspaces it wrote, and an empty array is truthy —
so the MCP section's `if (!(await save())) return` stopped guarding, and a failed
save ran the enablement bookkeeping its own comment says must not run. It asks
for the length.
The page declined to remount the tab that reported a save, but that tab was
remounting itself: the resource host on every save, the schedule host likewise.
The resource never needed it — its editor adopts the written value as its own
baseline — and the schedule remounts only when the save kept no edit, which is
the one case where re-reading is what the editor wants.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KcyBRxE8j3ujCYkum9KrC4
The same-path refresh reached the tab that did the save, remounting it over the
edit its own settle had just preserved — the teardown's flush and the fresh
editor's read race, and the older value can win. The reporting tab is skipped:
it has settled its own state, which is what the report is telling everyone else.
A save can also write several workspaces while only one event went out, labelled
with the acting one, and a discard deletes the draft of whichever workspace the
form is showing while claiming the acting one — so a linked workspace's tabs kept
a replaced baseline and the wrong tabs were closed. Each write now reports its
own workspace and the path it wrote there, which retires the acting-workspace
bookkeeping that stood in for it.
A tab re-pointed mid-write no longer swallows the report either: only the local
remount was ever its to suppress.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KcyBRxE8j3ujCYkum9KrC4
Editors on the same item share its draft cell but each keeps its own deployed
baseline, so a save made in one left the others offering to discard back to the
value it replaced — over a deployment. A save now reports the path it wrote to
whether or not that moved the item, and the tabs on it that stayed put are
re-read instead of re-pointed.
The variable drawer reopens on the committed path after a partial multi-workspace
failure rather than re-keying: re-keying brought its handles back from the values
the drawer opened on, which describe neither what the committed workspace has
deployed nor the draft the failed one still holds.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KcyBRxE8j3ujCYkum9KrC4
Warm sessions share one draft cell and one server row per item, so a rename or a
draft-only discard made in one left the others editing a path that is gone. The
editor's report now goes to the page, which re-points every tab on that item in
that workspace — the same fan-out the chat tools already use.
Matching on the item is also what the host's own staleness check was for: a
report that lands after its tab has been re-pointed simply finds nothing to
match, so `stillShowing` goes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KcyBRxE8j3ujCYkum9KrC4
The fact is sticky by design, so a delete that landed earlier for a key kept
answering after the server moved out from under it: a discard that conflicted
left it standing, and the conflict itself is tracked apart from the state the
verdict reads, so both said the draft was gone. It is dropped now wherever the
server stops being ours to describe — on a conflict, and on a remote resync.
Also corrects what the partial-save comments claim. Following the committed
rename re-keys every workspace's handle, so a workspace whose write never
happened does lose sight of its draft in this editor; the draft is still on the
server under the path that workspace holds the item at, which is where it stays
reachable. Saying it stayed reachable here was wrong.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KcyBRxE8j3ujCYkum9KrC4
The draft hint has a second writer: every editor clears it from an effect as
soon as a workspace sits at its baseline, which a discard does synchronously —
so a delete that then failed read as one that landed, and the host left on an
item still there. `postSave` now records which payload last landed for a key,
a fact only it can write, and the verdict reads that.
A delete winning is also not enough on its own. The form stays editable while
the flush is in flight and a flush only submits what it read at its start, so an
edit made in between sits on the debouncer and would recreate the draft behind
the host's back. The key has to be idle too.
The variable drawer no longer closes when a save failed partway: that is the
case where the workspace still needing one is retried from it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KcyBRxE8j3ujCYkum9KrC4
The discard verdict read the sync pipeline's own state, which settles whichever
write it ends up sending: the form stays editable after Discard, so typing then
replaces the queued delete with an upsert and the pipeline reports success for
it. The draft-state hint is written by the POST that actually landed, so it
answers what the pipeline cannot — whether the draft is still there.
A multi-workspace save writes in sequence and a later workspace throwing aborts
the rest, but what the acting workspace wrote is deployed either way. Both saves
now report that path, so a host is never left pointing at one the item has
already moved off.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KcyBRxE8j3ujCYkum9KrC4
A discard queues its delete through the autosave debouncer and returns, so the
host left for the list while the row was still there — and left just the same
when the delete failed outright. The three callbacks now wait for it and report
removal only when it landed; a delete that didn't means the item is still there.
`WsSpecificVersions` can point a form at a linked workspace, where a rename is
that workspace's alone. Both editors took the saved path from whichever version
was selected and handed it to a host acting on another one, and settled every
workspace's draft against that single path. The reported path now comes from the
acting workspace's own write, and each workspace settles against its own.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KcyBRxE8j3ujCYkum9KrC4
The resource save read its snapshot back out of the live form after the request
returned, so the "did anything change meanwhile" test compared the edit against
itself and always said no — the mid-request edit became the deployed baseline
instead. Its payloads, its submitted path and the schedule's config are now all
pinned before the first await, which also makes the schedule's test see edits to
`args`, `retry` and `labels` rather than only its scalar fields.
The three copies of the settling rule become `settleDraftAfterWrite`. A save
that moved the item resets the cell rather than keeping a diverged one: the edit
cannot follow — a freshly acquired cell reads only its own default — and the
alternative is a draft stranded under a path the item no longer occupies.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KcyBRxE8j3ujCYkum9KrC4
The forms stay editable during a save, and the post-write reset dropped the
local draft back to the state that was sent — so anything typed in between
vanished when the request returned. The draft is now dropped only while it
still holds what was written; an edit on top of the save survives as the
unsaved change it is, and the hosts that remount pick it back up.
A multi-workspace variable save also adopted its baselines only after every
write had succeeded, so a later workspace throwing left an earlier one's draft
dropped below a baseline that never moved: dirty forever, and retried as a
create it had already made. Each lands as its own write does.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KcyBRxE8j3ujCYkum9KrC4
The schedule and variable views re-pointed one live editor at each new path, so
the previous item's load — `openEdit`, `editVariable` and everything they await
— finished into the new item's form, baseline, draft cell and permissions,
last response winning. Keying each on its path gives every item its own
instance, with nothing of the next one's to land on. The resource host already
worked this way.
A report's guard also matched kind and path but not workspace, so re-scoping an
unsent session left a rename from the old workspace free to retarget the new
one's tab onto a path that exists only where it was written.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KcyBRxE8j3ujCYkum9KrC4
Guarding the host was not enough: an editor's own continuation runs first, and
every piece of state it touches after the await belongs to whatever the editor
shows by then. The variable save now pins its payloads, its saved path and its
draft key before the first write and only adopts a baseline while it is still
the variable it saved; the resource save and the schedule remount stop at the
same check.
The host guard also matched on path alone, so a late report about a schedule
reached a resource or variable that happened to share its path. It now matches
the entity kind too, bound at the branch that mounts each editor.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KcyBRxE8j3ujCYkum9KrC4
A tab id outlives what the tab points at. An editor reports a removal or a
rename after the write it awaited, and by then the tab may have been re-pointed
onto another item — so the report was applied to whatever the tab held, and the
URL built out of that unrelated location: discarding schedule A and opening
schedule B sent B back to the list. Every such report now carries the path it
started from, read before the await, and the host acts only while the tab is
still showing that entity.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KcyBRxE8j3ujCYkum9KrC4
A discard resolves asynchronously — the schedule's awaits a fetch of the
runnable — so by the time it lands the user may be looking at another tab.
Routing removal through `onBack` moved whichever tab was active, taking that
one to the list while the tab whose item was gone stayed on it. Removal now
retargets by tab id, as the deletion and rename paths already do.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KcyBRxE8j3ujCYkum9KrC4
The inline draft banner's Discard calls UserDraft.discard straight off, not the
discard_local_draft tool, so nothing told the tab about it. For a draft-only
item the draft IS the item: discarding deletes its only representation and the
tab kept editing a synthesized stand-in for something that no longer exists.
Each editor now reports whether the item survived, and the session views hand
that back to the tab, which returns to the list it was opened from.
A draft-only schedule also opens with its path editable — saving CREATEs — so
the save can land somewhere other than where the tab points. The schedule host
discarded onUpdate's saved path and only remounted, re-opening the old path;
it now retargets the tab like the resource and variable hosts do.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KcyBRxE8j3ujCYkum9KrC4