Skip to content

fix(cli): objectui doctor diagnoses Tailwind 4 instead of Tailwind 3 - #4064

Merged
yinlianghui merged 2 commits into
mainfrom
claude/issue-3891-doctor-tailwind4
Aug 10, 2026
Merged

fix(cli): objectui doctor diagnoses Tailwind 4 instead of Tailwind 3#4064
yinlianghui merged 2 commits into
mainfrom
claude/issue-3891-doctor-tailwind4

Conversation

@yinlianghui

Copy link
Copy Markdown
Collaborator

Fixes#3891

The Tailwind section of objectui doctor was written against v3 and got every question backwards on a v4 project — which is every project this repo ships. Per the triage direction note of 2026-08-09, this is the v4-only rewrite; no v3-tolerant dual path was added (see "Scope" below).

What was wrong, measured both ways

All three anchors in the card re-verified on origin/main before implementing. Evidence below is real output from the actual origin/maindoctor.ts (extracted with git show origin/main:packages/cli/src/commands/doctor.ts and executed as ESM — it carries no type annotations) versus the built new CLI, run in real directories of this repo.

directorybefore (origin/main)after
examples/console-starter — a correct v4 app⚠️ tailwind.config.js not foundFound 1 issue(s)Everything looks good! ✨
apps/console — carries an inert v3 config✓ tailwind.config.js found / ✓ Tailwind content paths configuredEverything looks good! ✨one finding: the config is inert, and what to do about it
repo root⚠️ tailwind.config.js not foundFound 1 issue(s)false positive gone; one genuine finding (below)

1. A missing tailwind.config.js counted as an issue. In v4 that file is not part of the setup: the engine reads CSS-first configuration (@import 'tailwindcss', @theme, @source) and only loads a JS config when a stylesheet opts in with @config. The command reported a problem that did not exist and pushed the reader toward creating a file Tailwind never reads.

2. That file was then graded on its content array, the v3 key @source replaced. The two tailwind.config.* files still tracked here are exactly that trap: apps/console and examples/byo-backend-console both declare a content array, and @config has zero occurrences across every .css file in the repo — so both files are inert, and the old check answered ✓ Tailwind content paths configured for them. A false green on a dead file, which is worse than silence.

3. @tailwindcss/postcss was never checked — the one dependency a v4 build cannot start without. v4 moved the PostCSS plugin out of tailwindcss into that package, and naming the old tailwindcss key in a PostCSS config resolves to a shim whose only job is to throw. That is the failure form #3852 measured on the generated app, and doctor printed ✓ Tailwind CSS installed straight through it.

What the checks are now

The v4 contract, matching what objectui init scaffolds (utils/app-generator.ts, read-only here):

  • @tailwindcss/postcss declared in package.json, or installed in a node_modules at the project or an ancestor.
  • The PostCSS config, when one exists, names @tailwindcss/postcss and not the v3 tailwindcss key. The v3 probe is evaluated independently of the v4 one, so a half-finished migration listing both is still named.
  • A CSS entry runs @import 'tailwindcss'; @source is acknowledged when present, and the v3 @tailwind base/components/utilities directives are called out as migration debt.
  • The declared tailwindcss major is read, so a v3 range no longer passes as ✓ installed. An unparseable range (workspace:*, catalog:) is never reported as wrong — "cannot tell" is not "wrong".

Two deliberate silences, because #3891 is about doctor asserting things it cannot see. A missingtailwind.config.* produces no finding of any level; only a present one does, and only when nothing opts into it via @config. And when no recognised CSS entry exists at all (a monorepo root, a bespoke layout), the CSS verdicts are skipped rather than guessed.

Scope

A v3-tolerant dual path — branching on the declared major and running two sets of checks — was not built, per the triage direction: it widens the product surface past this repo's v4-only posture. v3 spellings are diagnosed as migration debt, not supported as a second mode.

Region-exclusive as declared on the card: only packages/cli/src/commands/doctor.ts plus a new doctor-specific test file and a changeset. init.ts, app-generator.ts and app-generator.test.ts are untouched (#3892's surface). origin/main merged in before opening; the full packages/cli suite was re-run after the merge.

Structure

runDiagnostics(cwd) now returns structured findings carrying a stable id, and doctor() only renders and counts them. That split is what makes the matrix testable against real fixture directories instead of scraped console output, and the tests pin verdicts by id so wording can improve without the coverage evaporating.

One implementation note worth recording. The "plugin is installed" probe is a manual node_modules walk, deliberately notcreateRequire(...).resolve(). Node's resolver also consults NODE_PATH, and that is not hypothetical here: vitest sets NODE_PATH to pnpm's virtual store, so under test every package in the monorepo resolved from any directory — including a fresh os.tmpdir() fixture with an empty package.json. That made the "plugin is missing" branch unreachable in tests and would have shipped it unverified. It was caught by the branch going green when it should have been red, and the walk depends on nothing but the filesystem.

Verification

pnpm exec vitest run packages/cli/ — 4 files, 84 tests passed (21 of them new in doctor.test.ts), re-run after merging origin/main.

pnpm --filter @object-ui/cli type-check — clean. pnpm --filter @object-ui/cli lint0 errors; the 15 warnings are all pre-existing, in dev.ts / app-generator.ts / spec-vocabulary-hint.ts, none in doctor.ts or doctor.test.ts.

Reverse verification (direction predicted before running: red). Re-introducing just the deleted branch — a warn when tailwind.config.* is absent — turned 6 of 21 tests red, including the two that pin the defect directly: does NOT mention tailwind.config at all when the file is absent and reports zero issues for the shape objectui init generates. Restored and re-confirmed green.

Out-of-scope finding

Running the fixed doctor at the repo root surfaced a real, pre-existing gap, filed separately and not fixed here: the root postcss.config.mjs names @tailwindcss/postcss and autoprefixer as plugins, but the root package.json declares neither @tailwindcss/postcss nor postcss, and neither resolves from the root. See the linked issue.


Generated by Claude Code

…问题 (#3891)
doctor 的 Tailwind 段是按 v3 写的,在 v4 项目上三个判断全反:把「没有
tailwind.config.js」记成一条 issue(v4 根本不读这个文件,除非样式表用
@config 显式接入);对存在的该文件按 v3 的 content 数组打分(v4 已用
@source 取代),于是给两个死文件回了「✓ content paths configured」的假绿;
而 v4 真正跑不起来的依赖 @tailwindcss/postcss 一次都没查过。
现在检查的是 v4 契约,和 objectui init 生成物一致:@tailwindcss/postcss
已声明或已安装、PostCSS 配置写的是它而不是 v3 的 tailwindcss 键、CSS 入口
跑 @import 'tailwindcss'。缺 tailwind.config.* 不再产生任何一级的结论;
存在且无人 @config 接入时才报「它是死的」。识别不到 CSS 入口(monorepo
根、非常规布局)时整段跳过,不猜。
runDiagnostics(cwd) 返回带稳定 id 的结构化结论,doctor() 只负责渲染和计数
—— 测试因此能按 id 钉住判定,而不是去刮 console 输出。
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 2:54am

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

Labels

Projects

None yet

2 participants

@yinlianghui@claude