From 1cf8e24507032bb2c120316e686d7591ad532e58 Mon Sep 17 00:00:00 2001 From: Dawson Toth Date: Thu, 23 Jul 2026 11:36:58 -0400 Subject: [PATCH] fix(browse): make record editors worker-free to end Monaco JSON OOM (#1370/#1499) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The browse Add/Edit record editors used `language="json"`, whose worker receives the model's full text (and every change delta) over a structured-clone postMessage. A large record — a big blob/array field, or a bulk-insert array pasted into Add row — overflows that clone and crashes the worker with "DataCloneError: ... out of memory.", which Monaco retries in a tight loop (the RUM recurrence of #1370/#1499 this PR was opened for). A size-based json->plaintext switch (the earlier approach) can't close the paste path: @monaco-editor/react applies a changed language prop in a later effect, and once the model is synced the worker's own change listener posts the oversized delta before any switch runs. Instead register a worker-free JSON language (WORKER_FREE_JSON_LANGUAGE_ID) — Monaco's main-thread JSON tokenizer plus the standard JSON language configuration, and none of the worker-backed providers — and use it for both record editors. There is no language worker to overflow, at any size; highlighting and bracket/auto-close are unchanged. Because that removes the worker's validation markers, the live onValidate signal is replaced with a cheap main-thread parse for content under the size limit (keeps "Save disabled while invalid" for normal records without parsing a huge buffer per keystroke) plus a caught JSON.parse at submit time that toasts on malformed input instead of throwing. EditTableRowModal also resets its draft when a different record is opened, since that modal instance is reused across rows. Co-Authored-By: Claude Opus 4.8 --- .../modals/AddTableRowModal.test.tsx | 136 ++++++++++++++++++ .../databases/modals/AddTableRowModal.tsx | 97 +++++++------ .../modals/EditTableRowModal.test.tsx | 125 +++++++++++++--- .../databases/modals/EditTableRowModal.tsx | 43 ++++-- .../databases/modals/recordEditorJson.test.ts | 46 ++++++ .../databases/modals/recordEditorJson.ts | 43 ++++++ src/lib/monaco/monacoJsonTokenization.d.ts | 13 ++ src/lib/monaco/setup.ts | 6 + src/lib/monaco/workerFreeJsonLanguage.test.ts | 66 +++++++++ src/lib/monaco/workerFreeJsonLanguage.ts | 76 ++++++++++ 10 files changed, 576 insertions(+), 75 deletions(-) create mode 100644 src/features/instance/databases/modals/AddTableRowModal.test.tsx create mode 100644 src/features/instance/databases/modals/recordEditorJson.test.ts create mode 100644 src/features/instance/databases/modals/recordEditorJson.ts create mode 100644 src/lib/monaco/monacoJsonTokenization.d.ts create mode 100644 src/lib/monaco/workerFreeJsonLanguage.test.ts create mode 100644 src/lib/monaco/workerFreeJsonLanguage.ts diff --git a/src/features/instance/databases/modals/AddTableRowModal.test.tsx b/src/features/instance/databases/modals/AddTableRowModal.test.tsx new file mode 100644 index 000000000..37a23b0df --- /dev/null +++ b/src/features/instance/databases/modals/AddTableRowModal.test.tsx @@ -0,0 +1,136 @@ +/** + * @vitest-environment jsdom + */ +import { AddTableRowModal } from '@/features/instance/databases/modals/AddTableRowModal'; +import type { InstanceTable } from '@/integrations/api/api.patch'; +import { WORKER_FREE_JSON_LANGUAGE_ID } from '@/lib/monaco/workerFreeJsonLanguage'; +import { MAX_WORKER_MODEL_CHARS } from '@/lib/monaco/workerLimits'; +import { cleanup, fireEvent, render, screen } from '@testing-library/react'; +import { toast } from 'sonner'; +import { afterEach, beforeAll, describe, expect, it, vi } from 'vitest'; + +const { mutate } = vi.hoisted(() => ({ mutate: vi.fn() })); + +// Monaco can't load in jsdom; stub the editor with a