Repository navigation
feat(console): the build writes dist/sdui.manifest.json from the one generator (objectui#11403) - #11409
Conversation
…generator (objectui#11403) `pnpm build` runs a `build:sdui-manifest` step after `vite build`. It loads the console's registry eagerly under Node (`dev/manifest-registry.ts`, now the one registration list `dev/manifest-dump.tsx` also loads), calls `buildArtifacts` from `packages/sdui-parser/scripts/gen-manifest.ts` into a scratch directory, and copies only `sdui.manifest.json` into `dist`. A host serving this Console registers that file under SDUI_MANIFEST_SERVICE; before this the build wrote none, so the save door judged no page save on a host serving `dist` as-is. Claude-Session: https://claude.ai/code/session_01JG2jy8a9su7ia4Hx7zxv42 Co-authored-by: Claude <noreply@anthropic.com>
…o-console allows (objectui#11403) Claude-Session: https://claude.ai/code/session_01JG2jy8a9su7ia4Hx7zxv42 Co-authored-by: Claude <noreply@anthropic.com>
|
changeset-claim-re-read
|
✅ 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
|
…ger adjudicates the manifest pin, and the deployment guide names the manifest (objectui#11403) - scripts/__tests__/helpers/build-program.ts: `collectSegment` sends a `tsx SCRIPT` segment down the `node SCRIPT` branch (the script and what it imports), so `buildProgramFor()` no longer throws on the console's `build:sdui-manifest` step. - scripts/markdown-test-inputs.mjs: ADJUDICATED entry for `apps/console/src/__tests__/sdui-manifest-build-11403.test.ts`, `reads: []`: it walks generated output in a temp directory, and its `sdui-blocks.md` literal names a file the generator writes. - content/docs/guide/deployment.md: `pnpm build` also writes `dist/sdui.manifest.json`, which a host registers under `SDUI_MANIFEST_SERVICE`; a direct `vite build` does not produce it. Claude-Session: https://claude.ai/code/session_01JG2jy8a9su7ia4Hx7zxv42 Co-authored-by: Claude <noreply@anthropic.com>
…'s manifest step reads (objectui#11403) `apps/console`'s `build:sdui-manifest` step runs `tsx scripts/build-sdui-manifest.ts`, which imports `packages/sdui-parser/scripts/gen-manifest.ts` by relative path, and that file imports `../src/index.js` (the sdui-parser sources, not its dist). `$TURBO_DEFAULT$` hashes only the console's own directory, so these ten files are named as `$TURBO_ROOT$` build inputs: exactly the out-of-package set the build-inputs guard derives for the console once its walker resolves the `.js` specifier to the `.ts` source. Claude-Session: https://claude.ai/code/session_01JG2jy8a9su7ia4Hx7zxv42 Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JG2jy8a9su7ia4Hx7zxv42 Co-authored-by: Claude <noreply@anthropic.com>
✅ 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
|
✅ 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
|
…tion (objectui#11403) The build-inputs guard's walker threw at packages/sdui-parser's gen-manifest.ts, whose `'../src/index.js'` is the TypeScript ESM spelling of src/index.ts that tsc and tsx both resolve. resolveRelative now tries, after every existing candidate, the TypeScript source of an emitted extension: .js gives .ts then .tsx, .jsx gives .tsx, .mjs gives .mts and .cjs gives .cts. The sibling walker in side-effects-declaration-consistency.test.ts applies the same rule. No specifier that resolved before changes target, and one that resolves to nothing still throws. turbo-build-inputs.test.ts's docblock list of classified tools gains tsx. Claude-Session: https://claude.ai/code/session_01JG2jy8a9su7ia4Hx7zxv42 Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JG2jy8a9su7ia4Hx7zxv42 Co-authored-by: Claude <noreply@anthropic.com>
✅ 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
|
Fixes #11403
Clause-②: yes
What
pnpm --filter @object-ui/console buildnow ends withdist/sdui.manifest.json, the SDUI component manifest of the registry this build ships. A host serving thisdistregisters it under@objectstack/metadata-protocol'sSDUI_MANIFEST_SERVICE, and the metadata save door judges page sources against it (ADR-0080 §5). Before this PR the build wrote no such file.apps/console/package.json:buildis nowtsc && vite build && pnpm build:sdui-manifest && pnpm build:plugin. The newbuild:sdui-manifestrunstsx scripts/build-sdui-manifest.ts.apps/console/scripts/build-sdui-manifest.ts, the Node entry: it refuses whendist/index.htmlis absent, registers a resolve hook that maps a.cssside-effect import to an empty module (the dist of plugin-dashboard and plugin-map imports CSS), imports the registry, then callsemitSduiManifest(dist).apps/console/scripts/emit-sdui-manifest.ts: callsbuildArtifactsfrompackages/sdui-parser/scripts/gen-manifest.tsinto a scratch directory and copies onlysdui.manifest.jsonout. It serializes nothing itself, so there is no second generator.gen-manifest.tsis unchanged.apps/console/dev/manifest-registry.ts: the registration graph (src/register-plugins.tsplus the 16 eager registry imports), moved verbatim out ofdev/manifest-dump.tsx, which now imports it. The dump page and the build read one list.apps/console/tsconfig.json:includegainsscripts, sotsc(the build's first step, andtype-check) checks both scripts.apps/console/src/__tests__/sdui-manifest-build-11403.test.ts.src/register-plugins.ts: a comment only, naming the new importer.README.md: one paragraph.5943212486(claim amendments5943233612and5943628606):scripts/__tests__/helpers/build-program.tsclassifiestsx SCRIPTthrough itsnode SCRIPTbranch;scripts/__tests__/helpers/config-program.ts'sresolveRelativetries, after every existing candidate, the TypeScript source of an emitted extension (.jsto.tsthen.tsx,.jsxto.tsx,.mjsto.mts,.cjsto.cts);turbo.jsonbuild.inputsgains the 10packages/sdui-parserfiles the guard derives;scripts/markdown-test-inputs.mjsadjudicates the new pin, andcontent/docs/guide/deployment.mdgains one paragraph;scripts/__tests__/turbo-build-inputs.test.ts's docblock liststsx.Why the tarball gains one file, not three
buildArtifactswritessdui.manifest.json,sdui-intrinsics.d.tsandsdui-blocks.md. The card names the manifest alone, anddistis published (files). So the other two are written to a scratch directory and discarded. Measured:npm pack --dry-runinapps/consolelistsdist/sdui.manifest.jsonand nosdui-intrinsicsorsdui-blocksfile.The other shape the order allowed, a manifest-only export split out of
gen-manifest.ts, was not taken. It edits a second package for no difference in output, while the scratch-directory copy leaves the generator untouched.Measurements (the order's Zone 2)
tsc && vite build && pnpm build:plugin, read from7728c67c) run on this tree, whosevite.config.ts,public/andindex.htmlequal main's: exit 0, anddistholds nosdui.manifest.json.buildArtifactsitself writes three, which is the pin's control.gen-manifest.tshas no runner, and before this PRbuildArtifactshad zero callers (git grep buildArtifactshits only its definition). The prerequisite was met only by thedev/manifest-dump.tsxpage (which callsmanifestFromConfigsdirectly) and by the framework'sscripts/gen-sdui-manifest-node.mjs(plain Node over a built tree, with the same.csshook). This PR meets it the second way, through the dump page's own registry module.buildArtifactsrun over the same registry.dist) iscmp-identical to the manifestdev/manifest-dump.tsxcomputes under vitest (vite-transformedsrc, happy-dom) on the same tree. One-time measurement, not kept as a test.isUsableSduiManifest, read in the sister checkout (objectstackfbcc05f,packages/metadata-protocol/src/runtime-authoring-gate.ts), isisRec(value) && isRec(value.components), withisRecmeaning non-null, an object and not an array. The file has one top key,components, a record of 107 entries (59 with no tier stamp, 48 stampedhtml).@objectstack/metadata-protocolis not installed in objectui, so the pin restatesisRec.vercel.json, theci.ymlE2E build andlive-e2e.ymlrunvite builddirectly and never reach the step.performance-budget.ymlrunspnpm --filter @object-ui/console buildafter building./packages/*, so CI exercises the step there. turbo:dist/**is already in the outputs. The rootturbo.jsonbuild.inputsnow namespackages/sdui-parser/scripts/gen-manifest.tsand the 9packages/sdui-parser/src/*.tsfiles the build-inputs guard derives, so the console build's cache key carries them directly, beside the@object-ui/sdui-parser#buildhash it already inherits throughdependsOn: ["^build"](the patch rounds after review5943212486).build:pluginis unaffected (it runs last).Gates
Round 3, head
fe8d29f1:207 passed;scripts/__tests__project in full: 177 passed and 2 skipped of 179 files (5427 tests);dist/sdui.manifest.json(107 components);The rows below are from the first round, at
36fc04de, unless a row says otherwise.pnpm --filter @object-ui/console build, thentest -f dist/sdui.manifest.json, the new pin andpnpm --filter @object-ui/console type-check, in one run:VERDICT command-exit 0. The pin:Tests 4 passed (4).--listFilesshows all 4 new TS files inside the type-check program.--shard=1/2and--shard=2/2:70 passed (70)files /949 passed, and70 passed (70)/663 passed. Run at3d5109b9. The only later change swaps aconsole.logforprocess.stdout.writeinscripts/build-sdui-manifest.ts, which no test imports.packages/sdui-parser/tests:20 passed (20)/289 passed, at3d5109b9(the package is untouched).eslint --no-inline-config --format jsonover the 6 changed TS/TSX files gives 6 files linted, 0 errors and 0 warnings. Population: the rooteslint.config.js**/*.{ts,tsx}blocks. Invariance: lint is not type-aware (noparserOptionsorprojectServicein the config), and none ofeslint-rules/*.jsreads the disk, so this diff cannot move the verdict on an untouched file.check:control-bytes,check:new-line-citations(0 new citation(s)),check:test-path-roots,check:phantom-deps,check:unused-deps,check:unreferenced-sources,check:esm-specifiers,check:entry-guard,check:shell-escape-residue,check:changeset-claims,check:pending-changeset-literals,changeset:check,check:sdui-registration-pins, andnode scripts/check-changeset-presence.mjs(1 changeset declared).check-governed-queue-guard.mjs --testover the 10 changed paths:NOT GOVERNED.check:published-dist(full build): NOT MEASURED. It is a whole-repo build, a release-time gate that does not fit the foreground cap. A--no-buildrun read 39 packages and found 0 tooling artifacts. Its 4no-build-outputfindings are packages unbuilt in this container (cli, create-plugin, plugin-ai, runner), unrelated to this diff. Its criterion (*.test.*emits,*.tsbuildinfo) cannot matchsdui.manifest.json.check:readme-exports: not applicable. Its population is the READMEs underpackages/, andapps/console/README.mdis outside it. Locally the run also stops at its prerequisite (cli and plugin-ai unbuilt).Reverse verification
One-time, on the committed fix, each mutation landed through objectstack's
scripts/ablation-replace.mjs(anchor hit, blob changed), and each restore was proven (blob equals HEAD,git diff HEADempty).&& pnpm build:sdui-manifestfrom the build script. The wiring fact turns red,expected -1 to be greater than 1:1 failed | 3 passed.emitSduiManifestalso copysdui-blocks.md. The one-file fact turns red:1 failed | 3 passed. The first attempt was a no-op: the replacement still contained the anchor, so the tool refused before any test ran. The retry used an anchor that the mutation removes.emitSduiManifestre-serialize the manifest itself (compact JSON), i.e. a second serializer. The byte-equality fact turns red:1 failed | 3 passed.Acceptance notes
dist, while the vite bundle readssrcthrough its aliases. The two agree whenever the packages are built from the same tree first: turbo's^build, every CI and release caller, and the framework'sbuild-console.sh. A direct console build over stale package dists would ship the stale dists' manifest. The console'stscstep already has the same precondition (it type-checks against the dist.d.ts). The script header and the README say so.scripts/build-console.shcopies the tracked repo-rootsdui.manifest.jsonoverpackages/console/dist/sdui.manifest.jsonafter it copies thisdist. So@objectstack/consolekeeps shipping the tracked copy, which describes the framework's pin, i.e. the pin it builds.scripts/gen-sdui-manifest-node.mjsandscripts/sdui-manifest.record.jsonthere name the module order as that ofapps/console/dev/manifest-dump.tsx. The list now lives indev/manifest-registry.ts, which the dump page imports. No gate reads that prose.scripts/dump-public-manifest.mjshere still says the framework'sbuild-console.shwires it. The framework retired that path. Stale prose, left untouched (carrier: none).content/docs/guide/deployment.md, which enumeratesapps/console/dist/(review5943212486③6). The package README is updated too.dist/stats.html(the bundle visualizer, about 5 MB) and the two vite build reports..objectui-shamove, belongs to the cloud seat./opt/nvm/versions/node. This container's default v22.22.0 is refused by objectui's engine-strict install.Generated by Claude Code