The GitHub Actions job "Required Checks" on texera.git/gh-readonly-queue/main/pr-8540-ba2667a2259b6f5d7442979e5111d885ab57a4a5 has succeeded. Run started by GitHub user mengw15 (triggered by mengw15).
Head commit for run: e0f49897dbb525dc7faa6eac5f293fdb02b8f7f9 / yangzhang75 <[email protected]> fix(workflow): keep a newer local edit over a save's response (#8540) ### What changes were proposed in this PR? Closes #8536, the item deferred from #8456 on review. Every save's response is fed back as the workflow's metadata (the canvas autosave in `workspace.component.ts`, the menu's own save for a rename / description / revert, the Form View switch's save). Saves go out one at a time, so a response can land after a newer local edit and put the old name or description back: rename while an autosave is out and the title flips back until the rename's own save answers; rename while the Form View switch's save is out and the rename is lost: the switch's own response puts the old name back, the hand-over leaves at once, and the canvas's save on its way out (`persistBeforeLeaving`) stores that old name, queued behind the rename's own save. (When this PR was opened the switch was a page load that aborted the rename's save outright; since #8581 made it a route the save is sent, and the rename is lost this way instead.) Handled once, in `WorkflowPersistService`, the one place every save goes through: - Each response is relayed with the page's current name and description in place of the ones the save was sent with. Those are the two fields a user edits; everything else in the response (id, timestamps, publish state, default view) is the server's and arrives as before. A response is left alone when another workflow is open by the time it answers, or when the page was cleared meanwhile; the local name is kept for a workflow the save has just created (the page still holds the default id), which the id the save was sent with tells apart from a cleared page, since that one also holds the default id. The action service is looked up lazily at response time, so the dashboard, which also uses this service (retrieve, create, duplicate), does not construct the graph-owning service as a side effect. The callers that feed a response back as metadata (the workspace autosave, the menu's own save, the Form View switch, the form's save) are unchanged by this part; the settings panel's save and the workspace's unload save never read the response. - `whenSavesDrained()` emits once every save asked for so far has answered or failed (at once when none is pending). The Form View switch waits for it before leaving, so a save queued behind its own (a rename's, a description's, which save through the menu itself and do not go through `workflowChanged`) has answered while the menu that asked for it is still there to show its error or feed back its response, the same reason the switch already waits for its own save. A queued save that fails reports its error through its own caller and does not hold the hand-over. The wait is bounded (`HANDOVER_DRAIN_TIMEOUT_MS`, 10 s): a queued save that never answers does not hold the switch either; past the bound it leaves as it did before the wait existed. Verified in a real browser against today's main and this branch side by side (two dev servers on one backend, every persist request held 1.5 s by a proxy in front of each). Before: a rename made while an earlier rename's save was out flipped the title back to the old name when that response landed (1.6 s) until the second answered (3.2 s); a rename made during the Form View switch's save ended with the old name shown in the Form View and stored (the switch left at 1.5 s, the canvas's save on its way out carried the reverted name). After: the title stays on the new name; the switch leaves once the rename's save has landed (3.1 s) and the Form View shows, and the server stores, the new name; an operator dragged while the hand-over waited for the queue was saved before the route, with its new position in the stored content. ### Any related issues, documentation, discussions? Closes #8536. Follow-up to #8456 (threads on `menu.component.ts:692` and `:231`); part of the Form View feature (parent issue #8011). ### How was this PR tested? Unit tests (vitest): the persist service relays a response with the page's current name and description and the server's other fields, keeps the local name for a just-created workflow, leaves a response alone when another workflow is open or when the page was cleared while the save was out; `whenSavesDrained` emits at once when idle, only after the last of two queued saves has answered, and after a failed save; the menu's switch leaves only once the queue has drained, and an edit made while it waits for the queue is saved before it leaves; the service answers a caller asking from its own save's complete callback only once the save queued behind has answered too, and answers a call once (a drain caused by a later save does not reach a caller answered already); the menu's switch leaves after the bound when a queued save never answers, without saving again behind it. Each new guard was deletion-checked (removing it turns the corresponding test red). eslint, prettier and the production (AOT) build pass; every changed line is statement and function covered. ### Was this PR authored or co-authored using generative AI tooling? Yes. Generated-by: Claude Code (Claude Fable 5.1, Anthropic). Co-authored with Claude, reviewed line by line by the author before submission. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01FVvP3ttj22f9LB4p9u2anY --------- Co-authored-by: Claude Fable 5.1 <[email protected]> Report URL: https://github.com/apache/texera/actions/runs/36292592890 With regards, GitHub Actions via GitBox
