one buffer per file, shared by every panel showing it
The code editor held file contents per panel while Monaco keyed the model by
file URI globally, so two panels on one file had two buffers and one model.
@monaco-editor/react resolves a model as getModel(uri) || createModel(value),
so the second panel adopted the first one's model and its own value was
ignored; the value-sync effect then overwrote the shared model with
executeEdits, and the flag suppressing onChange around that write is
per-instance — so the OTHER panel's onChange fired, and the editor answers that
with setContent. One panel's buffer silently became the other's and the tab
went dirty with no keystroke.
useEditorState now keys buffers by path in one shared store and keeps only the
tab set and active path per panel. openFile discards the content it is handed
when the file is already open elsewhere, mirroring what Monaco does with the
model — that is what removes the divergence, because there is then no value for
the sync effect to reconcile. Buffers are refcounted by holders and dropped
when the last panel closes, so closing a tab cannot discard another panel's
unsaved work; closing the last one still discards, as before.
keepCurrentModel is the other half and was needed under any fix: the default
disposes the shared model on unmount, and key={activeFile.path} makes that
every tab switch rather than only a panel close.
Writes go through queryClient.setQueryData rather than useGlobal's setter,
which applies a functional update to the data captured in render — invisible
with one writer, wrong with one per panel.
Nine tests, and the editor had none before. The known-bugs card stays OPEN: this
removes a mechanism that produces the reported shape, not the mystery of where
the log text came from. The trap stays armed, and it can now fire legitimately
when two panels share a file, so the stack matters more than the count.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Officer-Session: f7a62308-0977-4cf4-aa29-c020853fd420
This commit is contained in:
@@ -64,6 +64,38 @@ writes job output into `useEditorState` — `openFile`/`setContent`/`reloadFile`
|
||||
shape, and it is not proven to be the reported instance. **The trap is still the thing that names the
|
||||
text's source.** Do not delete it on the strength of this section.
|
||||
|
||||
## Fixed 2026-08-25: the buffer is shared, because the model already was
|
||||
|
||||
`useEditorState` now keys buffers by path in one store shared across panels, and keeps only the tab set and
|
||||
the active path per panel. The rule that matters: **`openFile` discards the content it is handed when the
|
||||
file is already open somewhere else.** That mirrors what Monaco does with the model, and it is what removes
|
||||
the divergence — with both panels reading one buffer there is never a `value` for the sync effect to
|
||||
reconcile, so the `executeEdits` never runs and the cross-panel `onChange` never fires.
|
||||
|
||||
A buffer outlives the tab that opened it. `holders` is the set of panels with the file open, and the entry
|
||||
is dropped when the last one closes, so closing a tab in one panel cannot take unsaved work out from under
|
||||
another. Closing the last one still discards unsaved edits, exactly as before.
|
||||
|
||||
`<Editor keepCurrentModel>` is the other half, and it was needed under any fix: the default disposes the
|
||||
shared model on unmount, and `key={activeFile.path}` makes that every **tab switch**, not just a panel
|
||||
close. The cost is one retained model per file opened in a session.
|
||||
|
||||
Two things worth knowing:
|
||||
|
||||
- **Writes go through `queryClient.setQueryData`, not `useGlobal`'s setter.** `useGlobal` applies a
|
||||
functional update to the `data` captured in render rather than to what is in the cache now. With one
|
||||
writer that is invisible; this slot has one writer per panel, which is the whole scenario.
|
||||
- **The trap can now fire legitimately.** Two panels on one file share a model, so a large paste in one
|
||||
fires the other's `onChange` while it is unfocused — which is what `reportSuspiciousEdit` looks for. A
|
||||
report whose `stack` runs through the editor's own `onChange` with another panel open is that, not the
|
||||
bug. This is the reason to keep reading the `stack` rather than the count.
|
||||
|
||||
Pinned by `useEditorState.test.ts` (9 tests): adoption, cross-panel edit, close-with-holders, drop on last
|
||||
close, release on unmount, and that the tab set stays per panel.
|
||||
|
||||
**Still not closed.** This removes the mechanism, not the mystery: where the log text came from is still
|
||||
unknown, and nothing found so far writes job output into an editor buffer. Keep the trap.
|
||||
|
||||
## When it is reported
|
||||
|
||||
Ask for one thing. In the browser's console, on the machine where it happened:
|
||||
|
||||
Reference in New Issue
Block a user