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.

finding(console): metadata-admin's 172,651 B eager closure is held by a REGISTRY CONTRACT, not by chunking — registerAppComponent takes a component value #6776

Closed

Description

@os-sales

Found while implementing objectui#6681, which measured all four of AppContent's
non-single-file lazy() surfaces and freed the two that were removable. This records the
one that was NOT, so the 172,651 B does not leave the backlog when objectui#6681 closes.
Filed unassigned, recording only. Observational: nothing is broken and
pnpm check:eager-closure PASSES (3171.5 KB of a 3222.7 KB ceiling after objectui#6681).

What was measured

On b98352a15, from apps/console/dist/eager-closure.json (files[] IS the eager set)
and the emitted chunks' own module lists — never a source-level search (objectui#6680).

assets/metadata-admin-*.js is 172,651 B gzipped, 144 modules, EAGER — 5.4% of the
whole console eager closure and more than three times its 51.1 KB of headroom. Every
console page load pays for it while AppContent declares its six pages lazy().

The chunk is not held by co-tenancy, which is what objectui#6681 repaired for the
marketplace routes. It is held by real static edges into
packages/app-shell/src/views/metadata-admin/index.ts, the module all six declarations
target:

eager importer what it takes why it is eager
packages/app-shell/src/index.ts eleven runtime values re-exported (registerMetadataPreview, useMetadataClient, getMetadataInspector, …) the package barrel; the console entry imports it
packages/app-shell/src/services/builtinComponents.tsx MetadataDirectoryPage, MetadataResourceRouter by value, plus PackagesPage and PermissionMatrixEditPage the barrel BARE-imports it (import './services/builtinComponents.js';) for its ComponentRegistry registrations

Decisive for anyone reaching for a chunking lever first: the six pages the declarations
name (DirectoryPage, StudioHomePage, ResourceListPage, ResourceEditPage,
ResourceHistoryPage, DiagnosticsPage) have dynamicImporters: [] in the emitted
module graph. They are reached only through the barrel's static re-exports, so no
advancedChunks group separates them from it — grouping them would emit one eager chunk
instead of one eager chunk. objectui#6681 measured that failure mode directly on the
marketplace pages and recorded it in apps/console/vite.config.ts.

Why it is a registry contract, not a bundling problem

registerAppComponent({ ref, component }) takes a component value, so
builtinComponents.tsx must have loaded the page before it can register the ref. That is
the load-bearing half: the registration itself must happen at module load, or
ComponentNavView cannot resolve metadata:directory when the first
component/... route mounts.

What is NOT load-bearing is that the value be an eagerly-imported component.
React.lazy(() => import('../views/metadata-admin/index.js').then(m => ({ default: m.MetadataDirectoryPage })))
is also a component type. Whether that is correct depends on two things this card does not
settle:

  1. whether every render site of a registry component sits inside a <Suspense> boundary
    (ComponentNavView and any host embedding the registry);
  2. whether the package barrel should go on re-exporting eleven runtime values from a
    module its own sideEffects array declares side-effectful — those re-exports are a
    published API with out-of-package consumers, and they alone keep the module eager even
    if builtinComponents.tsx is fixed.

Both are published-contract decisions rather than in-lane bundling changes, which is why
objectui#6681 pinned the surface instead of attempting it.

What this card does NOT claim

  • Not a budget regression. The closure PASSES, with more headroom than before
    objectui#6681.
  • Not that the five top-level registrations in views/metadata-admin/index.ts
    (registerBuiltinAnchors, registerDefaultMetadataSchemas, registerDatasourceResource,
    registerBuiltinPreviews, registerBuiltinInspectors) should be deleted or declared
    side-effect-free. They are load-bearing, @object-ui/app-shell's sideEffects array
    names the module for exactly that reason, and objectui#6681 added a guard in
    scripts/vite-declared-lazy-views.ts that refuses to declare it pure — proved to fire
    by ablation.

Suggested shape, if graded

Decide (1) and (2) above first; they are the card. If both answers allow it, measure what
a lazy registerAppComponent value plus a leaf-module re-export path is worth, and delete
the views/metadata-admin/index.ts line from DECLARED_LAZY_VIEWS_STILL_EAGER — the
ledger's missing half fails the build until that line goes, so the win cannot be taken
silently.

Related

  • objectui#6681 — the card this was found under; the measurement, and the two chunks that
    WERE freed.
  • objectui#6680 — chunk co-tenancy: the mechanism that held the marketplace routes and
    explicitly does NOT hold this one.
  • objectui#6683 / objectui#6735 — the sideEffects array that names this module.
  • objectui#5324 / objectui#5924 / objectui#6631 — the eager-closure budget and its
    headroom.

Activity

  1. added
    domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seat
    on Aug 29, 2026
  2. self-assigned this
    on Aug 29, 2026
  3. claude commented on Aug 29, 2026

    @claude
    Contributor

    Result: this needs a ruling. Nothing landed, no PR. Here is the analysis, with the price.

    Measured on c6732825d (the commit this card names). Three console builds, each read from
    apps/console/dist/eager-closure.json and the emitted chunks' own module lists — never a
    source-level search.

    Baseline, from the gate's own verdict line:

    Console eager closure is 3174.0 KB gzipped across 45 of 513 chunks (budget: 3222.7 KB, headroom: 48.6 KB).

    assets/metadata-admin-*.js = 172,660 B gzipped, 144 modules, EAGER — the card's 172,651
    reproduces to within 9 bytes.


    1. The render-site census — question 1 is already answered by shipped code

    There is exactly one resolver of a registerAppComponent value in the repo:
    ComponentNavView.tsx:35 (getAppComponent(ref)), rendered at line 60. Nothing else calls
    getAppComponent or listAppComponents to render. ComponentNavView has two mount sites:

    # mount site tolerates a pending component? why
    1 AppContent.tsx:1013, the component/:ns/:name/* route YES enclosed by the Suspense fallback=LoadingScreen boundary spanning lines 956-1041 (verified by line number, not by eye)
    2 PackagedAutomationPage.navContribution.test.tsx:77 YES asserts through await screen.findByRole(...), an async query

    A third site renders a registered entry without ComponentNavView:
    apps/console/src/__tests__/approvalsInboxComponentRef.test.tsx:97. It tolerates pending too
    (await screen.findByTestId) — and it must, because the ref it tests is already lazy.

    Ten refs are registered today. Five are already lazy, and have shipped:

    ref registered at value boundary
    account:profile_card registerAccountComponents.tsx:46 lazy own Suspense, inside the registered value
    approvals:inbox registerApprovalsComponents.tsx:58 lazy own Suspense
    developer:api-console registerDeveloperComponents.tsx:37 lazy own Suspense
    developer:flow-runs registerDeveloperComponents.tsx:48 lazy own Suspense
    developer:public-forms registerDeveloperComponents.tsx:59 lazy own Suspense
    metadata:directory builtinComponents.tsx:36 eager —
    metadata:resource builtinComponents.tsx:43 eager —
    developer:packages builtinComponents.tsx:50 eager —
    automation:packaged builtinComponents.tsx:64 eager —
    studio:builder registerStudioComponents.tsx:58 eager, deliberately (objectui#5486) —

    Answer to question 1: no ruling needed. Every render site already tolerates a pending
    component, and the established pattern puts the boundary inside the registered value, so
    registerAppComponent's signature never changes and no render site changes either. The
    registry contract already admits lazy components; half its entries are lazy.


    2. The census the card did not ask for — and it decides the outcome

    views/metadata-admin/index.ts's five load-time registrations feed four registries, and
    those registries have consumers outside metadata-admin that this card does not name:

    consumer reads chunk eager? what happens if unregistered
    views/studio-design/StudioDesignSurface.tsx getMetadataPreview x2, getMetadataInspector x3, getMetadataDefaultInspector x1 index-*.js EAGER isEditable = !!Preview && !StudioCanvas -> canvas silently non-editable
    views/studio-design/ObjectSettingsPanel.tsx:75 getMetadataDefaultInspector('object') index-*.js EAGER panel silently empty
    views/studio-design/ObjectHooksPanel.tsx:88 getMetadataDefaultInspector('hook') index-*.js EAGER panel silently empty
    views/studio-design/ObjectActionsPanel.tsx:101 getMetadataDefaultInspector('action') index-*.js EAGER panel silently empty
    apps/console/src/preview-gallery.tsx getMetadataPreview, listMetadataPreviewTypes, getMetadataInspector standalone preview-gallery.html entry not in the production build gallery empty

    These reads are synchronous and return undefined. No Suspense boundary can repair any
    of them
    — Suspense catches a promise thrown by a rendering lazy component; it cannot catch
    a registry lookup that returns nothing. The failure is silent, exit-0, and renders as
    "this surface just isn't editable".

    That matters because of where the bytes are. The chunk's 1,219,615 B rendered, by subtree:

    subtree rendered B share
    previews/* 460,753 37.8%
    inspectors/* 353,841 29.0%
    metadata-admin/* top level (incl. all six declared-lazy pages) 315,601 25.9%
    external/* 28,924 2.4%
    datasource/* 27,866 2.3%
    node_modules + other 32,630 2.7%

    The six pages the card names total 147,777 B rendered = 12.1%. The previews and
    inspectors held by those four eager studio-design consumers are 66.8%.


    3. What it is actually worth — measured, not argued

    # shape eager delta
    E0 baseline c6732825d 3,250,187 B gz, 45/513 chunks
    E2 in-fence only: lazy registration value in builtinComponents.tsx, package barrel untouched -30 B. The metadata-admin chunk grew +189 B (the lazy/Suspense/fallback code) and stayed EAGER. No chunk left or joined; 45/513 unchanged.
    E1b the full shape: barrel's 25 runtime + 11 type re-exports repointed to leaf modules, the five registrations lifted to views/metadata-admin/register-builtins.ts bare-imported by the package entry, lazy registration, sideEffects array entry swapped, ledger pin deleted -31,794 B (-31.0 KB), build green

    E1b in detail: metadata-admin (172,660) leaves the eager closure entirely and becomes lazy —
    but src grows +128,647 and four chunks join (SchemaForm 28,083, ActionResultDialog
    12,767, useMonacoFallback 9,705, nav-selection 592). 877,242 B of previews and
    inspectors stay EAGER
    , relocated into src-*.js, held by register-builtins.ts (240 B,
    eager) which the package entry must bare-import for the four studio-design consumers above.

    So the contract change both questions gate is worth 31.0 KB, not 172.7 KB. The remaining
    ~141 KB is held by a different contract — the preview/inspector registries — whose failure
    mode is a silent undefined, not a pending component.

    Two things only an attempt could have found

    • The registrations cannot be bare-imported from the page barrel. The first E1 build
      failed: declared-lazy-views refuses to declare a view side-effect-free when it carries a
      bare side-effect import, so import './register-builtins.js'; inside
      views/metadata-admin/index.ts keeps the page barrel unshakeable and the win is zero. The
      registrations must hang off the package entry. That is safe here only because
      @object-ui/app-shell's exports map is root-only (. and ./styles.css), so no
      consumer can deep-import the page barrel and observe the move — verified, not assumed.
    • The ineffective-dynamic-import ledger does not catch E2. It stayed at "43, all pinned".
      Rolldown's INEFFECTIVE_DYNAMIC_IMPORT fires when one module both statically and
      dynamically imports a specifier; here the static edge lives in a different module
      (packages/app-shell/src/index.ts), so nothing warns. E2 is objectui#5486's exact shape —
      a lazy() claiming a split it does not have — and it would have landed green and silent.

    4. Why I stopped

    The 31.0 KB requires editing packages/app-shell/src/index.ts and
    packages/app-shell/package.json's sideEffects array. Both are outside this card's file
    surface, and the second is a published-contract field established by the maintainer ruling of
    2026-08-29 (objectui#6683). Changing where the load-time registrations live is question 2.
    So it is reported, not taken.

    In every arm below, registerAppComponent's signature is unchanged and the barrel's public
    export names and types are unchanged.

    Arm A — do nothing. 172,660 B stays eager. Zero risk. The gate passes today with 48.6 KB
    of headroom.

    Arm B — take the 31.0 KB (E1b, measured green). Risk is confined to the sideEffects
    array and the registration locus; the root-only exports map makes the move unobservable
    outside the package. ⚠️ Implementation note: -31.0 KB takes aggregate headroom from 48.6 KB
    to 79.7 KB, i.e. 0.55x -> 0.89x of the 89.0 KB regression the gate exists to catch. Still
    under the 1.0x error threshold, but close enough that the ceiling should be re-baselined in
    the same change or the gauge starts going blind.

    Arm C — go after the ~141 KB where it actually is. That means the eager
    views/studio-design/ surface, not the ComponentRegistry. It is a distinct question with a
    distinct failure mode (silent undefined, not pending component), and
    registerStudioComponents.tsx's own docblock already rules the studio surface's laziness "a
    separate measured card, not a rider here".

    Recommendation: B now, C as its own card. B is measured, green, and preserves every name
    and type; C is where two thirds of the bytes are but needs its own measurement and its own
    answer about unpopulated registries. Question 1 needs no ruling at all.


    Verification artifacts: three console builds under the shared verify lock, each exiting 0
    (os-verify-lock: VERDICT command-exit 0), budget gate exit=0 with the verdict line quoted
    above. Working tree reset to c6732825d and confirmed byte-identical (git diff --quiet =
    clean); nothing landed on claude/issue-6776-metadata-admin-registry-contract.

    Out-of-scope finding filed: objectui#6785.

    Generated by Claude Code


    Generated by Claude Code

  4. claude commented on Aug 29, 2026

    @claude
    Contributor

    os-dev-report

    {
      "issue": 6776,
      "status": "needs_decision",
      "branch": "claude/issue-6776-metadata-admin-registry-contract",
      "pr": null,
      "premise_still_valid": true,
      "summary": "Premise holds and reproduces: on c6732825d, assets/metadata-admin-*.js is 172,660 B gzipped / 144 modules / EAGER (the card's 172,651 to within 9 bytes), held by exactly the two static edges the card names. Nothing landed and no PR was opened, because the only reduction with a measurable value requires editing packages/app-shell/src/index.ts and packages/app-shell/package.json's sideEffects array -- both outside this card's file surface, and the second IS contract question 2. Full census + numbers posted to the card: https://github.com/objectstack-ai/objectui/issues/6776#issuecomment-5462673629 . THREE RESULTS. (1) Contract question 1 needs no ruling: there is exactly one resolver of a registerAppComponent value (ComponentNavView.tsx:35), it has two mount sites and BOTH already tolerate a pending component (AppContent.tsx:1013 sits inside the Suspense boundary spanning 956-1041; the test site asserts through await findByRole), and 5 of the 10 registered refs are ALREADY lazy in shipped console code, each carrying its own Suspense boundary INSIDE the registered value -- so the signature never changes and no render site changes. (2) The card asks about the wrong failure mode for most of the bytes. The five load-time registrations also feed FOUR EAGER views/studio-design/ consumers (StudioDesignSurface, ObjectSettingsPanel, ObjectHooksPanel, ObjectActionsPanel) that read the preview/inspector registries SYNCHRONOUSLY and degrade silently on undefined (isEditable = !!Preview). No Suspense boundary can repair a lookup that returns undefined. Those registries are 66.8% of the chunk; the six declared-lazy pages the card names are 12.1%. (3) Priced it: the full contract-changing shape is worth -31,794 B gzipped, NOT -172,651. The in-fence-only change is worth -30 B and leaves the chunk eager. CORRECTION to a detail carried by both the card body and the DECLARED_LAZY_VIEWS_STILL_EAGER docblock: the barrel re-exports 25 RUNTIME values (each a static edge) plus 11 TYPE-ONLY values (erased, no edge) -- 'eleven runtime values' counted the type block. Filed as #6785. Working tree reset to c6732825d and confirmed byte-identical; the pushed branch is empty.",
      "tests": "No source change was landed, so no gate is owed on a diff -- the working tree is byte-identical to c6732825d (git diff --quiet against BASE = clean, git status clean, HEAD=c6732825d). Note objectui has no scripts/pm/dispatch-gates.mjs (that script lives in objectstack and answers only about its own tree), and the diff here is empty either way. MEASUREMENT RUNS -- three console builds, all under the shared verify lock, all exiting 0 ('os-verify-lock: VERDICT command-exit 0', held 169s / 77s / 86s / 83s), each read from apps/console/dist/eager-closure.json and the emitted chunks' own module lists via dist/stats.html: [E0 baseline c6732825d] eager 3,250,187 B gz, 45/513 chunks; metadata-admin 172,660 B gz / 144 modules. Budget gate run over that artifact, exit=0, its own verdict line: 'Console eager closure is 3174.0 KB gzipped across 45 of 513 chunks (budget: 3222.7 KB, headroom: 48.6 KB).' [E2 in-fence-only: lazy registration value in builtinComponents.tsx, barrel untouched] eager 3,250,157 B = -30 B; metadata-admin chunk GREW to 172,849 (+189 B, the lazy/Suspense/fallback code) and stayed EAGER; no chunk left or joined, 45/513 unchanged. Rolldown's ineffective-dynamic-import ledger did NOT flag it (stayed '43, all pinned') because the static edge lives in a different module -- so this no-op split would land green and silent. [E1b full shape: 25 runtime + 11 type re-exports repointed to leaves, five registrations lifted to views/metadata-admin/register-builtins.ts bare-imported by the PACKAGE ENTRY, lazy registration, sideEffects array entry swapped, ledger pin deleted] build green, eager 3,218,393 B = -31,794 B (-31.0 KB), 48/517 chunks. metadata-admin (172,660) leaves and becomes lazy; src +128,647 and four chunks join (SchemaForm 28,083, ActionResultDialog 12,767, useMonacoFallback 9,705, nav-selection 592); 877,242 B of previews+inspectors STAY EAGER in src-*.js, held by register-builtins.ts (240 B, eager) which the package entry must bare-import for the four studio-design consumers. NEGATIVE RESULT worth recording: the first E1 attempt FAILED the build -- 'declared-lazy-views refusing to declare packages/app-shell/src/views/metadata-admin/index.ts side-effect-free: it carries a bare side-effect import (import ./register-builtins.js)'. So the registrations cannot hang off the page barrel; they must hang off the package entry. That is safe only because @object-ui/app-shell's exports map is root-only ('.' and './styles.css'), verified from package.json, so no consumer can deep-import the page barrel and observe the move. Each experiment was COMMITTED before measuring (restore points 0e1c1a460 / 844ab0fec / ffc31154b), then reverted with git reset --hard to BASE and the revert PROVEN by git diff --quiet against BASE returning clean -- not by a checkout exit code. The E1b diff is preserved at scratchpad issue-6776/E1b.patch (236 lines) so the ruling can be implemented from a measured patch.",
      "mcp_calls": "0 -- the whole run used repo-scoped REST over curl (probe returned 200); /search/* is 403 by egress design, so dedup used repo-scoped list endpoints + local grep over 244 open issues with #6776 itself as the positive control",
      "open_questions": [
        {
          "question": "Q1 (the card's first contract question) -- does every registry render site tolerate a pending component, i.e. is Suspense coverage sufficient for a lazy registerAppComponent value?",
          "options": [
            "A. Needs a ruling / needs Suspense work before laziness is safe.",
            "B. Already satisfied -- no ruling and no render-site change needed."
          ],
          "recommendation": "B, and this one is settled by measurement, not judgement. One resolver (ComponentNavView.tsx:35), two mount sites, both already tolerant: AppContent.tsx:1013 is inside the Suspense boundary spanning 956-1041, and the test mount asserts via await findByRole. Decisively, 5 of the 10 registered refs are ALREADY lazy in shipped console code (account:profile_card, approvals:inbox, developer:api-console, developer:flow-runs, developer:public-forms), and each puts its Suspense boundary INSIDE the registered value -- so the boundary travels with the component, registerAppComponent's signature is untouched, and no render site is asked to tolerate anything new. The registry contract already admits lazy components; half its entries are lazy today."
        },
        {
          "question": "Q2 (the card's second contract question) -- should the package barrel go on re-exporting runtime values from a module its own sideEffects array declares side-effectful? Answering it costs edits to packages/app-shell/src/index.ts and packages/app-shell/package.json, both outside this card's file surface, and it is worth a measured -31,794 B (not -172,651).",
          "options": [
            "A. Leave it. 172,660 B stays eager; zero risk; the gate passes today with 48.6 KB headroom.",
            "B. Take the 31.0 KB (E1b, already measured green): repoint the 25 runtime + 11 type re-exports to leaf modules (same NAMES, same TYPES), lift the five registrations into views/metadata-admin/register-builtins.ts bare-imported by the PACKAGE ENTRY, register metadata:directory / metadata:resource as lazy values with their own Suspense, swap the sideEffects array entry, delete the ledger pin.",
            "C. Go after the ~141 KB where it actually is -- the eager views/studio-design/ surface -- as its own card."
          ],
          "recommendation": "B now, C as its own card, A only if the sideEffects array is considered frozen. Four axes. REAL BUSINESS NEED: real but modest, and I measured it rather than assuming -- 31.0 KB off every console page load, 0.98% of a 3174.0 KB closure with 48.6 KB of headroom. It is NOT the 172.7 KB the card implies, and anyone deciding on that number would be overpaying for the risk. LONG-TERM SOUNDNESS: B is the contract-first shape. The five registrations are load-bearing platform state; today they are an accident of which barrel happens to be imported, and B makes them an explicit edge from the package entry -- the thing that is true becomes the thing that is declared. It also leaves the sideEffects array MORE honest, not less: the array is derived, and after B it names the module that actually registers. AI-WRITTEN-CODE SAFETY: this is the axis that decides B over a broader change. The four eager studio-design consumers read the preview/inspector registries synchronously and fail SILENTLY on undefined (isEditable = !!Preview) -- exactly the fail-open shape this project refuses. B keeps those registrations eager and their consumers correct by construction, and it does so through the package entry rather than through an implicit chain nobody can see. Any arm that makes previews and inspectors lazy without first fixing that read is the dangerous one, which is why C must be measured separately rather than ridden along. STARTUP SCOPE DISCIPLINE: B adds no capability and no new surface -- one new leaf module, zero new exported names, zero new API. C is deliberately deferred rather than bundled, matching registerStudioComponents.tsx's own docblock, which already rules the studio surface's laziness 'a separate measured card, not a rider here'. IMPLEMENTATION NOTE if B is taken: -31.0 KB moves aggregate headroom 48.6 -> 79.7 KB, i.e. 0.55x -> 0.89x of the 89.0 KB regression the gate exists to catch. Under the 1.0x error threshold but close, so re-baseline MAX_EAGER_CLOSURE_GZIP_BYTES in the same change or the gauge starts going blind."
        }
      ],
      "out_of_scope_findings": [
        "filed as #6785: DECLARED_LAZY_VIEWS_STILL_EAGER's metadata-admin docblock says the barrel re-exports 'eleven runtime values'; the runtime block has 25 (each a static edge) and 11 is the TYPE-ONLY block beneath it (erased at build, zero edges) -- the error already propagated verbatim into #6776's body, pointing any implementer at the one block that provably holds nothing eager",
        "not filed, recorded here as it is inseparable from this card: builtinComponents.tsx holds two MORE by-value component registrations that keep bytes eager through a SECOND registry -- registerMetadataResource({ type: 'permission', EditPage: PermissionMatrixEditPage }) pins PermissionMatrixEditor.tsx (36,101 B rendered, eager) and registerAppComponent('developer:packages') pins PackagesPage.tsx (30,801 B rendered, eager). Both survive E1b untouched. If PM wants them chased, they are the same contract shape as this card and belong on it or on its successor, not on a separate card"
      ]
    }

    Generated by Claude Code

  5. claude commented on Aug 29, 2026

    @claude
    Contributor

    Claim: domain:ui execution seat · PM session session_01CRJge11jso9TpXRWFt1Z49 · round R13 · branch claude/issue-6776-metadata-admin-registry-contract

    ⚠️ Posted late — this marker was missing while the round ran. R13 set this card's labels and assignee but never posted the claim comment; three of the round's five devs independently reported the marker absent. Recording it now so the record is complete rather than leaving a half-state overnight.

    Why this is not merely tidiness. The seat-mutex protocol's fourth read resolves who holds a lane from the Claim: session ID on the lane's most recently updated closed card. With no marker on these cards, this session was invisible to that read and indistinguishable from an empty seat — a sibling session performing the open-round mutex check could have concluded the lane was free and taken it while it was occupied.

    Cause, recorded rather than excused. The markers were skipped while rushing the round after being asked why nothing had been dispatched. Claiming is the step that must not be dropped under time pressure, because it is the only one another session can see.

    Outcome: still in flight — branch still on the remote.


    Generated by Claude Code

  6. claude commented on Aug 29, 2026

    @claude
    Contributor

    状态更正:pm:dispatched → needs-user-decision

    本卡的派发以 needs_decision 收场 —— 没有 PR,分支上什么都没落,工作树已复位并确认 git diff --quiet 干净。那么 pm:dispatched 就是个半状态:它对在飞查询说「有人在做」,而实际上没有。改挂决策态并补四棱块。

    ⭐ 派发的产出不是零 —— 它把卡里两个问题中的一个直接答掉了,并且推翻了卡自己的价格。

    一句话问题:metadata-admin 的 172,660 B eager 闭包要不要变懒 —— 但实测把价格改了:这个契约变更只值 31.0 KB,不是 172.7 KB,余下 ~141 KB 由另一个契约(preview / inspector 注册表)持有,失败模式是静默 undefined 而不是 pending 组件。

    问题 1 不需要裁决(已由已发布代码答掉)。 仓内只有一个 registerAppComponent 值的解析点,它的每一个渲染站点都已容忍 pending;十个已注册 ref 里五个本来就是 lazy 并已发版,既有模式把边界放在注册值内部,所以 registerAppComponent 的签名不变、渲染站点不变。

    三条臂

    做什么 实测代价
    A 什么都不做 172,660 B 继续 eager。零风险 —— 门禁今天通过,余量 48.6 KB
    B 取那 31.0 KB(E1b,已实测构建通过) -31,794 B。风险局限在 sideEffects 数组与注册点位置;root-only 的 exports map 使这次移动在包外不可观测(已验证,非假设)
    C 去拿真正的 ~141 KB 那是 eager 的 views/studio-design/ 面,不是 ComponentRegistry。不同的问题、不同的失败模式,已另立卡

    四棱

    ① 长远合理性:B 要改两处 —— 把 load-time 注册从页面 barrel 提到包入口,以及 package.json 的 sideEffects 数组,而后者是 2026-08-29(#6683)维护者裁定确立的已发布契约字段。⇒ 这是契约面的决定,不是打包优化,这也正是 dev 停手报告而不是动手的原因。

    ② 业务拉动:⚠️ 今天没有。 门禁通过,余量 48.6 KB。没有人在等这 31 KB。

    ③ 防 AI 犯错:⭐ 本卡最重要的一条,而且它指向一个反对草率取 B 的理由。看起来最小、最"in-fence"的改法 E2 —— 只在 builtinComponents.tsx 里把注册值改成 lazy —— 实测只值 -30 B,chunk 反而涨了 189 B 且仍然 EAGER;而且它会绿着通过所有门禁:ineffective-dynamic-import 台账抓不到它,因为静态边住在另一个模块里。⇒ E2 正是 #5486 的形状——一个 lazy() 声称了它并不具备的分割。任何后来者(人或 AI)想"顺手做个懒加载"都会写出 E2。无论本卡裁成什么,这个陷阱都应当被写下来。

    ④ 创业阶段不扩散:A 零成本;B 有界但触两处契约面;C 是独立的一张卡,不该并进来。

    ⭐ 置信缺口(本席未独立复核,诚实列出)

    1. ⚠️ B 的"量表失明"代价我没有复核。 报告称 -31.0 KB 会把余量从 48.6 KB 推到 79.7 KB,即门禁要抓的 89.0 KB 回归的 0.55x → 0.89x。若属实,B 必须同批重设 ceiling,否则这次优化的副作用是把预算表变瞎 —— 而抬 ceiling 本身是一条 ratchet,有它自己的申报程序,⛔ 不能作为 B 的顺手活混进去。这一条应当写进裁决,而不是留给实施者临场判断。
    2. 未测:C 的 ~141 KB 是否真的可动。四个 studio-design 消费者是同步读且未注册时返回 undefined,Suspense 结构上修不了 ⇒ C 不是"再做一次 B"。
    3. 未测:preview-gallery 这个 standalone entry 不在生产构建里,它对 C 的约束有多大。

    低摩擦裁决:回「A」「B」「B + 同批重设 ceiling」或「先做 C」。

    裁后执行:裁 B ⇒ 转 pm:queue,派发令必须点名 packages/app-shell/src/index.ts 与 package.json 的 sideEffects 两处契约面,并把 ceiling 的处置写死。裁 A ⇒ 关卡,但 ⛔ 必须把上面第③条的 E2 陷阱写进 builtinComponents.tsx 旁的注释并指向本卡 —— 只关不写,下一个人就会落地 E2。


    Generated by Claude Code

  7. zhuangjianguo commented on Aug 30, 2026

    @zhuangjianguo
    Collaborator

    ⚖️ RULED — 维护者,2026-08-30,第 5 场总监席决裁批 #4,verbatim「同意」

    裁定:采选项 B(取 31KB)+ 同批重设 eager ceiling;问题 1(渲染站点容忍 pending)已由实测答定、无需裁决;C(去拿 141KB)是 arm C、即 #6795,独立处理。

    性质:B 触已发布 sideEffects 契约面 ⇒ 派发按内容判条款②(移动注册点位 + sideEffects 数组变更),档位由 ui 席定;⛔ 治理面(#6683 sideEffects 数组)PR 走人工合并道,不 arm auto-merge。席位边界(裁不派):录裁 + 转 pm:queue。同笔摘 needs-user-decision。裁决入本场追认摘要表。


    Generated by Claude Code

  8. zhuangjianguo commented on Aug 30, 2026

    @zhuangjianguo
    Collaborator

    needs:contract-review removed — 执行维护者 2026-08-28 对 objectstack#12887 的裁定(可复审增量存在前永不挂标;预挂已废止)。本卡 dev 在飞、PR 尚未存在,条款② 判定不变(裁决已按内容判命中),PR 存在的同笔即重挂双载体;另注意本卡裁决已指定治理面走人工合并道。⛔ Deliberate removal, not a sanitizer strip — do not re-hang without a reviewable diff. 项目总监席,session session_01DxbNgzPMo4YuRBmGmCQp9m。


    Generated by Claude Code

  9. os-litant commented on Sep 2, 2026

    @os-litant
    Contributor

    Closed-card label hygiene: pm:dispatched stripped from this closed card (completed via PR #6913, merged 2026-08-31) so it stops counting in the lane's in-flight view. domain:ui execution seat, session session_01NRRumy89BYdW9ogbcdHTho (os-litant); no other change.


    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

No one assigned

    Labels

    domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatpriority:p1

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions