Repository navigation
finding(console): ~194 KB gzipped of AppContent's OTHER lazy() declarations is eager too -- metadata-admin alone is 181 KB, 6x today's headroom #6681
Description
Activity
- addeddomain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatobjectui ui stream: fix lands on the published library or apps — objectui execution seat
on Aug 28, 2026 huangyiirene commented
on Aug 28, 2026 CollaboratorMore actions定级:
Bug·priority:p1·domain:ui·pm:queue中央分诊席位,session
session_01Aujz2zykf5LXt3T98gRsGe。domain:ui—— 落点apps/console与views/**⇒ objectui 三流规则的"发布库与 apps"。Bug而非Task—— ⭐ 这些模块在源码里声明为lazy(),而实际每次首屏都被加载。⇒ 声明为假,这是缺陷,⛔ 不只是优化机会。priority:p1(卡自述 "Observational: nothing is broken") ——⚠️ 我不同意这句自评,理由是它把"门"当成了"用户"。
⭐ 为什么这张与同簇另两张不是一个量级
卡 性质 客户可感成本 #6631 余量为何变窄 —— ⭐ 本轮已了结(上限于 08-25 被 #6229 刻意下调 741,000 B,降幅大于载荷降幅) 无 #6680 chunk 共居机制,源码级搜索看不见 无(卡明说预算 PASS) 本卡 ~193.9 KB gz 声明为 lazy 却在首屏路径上 ⭐ 有:每一次控制台加载都在付 卡自己给出的换算就是定 p1 的依据:
roughly 5.9% of everything a console page load pays for
⇒ 门是绿的(
pnpm check:eager-closurePASS),⛔ 但"门绿"与"用户不付钱"是两件事。 用户为一段被声明为不会加载的代码,在每一次加载里付 194 KB。规模对照,卡给得很准:#6535 那八个 view 释放后值 5.3 KB;本卡是同一缺陷类,大一个数量级。
⚠️ 与 #6631 的交接(⛔ 不要让这条读数随那张卡关闭而消失)#6631 本轮已被我用常量历史了结 ⇒ 按其自身处置规则可关为 not planned。
⚠️ 但它留下一条必须转到本卡的读数:当前余量 29.3 KB 本卡的 metadata-admin 181 KB ⇒ 单项即为余量的 6.2 倍 本卡合计 193.9 KB ⇒ 6.6 倍⇒ ⭐ "重新基线是有意的"(#6631 的答案)并不意味着"余量够用"。 本卡的读数说明:当前余量小于一个已知的、单一的待办项。 ⛔ 关 #6631 时请把这条结论移交到本卡,而不是一起归档。
⚠️ 取卡者围栏 —— 卡自己立的三条我全部采纳并加重- ⛔ 不要用源码级搜索找静态边。⭐ 必须用发出的 chunk 自己的模块列表(
apps/console/dist/eager-closure.json)—— 理由见 finding(console): two declared-lazy views are held in the eager closure by CHUNK CO-TENANCY, a mechanism no source-level search can see #6680:chunk 共居会让一个没有任何源码级静态导入者的模块照样 eager,⛔ 而git grep对此完全失明。 - ⛔ 不要假设机制与 finding(console): 6 of the 8 views
AppContentdeclareslazy()are in the EAGER closure anyway — the package barrel re-exports them and the entry imports it statically #6535 相同。卡已给出反证:metadata-admin是advancedChunks式聚合,且views/metadata-admin/index.ts在模块加载时执行registerBuiltinPreviews⇒moduleSideEffects: false在那里是假的。⚠️ 先量,再动。 - ⛔ 不得简单删除或强制无副作用 —— 那个注册是承载性的(load-bearing)。
- ⭐ 修好之后要扩
DECLARED_LAZY_VIEWS_STILL_EAGER覆盖这四个面,防止漂回。⚠️ 注意 finding(console): 6 of the 8 viewsAppContentdeclareslazy()are in the EAGER closure anyway — the package barrel re-exports them and the entry imports it statically #6535 的解析器刻意只解析../views/NAME.js⇒ 扩到目录 barrel(../views/metadata-admin/index.js)、兄弟目录(./marketplace/*.js)与包(@object-ui/plugin-designer)是有意识的扩面,⛔ 不是顺手改正则。
⭐ 建议的执行顺序(三张卡)
本卡(p1,有客户成本)→ #6680(机制,决定修法可行性)→ #6631(已答,关卡时移交读数)
⚠️ 但 #6680 的分块策略实验可能是本卡四项中某几项的前提 ⇒ 取卡者先读 #6680 再决定切入点。相关
#6535(发现出处,同一次测量、更窄范围)· ⭐ #6680(chunk 共居机制)· ⭐ #6631(余量为何变窄 —— 本轮已了结)· #5324 / #5924(预算与其余量)
Generated by Claude Code
Claim:
domain:uiexecution seat, PM sessionsession_01CRJge11jso9TpXRWFt1Z49, round R12. Branch:claude/issue-6681-appcontent-eager-lazy.Scope
Four chunks that
AppContentdeclares withlazy()and that are eager anyway —metadata-admin(181,228 B gzipped, six pages),MarketplacePackagePage,MarketplacePage,MarketplaceInstalledPage— about 194 KB gzipped, roughly 5.9% of everything a console page load pays for and more than 6x the current headroom.For each of the four:
- Find the static edge into the eager closure the way finding(console): 6 of the 8 views
AppContentdeclareslazy()are in the EAGER closure anyway — the package barrel re-exports them and the entry imports it statically #6535 did — from the emitted chunk's own module list, ⛔ not a source-level search. finding(console): two declared-lazy views are held in the eager closure by CHUNK CO-TENANCY, a mechanism no source-level search can see #6680 is the recorded reason the source graph is not enough. - Decide per chunk whether the edge is removable.
⚠️ The mechanism is very likely NOT the one finding(console): 6 of the 8 viewsAppContentdeclareslazy()are in the EAGER closure anyway — the package barrel re-exports them and the entry imports it statically #6535 fixed:metadata-adminis anadvancedChunks-style aggregate holding a whole directory, andviews/metadata-admin/index.tsperforms top-level registration (registerBuiltinPreviews), so amoduleSideEffects: falsedeclaration would be false there. Measure before assuming. - Where an edge is removable, extend
DECLARED_LAZY_VIEWS_STILL_EAGERto cover the surface so it cannot drift back.
Fences
- ⛔ Do not delete these or blanket-declare them side-effect-free.
views/metadata-admin/index.tsregisters built-in previews at module load and that registration is load-bearing. Breaking it to win bundle bytes is a regression, not a fix. - This is not a budget regression —
pnpm check:eager-closurepasses today. Nothing is red; you are buying headroom, so a measured "this edge is not removable, here is why" is a legitimate result for any of the four. scripts/vite-declared-lazy-views.tsis YOURS this round.⚠️ A sibling dev on finding(components): container-declaration census — 8 more registrations renderschema.childrenwithout declaringisContainer, and this is the THIRD time the same gap has been found one registration at a time #6764 may add a new file underscripts/; it has been told never to touch yours. If you find it modified, stop and report rather than resolving it yourself.- Your other surface is
apps/console/. Four sibling devs holdpackages/plugin-timeline/,packages/components/src/renderers/,packages/react/src/andpackages/fields/src/widgets/.
Scale note
The eight views #6535 was filed about were worth 5.3 KB once freed. This is the same defect class an order of magnitude larger — which is also the reason to measure each of the four separately rather than treating them as one job.
Generated by Claude Code
- Find the static edge into the eager closure the way finding(console): 6 of the 8 views
- added a commit that references this issue
on Aug 29, 2026 os-dev-report
{ "issue": 6681, "status": "done", "branch": "claude/issue-6681-appcontent-eager-lazy", "pr": "https://github.com/objectstack-ai/objectui/pull/6775", "premise_still_valid": true, "summary": "All four chunks measured separately from the emitted chunks' own module lists (rolldown chunk.modules + module-level importedIds/importers, dumped from an instrumented console build), never a source-level search. Two were removable and are now lazy: MarketplacePackagePage (7,647 B gz) and MarketplaceInstalledPage (1,836 B gz) were held by CHUNK CO-TENANCY (objectui#6680's mechanism, not objectui#6535's) with no import edge to the page at all -- rolldown had parked components/SuggestedBindingsPanel.tsx (statically imported by the eager views/studio-design/StudioDesignSurface.tsx) in the first and console/marketplace/InstalledListWidget.tsx (bare-imported by the package barrel for its SDUI registration) in the second. An app-shell-eager-leaves advancedChunks group in apps/console/vite.config.ts isolates those co-tenants; eager closure 3180.2 KB -> 3171.5 KB gz (-8,888 B), 48 -> 45 of 513 chunks, headroom 42.5 -> 51.1 KB, per-chunk ceilings unmoved. metadata-admin (172,651 B gz) is NOT removable and is now pinned with the module list that proves it: views/metadata-admin/index.ts is statically imported by the package barrel (eleven runtime re-exports) and by services/builtinComponents.tsx (which the barrel bare-imports and which registers MetadataDirectoryPage and MetadataResourceRouter BY VALUE), and the six pages the declarations name have dynamicImporters == [] in the emitted graph, so no chunking policy separates them. scripts/vite-declared-lazy-views.ts now parses every RELATIVE lazy() specifier (12 files, up from 8) so the directory barrel and the marketplace routes are inside the ledger, and gained a second purity guard that reads the package's own sideEffects array -- bareSideEffectImport returns null for views/metadata-admin/index.ts because its five side effects are CALLS, so without it deleting the pin would have declared the load-bearing module pure. TWO PREMISE CORRECTIONS, both re-measured on my own base b98352a15 and both reported to the coordinator: (a) MarketplacePage is NOT eager any more (card said 3,129 B) and metadata-admin is 172,651 B not 181,228 B -- one of the four was already fixed by faac0d935 / objectui#6735 before this branch; (b) for objectui#6631's 'which side moved' question, git shows BOTH sides moved and by the SAME commit: faac0d935 lowered MAX_EAGER_CLOSURE_GZIP_BYTES 3_345_000 -> 3_300_000 (-45,000 B) while taking -51.5 KB of load off, which is why headroom widened. objectui#6631 not touched. Two notes: the dispatch order's path apps/console/src/views/metadata-admin/index.ts does not exist -- the real path is packages/app-shell/src/views/metadata-admin/index.ts (the issue body's relative spelling was right), so the diff necessarily lands in packages/app-shell-adjacent tooling plus apps/console, none of it held by a named sibling; and my single commit carries a 'Part of #6681' trailer while the PR body carries the ordered 'Fixes #6681' -- push had already happened when I noticed, and force-push is forbidden, so I left it; only the PR body is parsed for closing.", "tests": "Union re-run on the FINAL commit d853fdedb with a clean tree (git status --porcelain: 0 entries), all under scripts/pm/os-verify-lock.sh, exit codes captured before any pipe. GATES (all EXIT=0): check-eager-closure-budget, check-control-bytes, check-side-effects-array, check-sdui-registration-pins, check-changeset-presence, check-changeset-fixed, check-changeset-no-major, check-entry-guard, check-node-esm-load --specifiers-only, check-phantom-dependencies. Gate-printed verdict lines: check:eager-closure -- 'Console eager closure is 3171.5 KB gzipped across 45 of 513 chunks (budget: 3222.7 KB, headroom: 51.1 KB).' (baseline run on the same tree before the fix: '3180.2 KB gzipped across 48 of 513 chunks ... headroom: 42.5 KB'); check:sdui-registration-pins -- 'All 12 registration(s) a sideEffects array promises are present in the built console (513 chunks weighed; the 3 ruled control(s) are in the derived set).' including 'marketplace:installed-list 1 chunk(s)', which is the widget my chunk group moved; check:side-effects-array -- '@object-ui/app-shell: sideEffects names exactly the 10 module(s) that register at load time'. BUILD: npx turbo run build --filter=@object-ui/console --concurrency=2 --force, VERDICT command-exit 0, held the lock 251s; its ledger line reads '[plugin declared-lazy-views] 10/12 modules AppContent declares lazy are genuinely lazy; 2 eager, all pinned'. TESTS: npx vitest run --root . --maxWorkers=2 over scripts/__tests__/vite-declared-lazy-views.test.ts + check-eager-closure-budget.test.ts + console-vite-alias-closure-4925.test.ts -- 'Test Files 3 passed (3) / Tests 114 passed (114)', EXIT=0. TYPECHECK: pnpm --filter @object-ui/console type-check EXIT=0 with 'tsc --noEmit && tsc -b tsconfig.node.json --force' echoed, so it was not a zero-match run; npx tsc --noEmit -p tsconfig.scripts.json EXIT=0. LINT NARROWED, and the narrowing is measured: eslint --no-inline-config --format json over the three changed source files reports 'files linted: 3', every errorCount 0, EXIT=0; the fourth changed file is the changeset and eslint.config.js configures no markdown linting; eslint.config.js enables NO type-aware linting (no projectService, no parserOptions.project), so this diff cannot move the verdict on any untouched file. Full farm left to CI. ABLATION (reverse verification of the NEW guard): deleted the metadata-admin line from DECLARED_LAZY_VIEWS_STILL_EAGER and rebuilt to a separate outDir, under a trap on EXIT INT TERM whose restore leg is 'git checkout HEAD -- $REPO_ROOT/scripts/vite-declared-lazy-views.ts' with an ABSOLUTE path. Mutation confirmed ON DISK before the run, not from an editor exit code: git hash-object e1971ad302bf... -> ae9bce55b653..., pin lines 1 -> 0, and the run aborts with 'ablation NOT RUN' if either check fails -- it DID abort on the first attempt, when a perl one-liner silently matched nothing (hash unchanged), which is the whole reason the check exists. On the real run the console build exited 1 in buildStart with '[declared-lazy-views] refusing to declare packages/app-shell/src/views/metadata-admin/index.ts side-effect-free: its own package says otherwise'. Predicted direction was RED and RED is what happened, at buildStart rather than at the later generateBundle ledger half, which is what proves the new guard is reachable. Restore leg proven by observed state, not by exit code: git diff HEAD empty and git hash-object back to e1971ad302bf..., matching git rev-parse HEAD:scripts/vite-declared-lazy-views.ts exactly; dist-ablation removed. No dist was committed (gitignored).", "mcp_calls": "4 -- create_pull_request, one failed search_issues (GraphQL rate limit), issue_write for the follow-up, and this report comment. All issue/PR READS went through zero-quota channels (the public issue page's embedded JSON payload, and repo-scoped REST).", "open_questions": [], "out_of_scope_findings": [ "filed as #6776: metadata-admin's 172,651 B eager closure is held by the ComponentRegistry CONTRACT (registerAppComponent takes a component VALUE, so builtinComponents.tsx must load the pages to register the refs) plus the package barrel's eleven runtime re-exports from a module the sideEffects array declares side-effectful -- not by chunking, and the six pages have dynamicImporters == [] so no advancedChunks group can separate them. Unassigned, labels domain:ui + finding, with the two published-contract questions a grader has to settle first (Suspense coverage at every registry render site; whether the barrel should go on re-exporting runtime values from a side-effectful module).", "CHANNEL DECLARATION for that filing, so the dedup read is auditable: the REST search endpoint is blocked by this container's proxy ('sessions are bound to their configured repositories'), and the one targeted MCP search_issues call failed with 'API rate limit already exceeded'. Dedup therefore used the sanctioned repo-scoped REST fallback -- GET /repos/objectstack-ai/objectui/issues?state=open&labels=domain:ui&per_page=100 plus a local grep for metadata-admin / registerAppComponent / eager-closure / builtinComponents / declared-lazy. 100 open domain:ui issues on that single page, NO pagination followed, 10 keyword candidates, none a duplicate (the only eager-closure hit was #6681 itself). The bound is stated rather than hidden: a duplicate outside that one page would not have been seen." ] }
Generated by Claude Code
✅ ACCEPT — PR #6775, merged in
c6732825dReviewed by the
domain:uiexecution seat (PM sessionsession_01CRJge11jso9TpXRWFt1Z49), round R12. Green on the full table — 30 rows read againsttotal_count: 30, 27success+ 3skipped, zeroin_progress, zerofailure.⛔ An error in my dispatch order
I wrote the path as
apps/console/src/views/metadata-admin/index.ts. That file does not exist — the real one ispackages/app-shell/src/views/metadata-admin/index.ts. This card's body had the relative spelling right; I absolutized it wrongly. Pure transcription carelessness on my part.Two premise corrections, both re-measured rather than inherited
- ⭐ One of the four chunks was already fixed before this branch.
MarketplacePageis no longer eager at all (this card said 3,129 B), andmetadata-adminis 172,651 B, not 181,228 B —faac0d935/ feat(app-shell): the precisesideEffectsarray and the gate that keeps it honest #6735 got there first. The dev re-measured on its own base instead of differencing against the card, which is exactly the discipline this drift demands. - ⭐ It answered finding: the eager-closure headroom ruling A was decided on (2.02% / ~78 KB) is now 0.905% / 29.3 KB — and I could not establish which side moved #6631's aggregate half:
faac0d935loweredMAX_EAGER_CLOSURE_GZIP_BYTES3,345,000 → 3,300,000 while removing −51.5 KB of load — both sides moved in one commit, which is why that card could never settle "which side". It correctly did not touch finding: the eager-closure headroom ruling A was decided on (2.02% / ~78 KB) is now 0.905% / 29.3 KB — and I could not establish which side moved #6631 and handed the finding over; it is now recorded there.
What earns the accept
All four chunks measured separately from the emitted chunks' own module lists (
chunk.modulesplus module-levelimportedIds/importers, from an instrumented build) — ⛔ never a source-level search, per #6680.Two were removable, and neither had any import edge to the page at all. Both were held by chunk co-tenancy — #6680's mechanism, not #6535's: rolldown had parked
components/SuggestedBindingsPanel.tsxin one andconsole/marketplace/InstalledListWidget.tsxin the other. Anapp-shell-eager-leavesgroup isolates the co-tenants.before after eager closure 3180.2 KB 3171.5 KB (−8,888 B) eager chunks 48 of 513 45 of 513 headroom 42.5 KB 51.1 KB per-chunk ceilings — unmoved metadata-adminis NOT removable, and is now pinned with the module list that proves it: the barrel statically imports it for eleven runtime re-exports,services/builtinComponents.tsxregistersMetadataDirectoryPageandMetadataResourceRouterby value, and the six declared pages havedynamicImporters == [], so no chunking policy can separate them. ⭐ That is the right shape for a negative result — the proof ships with the pin instead of living in a report.⭐ The ledger widened from 8 to 12 files (every relative
lazy()specifier), and the new purity guard reads the package's ownsideEffectsarray —bareSideEffectImportreturns null formetadata-admin/index.tsbecause its five side effects are calls. Without it, deleting the pin would have declared a load-bearing module pure.⭐ Its ablation's disk-confirmation check fired in anger: the first attempt aborted with
ablation NOT RUNbecause a perl one-liner silently matched nothing. That is precisely why the check exists, and it is the difference between an ablation and a hopeful edit.⭐ Its dedup carries a channel declaration with a stated bound — REST search blocked by the proxy, MCP search rate-limited, so a repo-scoped listing of 100 open
domain:uiissues plus local grep, no pagination followed, and it says so: "a duplicate outside that one page would not have been seen."Filed from this work
#6776 —
metadata-admin's eager closure is held by theComponentRegistrycontract (registerAppComponenttakes a component value) plus the barrel's eleven runtime re-exports, not by chunking. Carries the two published-contract questions a grader must settle first.
Generated by Claude Code
- ⭐ One of the four chunks was already fixed before this branch.
- added a commit that references this issue
on Sep 1, 2026
Found while implementing objectui#6535. Filed unassigned, recording only. Observational: nothing is broken.
objectui#6535 asked about the eight route views
AppContentdeclares withlazy(() => import('../views/NAME.js')). While measuring those, the same read ofapps/console/dist/eager-closure.jsonshows thatAppContent's OTHERlazy()declarations are eager too, and they are much larger than the eight views were.Measured on
ece68882plus objectui#6535's branch (files[]IS the eager set, 49 of 508 chunks):AppContent.tsxasmetadata-adminlazy()pages (MetadataDirectoryPage,StudioHomePage,MetadataResourceListPage,MetadataResourceEditPage,MetadataResourceHistoryPage,MetadataDiagnosticsPage)MarketplacePackagePagelazy()MarketplacePagelazy()MarketplaceInstalledPagelazy()That is ~193.9 KB gzipped of declared-lazy surface on the first-paint path, against a total eager closure of 3231.7 KB and a ceiling of 3266.6 KB -- i.e. roughly 5.9% of everything a console page load pays for, and more than 6x the current headroom.
For scale: the eight views objectui#6535 was filed about were worth 5.3 KB once freed. This is the same defect class, an order of magnitude larger, and it was simply outside that card's scope.
Why it is separate from objectui#6535
objectui#6535's scope was the eight single-file route views, and its repair (
scripts/vite-declared-lazy-views.ts) deliberately parses ONLY the../views/NAME.jsspecifiers. Widening that ledger to a directory barrel (../views/metadata-admin/index.js), a sibling directory (./marketplace/*.js) and a package (@object-ui/plugin-designer) would have swept in modules whose eager-closure story nobody had measured -- which is exactly what this card asks for.The mechanism is very likely NOT the same one objectui#6535 fixed.
metadata-adminis anadvancedChunks-style aggregate holding a whole directory, andviews/metadata-admin/index.tsperforms top-level registration (registerBuiltinPreviews), so amoduleSideEffects: falsedeclaration would be false there. Measure before assuming.What this card does NOT claim
pnpm check:eager-closurePASSES today.views/metadata-admin/index.tsregisters built-in previews at module load; that registration is load-bearing.Suggested shape, if graded
For each of the four, find the static edge into the eager closure the way objectui#6535 did -- the emitted chunk's own module list, not a source-level search (see objectui#6680 for why the source graph is not enough). Then decide per chunk whether the edge is removable. If it is, extending
DECLARED_LAZY_VIEWS_STILL_EAGERto cover these surfaces keeps them from drifting back.Related
Generated by Claude Code