Skip to content

fix(dashboard): guard EditAgentDrawer save and rename against double submit - #14

Merged
ra-co88 merged 1 commit into
developfrom
fix/edit-agent-drawer-double-submit
Oct 12, 2026
Merged

ra-co88 merged 1 commit into
developfrom
fix/edit-agent-drawer-double-submit

Conversation

@ra-co88

@ra-co88 ra-co88 commented Oct 12, 2026

Copy link
Copy Markdown
Owner

What

  • handleSave awaited form.validateFields() and the root-dir probe before setSaving(true), so a second click in that window fired a second PATCH. React state can't close that gap; a synchronous savingRef latch now wraps the whole save (performSave is the unchanged body).
  • The list-rename modal gets the same ref guard (Enter + OK could race before confirmLoading rendered), and its Input is disabled while saving.

Verification

  • tsc -b clean; eslint: 0 errors, the 2 exhaustive-deps warnings are pre-existing on develop
  • vitest run src/pages/Experts: all pass
  • Diff: +19/−1, one file, no behaviour change apart from the dropped duplicate submit.

…submit

handleSave awaited form validation and the root-dir probe before
setSaving(true), so a second click in that window started a second PATCH.
Hoist a synchronous savingRef latch around the whole save. The list rename
modal gets the same ref guard (Enter + OK could race) and the rename Input
is disabled while saving.
@ra-co88
ra-co88 merged commit 5f8360e into develop Oct 12, 2026
3 checks passed
ra-co88 added a commit that referenced this pull request Oct 19, 2026
WorkspaceDrawer awaited a file read and then applied it unconditionally, so a
slow read could land after a fast one and overwrite the viewer: click file A,
then file B, and A's contents paint under B's name. Three sites had this shape -
activateSelection, handleEdit, and the create-file branch - and the file had no
generation counter of any kind.

createReadGuard issues a monotonic ticket; only the newest ticket may apply.
runExclusive was not reusable here: it *blocks* concurrent calls, which is right
for double-submit (PR #14) and wrong for reads, where every request should
proceed and only the latest result may win.

In activateSelection the ticket is taken BEFORE the media/non-text early
returns. Otherwise selecting an image would not invalidate an in-flight text
read, and that text would land on the image viewer - the same bug through a
different door.

The isCurrent checks guard the error path and the loading flag too, not just
setContent: a superseded read must not surface its failure as if it were the
current file's, and its finally must not clear a spinner the newer read owns.

Extracted as a plain function rather than a hook for the same reason runExclusive
was: WorkspaceDrawer is ~1500 lines and imports pdfjs at module scope, which
throws DOMMatrix in jsdom, so mounting it in a test hangs rather than fails.
readGuard.test.ts drives the ordering directly.

Verified: tsc -b clean; eslint 0 errors (the 3 exhaustive-deps warnings on this
file pre-exist - confirmed against a stashed tree); 6 new cases in
readGuard.test.ts; 20/20 passing across src/pages/Agent/Workspace.

The decisive check: reproducing the pre-fix code in isolation - reads with no
isCurrent guard, slow A then fast B - fails with "expected 'A' to be 'B'". That
is the bug, and the test catches exactly it.

Co-authored-by: pt-act <211776491+pt-act@users.noreply.github.com>
@ra-co88
ra-co88 deleted the fix/edit-agent-drawer-double-submit branch October 19, 2026 20:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant