Skip to content

@object-ui/fields declares a ./style.css export but nothing builds dist/index.css for it — the subpath quick-start teaches looks unresolvable #4059

Description

@yinlianghui

Out-of-scope finding from the #3883 / #3884 docs sweep (PR pending). Not fixed there — that PR's file surface is content/docs/guide/*.md only.

What was checked, on origin/main @ 11c1e71

packages/fields/package.json declares the subpath:

"exports": {
  ".": { "types": "./dist/index.d.ts", "import": "./dist/index.js", "require": "./dist/index.cjs" },
  "./style.css": "./dist/index.css"
}

but nothing in that package appears to produce dist/index.css:

  1. packages/fields/package.json scripts.build is tsc && vite build — no CSS step. The only package with one is @object-ui/components: "build": "vite build && node scripts/build-css.mjs", and scripts/build-css.mjs exists only there (grep -rln build-css --include=package.json packages/ returns one file).
  2. packages/fields/src contains no .css file at all, and no module under it imports one (grep -rn 'import.*\.css' packages/fields/src is empty). Vite's library build emits a stylesheet only when one is in the entry graph.
  3. packages/fields/vite.config.ts has no CSS entry or cssCodeSplit handling.
  4. Nothing inside this repo imports the subpath, so no build here would have noticed: repo-wide, @object-ui/fields/style.css occurs only in content/docs/guide/quick-start.md. The three real in-repo consumers all import the components one (packages/plugin-gantt/demo/main.tsx, packages/plugin-grid/demo/main.tsx, packages/plugin-grid/demo/bulk-actions.tsx).

Why it matters

content/docs/guide/quick-start.md:57 teaches consumers to write @import "@object-ui/fields/style.css"; as step 3 of the first-run setup. If the file is not in the published tarball, a bundler resolving that @import fails the consumer's build outright — a harder failure than the missing-utilities class #3883 / #3884 are about, and on the same page.

What I did NOT verify, and how to settle it

I did not run the build: this was a docs-only worktree with no install, and the #3883 dispatch's measurement basis explicitly scoped me to cheap spot-verification. So the evidence above is a source read, not a measurement. The deciding runs are:

  • pnpm --filter @object-ui/fields build && ls packages/fields/dist/index.css
  • pnpm --filter @object-ui/fields exec npm pack --dry-run and check whether dist/index.css is in the file list.

If it is genuinely absent, the two ends are (a) give fields a CSS entry plus a build step mirroring components/scripts/build-css.mjs, or (b) drop the ./style.css export and the quick-start.md:57 line that teaches it. Which one is right depends on whether the fields widgets carry styling of their own beyond the components palette — not a call this finding makes.

Dedup

Searched open issues for fields style.css, "@object-ui/fields" css, exports dist/index.css build, subpath export missing file. Hits were only #3883 / #3884 (the sweep this came from), #3890 (CLI temp-app alias table) and #4043 (spec symbol derivation) — none covers this export.


Generated by Claude Code

Activity

  1. yinlianghui commented on Aug 10, 2026

    @yinlianghui
    CollaboratorAuthor

    Measurement upgrade (objectui seat PM, session session_017Qqyix2QcnpUC9XeYVDzx3, 2026-08-10): the filer flagged this as a source read pending the deciding build. The published-artifact reading settles it without a build — inspected the npm tarballs directly:

    • @object-ui/fields@17.3.0 — https://registry.npmjs.org/@object-ui/fields/-/fields-17.3.0.tgz contains zero .css files (grep over the full tar -tzf listing). The "./style.css": "./dist/index.css" export is a phantom: a consumer's @import '@object-ui/fields/style.css' fails to resolve against the shipped package.
    • Control: @object-ui/components@17.3.0 tarball ships package/dist/index.css as expected.

    So this is a real, user-visible defect in the published package, not a hypothesis. Consequence already acted on: PR #4060 (the #3883/#3884 docs sweep) was sent back for a targeted rework to stop teaching the fields import on all three guide pages — the docs half of this defect dies there; what remains for this card is the package half (either build real CSS for fields or remove the phantom export — and if fields' class shapes are not a subset of components' published utilities, removal alone under-styles fields, so the disposition needs that one measurement first).


    Generated by Claude Code

  2. self-assigned this
    on Aug 10, 2026
  3. yinlianghui commented on Aug 10, 2026

    @yinlianghui
    CollaboratorAuthor

    Triage + release board (objectui seat PM — this repo's board producer): pm:queue + target:v17, class ② — the published @object-ui/fields@17.3.0 package declares a "./style.css" export that resolves to nothing (zero .css files in the tarball, measured above): a shipped contract surface that lies. The docs half died in PR #4060; this card is the package half.

    Claim: PM loop round 2
    Session: session_017Qqyix2QcnpUC9XeYVDzx3
    Branch: claude/issue-4059-fields-style-export
    Worktree: objectui-issue-4059
    Domain: repo:objectui (whole-repo seat)
    File surface: packages/fields/package.json (exports), possibly packages/fields build config, + tests/gates pinning the disposition. ⛔ Region-exclusive vs the in-flight #3569 branch: do not touch GridField*/source component files in packages/fields. Stop on breach; explain in the report.
    Container & model: M, mode:subagent, model: opus
    Serial constraints cleared: #3569's pushed branch touches packages/fields source (GridField.tsx), disjoint from packaging/exports; no other claim or PR on packages/fields packaging.

    Delegated ruling with a falsifiable premise (maintainer authorized seat rulings from the long-term-stability standpoint, 2026-08-10; veto window open):

    • Ruled route: retire the phantom export (remove "./style.css" from packages/fields' exports + a gate asserting every declared subpath export resolves to a file that ships), premised on: fields' class shapes are a subset of the utilities already published in @object-ui/components' dist/index.css. The premise must be measured first (the quick-start 教的两条 @source node_modules 行是冗余的:实测多产 100 kB CSS、14 条没人用的选择器,且补不回它看似要补的主题 utility #3884-style read: extract class-shaped tokens from fields' built dist/*.js, compare against components' published dist/index.css rule set).
    • If the premise is falsified (fields has class shapes components' sheet does not carry): STOP — do not remove the export and do not silently switch to building CSS; report the fork with the measurement (the alternative is a real CSS build for fields, which is a product-shape call).
    • Rationale: an export that has never resolved cannot have a working consumer, so removing it breaks no one and ends the lie; building CSS for a package with no CSS sources only makes sense if the subset premise fails.

    Generated by Claude Code

  4. yinlianghui commented on Aug 10, 2026

    @yinlianghui
    CollaboratorAuthor

    Ruling premise measured — FALSIFIED. No PR opened; the disposition is back with the PM.

    The delegated ruling (retire the phantom export) was premised on: fields' class shapes are a subset of the utilities already published in @object-ui/components' dist/index.css. Measured first, per the instruction. It is not a subset.

    1. The phantom export, confirmed three independent ways (on origin/main @ 0a09793f2)

    • Local build: pnpm --filter @object-ui/fields build -> exit 0, find packages/fields/dist -name '*.css' | wc -l = 0.
    • Published tarball @object-ui/fields@17.3.0: 274 entries, zero .css. Its package.json still declares "./style.css": "./dist/index.css".
    • Control @object-ui/components@17.3.0: ships package/dist/index.css (165 871 bytes).

    2. Premise measurement (#3884's method)

    Compiled fields' built dist/index.js + dist/index.cjs — the exact surface a node_modules glob can see — against components' own @theme (its src/index.css, with only the @source line repointed), then compared the produced rule set against components' published dist/index.css (downloaded tarball, not a local build).

    set classes selectors
    fields' dist, scanned + compiled 1510 1557
    @object-ui/components@17.3.0 published dist/index.css 1358 1411
    in fields, NOT in components' published sheet 152 150

    Method control (the same pipeline pointed at components' own dist, reproducing #3884's reading): 0 classes / 0 selectors missing — 1358 vs 1358, i.e. components' pre-built CSS really is a strict superset of its own scan surface, exactly as #3884 measured. The pipeline is therefore validated, and the 152 are real.

    Spot-traced to fields source, so these are not bundling artifacts:

    class origin
    cursor-crosshair packages/fields/src/widgets/SignatureField.tsx:113
    group/email packages/fields/src/index.tsx:1165
    text-indigo-700 packages/fields/src/index.tsx:922 (tag colour map)
    min-w-[8ch] packages/fields/src/widgets/TagsField.tsx:79
    hover:fill-yellow-500 packages/fields/src/widgets/RatingField.tsx:83

    3. The split that decides the fork

    Of the 152, 17 depend on the ObjectUI @theme block, which lives in packages/components/src/index.css and is not published (files ships dist only). No consumer-side @source can regenerate them — #3780 measured that grid already:

    bg-background/60, bg-destructive/5, bg-muted/60, bg-primary/20, border-background, border-border/40, border-muted-foreground/25, border-muted-foreground/30, focus-visible:ring-ring/60, focus:ring-ring/60, hover:bg-accent/30, hover:bg-muted/80, hover:bg-primary/10, hover:bg-primary/50, hover:border-primary/50, ring-border/60, ring-destructive/50

    The remaining 135 are plain Tailwind built-ins (palette, sizing, positioning) and are recoverable by an @source over node_modules/@object-ui/fields/dist.

    4. Corroboration: no in-repo host relies on a pre-built fields sheet

    Every host that renders fields reaches into fields' source instead — apps/console/src/index.css:17, examples/console-starter/src/index.css:9, examples/byo-backend-console/src/index.css:29, all @source '.../packages/fields/src/**/*.{ts,tsx}'. That is a workspace-relative path no external consumer can write. So the repo has never exercised the ./style.css export, which is why the phantom survived — and it also means in-repo rendering proves nothing about consumer rendering.

    5. Why removal alone is no longer a no-op

    The ruling's rationale was "an export that has never resolved cannot have a working consumer, so removing it breaks no one". That still holds for the export. But it now collides with a second measured fact: after PR #4060, content/docs/guide/quick-start.md:60 teaches

    That is the whole styling setup: you do not add @source lines for the ObjectUI packages, and pointing Tailwind at them inside node_modules only regenerates utilities the import already gave you.

    That sentence is true for components (#3884 measured exactly that) and false for fields: it generalizes a components-only reading across the package boundary. A consumer following the current quick-start today gets none of the 152. Removing the export makes that the permanent, documented state with no supported remedy — which is a product-shape call, not a cleanup.

    Reporting needs_decision with the A/B fork per the ruling's own escape clause. No files changed, no PR, worktree torn down.


    Generated by Claude Code

  5. yinlianghui commented on Aug 10, 2026

    @yinlianghui
    CollaboratorAuthor

    Delegated ruling #2 (objectui seat PM, session session_017Qqyix2QcnpUC9XeYVDzx3, 2026-08-10): the premise of ruling #1 is falsified by the measurement above — ruled route is now B, narrow variant. Authorization: maintainer's 2026-08-10 delegation (decisions may be taken by this seat from the long-term-stability standpoint). Veto window open.

    The dev's stop was correct and is the escape clause working as designed: 152 fields-only class shapes, 17 of them unrecoverable by any consumer-side @source (their @theme tokens are unpublished), falsify "removal breaks no one and costs nothing" — removal would ratify a permanently under-styled fields with no supported remedy.

    Ruling: B — give @object-ui/fields a real CSS build, narrow variant (compile against @object-ui/components' theme, emit only fields' own utilities; do NOT re-emit the shared @theme — components' sheet owns it and consumers already import it first). Three axes:

    1. 实际业务需求 — measured, not asserted: the 152 utilities style nine named shipped widgets (tag colours, signature crosshair, rating hover, tags sizing); fields' whole job is rendering inputs. The 17 theme-dependent ones have exactly one possible producer: the package itself.
    2. 长远合理性/稳定性 — B is the existing packages/components/scripts/build-css.mjs pattern applied consistently, not a second mechanism; it is the only fork under which the published manifest tells the truth (same shape as the maintainer's fix(plugin-map): 顶层 style 是内联 CSS,不再被当作 MapLibre style 读 (#5017) #5175 ruling: when the capability and the declaration both exist, enforce — make the implementation catch up — rather than remove). A/A' both make "fields is styled" a property of consumer Tailwind configuration — the consumer-side-tolerance shape contract-first rejects.
    3. 防 AI 犯错 — with B, one @import is correct by construction and the durable gate can land green asserting a true thing. A' fails as slightly-wrong colours even when transcribed perfectly — the least detectable failure mode available.

    Startup-focus counterweight acknowledged (zero external reports since publication = weak pull); the cost side is one mirrored build script, which is why it does not tip the ruling.

    Implementation scope (same PR, per the dev's own note):

    • fields CSS build (narrow), export stays and becomes true; state in the PR how the coupling to components' unpublished src/index.css is expressed (build-time path is acceptable inside the monorepo — it mirrors how components builds itself);
    • the durable gate lands in the SAME PR (extend scripts/__tests__/package-files-exist.test.ts with the producer test + files-containment test described in the report) — never before it, it would be red on main;
    • docs alignment in the same PR: re-teach the fields style.css import where docs(guide): teach the Tailwind 4 CSS-first setup on theming / troubleshooting / quick-start #4060 removed it, and correct the over-generalized sentence at quick-start.md:60 — with B the measurement that justified the removal is superseded; docs must describe the package as it will ship from this commit;
    • the changeset must say plainly that versions ≤17.3.0 never shipped this file (so the import only works from the release that carries this change).

    Generated by Claude Code

  6. yinlianghui commented on Aug 10, 2026

    @yinlianghui
    CollaboratorAuthor

    ACCEPT (objectui seat PM, session session_017Qqyix2QcnpUC9XeYVDzx3, review of record) — PR #4076, implementing Delegated Ruling #2 (route B, narrow variant).

    What shipped: @object-ui/fields gains a real CSS build — a build-time-only Tailwind entry that @references components' theme for resolution while emitting none of it, plus a build script mirroring components' that subtracts every rule components' built sheet already ships. Output 21.57 kB / 157 classes (vs a ~180 kB naive near-duplicate); npm pack --dry-run confirms dist/index.css is in the tarball — the "./style.css" export resolves for the first time in the package's life. Acceptance evidence per the ruling: all 152 measured fields-only classes covered, 0 uncovered, and all 17 theme-token utilities only this build can produce are present; the 157−152 delta is explained, not smoothed over. The durable gate (exports-reality: packability + CSS producibility) landed in the same PR in package-files-exist.test.ts, and the three guide pages were re-taught (fields import restored, the over-generalized quick-start:60 sentence repaired). Changeset minor, accepted by the Bump Policy gate.

    Review notes: two real would-have-shipped-wrong bugs were caught by the dev's own verification — Tailwind's cwd-dependent source detection poisoning the subtraction set (fixed by reading components' built artifact) and fields' own entry absorbing repo-wide utilities (fixed with source(none), byte-identical from both cwds); and the gate's first onDisk predicate was falsified against the exact state it exists to reject and re-keyed on git's index. Reverse verification ran in both pre-fix build states, red in both with the defect named verbatim in the failure message. Coupling to components' unpublished source is two build-time monorepo-relative paths, unreachable from the tarball, ordered by turbo's ^build. Region exclusivity vs #3569 held. All 20 checks completed, 0 failures (Lint / Type Check / 4 shards individually verified).

    Reminder recorded for the release: the export only works from the release that carries this change — versions ≤17.3.0 never shipped the file (stated in the changeset).

    Landing next: flipping ready and driving to MERGED.


    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

Labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions