fix: prevent canvas component loss when saving default filter#9494
Merged
djbarnwal merged 1 commit intoMay 25, 2026
Merged
Conversation
The previous flow captured the YAML Document before awaiting the metrics-SQL conversion and then mutated and wrote that same reference. Any component edit that landed in editorContent during the await was stranded on a fresh Document, so the write reverted the canvas to a pre-edit state and the runtime reconciliation deleted the missing components from the in-memory store. Collect inputs first, run Promise.all, then read parsedContent and apply all mutations synchronously before writing. This keeps the read-modify-write atomic relative to editorContent updates. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
djbarnwal
approved these changes
May 25, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Root cause
CanvasEntity.saveDefaultFilters(web-common/src/features/canvas/stores/canvas-entity.ts) did a read → await → mutate → write on the canvas YAML, capturing theDocumentreference before the await:const yaml = get(this.parsedContent)—parsedContentis aderivedstore overeditorContent; eacheditorContentchange emits a fresh parsedDocument. The localyamlonly points at one snapshot.await Promise.all(...)— onequeryServiceConvertExpressionToMetricsSQLround-trip per metrics view in scope (100–500 ms typical).yaml.setIn(["defaults", "filters"], filterMap)thenyaml.toString()thenupdateEditorContent(newContent, false, true)— writes the pre-await document back, immediately, viaruntimeServicePutFile.Anything that mutated
editorContentduring the await window was stranded on a newerDocumentthat the saver never saw:CanvasBuilder.updateContents→updateEditorContent(..., false, true)runssaveContentimmediately, not debounced. A drag/drop or resize finishing right around the click writes a newer YAML to disk.FILE_EVENT_WRITEfrom the runtime;file-invalidators.tsre-fetches the file and, whenfileUntouched, pushes the fresh blob intoeditorContent, re-derivingparsedContent.When the stale YAML hit disk, the runtime reconciled a truncated canvas spec.
specStore.subscribe→processSpec→processRowsthen deleted every component name no longer present inrows[].items[].componentfrom the in-memorycomponentsStore— which is what the user perceived as "components disappeared."The pre-existing
setTimeout(100)at the top of the method is unrelated (it waits for filter-input blur) and does nothing to make the read-modify-write atomic.Fix
Promise.allfirst.parsedContentafter the await, then apply all mutations and write in a single synchronous block.clearDefaultFilterswas already synchronous and is unchanged.filters.pinnedanddefaults.*are touched;rows/itemsstay byte-identical.Checklist:
Developed in collaboration with Claude Code