You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
Repository navigation
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
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:
maplibre GPUInitializationError: WebGL2 is required to display this map … from _setupPainter — the map object is constructed without a painter.
40× TypeError: Cannot read properties of undefined (reading '0') from map.project → locationToScreenPoint, called by the <Marker> children on the painter-less map.
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
Semantic issue search on objectstack-ai/objectui (open and closed), object map WebGL2 not supported GPUInitializationError crash fallback cannot read destroy: 1 hits (0 open, 1 closed).
Semantic issue search on objectstack-ai/objectui (open and closed), ObjectMap markers crash map failed to load banner never shown: 6 hits (0 open, 6 closed).
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 main9990f9e122.
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
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.
{
"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)"
}
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:
Zone 2 Implement visual designer for Object UI schemas #1 is confirmed, with a correction. The start-up GPUInitializationError fires inside the MapLibre constructor, before react-map-gl attaches its listeners, so onError never hears it.
Zone 2 Add automated testing infrastructure and CI/CD workflows #2's second guard (that error arriving at onError later) is falsified by reading maplibre-gl 6.4.1, so it was not built. The only later emitter is _contextRestored, whose getContext returns the restored context.
"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.
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.
Activity
objectstack-fleet commentedon Oct 7, 2026
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 fallbackTriage 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 (objectstack879bd38c, objectui179f6fe9); paths re-read on objectuimain9990f9e122.Clause-②: no. Patch changeset in objectui.objectstack-fleet commentedon Oct 8, 2026
Claim: PM loop round 3
Session:
session_01CGZy1BGCjdN5cXqL9cnvB8Account:
os-support-aiBranch:
claude/issue-11819-map-webgl2-fallbackWorktree:
objectui-issue-11819Domain:
domain:uiSeat:
domain:ui#3File surface (on
cef0eee):packages/plugin-map/src/ObjectMap.tsx:handleMapError(about:651) and theMapGLmount with itsMarker/Popupchildren (about:1348–:1440);:1365), which gains the WebGL2 cause;.changeset/11819-*.md.⛔ Not on it: the map's style, tile and geocoding configuration;
react-map-gl/maplibre-glversions and anypackage.jsonchange. 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 MapThread-read: 6043498035
Serial constraints cleared:
noneblocking. No in-flight claim in the lane namespackages/plugin-map/, and no open objectui PR touches it (read 2026-10-08T03:31Z). This seat's objectui#11812 (area:records) holdschrome/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
objectstack-fleet commentedon Oct 8, 2026
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
objectstack-fleet commentedon Oct 8, 2026
ACCEPT —
domain:uiseat 3,session_01CGZy1BGCjdN5cXqL9cnvB8, 2026-10-08T04:50Z. PR objectui#11888, head3eda62e.PR shape: draft against
main, branched fromcef0eee, no rebase.git merge-treeof the currentmain(9cb4e29) and this head is clean; the queue tests that merge. First lineFixes #11819, no other closing keyword.Clause-②: noat line start. Both commits carry only the model-free trailer pair. Assigned toos-support-ai.Scope: 34 files, +411/−4, on the claim's surface (
6051604151):packages/plugin-map/src/ObjectMap.tsx;src/__tests__/webgl2Available.ts;patchon@object-ui/plugin-map);plugin-maptest 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-glversions andpackage.jsonare untouched. Nothing is added to the package entry. No governed path.Diff read (the seat's own):
canStartMap()askscanvas.getContext('webgl2')once per mount (auseStateinitializer), beforeMapGLis ever mounted. It releases the probe's context throughWEBGL_lose_context, answers yes with no DOM, and answers no on a throw.MapGLand itsMarker/Popupchildren are never rendered, so no map is constructed and nothing is cleaned up. The amberrole="alert"strip names WebGL2, and a list of the samefilteredMarkersthe search box narrows is shown. Each row runsselectMarker, which is now the single click path the cluster marker uses too.handleMapError's alert over a working map, unchanged.Brief readings, measured:
GPUInitializationErrorfires inside the MapLibre constructor, before react-map-gl attaches its listeners, soonErrornever hears it.onErrorlater) is falsified by reading maplibre-gl 6.4.1, so it was not built. The only later emitter is_contextRestored, whosegetContextreturns the restored context.filteredMarkers(title and description), with no new list component.Ruling honoured (triage
6043498035):Changeset sentences checked against the head:
ObjectMapnow asks the browser for a WebGL2 context before it mounts the map. When there is none it never constructs a map";Reverse verification (dev report):
webgl2answering null.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 registerobject-map: 807 tests passed.--disable-webgl2, the console renderingobject-mapthrough the realSchemaRenderer): 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_shaequals the PR head): 43 runs, 40 success, 3 skipped (Test (coverage), its shard template,dependabot), 0 failure.LintandType Checkare success. Mergeable stateclean.Contract review: not required (
Clause-②: no).Findings:
examples/schema-catalog/test/catalog-gallery-render.test.tsxandapps/console/src/__tests__/public-block-binding-reach.test.tsxstill say that anobject-mapunder happy-dom crashes on unmount or fetches the demo style. Both suites stay green. Prose only →Acceptance notesAcceptance notesLanding: ready + auto-merge through the queue, now.
Generated by Claude Code
objectstack-fleet commentedon Oct 8, 2026
Landed —
domain:uiseat 3,session_01CGZy1BGCjdN5cXqL9cnvB8, 2026-10-08T05:11Z. PR objectui#11888 merged through the queue asfbaa79dat 2026-10-08T05:10Z.d4c39de, an ancestor oforigin/main.git merge-treeof that parent and the accepted head3eda62e(tree92b80529on both).d4c39de→fbaa79d, inpackages/plugin-map/src/ObjectMap.tsx:function canStartMap: 0 → 1.data-testid="map-webgl2-fallback": 0 → 1.const selectMarker: 0 → 1.index.tsxnamescanStartMap0 times, so the probe stays module-private.packages/plugin-map/package.jsonis byte-identical on both sides (no dependency or version change).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:dispatchedis removed.Generated by Claude Code
fix(plugin-map): without WebGL2 the map lists its records instead of …