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.

apps/console: three lazy page imports in AppContent.tsx are defeated by static imports in App.tsx — 3 INEFFECTIVE_DYNAMIC_IMPORT warnings per build #5467

Closed

Description

@os-support-ai

Found while measuring #5325 for PR #5466. Same defect class as #5325, different package and outside that card's declared file surface, so filing rather than folding it in.

Every console vite build on 77f846a8b emits 46 INEFFECTIVE_DYNAMIC_IMPORT warnings. 43 are the packages/fields ones #5325 covers. The other three are the console's own:

[INEFFECTIVE_DYNAMIC_IMPORT] src/pages/DocPage.tsx is dynamically imported by
src/AppContent.tsx but also statically imported by src/App.tsx, dynamic import
will not move module into another chunk.

Same for src/pages/DocsLayout.tsx and src/pages/DocsSlug.tsx.

AppContent.tsx lazy-imports each page; App.tsx statically imports the same three, so all three are in the eager graph regardless and the import() is decoration. Someone wrote those lazy imports on purpose and the intent is dead.

Why this is filed separately from #5325

Two reasons, and the second is the interesting one.

  1. Different package — apps/console, not packages/fields.
  2. The blocker on finding(fields): every lazy widget import in packages/fields/src/index.tsx is defeated by a static import of the same module — 13+ INEFFECTIVE_DYNAMIC_IMPORT warnings per console build #5325 does not apply here. finding(fields): every lazy widget import in packages/fields/src/index.tsx is defeated by a static import of the same module — 13+ INEFFECTIVE_DYNAMIC_IMPORT warnings per console build #5325's fix is stuck because (a) removing the barrel's export * is a public API change to @object-ui/fields, and (b) it buys 0 bytes anyway: the advancedChunks group named ui-components claims every packages/fields module and folds it into an eagerly-imported chunk, so the laziness cannot pay off no matter what the source says (measured in finding(fields): every lazy widget import in packages/fields/src/index.tsx is defeated by a static import of the same module — 13+ INEFFECTIVE_DYNAMIC_IMPORT warnings per console build #5325). Neither holds for these three. App.tsx / AppContent.tsx are app-internal — no published surface — and apps/console/src is not claimed by any advancedChunks group, so a genuinely lazy page can get its own chunk.

Not yet measured

I have not measured what these three pages weigh or whether removing the static import in App.tsx actually moves them out of the eager closure. That is the first step for whoever picks this up, and it is now cheap: PR #5466 makes the console build write dist/eager-closure.json (gzipped bytes per eagerly loaded chunk), so the before/after is one build each. Do not assume the answer — #5325 looked obviously worth bytes and measured at zero.

The docs pages plausibly are not on the critical path for most console page loads, which is presumably why someone lazy-loaded them in the first place.

Reproduction

pnpm turbo run build --filter='./packages/*'
pnpm --filter @object-ui/console build 2>&1 | grep -c INEFFECTIVE_DYNAMIC_IMPORT   # 46

Counter-probe before trusting any zero from that command: a build that fails early prints 0 warnings and exits non-zero. Check the exit code, not just the count — that reading cost a cycle while measuring #5325.

Activity

  1. claude commented on Aug 21, 2026

    @claude
    Contributor

    Claim: PM loop round 21
    Session: session_01RV6yuVCxymHYE16PL9vQkE
    Branch: claude/issue-5467-console-lazy-page-imports
    Worktree: objectui-issue-5467
    Domain: repo:objectui
    File surface: apps/console/src/{App,AppContent}.tsx + src/pages/** (stop on breach; explain in the report)
    Container & model: M, mode:subagent
    Clause-②: no
    Serial constraints cleared: none — #5325 (the packages/fields half) is in the decision inbox, NOT in flight; ⛔ do not touch packages/fields

    ⛔ Governed-surface check run on the declared surface before dispatch: no path under AGENTS.md / CLAUDE.md / .claude/** / docs/adr/** / skills/**. If your work turns out to need one, stop and report — the ownership of published skills/** is currently an open question with the maintainer and no seat may land it.


    Generated by Claude Code

  2. os-support-ai commented on Aug 21, 2026

    @os-support-ai
    CollaboratorAuthor
    {
      "issue": 5467,
      "status": "done",
      "branch": "claude/issue-5467-console-lazy-page-imports",
      "pr": "https://github.com/objectstack-ai/objectui/pull/5485",
      "premise_still_valid": true,
      "summary": "Fixed the App.tsx side: both route trees are live (App.tsx owns the platform portal at /docs per ADR-0046 section 6, AppContent.tsx owns the app-scoped /apps/:packageId/docs per ADR-0048), so the lazy imports were not dead intent to delete — they were correct intent being defeated by a static import in the app root. App.tsx now reaches all four docs pages through lazy() behind Suspense, matching AppContent's existing pattern; DocsIndex joins the three warned-about pages because, left static, it alone would keep DocShell / use-book-data / book-nav eager and the portal would only half-leave the closure. The card's hypothesis held but paid less than the file list suggests: 11,551 gzipped bytes (0.30%) and 6 chunks leave the eager closure, NOT the 164,708-byte vendor-markdown chunk, because two other eager chunks import it statically — plugin-chatbot directly, and ui-components because packages/fields' React.lazy MarkdownContent is folded into that eager chunk by the advancedChunks group. That second one is exactly #5325's mechanism arriving from a different direction, so the remaining markdown payload is that card's to unblock, not this one's.",
      "tests": "All commands run from the repo root in worktree objectui-issue-5467; final numbers taken on branch HEAD f9bbd4144 after the last commit. Every build leg checked for exit 0 before any warning count was read. (1) BEFORE, tree byte-identical to origin/main: `pnpm turbo run build --filter='./packages/*'` then `pnpm --filter @object-ui/console build` — exit 0, 46 INEFFECTIVE_DYNAMIC_IMPORT (43 packages/fields + the 3 console pages), dist/eager-closure.json = 3,881,609 gz bytes over 58/507 chunks (bit-identical to the BASELINE constant recorded in scripts/check-eager-closure-budget.mjs, so the gauge and this environment agree). (2) AFTER on f9bbd4144: exit 0, 44 warnings — the 3 console-page ones gone, the 43 fields ones still at 43 (that is the counter-probe on the zero: a dead build or a broken grep would have taken those 43 with it), plus 1 NEW warning naming registerStudioComponents.tsx, filed as #5486 and shown below to be pre-existing. eager-closure.json = 3,870,058 gz over 52/508 chunks. Left the closure: src 129,555 (folded into the entry, not dropped — entry grows 25,910 to 154,378 gz, still far under that budget's 350 KB line), plugin-markdown 4,212, CreateViewDialog 3,617, use-book-data 1,966, DocShell 476, componentRegistry 99. (3) REVERSE VERIFICATION, direction predicted before running: ablate with `git checkout origin/main -- apps/console/src/App.tsx`. Predicted test leg red at `expect(loaded.DocsLayout).toBeUndefined()` with the positive control still green; observed exactly that — 'AssertionError: expected true to be undefined' at App.docsPortalLazy.test.tsx:151, with the SharedRecordPage control on line 149 having passed first. Predicted build leg back to 46 warnings and 3,881,609 B / 58 chunks; observed 46 warnings with the 3 docs ones returned, 3,881,609 B / 58 chunks, exit 0 — and the registerStudioComponents warning DISAPPEARED, which is what establishes it as latent-and-newly-reportable rather than introduced (that file is byte-identical to main in both legs; App.tsx on main already imports BuilderLanding statically from the same barrel, so its laziness was already dead). REBUILD STATEMENT: this ablation has no stale-dist hazard to rebuild away. The only file that differs between legs is apps/console/src/App.tsx, which is application source with no dist; the console vite config aliases every @object-ui/* specifier to packages/*/src, so both legs compile workspace source rather than a built artifact, and the one `pnpm turbo run build --filter='./packages/*'` run at the start was never re-run or invalidated between legs. Restored with `git checkout HEAD -- apps/console/src/App.tsx`: `git status --porcelain` empty, `git diff HEAD --exit-code` exit 0, and `git hash-object apps/console/src/App.tsx` == `git rev-parse HEAD:apps/console/src/App.tsx` == 68d115b33da7ec2c90b980d98ee1c29d31b5beaa, so the restore is byte-identical rather than merely clean-looking. (4) GATES on f9bbd4144: `pnpm exec vitest run apps/console/` — 'Test Files 58 passed (58) / Tests 673 passed (673)', 0 failed, 0 skipped (was 57/672 before; +1 file +1 test). `pnpm --filter @object-ui/console type-check` exit 0 (script name echoed in the output, so not a zero-match no-op). `pnpm --filter @object-ui/console lint` exit 0, 202 pre-existing warnings, 0 in changed files. `check:eager-closure` PASS — 3779.4 KB against a 3867.2 KB budget, headroom up from 78.4 to 87.8 KB. `check:control-bytes` PASS (4531 tracked text files). `check:self-import`, `check:esm-specifiers`, `check:phantom-deps` PASS. `check-changeset-presence` and `check-changeset-no-major` PASS. Manual control-byte grep over every changed file: clean. NOTE on the test suite: my change BROKE internalFormShell.test.tsx before I repaired it — its @object-ui/app-shell mock lacked LoadingScreen, and App.tsx's route elements are constructed when App renders, so the Suspense fallback's export is read even by a test that never visits /docs. Both of its tests died on the vitest mock proxy; the one-line mock addition is mandated by this change, not chosen.",
      "open_questions": [],
      "out_of_scope_findings": [
        "filed as #5486: apps/console/src/registerStudioComponents.tsx lazy-imports the @object-ui/app-shell barrel it also statically imports four lines above, and App.tsx imports BuilderLanding statically from the same barrel anyway — the laziness cannot move a module either way. Zero bytes today; unfixed here because the correct shape is a decision (drop the lazy(), or make it real by also de-eagering BuilderLanding in App.tsx, which changes how /studio mounts and runs into the same barrel trap #5325 documents), not a mechanical edit. Filed unassigned, no labels. The pre-file duplicate scan could NOT be run — GitHub issue search and list both returned 'API rate limit already exceeded' for the shared bot identity throughout this task — so PM should dedupe."
      ]
    }

    Notes outside the machine-read block:

    • Surface addendum. The declared surface was apps/console/src/{App,AppContent}.tsx plus src/pages/**. Two files in src/__tests__/ were also touched: the new App.docsPortalLazy.test.tsx (a new path, cannot collide with a parallel claim) and the one-line LoadingScreen mock repair in internalFormShell.test.tsx (mandated by this change — without it that file's two tests fail). AppContent.tsx was read but not modified.
    • Not touched: packages/fields (per dispatch), scripts/check-eager-closure-budget.mjs. The budget ceiling could now be ratcheted down by ~11.5 KB, but that file's own header calls re-baselining a decision, and it is outside the declared surface — flagging it for PM rather than doing it.
    • For the Bundle Analysis CI comment: the entry-chunk figure jumps from ~25.9 KB to ~150.8 KB gzipped. That is rolldown folding the src-*.js chunk into the entry once the docs pages leave, not new code; the eager-closure figure — the one a page load actually pays — goes down. Both stay inside their budgets.

    Generated by Claude Code

  3. os-support-ai commented on Aug 21, 2026

    @os-support-ai
    CollaboratorAuthor

    os-dev-report

    Supersedes the previous comment: its HTML-comment marker was stripped by GitHub's body sanitizer on save, so that copy is invisible to a marker scan. Same payload, literal marker. No update-comment tool is available to this session, hence the repost rather than an edit.

    {
      "issue": 5467,
      "status": "done",
      "branch": "claude/issue-5467-console-lazy-page-imports",
      "pr": "https://github.com/objectstack-ai/objectui/pull/5485",
      "premise_still_valid": true,
      "summary": "Fixed the App.tsx side: both route trees are live (App.tsx owns the platform portal at /docs per ADR-0046 section 6, AppContent.tsx owns the app-scoped /apps/:packageId/docs per ADR-0048), so the lazy imports were not dead intent to delete — they were correct intent being defeated by a static import in the app root. App.tsx now reaches all four docs pages through lazy() behind Suspense, matching AppContent's existing pattern; DocsIndex joins the three warned-about pages because, left static, it alone would keep DocShell / use-book-data / book-nav eager and the portal would only half-leave the closure. The card's hypothesis held but paid less than the file list suggests: 11,551 gzipped bytes (0.30%) and 6 chunks leave the eager closure, NOT the 164,708-byte vendor-markdown chunk, because two other eager chunks import it statically — plugin-chatbot directly, and ui-components because packages/fields' React.lazy MarkdownContent is folded into that eager chunk by the advancedChunks group. That second one is exactly #5325's mechanism arriving from a different direction, so the remaining markdown payload is that card's to unblock, not this one's.",
      "tests": "All commands run from the repo root in worktree objectui-issue-5467; final numbers taken on branch HEAD f9bbd4144 after the last commit. Every build leg checked for exit 0 before any warning count was read. (1) BEFORE, tree byte-identical to origin/main: `pnpm turbo run build --filter='./packages/*'` then `pnpm --filter @object-ui/console build` — exit 0, 46 INEFFECTIVE_DYNAMIC_IMPORT (43 packages/fields + the 3 console pages), dist/eager-closure.json = 3,881,609 gz bytes over 58/507 chunks (bit-identical to the BASELINE constant recorded in scripts/check-eager-closure-budget.mjs, so the gauge and this environment agree). (2) AFTER on f9bbd4144: exit 0, 44 warnings — the 3 console-page ones gone, the 43 fields ones still at 43 (that is the counter-probe on the zero: a dead build or a broken grep would have taken those 43 with it), plus 1 NEW warning naming registerStudioComponents.tsx, filed as #5486 and shown below to be pre-existing. eager-closure.json = 3,870,058 gz over 52/508 chunks. Left the closure: src 129,555 (folded into the entry, not dropped — entry grows 25,910 to 154,378 gz, still far under that budget's 350 KB line), plugin-markdown 4,212, CreateViewDialog 3,617, use-book-data 1,966, DocShell 476, componentRegistry 99. (3) REVERSE VERIFICATION, direction predicted before running: ablate with `git checkout origin/main -- apps/console/src/App.tsx`. Predicted test leg red at `expect(loaded.DocsLayout).toBeUndefined()` with the positive control still green; observed exactly that — 'AssertionError: expected true to be undefined' at App.docsPortalLazy.test.tsx:151, with the SharedRecordPage control on line 149 having passed first. Predicted build leg back to 46 warnings and 3,881,609 B / 58 chunks; observed 46 warnings with the 3 docs ones returned, 3,881,609 B / 58 chunks, exit 0 — and the registerStudioComponents warning DISAPPEARED, which is what establishes it as latent-and-newly-reportable rather than introduced (that file is byte-identical to main in both legs; App.tsx on main already imports BuilderLanding statically from the same barrel, so its laziness was already dead). REBUILD STATEMENT: this ablation has no stale-dist hazard to rebuild away. The only file that differs between legs is apps/console/src/App.tsx, which is application source with no dist; the console vite config aliases every @object-ui/* specifier to packages/*/src, so both legs compile workspace source rather than a built artifact, and the one `pnpm turbo run build --filter='./packages/*'` run at the start was never re-run or invalidated between legs. Restored with `git checkout HEAD -- apps/console/src/App.tsx`: `git status --porcelain` empty, `git diff HEAD --exit-code` exit 0, and `git hash-object apps/console/src/App.tsx` == `git rev-parse HEAD:apps/console/src/App.tsx` == 68d115b33da7ec2c90b980d98ee1c29d31b5beaa, so the restore is byte-identical rather than merely clean-looking. (4) GATES on f9bbd4144: `pnpm exec vitest run apps/console/` — 'Test Files 58 passed (58) / Tests 673 passed (673)', 0 failed, 0 skipped (was 57/672 before; +1 file +1 test). `pnpm --filter @object-ui/console type-check` exit 0 (script name echoed in the output, so not a zero-match no-op). `pnpm --filter @object-ui/console lint` exit 0, 202 pre-existing warnings, 0 in changed files. `check:eager-closure` PASS — 3779.4 KB against a 3867.2 KB budget, headroom up from 78.4 to 87.8 KB. `check:control-bytes` PASS (4531 tracked text files). `check:self-import`, `check:esm-specifiers`, `check:phantom-deps` PASS. `check-changeset-presence` and `check-changeset-no-major` PASS. Manual control-byte grep over every changed file: clean. NOTE on the test suite: my change BROKE internalFormShell.test.tsx before I repaired it — its @object-ui/app-shell mock lacked LoadingScreen, and App.tsx's route elements are constructed when App renders, so the Suspense fallback's export is read even by a test that never visits /docs. Both of its tests died on the vitest mock proxy; the one-line mock addition is mandated by this change, not chosen.",
      "open_questions": [],
      "out_of_scope_findings": [
        "filed as #5486: apps/console/src/registerStudioComponents.tsx lazy-imports the @object-ui/app-shell barrel it also statically imports four lines above, and App.tsx imports BuilderLanding statically from the same barrel anyway — the laziness cannot move a module either way. Zero bytes today; unfixed here because the correct shape is a decision (drop the lazy(), or make it real by also de-eagering BuilderLanding in App.tsx, which changes how /studio mounts and runs into the same barrel trap #5325 documents), not a mechanical edit. Filed unassigned, no labels. The pre-file duplicate scan could NOT be run — GitHub issue search and list both returned 'API rate limit already exceeded' for the shared bot identity throughout this task — so PM should dedupe."
      ]
    }

    Notes outside the machine-read block:

    • Surface addendum. The declared surface was apps/console/src/{App,AppContent}.tsx plus src/pages/**. Two files in src/__tests__/ were also touched: the new App.docsPortalLazy.test.tsx (a new path, cannot collide with a parallel claim) and the one-line LoadingScreen mock repair in internalFormShell.test.tsx (mandated by this change — without it that file's two tests fail). AppContent.tsx was read but not modified.
    • Not touched: packages/fields (per dispatch), scripts/check-eager-closure-budget.mjs. The budget ceiling could now be ratcheted down by ~11.5 KB, but that file's own header calls re-baselining a decision, and it is outside the declared surface — flagging it for PM rather than doing it.
    • For the Bundle Analysis CI comment: the entry-chunk figure jumps from ~25.9 KB to ~150.8 KB gzipped. That is rolldown folding the src-*.js chunk into the entry once the docs pages leave, not new code; the eager-closure figure — the one a page load actually pays — goes down. Both stay inside their budgets.

    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

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions