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.

map view: without WebGL2 the map page crashes with "Cannot read properties of undefined (reading 'destroy')" instead of showing its own "Map failed to load" fallback #11819

Closed

Description

@objectstack-fleet

Filing gate ① — product defect with a named location and a reproduction. reach: showcase Work Map page (/apps/com.example.showcase/page/showcase_task_map) in a browser without WebGL2 (here headless Chromium; also GPU-blocklisted browsers, hardware acceleration off, some VDI/remote desktops).

Who acts on it: objectui triage → the console / records owner. ⛔ Not a claim. Found in a manual browser QA pass of the end-user console on 2026-10-07; filed one card per finding on the maintainer's word: 「你发现的问题全部提交 issue,然后继续测试前端」, and on the one-card-per-finding question 「覆盖规则,逐条立卡」.

What happens

The page body is replaced by the render-failure card: Component "object-map" failed to render — Cannot read properties of undefined (reading 'destroy') with Retry. The message names neither the map nor the real cause. Console sequence on one load:

  1. maplibre GPUInitializationError: WebGL2 is required to display this map … from _setupPainter — the map object is constructed without a painter.
  2. 40× TypeError: Cannot read properties of undefined (reading '0') from map.project → locationToScreenPoint, called by the <Marker> children on the painter-less map.
  3. The error boundary unmounts the map; react-map-gl's cleanup calls map.remove(), which throws … (reading 'destroy') — the error the card finally shows.

Where it comes from (read in source)

ObjectMap.tsx (plugin-map) already has the right fallback: handleMapError stores the message and the map renders an amber alert "Map failed to load (…). Markers below are still listed in the search box". Maplibre 6.4 reports the missing WebGL2 through that same error event, but the markers keep rendering against the map, so the component crashes before the alert can show.

Expected

Without WebGL2 the view shows its own fallback — the alert plus the record list/search — and the page stays usable; no crash card.

Suggested direction (triage to rule)

Probe canvas.getContext('webgl2') before mounting MapGL (or treat an error whose name is GPUInitializationError as fatal) and, in that state, render the record list instead of MapGL and its Marker/Popup children; a jsdom test with getContext returning null pins it.

Environment

objectstack 879bd38c · examples/app-showcase booted with objectstack dev --ui --seed-admin on an isolated port and SQLite file · objectui 179f6fe9 (HEAD; the framework pin .objectui-sha is a58626c8) served by the console's Vite dev server, perf numbers from a vite build of the same commit · Chromium 141 at 1440×900 · signed in as the seeded platform admin admin@objectos.ai unless stated.

Duplicate check

Dedupe words: object map webgl2 unavailable crash · GPUInitializationError fallback · map.remove destroy undefined error boundary

Filed by Claude Code (session session_01D76mrPJrSSdaKRxR2rvrMG) from that QA pass.


Generated by Claude Code

Activity

  1. added
    bugSomething isn't working
    domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seat
    area:recordsBusiness objects, records, the views that show data, usable forms, search
    on Oct 7, 2026
  2. objectstack-fleet commented on Oct 7, 2026

    @objectstack-fleet
    ContributorAuthor

    Path: ② the capabilities an end user meets in the app — a map page | 缺项 | P3

    Triage: first grade, bug · priority:p3 · domain:ui · area:records · pm:queue. Direction: a map that cannot start shows the plugin's own fallback

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

    Triage: lands in packages/plugin-map/src/ObjectMap.tsx (its "Map failed to load" alert at :1365) ⇒ domain:ui; rationale: a Studio or console surface in objectui. Filed from the browser QA pass of 2026-10-07 (objectstack 879bd38c, objectui 179f6fe9); paths re-read on objectui main 9990f9e122.

    • Why p3: only browsers without WebGL2 are affected, and the rest of the app keeps working.
    • Direction:
      • detect the initialization failure (no WebGL2, or the map's error event) before mounting the markers
      • show the existing "Map failed to load" alert, naming WebGL2
      • cleanup does not call into a map that never started
    • Clause-②: no. Patch changeset in objectui.
  3. objectstack-fleet commented on Oct 8, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 3
    Session: session_01CGZy1BGCjdN5cXqL9cnvB8
    Account: os-support-ai
    Branch: claude/issue-11819-map-webgl2-fallback
    Worktree: objectui-issue-11819
    Domain: domain:ui
    Seat: domain:ui#3
    File surface (on cef0eee):

    • packages/plugin-map/src/ObjectMap.tsx:
      • the map's start-up and its failure state: handleMapError (about :651) and the MapGL mount with its Marker / Popup children (about :1348–:1440);
      • the existing "Map failed to load" alert (about :1365), which gains the WebGL2 cause;
    • at most one module-private probe helper beside it;
    • the tests beside them;
    • .changeset/11819-*.md.

    ⛔ Not on it: the map's style, tile and geocoding configuration; react-map-gl / maplibre-gl versions and any package.json change. 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: no path-derived mandate; default tier)
    Clause-②: no
    Responsibility: objectui plugin-map: without WebGL2, maplibre starts a map with no painter, the Marker children call map.project on it and throw, and the error boundary's cleanup throws again, so the page shows a crash card instead of the plugin's own "Map failed to load" alert | the platform path: ObjectMap already has that alert, fed by the map's error event, and a search list of the same records | every user of a map page in a browser without WebGL2 (GPU-blocklisted, hardware acceleration off, some remote desktops), starting with the showcase's Work Map
    Thread-read: 6043498035
    Serial constraints cleared: none blocking. No in-flight claim in the lane names packages/plugin-map/, and no open objectui PR touches it (read 2026-10-08T03:31Z). This seat's objectui#11812 (area:records) holds chrome/CommandPalette.tsx, which is disjoint.

    Why Clause-②: no: start-up handling inside one component of @object-ui/plugin-map. A published export, prop, type member or language-pack key is a stop: the dev reports it before opening the PR, and the seat amends this line.


    Generated by Claude Code

  4. objectstack-fleet commented on Oct 8, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
      "issue": 11819,
      "status": "done",
      "branch": "claude/issue-11819-map-webgl2-fallback",
      "pr": "https://github.com/objectstack-ai/objectui/pull/11888",
      "session": "session_01CGZy1BGCjdN5cXqL9cnvB8 — mode:subagent, the parent's id",
      "premise_still_valid": true,
      "summary": "ObjectMap (plugin-map) now probes canvas.getContext('webgl2') once per mount, before MapGL mounts, through a module-private canStartMap() that releases its context via WEBGL_lose_context. Without WebGL2 it never constructs a map. It renders the amber role=alert 'Map failed to load (...)' strip naming WebGL2, plus a list of the same filteredMarkers the existing search box narrows; each row runs the marker's own click path, now one function (selectMarker) shared with the Marker onClick. A style or tile failure is unchanged. Zone 2: H1 confirmed with a correction: the start-up GPUInitializationError fires inside the MapLibre constructor before react-map-gl attaches listeners, so onError/handleMapError never sees it. H2's second guard (GPUInitializationError arriving at onError) was falsified by reading maplibre-gl 6.4.1 and not built: the only post-construction emitter is _contextRestored, whose getContext on the same canvas returns the restored context; and switching away from MapGL after such an event would still run map.remove() on a constructed map. H3 is kept as a control. H4: the component has no record list besides the map (the search box only filters the markers), so the fallback lists filteredMarkers by title and description; no new list component. happy-dom has no canvas (getContext returns null for every type), so 29 existing plugin-map test files that stub react-map-gl went red (162 tests); each now imports the new test helper src/__tests__/webgl2Available.ts (one line) to stand in a browser whose map starts.",
      "tests": "Final head 3eda62e. pnpm --filter @object-ui/plugin-map type-check (echoed 'tsc --noEmit && tsc -p tsconfig.test.json') exit 0. pnpm exec vitest run packages/plugin-map/ exit 0: 'Test Files 39 passed (39)' / 'Tests 262 passed (262)' (first run before the helper import: 'Test Files 29 failed | 8 passed (37)', 'Tests 162 failed | 93 passed (255)'). Twelve test files outside the package that render or register object-map (console filterContextTokensSweep-10666 and public-block-binding-reach, schema-catalog catalog-gallery-render and objectql-record-source-render-identity-6939, four packages/react SchemaRenderer tests, plugin-list ListView.mapFlatten and mapViewLevelConfig, plugin-view ObjectView.mapFlatten, app-shell InterfaceListPage.mapConfig) exit 0: 'Test Files 12 passed (12)' / 'Tests 807 passed (807)'. New pins: ObjectMap.webgl2Fallback-11819.test.tsx (REAL react-map-gl + REAL maplibre-gl, webgl2 answering null; 4 passed) and ObjectMap.webgl2Controls-11819.test.tsx (stubbed map: WebGL2 mounts map+markers; style error keeps map+markers+alert; probe asked once per mount and loseContext called once; 3 passed). Ablation, on commit 3eda62e via /home/user/objectstack/scripts/ablation-replace.mjs in WRAP mode under an outer trap: anchor '    if (!gl) return false;' replaced by '    if (!gl) return true; // ABLATION-11819'; tool readout 'anchor x1 -> x0', 'replace x0 -> x1', blob 61631bb33d1f -> 2c1f01460b46; own grep during: anchor=0 marker=1. Fallback pin: 'Tests 4 failed (4)', first failure lists the caught errors \"Cannot read properties of undefined (reading '0')\" x2 and \"Cannot read properties of undefined (reading 'destroy')\", which is the card's sequence reproduced in-process. Restore: 'blob == HEAD (61631bb33d1f) and git diff HEAD is empty', own check RESTORE OK; the unablated run is 'Tests 4 passed (4)'. No build/dist leg: the pin imports ./ObjectMap by relative path. Real browser (/opt/pw-browsers/chromium via Playwright, console Vite dev server, temporary uncommitted harness rendering object-map through the real SchemaRenderer with 3 inline records; headless Chromium here HAS WebGL2, so --disable-webgl2 is the no-WebGL2 reading): before cef0eee + --disable-webgl2 = crash card 'Component \"object-map\" failed to render / Cannot read properties of undefined (reading 'destroy') / Retry', console GPUInitializationError then 3x (reading '0') then (reading 'destroy'); after 3eda62e + --disable-webgl2 = alert 'Map failed to load (this browser does not provide WebGL2, which the map needs)...', 3 rows listed, search 'boiler' leaves 1, no .maplibregl-map, no GPU error; with WebGL2 before and after both = 1 map container, 3 markers, the style alert (proxy blocks demotiles.maplibre.org). NOT MEASURED: repo-wide pnpm lint and the full CI farm, reason: CI-owned per the dispatch; eslint ran on the 33 touched .ts/.tsx files only (0 errors).",
      "mcp_calls": "0 — no MCP GitHub tool was called",
      "api_writes": "3 — each one repository_dispatch POST /repos/objectstack-ai/objectstack/dispatches executed by the fleet-write relay as objectstack-fleet[bot]: (1) pr_create => POST /repos/objectstack-ai/objectui/pulls (#11888, draft; relay read-back 11732 bytes sent, 11732 stored, identical); (2) label-write --assign os-support-ai => POST /repos/objectstack-ai/objectui/issues/11888/assignees (read-back MATCHES); (3) this os-dev-report => POST /repos/objectstack-ai/objectui/issues/11819/comments. Plus git push of the branch (not REST).",
      "open_questions": [],
      "out_of_scope_findings": [
        "carrier: 承接者:无 · noted, not filed — stale test prose after this fix: examples/schema-catalog/test/catalog-gallery-render.test.tsx (the three plugin-map/* exclusion reasons and the teardown comment) and apps/console/src/__tests__/public-block-binding-reach.test.tsx (the swallow-on-unmount comment) say an object-map under happy-dom crashes on unmount or fetches the demo style; it now shows the fallback, constructs no map and fetches nothing. Both suites stay green (measured). In the PR's Acceptance notes.",
        "carrier: 承接者:无 · noted, not filed — existing copy of the style alert says 'Markers below are still listed in the search box'; the search box is an input that filters the map's markers and lists nothing. Wording only. In the PR's Acceptance notes."
      ],
      "gates": [
        {
          "command": "pnpm --filter @object-ui/plugin-map type-check",
          "exit": 0,
          "verdict": "no diagnostics (script echoed: tsc --noEmit && tsc -p tsconfig.test.json)"
        },
        {
          "command": "pnpm exec vitest run packages/plugin-map/",
          "exit": 0,
          "verdict": "Test Files 39 passed (39) · Tests 262 passed (262)"
        },
        {
          "command": "pnpm exec vitest run (12 cross-package files rendering or registering object-map)",
          "exit": 0,
          "verdict": "Test Files 12 passed (12) · Tests 807 passed (807)"
        },
        {
          "command": "pnpm exec eslint (33 touched .ts/.tsx files)",
          "exit": 0,
          "verdict": "0 errors, 221 warnings (none on an added line)"
        },
        {
          "command": "pnpm check:control-bytes",
          "exit": 0,
          "verdict": "check-control-bytes: OK (scanned 7864 tracked text file(s); skipped 85 binary)."
        },
        {
          "command": "pnpm check:test-path-roots",
          "exit": 0,
          "verdict": "check-test-path-roots: OK"
        },
        {
          "command": "pnpm check:changeset-claims",
          "exit": 0,
          "verdict": "No pending changeset names a file this change touches."
        },
        {
          "command": "pnpm check:pending-changeset-literals",
          "exit": 0,
          "verdict": "No test source names a pending changeset."
        },
        {
          "command": "pnpm check:new-line-citations",
          "exit": 0,
          "verdict": "VERDICT new-cross-file-line-citations: 0 new citation(s), enforcement report-only -> exit 0"
        },
        {
          "command": "pnpm check:vi-mock-specifiers",
          "exit": 0,
          "verdict": "check-vi-mock-specifiers: OK"
        },
        {
          "command": "pnpm check:vi-mock-inherit",
          "exit": 0,
          "verdict": "check-vi-mock-inherit: OK"
        },
        {
          "command": "pnpm check:vi-mock-override-shape",
          "exit": 0,
          "verdict": "check-vi-mock-override-shape: OK"
        },
        {
          "command": "pnpm check:phantom-deps",
          "exit": 0,
          "verdict": "Every in-scope import is declared by the package that publishes it."
        },
        {
          "command": "pnpm check:unreferenced-sources",
          "exit": 0,
          "verdict": "OK  Every shipped source file in every covered package is reachable."
        },
        {
          "command": "pnpm check:published-tsconfig-exclude",
          "exit": 0,
          "verdict": "OK -- all 34 enforced package(s) carry the directory form."
        },
        {
          "command": "pnpm check:pre-install-import-graph",
          "exit": 0,
          "verdict": "check-pre-install-import-graph: OK"
        },
        {
          "command": "node scripts/check-changeset-presence.mjs",
          "exit": 0,
          "verdict": "33 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s): .changeset/11819-map-webgl2-fallback.md."
        },
        {
          "command": "node scripts/check-changeset-no-major.mjs",
          "exit": 0,
          "verdict": "No changeset declares a `major` bump."
        },
        {
          "command": "CI on PR #11888",
          "exit": null,
          "verdict": "in_progress — not awaited (CI convergence is the seat's)"
        }
      ],
      "deviations": [
        "Test breadth: 29 existing plugin-map test files each gained one line, import of ./__tests__/webgl2Available (helper new in src/__tests__/). Within the claim's 'the tests beside them', but wider than the card implies; the cause is measured (happy-dom getContext returns null for every type, so the stubbed-map tests became no-WebGL2 browsers: 29 files / 162 tests red before the import).",
        "Zone 2 H2 / Zone 3 second pin not built: the onError GPUInitializationError guard was falsified by reading (see summary), so there is no pin for it.",
        "Real-browser reading narrowed: a temporary harness page (never committed, deleted) served by the console Vite dev server, rendering object-map through the real SchemaRenderer with inline records, not the showcase Work Map behind an objectstack backend (the shared objectstack checkout has no install or build). No-WebGL2 = Chromium --disable-webgl2, because headless Chromium in this container provides WebGL2.",
        "Built plugin-map's dependency closure (pnpm --workspace-concurrency=2 --filter '@object-ui/plugin-map^...' build, 14 packages) under the lock so type-check could resolve @object-ui/* declarations.",
        "Commit trailers use the model-free pair (Claude-Session + Co-authored-by: Claude) per objectui AGENTS.md, which overrides the harness reminder's model-named trailer."
      ],
      "files_changed": [
        "packages/plugin-map/src/ObjectMap.tsx",
        "packages/plugin-map/src/__tests__/webgl2Available.ts",
        "packages/plugin-map/src/ObjectMap.webgl2Fallback-11819.test.tsx",
        "packages/plugin-map/src/ObjectMap.webgl2Controls-11819.test.tsx",
        ".changeset/11819-map-webgl2-fallback.md",
        "29 existing tests, +1 import line each: packages/plugin-map/src/ObjectMap.{busReread-10623,camera,catalogRecordSource-6939,configMemo,contractEnvelope-6839,dataConfigMemo,dataProp,descriptionDisplay-10456,discardedConfigMemo,fetchGate-10664,filterConfig,inlineQueryKeys-9061,invalidationRefetch-10623,listViewMapConfigReach,markerTitle,propertiesBag-10859,rowCeiling-7210,ruleArrayFilter-10767,schemaAlignment,schemaDataShorthand,strictConfigWarn-5157,styleFallback,styleRemedyText-10002,topLevelStyle,unboundRefusal-8169,zoomSeed}.test.tsx, packages/plugin-map/src/ObjectMap.test.tsx, packages/plugin-map/src/__tests__/objectMapInputs-11168.test.tsx, packages/plugin-map/src/index.recordSourceInputs-10394.test.tsx"
      ],
      "line_budget": "not applicable (no skills/** surface)"
    }

    Generated by Claude Code

  5. objectstack-fleet commented on Oct 8, 2026

    @objectstack-fleet
    ContributorAuthor

    ACCEPT — domain:ui seat 3, session_01CGZy1BGCjdN5cXqL9cnvB8, 2026-10-08T04:50Z. PR objectui#11888, head 3eda62e.

    • PR shape: draft against main, branched from cef0eee, no rebase. git merge-tree of the current main (9cb4e29) and this head is clean; the queue tests that merge. First line Fixes #11819, no other closing keyword. Clause-②: no at line start. Both commits carry only the model-free trailer pair. Assigned to os-support-ai.

    • Scope: 34 files, +411/−4, on the claim's surface (6051604151):

      • packages/plugin-map/src/ObjectMap.tsx;
      • one new test helper, src/__tests__/webgl2Available.ts;
      • two new pin files and the changeset (patch on @object-ui/plugin-map);
      • 29 existing plugin-map test files, each +1 line: the helper's import.

      The 29 one-line edits are the declared deviation, and they are measured. happy-dom has no canvas, so every stubbed-map test became a browser without WebGL2: 29 files and 162 tests were red before the import. Each test stands in for a browser whose map starts, and now says so. Map style, tiles, geocoding, the react-map-gl / maplibre-gl versions and package.json are untouched. Nothing is added to the package entry. No governed path.

    • Diff read (the seat's own):

      • canStartMap() asks canvas.getContext('webgl2') once per mount (a useState initializer), before MapGL is ever mounted. It releases the probe's context through WEBGL_lose_context, answers yes with no DOM, and answers no on a throw.
      • Without WebGL2, MapGL and its Marker / Popup children are never rendered, so no map is constructed and nothing is cleaned up. The amber role="alert" strip names WebGL2, and a list of the same filteredMarkers the search box narrows is shown. Each row runs selectMarker, which is now the single click path the cluster marker uses too.
      • A style or tile error keeps handleMapError's alert over a working map, unchanged.
    • Brief readings, measured:

    • Ruling honoured (triage 6043498035):

      • "detect the initialization failure … before mounting the markers": the probe runs before the map mounts;
      • "show the existing "Map failed to load" alert, naming WebGL2";
      • "cleanup does not call into a map that never started": none is constructed.
    • Changeset sentences checked against the head:

      • "ObjectMap now asks the browser for a WebGL2 context before it mounts the map. When there is none it never constructs a map";
      • "lists the records the markers would have drawn. The search box above narrows that list, and choosing a record does what clicking its marker does";
      • "A style or tile failure is unchanged".
    • Reverse verification (dev report):

      • The fallback pin runs the REAL react-map-gl and maplibre-gl with webgl2 answering null.
      • Ablation through ablation-replace.mjs: the probe answering "can start" gives 4 of 4 red. The first failure lists the caught "(reading '0')" ×2 and "(reading 'destroy')", which is the card's sequence reproduced in-process. Restored with the blob equal to HEAD.
      • packages/plugin-map/: 39 files, 262 tests passed. Twelve cross-package files that render or register object-map: 807 tests passed.
      • Real browser (Chromium with --disable-webgl2, the console rendering object-map through the real SchemaRenderer): before, the crash card; after, the alert, 3 listed rows, and search narrowing them to 1. With WebGL2, before and after both show the map with its markers.
    • Gates: CI on 3eda62e (head_sha equals the PR head): 43 runs, 40 success, 3 skipped (Test (coverage), its shard template, dependabot), 0 failure. Lint and Type Check are success. Mergeable state clean.

    • Contract review: not required (Clause-②: no).

    Findings:

    • Test comments in examples/schema-catalog/test/catalog-gallery-render.test.tsx and apps/console/src/__tests__/public-block-binding-reach.test.tsx still say that an object-map under happy-dom crashes on unmount or fetches the demo style. Both suites stay green. Prose only → Acceptance notes
    • The existing style alert says "Markers below are still listed in the search box", but the search box lists nothing. Wording only, and it predates this card → Acceptance notes

    Landing: ready + auto-merge through the queue, now.


    Generated by Claude Code

  6. objectstack-fleet commented on Oct 8, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed — domain:ui seat 3, session_01CGZy1BGCjdN5cXqL9cnvB8, 2026-10-08T05:11Z. PR objectui#11888 merged through the queue as fbaa79d at 2026-10-08T05:10Z.

    • The merge:
      • One parent, d4c39de, an ancestor of origin/main.
      • The landed tree equals git merge-tree of that parent and the accepted head 3eda62e (tree 92b80529 on both).
      • 34 files, +411/−4.
    • Content check, d4c39de → fbaa79d, in packages/plugin-map/src/ObjectMap.tsx:
      • function canStartMap: 0 → 1.
      • The fallback data-testid="map-webgl2-fallback": 0 → 1.
      • The shared const selectMarker: 0 → 1.
      • The package entry index.tsx names canStartMap 0 times, so the probe stays module-private.
      • Control: packages/plugin-map/package.json is byte-identical on both sides (no dependency or version change).
    • Closures: the PR's only closing keyword was Fixes #11819, and this merge closed only objectui#11819 (05:10Z). The other closures since 05:00Z (objectui#11818 by PR objectui#11887) are another seat's. pm:dispatched is removed.
    • Follow-ups: none filed. The stale test comments and the old alert's "listed in the search box" wording are in the PR's Acceptance notes.

    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:recordsBusiness objects, records, the views that show data, usable forms, searchbugSomething isn't workingdomain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatpriority:p3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions