set a trap for the editor buffer corruption
Not a fix — I could not find the cause by reading, and shipping a guess dressed as
one is worse than saying so.
What reading DID settle, and it removes two of the three candidates:
- The end-of-turn refresh cannot be it. `reloadFile` sets `originalContent` and
clears `isDirty`, so no refresh can produce the dirty dot in the screenshot.
- Nothing in the app types into the page: no `execCommand('insertText')`, no
synthetic keyboard events, no clipboard writes anywhere near the editor.
That leaves Monaco's own change event, which means the model was edited the way a
paste or a programmatic `executeEdits` edits it. Neither is identifiable from a
screenshot taken afterwards, which is why this needs to be caught in the act.
`reportSuspiciousEdit` logs any insertion over 200 characters arriving while the
editor does NOT have focus, with an Error constructed at that moment so the stack
names the caller. A person cannot type 200 characters into an editor they are not
focused on; whatever does will be in that trace.
It prevents nothing and changes no behaviour. Delete it once the cause is known.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -28,6 +28,41 @@ type CodeEditorViewProps = {
|
||||
panelId?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Shout when the buffer changes in a way a person could not have typed.
|
||||
*
|
||||
* Background-job output has twice appeared spliced into an open file — the model mutated, the tab marked
|
||||
* dirty, the file on disk untouched. Two explanations are already ruled out: the end-of-turn refresh
|
||||
* cannot do it (`reloadFile` clears `isDirty`), and nothing in the app types into the page. That leaves
|
||||
* Monaco's own change event, which means it looks like a paste or a programmatic edit — and neither can
|
||||
* be identified from a screenshot after the fact.
|
||||
*
|
||||
* So this does not prevent anything. It records the one thing missing: what arrived, how big it was, and
|
||||
* whether the editor even had focus at the time. A large insertion into an UNFOCUSED editor is not a
|
||||
* user, and the stack trace attached to it names whatever called it.
|
||||
*
|
||||
* Delete this once the cause is known. It is a trap, not a feature.
|
||||
*/
|
||||
const SUSPICIOUS_INSERT = 200;
|
||||
|
||||
function reportSuspiciousEdit(
|
||||
editor: MonacoEditor.IStandaloneCodeEditor | null,
|
||||
file: { path: string; content: string },
|
||||
next: string,
|
||||
): void {
|
||||
const grew = next.length - file.content.length;
|
||||
if (grew < SUSPICIOUS_INSERT) return;
|
||||
if (editor?.hasTextFocus()) return;
|
||||
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn(
|
||||
`[editor] ${grew} characters appeared in ${file.path} while the editor was NOT focused. ` +
|
||||
`This is the background-job-output bug (TODO.md → Known bugs). First 120 chars:\n` +
|
||||
next.slice(0, 120),
|
||||
new Error('stack at the moment of the edit'),
|
||||
);
|
||||
}
|
||||
|
||||
export const CodeEditorView = ({
|
||||
className,
|
||||
theme = 'vs-dark',
|
||||
@@ -294,7 +329,10 @@ export const CodeEditorView = ({
|
||||
theme={theme}
|
||||
language={getLanguage(activeFile.name)}
|
||||
value={activeFile.content}
|
||||
onChange={(value) => setContent(activeFile.path, value ?? '')}
|
||||
onChange={(value) => {
|
||||
reportSuspiciousEdit(editorRef.current, activeFile, value ?? '');
|
||||
setContent(activeFile.path, value ?? '');
|
||||
}}
|
||||
beforeMount={configureMonacoTypescript}
|
||||
onMount={handleEditorMount}
|
||||
options={{
|
||||
|
||||
Reference in New Issue
Block a user