Repository navigation
perf(console): the markdown highlighter leaves the first load; vendor-markdown claims only what it reaches (objectui#11854) - #11900
Merged
objectstack-fleet[bot] merged 3 commits intoOct 8, 2026
Conversation
…s (objectui#11854) Tag the `vendor-markdown` chunk group `$initial`, as `vendor-objectstack` is, so the family members only `plugin-markdown` reaches (rehype-highlight with lowlight and highlight.js, rehype-slug, rehype-autolink-headings, remark-github-blockquote-alert and their helpers) follow that plugin's lazy chunk instead of riding the eager one. `react-markdown`, needed by the eager `ui-components` co-tenant `MarkdownContent`, gets a group of its own so it does not land on the budgeted `ui-components` line. Claude-Session: https://claude.ai/code/session_01MgfduSkFrfM3eorB3UGfAU Co-authored-by: Claude <noreply@anthropic.com>
…dor-markdown shed (objectui#11854) `MAX_EAGER_CLOSURE_GZIP_BYTES` comes down from 3,444,622 to 3,386,364, exactly the bytes that left the console's eager closure when `vendor-markdown` stopped claiming the family members only `plugin-markdown` reaches (`9cb4e29` 3,433,602 -> `406f760` 3,375,344 gzipped, same container, same instrument). `BASELINE` is re-pinned to `406f760` in the same commit, keeping the 11,020 bytes (0.12x) of headroom `main` had. The unit test's rendered-baseline literal moves from 3338.1 to 3296.2. No per-chunk row moved. Patch changeset for `@object-ui/console`. Claude-Session: https://claude.ai/code/session_01MgfduSkFrfM3eorB3UGfAU Co-authored-by: Claude <noreply@anthropic.com>
…d the lowered band (objectui#11854) Two cases in `check-eager-closure-budget.test.ts` read state this change moves. The objectui#11101 control lists the exact set of console chunk groups carrying `$initial`; it now names `vendor-markdown` beside `vendor-objectstack`. The "one chunk over its ceiling while the aggregate is green" run grew one chunk without shrinking the rest, so its total rode on the aggregate headroom, and the lowered ceiling left less headroom than that chunk's overage; it now offsets the rest of the closure by the same bytes, derived from the constants, so the total stays on the baseline. The `BASELINE` docblock records the ordinary-build calibration. Claude-Session: https://claude.ai/code/session_01MgfduSkFrfM3eorB3UGfAU Co-authored-by: Claude <noreply@anthropic.com>
Contributor
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
objectstack-fleet
Bot
deleted the
claude/issue-11854-vendor-markdown-eager
branch
October 8, 2026 06:02
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #11854
Clause-②: no
The console's first load no longer downloads the markdown highlighter and the docs-only markdown plugins. The eager closure goes from 3,433,602 to 3,375,344 gzipped bytes (−58,258, −1.70%), measured on
main9cb4e29and on this branch by the same method in one container. Thevendor-markdownchunk stays eager, because the chat message renderer reaches 243 of its 296 modules on every page. That is why 104,286 of its bytes stay, and below says which modules.What changed
vendor-markdownclaims only what the first load reaches. The group inapps/console/vite.config.tsgetstags: ['$initial'], the same option and the same reason asvendor-objectstack(objectui#11101). The 52 family modules that only@object-ui/plugin-markdownreaches now load with that plugin's own lazy chunk:rehype-highlightwithlowlightandhighlight.js,rehype-slug,github-slugger,rehype-autolink-headings,remark-github-blockquote-alert, and five hast and unist helpers. That plugin loads behind its lazymarkdownregistration and the docs reader's lazy route.react-markdowngets a group of its own (vendor-react-markdown). It is the 53rd module no first-load import reaches, but the first load still needs it.MarkdownContentinpackages/fieldsimports it statically, and that widget sits in the eagerui-componentschunk although onlyReact.lazyreaches it, the co-tenancy recorded inscripts/vite-ineffective-dynamic-imports.ts(objectui#5325). Without its own group,ui-components(priority 80) would take it by its own capture, onto a budgeted line with 4.5 KB of headroom. With it, the chunk is a 1,144-byte eager chunk that follows its importers.scripts/check-eager-closure-budget.mjs,MAX_EAGER_CLOSURE_GZIP_BYTESgoes from 3,444,622 to 3,386,364, down by exactly the 58,258 bytes that left. That keeps the 11,020 bytes (0.12x) of headroommainhad on9cb4e29.BASELINEis re-pinned in the same change to406f760, at 3,375,344 bytes across 334 of 2457 chunks. The record is a new objectui#11854 entry on the ceiling, with the table below. No per-chunk row moved, and no gate, check or ceiling row was added.scripts/__tests__/check-eager-closure-budget.test.tsfollow the change.$initial, and now namesvendor-markdownbesidevendor-objectstack. That also pins this group's tag.@object-ui/console.Acceptance notes
Eager size, before and after
The same instrument on both sides: the console's own
apps/console/vite.config.tsrun through Vite'sbuild()with one read-only module-graph dump appended (scratch, not committed). It reads theeager-closure.jsonthe build writes, and each build ran underscripts/pm/os-verify-lock.sh.9cb4e29ismain.406f760is that tree plus thevite.config.tschange, and nothing else. The gate's own verdict lines, fromnode scripts/check-eager-closure-budget.mjs --reporton each build's report:main9cb4e29main's constantsConsole eager closure is 3353.1 KB gzipped across 333 of 2456 chunks (budget: 3363.9 KB, headroom: 10.8 KB)Console eager closure is 3296.2 KB gzipped across 334 of 2457 chunks (budget: 3363.9 KB, headroom: 67.7 KB), aggregate 0.76xConsole eager closure is 3353.1 KB gzipped across 333 chunks — 46.1 KB over the 3307.0 KB budget.Console eager closure is 3296.2 KB gzipped across 334 of 2457 chunks (budget: 3307.0 KB, headroom: 10.8 KB), aggregate 0.12xThe bottom-left cell is the gate-level reverse check: the
9cb4e29build is this change reverted, and the lowered ceiling refuses it. Gzipped bytes per chunk:9cb4e29406f760vendor-markdownvendor-react-markdown, newui-componentsThe +20 is import bookkeeping spread over 19 chunks, none of them by more than 5 bytes.
vendor-objectstack,framework,i18n-locale-enand everyvendor-icon-*chunk are unmoved. Raw eager bytes go from 11,621,645 to 11,441,276 (−180,369). The lazyplugin-markdownchunk goes from 4,342 to 62,888 gzipped.vendor-markdown's eager share goes from 163,717 of 3,433,602 (4.77%) to 104,286 of 3,375,344 (3.09%), or 105,430 (3.12%) countingvendor-react-markdown.Calibration: the console's ordinary build of
57a1066(CI=true pnpm exec turbo run build --filter=@object-ui/console... --concurrency=2,Tasks: 35 successful, 35 total) wroteeager closure: 334/2457 chunks, 3375344 bytes gzipped. That is the measuring build's figure to the byte.57a1066differs from406f760only in the ledger, its test and the changeset.What the first load reaches (the card's H1, measured)
The method is a breadth-first walk from the entry module over rolldown's static
importedIds(getModuleInfo), on the9cb4e29build.vendor-markdownheld 296 modules, 906,232 rendered bytes. Static imports reach 243 of them (609,027 rendered); the other 53 (297,205 rendered) were unreached, the card's figures exactly. Every reached member is reached through one path:plugin-chatbot's message renderer importsstreamdown.streamdownstatically importsremark-parse,remark-gfm,remark-rehype,rehype-raw,rehype-sanitize,rehype-harden,unifiedandhast-util-to-jsx-runtime. Of the 53 unreached modules:plugin-markdownand themselves.highlight.jsis 39 modules and 259,089 rendered bytes of them.MarkdownImpl.tsximportsrehype-highlight,rehype-slug,rehype-autolink-headingsandremark-github-blockquote-alert, andtoc.tsimportsgithub-slugger. Their helpers are imported only by those.react-markdownis also imported bypackages/fields/src/widgets/MarkdownContent.tsx, which is a co-tenant of the eagerui-componentschunk.On
406f760,vendor-markdownholds exactly the 243 reached modules and 0 unreached.plugin-markdownholds its own 5 modules plus the 52.Against the two candidates in the dispatch:
plugin-chatbot,ui-components).$initialtag is (a)'s split without a name list, and the remainder falls to its importer as (b) describes.react-markdownis the one name the split must carry, and the reason is the co-tenancy above.Why 104,286 bytes stay eager and not the card's ~160 KB. The 243 modules left in
vendor-markdownare the pipeline the chat renderer runs on every page. Moving them would lazy-load modules the first load reaches, which belongs to objectui#11798 under the objectui#6795 ruling, and this PR does not do it.react-markdown(1,144) stays eager because of the objectui#5325 co-tenancy, whose repair is held by that card's ruling.Chunk cycles (H2)
The cycle check is a strongly-connected-components pass over every chunk's static
imports, in the same scratch analysis of each build's dump. Both builds have exactly one cycle,data-adapter/framework/i18n-runtime, the pre-existing one thei18n-runtimenote invite.config.tsrecords. 0 cycles involve a markdown chunk. On406f760:vendor-markdownimports onlyrolldown-runtime.vendor-react-markdownimportsrolldown-runtime,vendor-reactandvendor-markdown.plugin-markdownimportsrolldown-runtime,vendor-react,ui-components,framework,vendor-markdownandvendor-react-markdown.None of them is imported back.
Real-browser control (H2)
The setup reuses objectui#11798's: Playwright Chromium (
/opt/pw-browsers/chromium) at 1440x900 with cold contexts, the built console served statically with an injected base href, and the API answered by a scratch route mock. The mock serves a session, discovery, onedocitem and one shared conversation, and nothing else. The control was run on both builds, with three phases:/, which lands on/home./docs/probe/markdown_probe_11854. The docs reader renders the item through@object-ui/plugin-markdown'sMarkdownRenderer. Its body has a heading, bold text, a link, a GFM table, ajscode block and a GitHub alert./s/tok-11854for a shared AI conversation.ChatbotEnhancedrenders the assistant reply throughstreamdown, the eager path.9cb4e29406f760vendor-markdownamong themvendor-markdown+vendor-react-markdown;plugin-markdown✗DocsLayout,use-book-data,DocPage,DocShell,BookPage,plugin-markdownplugin-markdownnow carries the highlighterhljsspans,markdown-alert-noteFailed to fetch dynamically imported module/ failed requests / non-200 JSSo the markdown renders on both paths, there are no TDZ or uncaught errors, and on this branch the highlighter's chunk is fetched only when the docs page opens. A markdown field could not be reached: it needs object metadata and record fixtures this mock does not serve. Its renderer,
react-markdown, is exercised on the docs page, whoseMarkdownImplimports the samevendor-react-markdownchunk.Reverse check of the updated pin
Prediction recorded before the run: one case red, the
$initialcontrol. The run went throughscripts/ablation-replace.mjs(objectstack). It removedtags: ['$initial']fromvendor-markdown, with the anchor going 1 → 0 and the blob0e32a097d0f1→d49fa56f23c7. It then ranpnpm exec vitest run scripts/__tests__/check-eager-closure-budget.test.tsand gotTests 1 failed | 178 passed (179). The failing case is the one named "narrows vendor-objectstack to the entry's static closure, so an import() stays lazy". The tool then restored the file: the blob equals HEAD's0e32a097d0f1andgit diff HEADis empty.Gates, on
8027e1e(the last commit)CI=true pnpm exec turbo run build --filter=@object-ui/console... --concurrency=2under the lock, exit 0,Tasks: 35 successful, 35 total. It ran on57a1066; the commit after it changes no bundler input.pnpm --filter @object-ui/console type-check: exit 0.pnpm exec vitest run --maxWorkers=2over 24 files, exit 0,Test Files 24 passed (24)/Tests 1114 passed (1114). The files are every test that reads the console Vite config or the gate module: 17 underscripts/__tests__, the 5 console tests that name the config,acceptInvitationLink.mountandcreate-plugin/templates.pnpm check:eager-closure: exit 0,Console eager closure is 3296.2 KB gzipped across 334 of 2457 chunks (budget: 3307.0 KB, headroom: 10.8 KB).pnpm check:eager-locale-catalogues0.pnpm check:docs-route-closure0.node scripts/check-changeset-presence.mjs0, withNo source or published contract of a released package changed in this rangeand the changeset declared as directed.node scripts/check-changeset-no-major.mjs0.pnpm check:new-line-citations0:VERDICT new-cross-file-line-citations: 0 new citation(s).pnpm check:control-bytes0.pnpm check:changeset-claims0.pnpm check:pending-changeset-literals0.pnpm check:test-path-roots0.pnpm exec eslinton the three touched source files: exit 0, no output.node scripts/check-governed-queue-guard.mjs --teston the four paths:NOT GOVERNED.Not in this PR
plugin-chatbot's renderer is objectui#11798's open question under the objectui#6795 ruling.MarkdownContent's co-tenancy inui-componentsis objectui#5325's Option A, which that card's ruling C keeps on hold. It is noted here and not changed.Session:
https://claude.ai/code/session_01MgfduSkFrfM3eorB3UGfAUGenerated by Claude Code