Skip to content

fix(cli): make the objectui init scaffold register components and load their styles (#4061, #4062) - #4067

Merged
yinlianghui merged 1 commit into
mainfrom
claude/issue-4061-init-scaffold-runtime
Aug 10, 2026
Merged

fix(cli): make the objectui init scaffold register components and load their styles (#4061, #4062)#4067
yinlianghui merged 1 commit into
mainfrom
claude/issue-4061-init-scaffold-runtime

Conversation

@yinlianghui

Copy link
Copy Markdown
Collaborator

Fixes#4061
Fixes#4062

A sweep pair: both cards are one generated file each in packages/cli/src/commands/init.ts, and both are the same defect shape in opposite directions — the scaffold declared @object-ui/components and never imported it, and never loaded the stylesheet that package publishes. Both premises were re-verified against post-#4063origin/main (0a09793f2) before implementing; both still held, at the exact lines the issues cite.

Per-item checklist

#Landing pointBeforeAfter
1 (#4061)generated src/App.tsximport { SchemaRenderer } from '@object-ui/react'; was the only @object-ui/* importplus a side-effect import '@object-ui/components'; (and a two-line comment saying why it is not removable)
2 (#4062)generated src/index.css@import 'tailwindcss'; — the whole fileplus @import '@object-ui/components/style.css'; on the next line
3 (both)init.ts structureinit() wrote 11 files inline with fs, so no gate could judge themexported buildInitFiles(name, template) returns the file map; init() writes that map and nothing else
4 (both)app-generator.test.tsits two import/declaration gates ran over the two temp-app generators onlyboth gates now also judge the init file map, plus a generated init scaffold sources block
5 (both)cli-bin.test.tsinit assertions covered the manifest and the absent tailwind.config.jsthree cases through the real bin: registry import, stylesheet import, and map-equals-disk byte for byte

git diff --stat is 1:1 with that list — 4 files, nothing outside the declared surface. doctor.ts (in-flight #3891) is untouched, and so are app-generator.ts and scaffold-dependencies.ts.

Why item 3 was necessary

Both cards ask for app-generator.test.ts's two gates to be ported onto the init generator. Those gates take a file map as input; init() had no map — it called writeFileSync eleven times. buildInitFiles is the same split buildInitPackageJson already received in #3892, and the same shape app-generator.ts has exported as buildAppFiles all along. It is additive: nothing outside this file reads it except the tests.

The refactor is behaviour-preserving, and that is measured rather than argued. I scaffolded all three templates with the real bin built from origin/main, then again from this branch, and diffed the two trees:

diff -ru before/ after/ # 3 templates x 11 files

The entire diff is the two lines in the table above, repeated once per template. Every other byte of all 33 generated files — app.json, README.md, .gitignore, package.json, vite.config.ts, postcss.config.js, index.html, src/main.tsx, tsconfig.json — is identical, including the per-file Created ... console lines and their order.

Why components only, and no plugins

#4061 left this cell open. Measured rather than assumed: the distinct node types across all three templates are exactly button, card, div, input, text, textarea, and every one of them is registered by @object-ui/components itself (checked against that package's ComponentRegistry.register calls — 112 types). No template names a plugin type. The temp-app generator imports nine packages because it renders arbitrary user schemas; adding nine dependencies to a minimal scaffold that uses none of them is the declared-but-unused direction #3755 removed. A test pins the six-type set, so a future template that adds a plugin type fails here instead of silently rendering an error box.

Why the components stylesheet only, and not fields

#4062 left this cell open too. @object-ui/components declares "./style.css": "./dist/index.css" with dist in files — a real published export. @object-ui/fields is deliberately not imported: it is not a dependency of this scaffold, and per the published-tarball measurement on #4059@object-ui/fields@17.3.0 ships zero CSS, so copying quick-start's second line would point an import at an empty file from a package the manifest does not declare.

The gate is written as a rule, not a string pin: of the scaffold's runtime @object-ui/* dependencies, exactly those whose package.json declares a ./style.css export must be imported by the generated CSS. That is discriminating in both directions here — components exports one, react exports none.

Quote style: the scaffold's existing line is single-quoted (@import 'tailwindcss';) and an existing test pins that spelling, so the new line is single-quoted to match. CSS treats both identically; the issue title's double quotes carry no semantics.

Flagged for the maintainer, deliberately unchanged

#4062's second open cell is out of scope here and nothing about it was touched: the scaffold's Tailwind 4 pipeline is @tailwindcss/postcss + autoprefixer in postcss.config.js, while content/docs/guide/quick-start.md teaches @tailwindcss/vite. Both are valid Tailwind 4 setups and this fix is correct under either, but a new user reads the two pages as one story. Left for a ruling rather than guessed at.

Second observation, same character: the temp-app generator solves the styling problem a different way — it inlines the theme tokens into the CSS it generates (APP_THEME_TOKENS) instead of importing the published sheet. That is coherent for a temp app resolved against the workspace, and the import is the right answer for a scaffold that installs published packages, but the two generators now diverge on styling strategy by design rather than by accident. Noted, not changed.

Verification

Local, from the repo root:

  • pnpm exec vitest run packages/cli/ --maxWorkers=276 passed (76), 3 files. 12 of those cases are new.
  • pnpm --filter @object-ui/cli type-check — clean.
  • pnpm --filter @object-ui/cli lint — 0 errors. The 19 warnings are pre-existing and none is in init.ts; the two in cli-bin.test.ts (execFileSync, rmSync unused) predate this branch.
  • Control-byte self-scan over the three edited files — clean.

Reverse verification — direction predicted before running, then run: with both lines deleted from init.ts and the bundle rebuilt, 6 tests go red, and the split is itself informative.

Red, as predicted:

AssertionError: expected [ '@object-ui/components' ] to deeply equal []
packages/cli/src/__tests__/app-generator.test.ts:473
  • declares no versioned runtime dependency the generated sources never import — the ported gate, naming the package
  • imports the package registration is a side effect of
  • imports the published stylesheet of every declared dependency that ships one
  • keeps the Tailwind entry first, so the library sheet cannot precede it
  • writes a src/App.tsx that fills the component registry (real bin)
  • writes a src/index.css that loads the library stylesheet (real bin)

Green, also as predicted, and worth stating because it is the asymmetry the two gates have by construction: the other gate, declares every package the generated sources import, stays green — removing an import cannot create an undeclared one, so only the reverse-direction gate can catch this defect class. That is precisely why #4061 survived #3892, which extended the anchor table but not these gates. The two self-tests (reports @object-ui/components as unused when the registration import is removed, is missing that stylesheet when the pre-fix CSS is restored) also stay green by design — they plant the pre-fix text back themselves, so they are gates on the rule rather than on the fix.

What was NOT verified, stated plainly: no npm install of the published packages was run. CI does not install a scaffolded project against the registry, and neither did I. The evidence for #4062 is therefore structural and documentary — the export exists in packages/components/package.json, and #3884's matrix measured the prebuilt sheet as a strict superset (1410 rules vs the 1331 a node_modules scan reaches) of what a consumer can otherwise obtain. The claim that the missing import costs a specific rule count in a real install remains unmeasured, as #4062 itself notes.


Generated by Claude Code

…oad their styles (#4061, #4062)
The generated `src/App.tsx` imported only `SchemaRenderer` from
`@object-ui/react`, which does not depend on `@object-ui/components` —
and registration is a side effect of importing that package. Every node
of all three templates took `SchemaRenderer`'s miss path and rendered
"Unknown component type". The manifest already declared the package:
declared and never imported.
The generated `src/index.css` was a bare `@import 'tailwindcss';`, so the
theme utilities the templates lean on had no tokens behind them; the
`@theme` block that declares those tokens is not published, making the
prebuilt `@object-ui/components/style.css` the only source (#3884).
Both were invisible because this generator's SOURCES had never been
judged against its manifest — #3892 anchored its ranges, but
`app-generator.test.ts`'s two import/declaration gates ran only over the
two temp-app generators. `buildInitFiles` exposes the scaffold as a file
map (the shape `buildAppFiles` already had) and both gates now cover it,
each with a self-test that plants the defect back.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017Qqyix2QcnpUC9XeYVDzx3
@vercel

vercelBot commented Aug 10, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
ProjectDeploymentActionsUpdated (UTC)
objectuiIgnoredIgnoredAug 10, 2026 3:19am

Request Review

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Main entry (gzip)28.1 KB350 KB
Entry fileindex-DZ5g8zc6.js
StatusPASS

📦 Bundle Size Report

PackageSizeGzipped
app-shell (index.js)8.66KB3.13KB
app-shell (runtime-config.js)7.42KB2.32KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)7.57KB2.97KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)1.17KB0.53KB
auth (AuthProvider.js)22.10KB4.37KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.13KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.64KB2.21KB
auth (SocialSignInButtons.js)9.60KB3.89KB
auth (UserMenu.js)3.40KB1.22KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)35.76KB9.11KB
auth (createAuthenticatedFetch.js)4.37KB1.69KB
auth (index.js)2.35KB1.07KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)4.91KB0.87KB
auth (useIsWorkspaceAdmin.js)1.61KB0.85KB
collaboration (CommentThread.js)26.07KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.65KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)483.72KB106.71KB
core (index.js)3.04KB1.15KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)139.61KB35.99KB
fields (index.js)228.51KB56.69KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (i18n.js)4.32KB1.77KB
i18n (index.js)2.65KB1.06KB
i18n (pickLocalized.js)1.70KB0.83KB
i18n (provider.js)9.48KB3.27KB
i18n (useObjectLabel.js)27.59KB6.63KB
i18n (useSafeTranslation.js)4.52KB1.96KB
layout (index.js)38.84KB10.80KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.74KB
mobile (index.js)1.50KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.71KB0.42KB
mobile (useResponsiveConfig.js)1.36KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)8.75KB3.06KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)3.67KB1.12KB
permissions (evaluator.js)4.41KB1.44KB
permissions (index.js)0.91KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.52KB
permissions (usePermissions.js)1.55KB0.71KB
plugin-ai (index.js)15.71KB3.79KB
plugin-calendar (index.js)45.23KB12.45KB
plugin-charts (index.js)61.49KB17.48KB
plugin-chatbot (index.js)180.33KB42.79KB
plugin-dashboard (index.js)118.50KB30.66KB
plugin-designer (index.js)210.51KB42.51KB
plugin-detail (index.js)237.80KB59.48KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)113.37KB27.40KB
plugin-gantt (index.js)162.79KB39.67KB
plugin-grid (index.js)187.97KB49.79KB
plugin-kanban (index.js)48.53KB13.38KB
plugin-list (index.js)109.73KB26.55KB
plugin-map (index.js)17.00KB5.32KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)40.58KB10.58KB
plugin-timeline (index.js)26.21KB7.52KB
plugin-tree (index.js)8.50KB2.88KB
plugin-view (index.js)84.03KB20.55KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.71KB3.53KB
providers (index.js)0.44KB0.22KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.67KB2.37KB
react (LazyPluginLoader.js)3.77KB1.33KB
react (SchemaRenderer.js)23.71KB7.95KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)1.23KB0.66KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)4.09KB1.74KB
sdui-parser (index.js)4.47KB2.03KB
sdui-parser (parse.js)10.04KB2.82KB
sdui-parser (types.js)0.29KB0.24KB
sdui-parser (validate.js)4.69KB1.48KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)0.20KB0.18KB
types (crud.js)0.20KB0.18KB
types (data-display.js)0.20KB0.18KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.87KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-retry.js)4.32KB2.02KB
types (index.js)2.71KB1.34KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)0.20KB0.18KB
types (ui-action.js)3.40KB1.71KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment