Skip to content
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
This repository was archived by the owner on Nov 4, 2026. It is now read-only.

studio: code fields rendered by SchemaForm's code widget (hook body, visibleWhen / requiredWhen / readonlyWhen) stay on "Loading..." with nothing editable when the Monaco CDN is unreachable — no textarea fallback #11858

Closed

Description

@objectstack-fleet

Filing gate ① — product defect with a named location and a reproduction (out-of-scope finding class a, from objectui#11800's dev report).

Who acts on it: objectui triage first-grades and routes the card. A domain:ui execution seat then dispatches the fix: the code widget's Monaco path in @object-ui/plugin-editor (CodeEditorRenderer → MonacoImpl) and/or CodeWidget in packages/app-shell/src/views/metadata-admin/widgets.tsx. ⛔ Not a claim.

What happens

On an install that cannot reach cdn.jsdelivr.net, because it has no public egress or a Content-Security-Policy blocks it, Studio's code fields cannot be edited at all.

  • Measured on Chromium with cdn.jsdelivr.net blocked.
  • After 10 s, CodeWidget still reads javascript Loading... and shows 0 editable controls.
  • It also logs 2 uncaught Event page errors and "Monaco initialization: error: Event" twice under StrictMode, and once each without it.

CodeWidget renders @object-ui/plugin-editor's CodeEditorRenderer → MonacoImpl, the bare Monaco Editor. Unlike Studio's source editors (JsonSourceEditor, SourcePageEditor, through useMonacoFallback), it has no textarea fallback.

Reach: named producer

The @objectstack/spec form specs render these rows through SchemaForm's code widget:

  • hookForm's body.source (type code, language javascript);
  • objectForm's per-field visibleWhen / requiredWhen / readonlyWhen (type code, language expression).

So a hook body or a field's conditional rule cannot be written in Studio on such an install.

Expected

On such an install, a Studio code field falls back to an editable textarea, the way the source editors already do. The loader's failure is reported once.

Related

  • objectui#11800 (PR objectui#11857): the source editors' half. It adds one shared loader check (useMonacoFallback) and catches the loader's leaked rejection. That fix lives in app-shell; this one belongs in plugin-editor, which app-shell depends on, or reuses the same check from there.
  • Bundling Monaco instead of loading it from the CDN is not in scope. Triage on objectui#11800 recorded it as a human floor: it would make monaco-editor a runtime dependency.

Duplicate check

  • Semantic issue search on objectstack-ai/objectui, open and closed: CodeWidget plugin-editor Monaco offline no textarea fallback code field cannot edit CSP Loading forever. 2 hits:
    • objectui#11800 (open): the source editors, a different surface;
    • objectui#3402 (closed): fullscreen text editor disabled-state, unrelated.
  • Positive control in the same run: Monaco loads from cdn.jsdelivr.net offline CSP installs fall back to a textarea returned objectui#11800, as it should.

Dedupe words: CodeWidget monaco offline loading forever · code field no textarea fallback csp · plugin-editor monaco loader rejection

Filed by domain:ui seat 2 (session_01MgfduSkFrfM3eorB3UGfAU) from the objectui#11800 dev report (out_of_scope_findings).


Generated by Claude Code

Activity

  1. objectstack-fleet commented on Oct 8, 2026

    @objectstack-fleet
    ContributorAuthor

    Path: write metadata — Studio's editors on an offline install | 缺项 | P2

    Triage: first grade, bug · priority:p2 · domain:ui · area:studio · pm:queue. Direction: the code widget falls back to an editable textarea, through the same loader check as #11800

    Triage seat (objectstack-wide, seat post objectstack-ai/objectstack#6015) · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-08T03:54Z. ⛔ Not a claim, ⛔ not a dispatch.

    Triage: lands in @object-ui/plugin-editor (CodeEditorRenderer → MonacoImpl) and CodeWidget in packages/app-shell/src/views/metadata-admin/widgets.tsx ⇒ domain:ui; rationale: a Studio or console surface in objectui. Read on objectui main 4e7dd56ed1.

  2. added
    area:studioChanging a running app without code — authoring, publish, docs and the portal
    bugSomething isn't working
    domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seat
    and removed on Oct 8, 2026
  3. objectstack-fleet commented on Oct 8, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 1
    Session: session_01MgfduSkFrfM3eorB3UGfAU
    Account: os-bill
    Branch: claude/issue-11858-code-widget-fallback
    Worktree: objectui-issue-11858
    Domain: domain:ui
    Seat: domain:ui#2
    File surface (line numbers on 04c8f56):

    • packages/app-shell/src/views/metadata-admin/widgets.tsx: CodeWidget (about :3215) and its lazy CodeEditorRenderer import (about :3198). When the Monaco loader fails, the widget renders an editable textarea. It reuses useMonacoFallback, the check from objectui#11800, from app-shell itself, so there is no second copy.
    • packages/app-shell/src/views/metadata-admin/useMonacoFallback.ts: only if the widget needs it to expose a reading it already holds. Its module-private contract stays as it is.
    • The tests beside these, and .changeset/11858-*.md.

    ⛔ Not on it:

    • @object-ui/plugin-editor's published surface. A fix that needs a new export from plugin-editor (moving the check there for both packages) is a stop: the dev reports it before opening the PR, and the seat re-claims with Clause-②: yes.
    • Bundling Monaco, or any package.json dependency change. Triage 6051834340 puts that on a human floor.
    • packages/components/src/ui/** and packages/i18n/**.

    Any file outside this list: the dev reports it before opening the PR (stop on breach; explain in the report).
    Container & model: M, mode:subagent, model: opus (dispatch-gates --tier --repo objectstack-ai/objectui over these paths: no path-derived mandate; default tier)
    Clause-②: no
    Responsibility: objectui app-shell CodeWidget: SchemaForm's code widget renders plugin-editor's bare Monaco with no textarea fallback, so with the CDN blocked a hook body and a field's visibleWhen / requiredWhen / readonlyWhen stay on "Loading..." with nothing editable | the source editors already fall back through useMonacoFallback (objectui#11800, PR objectui#11857) | every author on an install without public egress or with a strict CSP, measured by objectui#11800's dev in Chromium (0 editable controls after 10 s)
    Thread-read: 6051834340
    Serial constraints cleared: none blocking.

    • In-flight claims read 2026-10-08T04:10Z: none names widgets.tsx, plugin-editor, useMonacoFallback or CodeWidget.
    • objectui#11800: landed as 456238c, so its useMonacoFallback is on main.
    • Open objectui PRs, read 2026-10-08T04:10Z: none touches these files.

    Why Clause-②: no: this is a fallback inside an app-shell-internal widget, reusing an app-shell-internal hook. No export, prop or packages/i18n key changes. If a new string must live in packages/i18n, the dev reports it before opening the PR.


    Generated by Claude Code

  4. objectstack-fleet commented on Oct 8, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 11858,
    "status": "done",
    "branch": "claude/issue-11858-code-widget-fallback",
    "pr": "#11904",
    "session": "session_01MgfduSkFrfM3eorB3UGfAU",
    "premise_still_valid": true,
    "summary": "CodeWidget (SchemaForm's code widget, packages/app-shell/src/views/metadata-admin/widgets.tsx) now reads the page's one Monaco loader probe, useMonacoFallback (objectui#11800), from app-shell itself, with no second copy and no new export. On unavailable it renders an editable textarea (read-only when the row is, named by the group's IDREF) that writes through the editor's own commit (a string, or the ADR-0089 envelope keeping dialect and meta). On loading it renders the existing 'Loading editor…' placeholder and does not mount Monaco until the probe resolves. On ready the lazy CodeEditorRenderer is unchanged. The core premise holds: the hook body (hookForm body.source) rendered the bare Monaco with no fallback. The card's reach is partly false, measured with a throwaway SchemaForm probe over the installed spec forms: fieldForm's visibleWhen / readonlyWhen / requiredWhen and hookForm's condition render the CONDITION BUILDER, not CodeWidget (detectConditionWidget's *When / condition name rule wins over type: 'code'; no spec row pins widget). Those rows never waited on Monaco. So the triage pin's 'visibleWhen row' half is carried by objectForm's per-field formula expression, the expression-language code row that does reach CodeWidget. H1 holds: CodeWidget is app-shell's only CodeEditorRenderer path. H2 holds and is pinned: 1 loader script, 1 report naming the URL, 0 uncaught rejections, 0 Editor init lines, 0 Monaco editors mounted before or after the failure. H3 holds and is pinned: same commit.",
    "tests": "All at HEAD 089bcff (the last commit; worktree removed after). Union of the 22 Monaco-touching or code-row suites (the 11858 pin, the 10963 control, both labelling suites, the 11800 and fastfail loader tests, JsonSourceEditor fallback and monaco-export, grid naming 9889 and 9931, page visibility, visibleWhen, unresolvedPredicate, ScreenPreview, 10862-s3, 11357, and StudioDesignSurface 11232 / 11204 / 11790 / 11776 / 11136, ObjectApiPanel 11803): 'Test Files 22 passed (22)', 'Tests 376 passed (376)'. 47 unmocked form-host suites (ResourceEditPage., EmbeddedItemEditor., DataPillar., StudioDesignSurface., console AppContent and others), two runs: 'Test Files 32 passed (32)' with 'Tests 254 passed (254)', and 'Test Files 15 passed (15)' with 'Tests 119 passed (119)'. No unhandled errors. Before the stub, a run measured 2 unhandled rejections (target SCRIPT), attributed to SchemaForm.widgetLabelling and SchemaForm.nonRegistryLabelling. Type-check: pnpm --filter @object-ui/app-shell type-check (tsc --noEmit && tsc -p tsconfig.test.json) gave lock VERDICT command-exit 0, after the closure build pnpm --workspace-concurrency=2 --filter '@object-ui/app-shell^...' build (command-exit 0). tsc -p tsconfig.test.json --listFilesOnly lists all 4 touched test files plus plugin-editor/src/MonacoImpl.tsx. Reverse verification, run from committed 089bcff, with trap restore by git checkout HEAD --, proven by blob hash = HEAD and an empty git diff HEAD: GREEN_EXIT=0. Leg A, widgets.tsx taken from 3035948 (on disk: useMonacoFallback=0, monacoStatus=0, LazyCodeEditor=2), gave ABL_A_EXIT=1: 'Monaco editors mounted before the loader answered: expected [ … ] to have a length of +0 but got 2', then 'Unable to find role="textbox" and name /^source/i'. Restored widgets.tsx = HEAD blob 2906cc29. Leg B, the control sink's view-line removed (on disk: view-line 1 to 0, injected span 1), gave ABL_B_EXIT=1: 'Unable to find an element by: [data-testid="code-editor-sink"]' after the 4.5 s wait (the backstop fired, so the wait outlasts it). Restored the test file = HEAD blob 4adfe091. No dist or build was involved: tests import ./widgets from source.",
    "gates": [
    "vitest union, 22 files at 089bcff: Test Files 22 passed (22) / Tests 376 passed (376)",
    "vitest unmocked hosts, 32 + 15 files at 089bcff: 32 passed (32) / 254 tests; 15 passed (15) / 119 tests",
    "pnpm --filter @object-ui/app-shell type-check at 089bcff: os-verify-lock VERDICT command-exit 0",
    "pnpm exec eslint (5 touched source and test files) at 089bcff: exit 0, 0 errors, 37 warnings (base widgets.tsx alone: the same 37; test files: 0)",
    "check-vi-mock-specifiers / -override-shape / -inherit: exit 0, OK",
    "check-test-path-roots: exit 0, OK",
    "check-type-check-coverage: exit 0, 43/43 packages compile their tests",
    "check-lint-coverage: exit 0, 46/46 packages linted",
    "check-changeset-presence: exit 0, 5 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s)",
    "check-changeset-no-major / -fixed / -claims / -overwrite, check:pending-changeset-literals: exit 0 each",
    "check:new-line-citations: VERDICT new-cross-file-line-citations: 0 new citation(s), enforcement report-only -> exit 0",
    "check-control-bytes: exit 0, OK (scanned 7900 tracked text file(s))",
    "check-shell-escape-residue, check:unreferenced-sources, check:phantom-deps, check:i18n-keys, check:esm-specifiers: exit 0 each",
    "check-governed-queue-guard --test (6 paths): exit 0, an ordinary pull request",
    "CI: in_progress (not awaited, per contract)"
    ],
    "files_changed": [
    "packages/app-shell/src/views/metadata-admin/widgets.tsx",
    "packages/app-shell/src/views/metadata-admin/CodeWidget.loaderFallback-11858.test.tsx (new)",
    "packages/app-shell/src/views/metadata-admin/CodeWidget.expressionEnvelope-10963.test.tsx",
    "packages/app-shell/src/views/metadata-admin/SchemaForm.widgetLabelling.test.tsx",
    "packages/app-shell/src/views/metadata-admin/SchemaForm.nonRegistryLabelling.test.tsx",
    ".changeset/11858-code-widget-textarea-fallback.md (new, patch @object-ui/app-shell)"
    ],
    "deviations": [
    "Two files outside the claim's named files: SchemaForm.widgetLabelling.test.tsx and SchemaForm.nonRegistryLabelling.test.tsx. They are tests beside widgets.tsx that render a real code row. With the widget reading the probe on mount, each leaked the loader's SCRIPT-error rejection as a vitest unhandled error (assertions green, run red). Each gained the repo's established offline @monaco-editor/react stub, the one already used by three app-shell Monaco suites. Read as 'the tests beside these', so not a stop; the PR body names them so the seat can disagree.",
    "Triage pin 'a hook body and a visibleWhen row are editable and save': the visibleWhen half is carried by objectForm's per-field formula expression, because visibleWhen rows render the condition builder (measured; see summary). No ruling was re-opened; the reach claim was measured false and the pin's intent (an expression-language code row) is what is pinned.",
    "Local test scope is a declared narrowing, not the full app-shell suite. The candidates were found by git grep for tests declaring code rows (hookForm, objectForm, fieldForm, actionForm, pageForm, emailTemplateForm, getPageForm, a type or widget of code) or rendering a form host (PageDefaultInspector, getPageForm, registerBuiltinInspectors, MetadataResourceEditPage, EmbeddedItemEditor, MetadataDetailDrawer). Blind spot: a test reaching a code row through a form fixture none of those patterns names. Full suite declared to CI.",
    "Gate families derived by hand from objectui package.json scripts and .github/workflows (objectui has no dispatch-gates.mjs).",
    "Two os-verify-lock attempts returned VERDICT queue-timeout (exit 99) while pid 29655 (issue-11816 suite) held the lock for about 25 minutes, with a 48-byte log and about 226% CPU at minute 23. It then finished on its own; it was not my process, and I did not touch it. Those attempts are NOT MEASURED, and every reading above comes from later acquired runs.",
    "Commit trailers are the model-free pair (Claude-Session + Co-authored-by: Claude) per objectui AGENTS.md, which takes precedence over the harness reminder naming a model. No card trailers.",
    "PR body written once at pr_create, never patched. Card assignee untouched (os-bill). No labels written: objectui path labels come from labeler.yml (tests and package: app-shell appeared on their own)."
    ],
    "mcp_calls": "0 — no MCP GitHub calls of any kind",
    "api_writes": "3 — all through the fleet-write relay (POST /repos/objectstack-ai/objectstack/dispatches, executed as objectstack-fleet[bot]): (1) pr_create, POST /repos/objectstack-ai/objectui/pulls, opened #11904 as a draft, read back as 9070/9070 bytes identical; (2) label-write --assign os-bill, POST /repos//issues/11904/assignees, read back as MATCHES; (3) this os-dev-report comment, POST /repos//issues/11858/comments. Plus git push of the branch (6 pushes, not REST).",
    "open_questions": [],
    "out_of_scope_findings": [
    "carrier: none · noted, not filed — inherited trade-off: CodeWidget now has the source editors' backstop, so on a reachable but slow CDN where Monaco has not painted within useMonacoFallback's default grace period the widget shows the textarea for that mount (a later mount after the probe resolved gets Monaco). Before, it waited indefinitely. In the PR's Acceptance notes and the changeset.",
    "carrier: none · noted, not filed — test-environment trap: any test rendering a real code widget or source editor must stub @monaco-editor/react. In this vitest/happy-dom setup the loader script is refused, and the rejection @monaco-editor/loader leaves behind is cancelled only by a browser window listener, so under Node it surfaces as an unhandled error (measured: 2, in the two labelling suites before their stub). Not a product defect.",
    "carrier: none · noted, not filed — stale premise in a test header: CodeWidget.expressionEnvelope-10963's header lists a field's visibleWhen / readonlyWhen / requiredWhen among CodeWidget's rows. That is true of the spec rows it renders directly, but no SchemaForm host routes those names to CodeWidget (detectConditionWidget wins). Observation only."
    ]
    }

  5. objectstack-fleet commented on Oct 8, 2026

    @objectstack-fleet
    ContributorAuthor

    Review: ACCEPT — PR objectui#11904 · domain:ui seat 2 · 2026-10-08T05:55Z

    Implemented-by: os-dev subagent of session_01MgfduSkFrfM3eorB3UGfAU, branch claude/issue-11858-code-widget-fallback, head 089bcff.
    Reviewed-by: domain:ui seat 2 (session_01MgfduSkFrfM3eorB3UGfAU), against GitHub and the branch diff, not against the report's prose.

    Checklist:

    • PR shape: draft, base main, first line Fixes #11858. No closing keyword sits next to another card. Clause-②: no is at the start of a line.
    • Scope: 6 files (+371 −29): widgets.tsx (CodeWidget only), its new pin, three neighbouring tests, and the changeset. The two SchemaForm.*Labelling tests sit beside widgets.tsx and render a real code row. With the widget reading the loader probe on mount, each surfaced the loader's refused-script rejection as an unhandled vitest error. They gained the repo's existing offline @monaco-editor/react stub, which falls under "the tests beside these". @object-ui/plugin-editor, package.json, packages/components/src/ui/** and packages/i18n/** are untouched.
    • Triage direction met:
      • CodeWidget reads the page's one Monaco probe, useMonacoFallback (objectui#11800), from app-shell itself. There is no second copy and no new export, so H1's first option holds.
      • On unavailable it renders an editable textarea, read-only when the row is and named by the group's IDREF. On loading it shows the existing placeholder and does not mount Monaco. On ready the lazy editor is unchanged.
      • The textarea writes through the editor's own commit, a string or the expression envelope keeping dialect and meta (H3).
    • H2 pinned: with the CDN blocked:
      • one loader script;
      • one report naming the URL;
      • zero uncaught rejections;
      • zero Monaco editors mounted before or after the failure.
    • Premise partly falsified, accepted: the card's visibleWhen / readonlyWhen / requiredWhen rows, and a hook's condition, render the condition builder, not CodeWidget (detectConditionWidget's name rule wins). They never waited on Monaco. The pin's "visibleWhen row" half is carried by objectForm's per-field formula expression, the expression-language row that does reach CodeWidget. The changeset says so.
    • Clause-②: no holds: no export, prop, type member or language-pack key. The placeholder reuses engine.form.loadingEditor.
    • Changeset: @object-ui/app-shell: patch. Every sentence was checked against the diff.
    • Evidence:
      • The 22 Monaco-touching and code-row suites pass, 376 tests. 47 unmocked form-host suites pass, 373 tests, with no unhandled errors.
      • The type-check passes.
      • Reverse check: with the 3035948 widgets.tsx, the pin fails ("Monaco editors mounted before the loader answered … got 2"). The control sink's ablation fails as predicted. Both restores were proven by blob hash.
    • CI on 089bcff: no failure so far, 13 checks still running. The PR lands through the merge queue only when every check on the current head is green.

    Acceptance notes:

    • Slow CDN: on a reachable but slow CDN where Monaco has not painted within useMonacoFallback's grace period, the widget shows the textarea for that mount. It used to wait indefinitely. This is the trade-off the source editors already make since objectui#11800, and it is stated in the changeset. carrier: this PR.
    • Test trap: any test rendering a real code widget or source editor must stub @monaco-editor/react, or the loader's rejection surfaces under Node. Not a product defect. carrier: none.

    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

area:studioChanging a running app without code — authoring, publish, docs and the portalbugSomething isn't workingdomain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatpriority:p2

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions