Repository navigation
@object-ui/plugin-dashboard imports react-grid-layout's stylesheet at module scope — the last load-leg blocker, worth 2 entries not 1 #5384
Description
Filed by the repo:objectui execution seat (PM round 19, session session_01RV6yuVCxymHYE16PL9vQkE) while accepting PR5383 (#5357). Filed unassigned and unlabelled — grading and routing belong to the triage seat. ⛔ Not a grading.
This is the follow-up the accepting seat owes, recorded now rather than left in a PR body.
Why it is worth a card of its own
pnpm check:node-esm-load stands at 34 of 39 published ESM entries. After #5214 and #5357 cleared the entire extensionless-specifier class, the remaining 5 break down as:
| package | reason | ledger |
|---|---|---|
@object-ui/plugin-dashboard |
imports react-grid-layout's stylesheet at module scope; Node cannot load .css at all |
LOAD_DEBT |
@object-ui/app-shell |
reaches that same stylesheet through static @object-ui/plugin-dashboard imports in DashboardView.tsx and ReportView.tsx |
LOAD_DEBT |
@object-ui/plugin-map |
imports maplibre-gl's stylesheet; same question |
LOAD_DEBT |
create-plugin |
bin-only CLI | not importable by design |
runner |
built web app | not importable by design |
So this single change would clear two entries, not one — plugin-dashboard and app-shell together, taking the load leg 34 → 36. That is by a wide margin the largest remaining unlock, and per PR5383's dev it is "the only one left that is a code change rather than a design decision."
ERR_MODULE_NOT_FOUND on its own file, which masked the .css failure entirely — the same first-failure shape #5214 measured when it predicted 5 red and observed 10. It only became legible once PR5383 removed the mask.
The question, which is not obviously one shape
Node cannot evaluate a .css import at all, so this is not a spelling fix. Plausible directions, not a recommendation — the choice is the graders':
- Move the stylesheet import off module scope (a consumer-side
importthe host performs, or a documented CSS entry the package advertises rather than executes). - Decide that unbundled Node consumers are not supported for style-carrying plugin packages, and make
LOAD_DEBTsay that as a permanent statement rather than as debt. - Something upstream-shaped for
react-grid-layout/maplibre-glspecifically.
The second option is a legitimate answer and would make the ledger honest rather than aspirational. plugin-map, so whichever way this goes it should be taken over the style-carrying packages as a group, not one at a time.
Why LOAD_DEBT is the right place for these three today, and what it costs
LOAD_DEBT is deliberately kept separate from SPECIFIER_DEBT — its own header says merging them "would let this gate become a grab-bag in which the defect it exists for stops being visible." These three genuinely answer a different question (is an unbundled consumer supported at all?), so the entries are truthful.
The cost, recorded honestly by PR5383's dev rather than discovered later: LOAD_DEBT matches by package name, not by error code. So the load leg alone would now also stay quiet about a genuine specifier regression in any of these three. What carries that guard instead is the specifier leg, which became a hard requirement the moment SPECIFIER_DEBT emptied — PR5383's ablation confirmed it reddens on a reverted app-shell while the load leg prints ledgered.
That is a working arrangement, not a hole — but it is one more reason this card is worth closing rather than living with: every package in LOAD_DEBT is a package the load leg cannot speak for.
Related
- @object-ui/app-shell emits 1259 extensionless relative specifiers, so plain Node ESM cannot load its published entry #5357 / PR5383 — cleared app-shell's specifiers and uncovered this; added the
LOAD_DEBTentry - Seven published packages still emit extensionless relative specifiers, so plain Node ESM cannot load them #5214 — the seven-package family, complete as of PR5383
- check-node-esm-load's comment mask is not line-comment aware, so a
/*inside a//line hides live extensionless specifiers from the gate #5382, check-node-esm-load.mjs's readTsconfig() destroys any tsconfig whose JSON strings contain/*, so effectiveNoEmit() silently answers from its catch block for packages/auth and packages/fields #5367 — two separate blind spots incheck-node-esm-load.mjs's own parsing, both open - @object-ui/components ships dist/index.d.ts with extensionless relative re-exports, so any consumer on moduleResolution nodenext sees every named export as missing #5365 — extensionless re-exports in
@object-ui/components(21) and@object-ui/layout(7).d.tsemits, the blocker on any downstreamnodenextpin
Activity
os-zhuang commentedon Aug 20, 2026
Triage: entered the decision inbox (
needs-user-decision, type Task). The ruling should be taken over the three style-carrying packages (plugin-dashboard/plugin-map/ the family) at once — it also decides whether theirLOAD_DEBTentries are debt or permanent.<!-- os-decision-facets -->Generated by Claude Code
os-zhuang commentedon Aug 20, 2026
Maintainer ruling recorded (2026-08-20, live decision-inbox session with the triage seat, session
session_01PjAP6vbcsg2yMtvySPv1Qo)Ruled: the second direction — unbundled Node consumption is NOT supported for style-carrying plugin packages, as a permanent statement rather than debt. Provenance: maintainer accepted the batch, verbatim: 「其他接受你的建议。」Taken over the group as the card asks:
@object-ui/plugin-dashboard,@object-ui/app-shell(via its static imports), and@object-ui/plugin-maptogether. No known unbundled-Node consumer exists; converting a zero-pull capability gap into permanent maintained machinery is the shape the startup rule rejects.Implementation: reword
LOAD_DEBTfor these three from debt to a stated permanent boundary (the ledger becomes honest rather than aspirational), keep the specifier leg as the hard guard it already is, and note the boundary in the packages' READMEs so a future Node consumer learns it from the package, not from a red import. Revisit only on a real consumer request — which would reopen this as a design card, not a debt payment.State transition in the same stroke:
needs-user-decision→pm:queue.Generated by Claude Code
claude commentedon Aug 20, 2026
Claim: PM loop round 21
Session:
session_01RV6yuVCxymHYE16PL9vQkEBranch:
claude/issue-5384-loaddebt-permanent-exemptionWorktree:
objectui-issue-5384Domain:
repo:objectuiFile surface: the LOAD_DEBT ledger + its gate/docs; ⛔ NOT the CSS imports (stop on breach; explain in the report)
Container & model: S,
mode:subagentClause-②: no
Serial constraints cleared: none
Generated by Claude Code
docs(esm-load-gate): state the unbundled-Node boundary instead of car…
os-support-ai commentedon Aug 20, 2026
{ "issue": 5384, "status": "done", "branch": "claude/issue-5384-loaddebt-permanent-exemption", "pr": "https://github.com/objectstack-ai/objectui/pull/5455", "premise_still_valid": true, "summary": "Reworded the LOAD_DEBT ledger from debt into a stated permanent boundary over the three style-carrying packages as a group, per the ruling — the CSS imports are untouched (that was option A, ruled against). The map is now UNBUNDLED_NODE_UNSUPPORTED; its docblock carries the ruling, its date, the verbatim provenance, the measured failure (ERR_UNKNOWN_FILE_EXTENSION for all three), and what would reopen it; each entry states its own boundary and cites objectui#5384; the run prints them as `by design` rather than `ledgered` plus a named summary line. The ruling's other two clauses are done too: the specifier leg is untouched and is documented as the guard that carries these three, and plugin-dashboard/plugin-map/app-shell each gained a 'Requires a bundler' README section with the real error text. A new unit pin makes a bare, un-cited exemption fail the suite. The ruling's premise was re-measured, not inherited: every consumer goes through a bundler (vite; Next transpilePackages, not serverExternalPackages), examples/schema-catalog only imports JSON files from plugin-named directories, and the sibling objectstack repo's four hits are all string literals — every zero counter-probed with a term known present.", "tests": "Local union re-run at HEAD=77d894b76 (tree clean), all green: check:esm-specifiers ('no un-ledgered package emits an extensionless relative specifier'), check:control-bytes (4510 files), changeset:check, docs:check-links ('valid across 13 scan roots'), type-check:scripts, check:doc-snippets ('87 of 87 block(s) judged, 0 failed'), vitest check-node-esm-load.test.ts + ci-cd-pipeline-doc.test.ts ('Tests 62 passed (62)'), and the full load leg. BEFORE/AFTER counts, measured over ONE build of every published package (turbo: '43 successful, 43 total') by running the origin/main gate and this branch's gate against the SAME artifacts with --no-build: before = 'Load leg: 34 of 39 published ESM entries imported and evaluated', exit 0; after = 'Load leg: 34 of 39 published ESM entries imported and evaluated', exit 0. THE NUMBER DOES NOT MOVE, as this card intends — only the three lines change from 'ledgered …' to 'by design …' plus a new summary line naming them. All three fail with ERR_UNKNOWN_FILE_EXTENSION, confirming the ledger's stated reason. Reverse verification of the new pin, predicted direction 'reddens' and observed as predicted: stripping 'by ruling objectui#5384' from the plugin-map entry gave 'Tests 1 failed | 29 passed (30)' on expect(reason).toMatch(/objectui#\\d+/); restoring gave '30 passed (30)'. No rebuild applies to this ablation and none was needed — the suite imports the gate as a source .mjs by relative path, not through a package exports map, so no dist/ sits between the edit and the assertion; the fix was committed before the mutation and restored by git checkout, verified byte-identical by a clean git status.", "open_questions": [], "out_of_scope_findings": [] }Generated by Claude Code
os-support-ai commentedon Aug 20, 2026
os-dev-report
(Supersedes the previous comment, whose HTML-comment marker the GitHub sanitizer stripped on write — the marker is spelled as literal text here so a PM scan can see it. Same report, unchanged.)
{ "issue": 5384, "status": "done", "branch": "claude/issue-5384-loaddebt-permanent-exemption", "pr": "https://github.com/objectstack-ai/objectui/pull/5455", "premise_still_valid": true, "summary": "Reworded the LOAD_DEBT ledger from debt into a stated permanent boundary over the three style-carrying packages as a group, per the ruling — the CSS imports are untouched (that was option A, ruled against). The map is now UNBUNDLED_NODE_UNSUPPORTED; its docblock carries the ruling, its date, the verbatim provenance, the measured failure (ERR_UNKNOWN_FILE_EXTENSION for all three), and what would reopen it; each entry states its own boundary and cites objectui#5384; the run prints them as `by design` rather than `ledgered` plus a named summary line. The ruling's other two clauses are done too: the specifier leg is untouched and is documented as the guard that carries these three, and plugin-dashboard/plugin-map/app-shell each gained a 'Requires a bundler' README section with the real error text. A new unit pin makes a bare, un-cited exemption fail the suite. The ruling's premise was re-measured, not inherited: every consumer goes through a bundler (vite; Next transpilePackages, not serverExternalPackages), examples/schema-catalog only imports JSON files from plugin-named directories, and the sibling objectstack repo's four hits are all string literals — every zero counter-probed with a term known present.", "tests": "Local union re-run at HEAD=77d894b76 (tree clean), all green: check:esm-specifiers ('no un-ledgered package emits an extensionless relative specifier'), check:control-bytes (4510 files), changeset:check, docs:check-links ('valid across 13 scan roots'), type-check:scripts, check:doc-snippets ('87 of 87 block(s) judged, 0 failed'), vitest check-node-esm-load.test.ts + ci-cd-pipeline-doc.test.ts ('Tests 62 passed (62)'), and the full load leg. BEFORE/AFTER counts, measured over ONE build of every published package (turbo: '43 successful, 43 total') by running the origin/main gate and this branch's gate against the SAME artifacts with --no-build: before = 'Load leg: 34 of 39 published ESM entries imported and evaluated', exit 0; after = 'Load leg: 34 of 39 published ESM entries imported and evaluated', exit 0. THE NUMBER DOES NOT MOVE, as this card intends — only the three lines change from 'ledgered …' to 'by design …' plus a new summary line naming them. All three fail with ERR_UNKNOWN_FILE_EXTENSION, confirming the ledger's stated reason. Reverse verification of the new pin, predicted direction 'reddens' and observed as predicted: stripping 'by ruling objectui#5384' from the plugin-map entry gave 'Tests 1 failed | 29 passed (30)' on expect(reason).toMatch(/objectui#\\d+/); restoring gave '30 passed (30)'. No rebuild applies to this ablation and none was needed — the suite imports the gate as a source .mjs by relative path, not through a package exports map, so no dist/ sits between the edit and the assertion; the fix was committed before the mutation and restored by git checkout, verified byte-identical by a clean git status.", "open_questions": [], "out_of_scope_findings": [] }Generated by Claude Code
docs(esm-load-gate): state the unbundled-Node boundary instead of car…