Skip to content
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
This repository was archived by the owner on Nov 4, 2026. It is now read-only.

发布态消费者渲染插件组件没有样式路径:插件包不发 style.css,两张已发布表也不含插件的 21 个主题化 utility #4929

Closed

Description

@yinlianghui

发现来源

objectui#4865 的实施(分支 claude/issue-4865-skill-entry-residue)。该卡要求「改写 eval 3 前先从树上回答 plugin-package style.css 的实际问题」。查证结论足够改 eval,但同时量出一个 eval 不该替仓库现场发明答案的缺口,按「相邻缺陷立新单不扩围」单独记录,未在 #4865 的 PR 里修。

事实(实测,worktree 基线 40d3a3318)

1. 只有两个包发布样式表

遍历全部 40 个 packages/*/package.json 的 exports,声明 "./style.css": "./dist/index.css" 的只有 @object-ui/components 与 @object-ui/fields。没有任何 @object-ui/plugin-* 有这个导出。

2. 插件包根本不产出 CSS

包 scripts.build
@object-ui/components vite build && node scripts/build-css.mjs
@object-ui/fields tsc && vite build && node scripts/build-css.mjs
@object-ui/plugin-grid vite build
@object-ui/plugin-kanban vite build

scripts/build-css.mjs 正是把 src/index.css 编成 dist/index.css 的那一步,插件包没有。packages/plugin-grid/src 与 packages/plugin-kanban/src 下一个 .css 文件都没有(find ... -name '*.css' 零命中)。

3. 两张已发布表按构造就不含插件的类

  • packages/components/src/index.css:14 —— @source '../src/**/*.{ts,tsx}';,只扫自己 src。
  • packages/fields/src/index.css:43 —— @source './**/*.{ts,tsx}';,同样只扫自己。

所以插件独有的候选类不可能出现在任一张表里。逐字面 grep 量了一遍:21 个主题化 utility 被 packages/plugin-grid/src / packages/plugin-kanban/src 使用,而在 packages/components/src 与 packages/fields/src 里零出现——

text-muted-foreground/80  focus:border-input  focus:ring-destructive/30  border-foreground
divide-border/50  bg-card/60  hover:border-primary/40  text-primary/90  bg-card/20
text-foreground/85  bg-muted/10  bg-muted/15  ring-primary/60  ring-destructive/30
bg-muted/70  border-primary/60  text-muted-foreground/60  bg-foreground/70
bg-muted-foreground/30  shadow-primary/25  ring-primary/40

(另有约 140 个非主题化的插件独有类,同样不在两张表内 —— 那些理论上消费者自己扫 dist 能生成,主题化这 21 个不能,因为 @theme 块在未发布的源码里。)

4. 今天的路只覆盖工作区

  • 工作区:apps/console/src/index.css:19-30 为每个插件包各写一行 @source '../../../packages/plugin-*/src/**/*.{ts,tsx}',并自己声明 @theme —— 成立。
  • 发布态:packages/cli/src/commands/init.ts:537 生成的 scaffold 只有 @import 'tailwindcss' + @import '@object-ui/components/style.css',模板不含任何插件类型,所以没暴露问题。仓内唯一覆盖发布态插件的实现是 packages/cli/src/utils/app-generator.ts:335 的临时 app —— 它逐条重述自己的 @theme(注释原话「Token-for-token the set packages/components/src/index.css declares」)再 @source '../node_modules/@object-ui/*/dist/**/*.js'。那是 objectui run 的临时 app,不是任何指南教给手写消费者的形状。

影响

装了 @object-ui/plugin-grid / plugin-kanban 的发布态 Vite 应用,照 quick-start 导两张 style.css 之后渲染 grid / kanban,上列 21 个主题化 utility 没有任何来源:bg-muted/10、bg-card/60、text-muted-foreground/60 都是常规外观类,不是边角分支。#4858 修正后的三份指南在「不要扫 node_modules」这一句上是对的(对 components / fields 成立,#3884 实测过 dist 是超集),但那条论据的前半句对插件不成立 —— 两张表没有这些类,于是发布态插件消费者被留在没有答案的位置上。

可能的方向(未定,交维护者)

  1. 给每个 @object-ui/plugin-* 加 scripts/build-css.mjs + "./style.css" 导出,按 fields 的减法形状(@reference components 的 theme,再减掉 components 已发的规则),消费者多导一张表。
  2. 把插件类合并进 @object-ui/components/style.css(components 的 @source 扩到插件 src)—— 但那会让不装插件的消费者也背上体积,且方向上是包依赖倒挂。
  3. 只补文档:告诉发布态插件消费者走 app-generator 那条「自己声明 @theme + 扫 node_modules/@object-ui/*/dist」的路。

三者是公开契约层面的取舍(是否新增包导出、体积归属、是否把「自己声明 theme」重新变成推荐做法),不适合由实施方替维护者定。

关联:#4865(本单来源)、#4858 / PR #4866(三份指南的修正)、#4059(style.css 为何必须存在)、#3884(dist 是 node_modules glob 的超集,该结论只对 components 量过)。未认领。

Activity

  1. os-zhuang commented on Aug 17, 2026

    @os-zhuang
    Contributor

    Maintainer ruling (2026-08-17, recorded by PM session session_01NYgmGheCzM6NrHZN436Cxf). The maintainer accepted the decision-inbox report's recommendations in full, verbatim: 「其他接受你的所有建议」. For this card:

    Ruled: Direction 1 — every @object-ui/plugin-* ships its own stylesheet.

    First-touch grading completes: label flipped needs-user-decision → pm:queue in the same stroke.


    Generated by Claude Code

  2. added
    domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seat
    on Aug 21, 2026
  3. os-support-ai commented on Aug 25, 2026

    @os-support-ai
    Collaborator

    Claim: PM session session_011SfZeFWrhGLHmfq61xbz4q (domain:ui execution seat) — branch claude/issue-4929-plugin-published-stylesheets. pm:queue → pm:dispatched in the same label write.

    ⛔ First, a correction to this seat's own handling of this card

    This card sat undispatched for an entire round on my note "has a maintainer ruling, but touches ~20 published packages — needs a size call; the skill routes L/XL to cloud, unavailable this session."

    That premise was wrong, and the answer was on this card the whole time. The 2026-08-17 ruling ends with:

    Scope today: plugin-grid + plugin-kanban (the two with measured classes); the build step is the pattern any future plugin inherits.

    Two packages. I read the card's measurement — a survey of all 40 packages/*/package.json finding no plugin declares the export — and inferred the scope from it, without reading the ruling's scope sentence. ⭐ Third instance this round of the same error: a check was run against the wrong surface. I had even recorded "has a maintainer ruling (Direction 1)" in my own notes and still never read the line that sized it.

    No size call was ever needed. Dispatching at M.

    Pre-dispatch gate

    1. Thread read. The 2026-08-17 maintainer ruling read in full, including the scope line above and both rejections.

    2. Shadow check. Nothing in flight touches packages/plugin-grid/ or packages/plugin-kanban/ — #6269 is in packages/types/. #6393 (in the merge queue) is plugin-form. No hold; merge origin/main again before you push.

    3. Precedent verified against the tree — the ruling names it and it is complete and copyable:

    packages/fields/package.json      build: tsc && vite build && node scripts/build-css.mjs
                                      exports["./style.css"]: ./dist/index.css
    packages/fields/scripts/build-css.mjs        exists
    packages/components/scripts/build-css.mjs    exists
    packages/fields/src/index.css     carries the @reference subtraction shape, with its
                                      rationale documented in-source (objectui#4059)
    

    And the two targets today:

    packages/plugin-grid/package.json     build: vite build     style export: (none)
    packages/plugin-kanban/package.json   build: vite build     style export: (none)
    

    Scope — Direction 1, as ruled

    plugin-grid and plugin-kanban each gain the scripts/build-css.mjs step and a "./style.css": "./dist/index.css" export, built in the subtraction shape @object-ui/fields already uses: @reference the components theme so this compilation gets its tokens, its class-based dark variant and its tailwindcss-animate plugin without emitting a byte of them, and emit only what components' published sheet does not already carry.

    ⭐ Read packages/fields/src/index.css's header comment before you write anything. It explains why the narrow shape works, and that reasoning is the whole design — copying the mechanism without it is how the subtraction silently becomes a duplication.

    Follow-through, also ruled: the quick-start / plugin guides gain the one-line import per plugin. The #4858-corrected "don't scan node_modules" prose stays true and must stay — it holds once the sheets exist. ⛔ Do not re-introduce any advice to scan node_modules or to hand-declare @theme.

    ⛔ Both rejected directions — do not drift into either

    ⚠️ Direction 3 is the tempting one because it is cheap. It is ruled out by name.

    Measure, don't inherit

    The card's numbers are from 40d3a3318 and are ~4 days old. Re-derive them on the merged tree: the 21 themed utilities used by the two plugins and absent from both published sheets, and the ~140 plain ones. Report your counts next to the card's and say where they differ.

    ⭐ The 21 are the load-bearing set, and the card says why: the ~140 plain classes a consumer could in principle generate by scanning dist, but the themed ones cannot come from anywhere, because the @theme blocks live in unpublished source. If your re-measure finds the themed count is now zero, that is a premise fork — stop and report.

    Tests

    • Pin that each new sheet actually contains the themed utilities that motivated it, and that it does not re-emit what @object-ui/components' sheet already carries — the subtraction is the point, and a sheet that duplicates the base passes a naive "does it have the class" test.
    • ⛔ Ghost-assertion guard (mandatory): run each new assertion against unmodified origin/main, capture it failing, then passing. Both readings in the PR body.
    • ⛔ Degenerate-control guard: assert a class that components' sheet does carry is absent from the plugin sheet. Without that control, "the sheet has classes" proves nothing about subtraction.
    • objectui run's temporary app (packages/cli/src/utils/app-generator.ts:335) currently restates its own @theme token-for-token. If the published sheets make that restatement removable, report it — do not remove it here. That is a separate card.

    Ground rules

    Worktree-first: git worktree add ../objectui-4929 -b claude/issue-4929-plugin-published-stylesheets main && cd ../objectui-4929 && pnpm install. ⛔ Never git stash — refs/stash lives in the common .git dir and is shared across every worktree of this repo; a pop silently restores another agent's work and reports success. ⛔ Never touch content/docs/releases/. Changeset: patch, one entry per package changed. Run the repo's fast checks before pushing — and build both packages to confirm the CSS step actually emits. Open the PR as draft with Fixes #4929 and report back — the PM lands it, ⛔ never self-merge.

    Size: M. Model: opus.


    Generated by Claude Code

  4. claude commented on Aug 25, 2026

    @claude
    Contributor

    os-dev-report

    {
      "issue": 4929,
      "status": "done",
      "branch": "claude/issue-4929-plugin-published-stylesheets",
      "pr": "https://github.com/objectstack-ai/objectui/pull/6403",
      "premise_still_valid": true,
      "summary": "Direction 1 as ruled 2026-08-17: `@object-ui/plugin-grid` and `@object-ui/plugin-kanban` each gain `src/index.css` (the `@reference` narrow shape), a `scripts/build-css.mjs` step and a `\"./style.css\": \"./dist/index.css\"` export, built by subtracting every rule `@object-ui/components`' published sheet already ships — 16.30 kB and 11.41 kB rather than the 172/164 kB the unsubtracted compile weighs. The build step is shared (`scripts/build-plugin-stylesheet.mjs`, thin per-package wrappers) so the next plugin inherits the pattern instead of copying it; `packages/fields`' own copy is deliberately untouched and filed as #6405. Guides, both plugin pages and both READMEs gain the one-line import per plugin, and the #4858 'do not scan node_modules / do not hand-declare @theme' prose is untouched and now true for plugins too. Three CI failures on the first push (entry-guard, turbo-build-inputs, package-files-exist) were reproduced locally, fixed and re-verified in one push.",
      "tests": "MEASUREMENT (re-derived on the merged tree, compiled-diff method rather than the card's literal grep): 128 plugin-only classes absent from BOTH published sheets — 25 themed, 103 plain; grid 83 (7 themed), kanban 47 (19 themed). All 21 themed utilities the card listed at 40d3a3318 still hold, plus 4 the grep method could not see ([&>h3]:text-foreground/80, border-l-primary/40, border-primary/30, hover:text-primary). Themed count is 25, not zero: no premise fork. || BUILD (both sheets deleted first, so emission is observed not assumed): `turbo run build --filter=@object-ui/plugin-grid --filter=@object-ui/plugin-kanban` -> 15/15 tasks, 'plugin-grid:build: OK built dist/index.css (16.30 kB) - 128 rules kept (126 classes), 1356 rules + 75 at-rules already in @object-ui/components sheet', kanban '(11.41 kB) - 52 rules kept (50 classes)'; files back on disk at 16696 and 11684 bytes. || SUITE: new `scripts/__tests__/plugin-published-stylesheet.test.ts`, 14 assertions; it COMPILES rather than reading dist/ because CI runs tests on an unbuilt worktree. Union at pushed HEAD 820631805: `vitest run packages/plugin-grid packages/plugin-kanban scripts/__tests__` -> 'Test Files 188 passed (188) / Tests 3245 passed (3245)'. `pnpm lint` (47 tasks) 0 errors; lint:root, both packages' lint, type-check:scripts, both packages' type-check all exit 0 (tsc --listFiles confirms all four new files are in the scripts program). Gates green at that head: check:entry-guard (--self-test 63 cases + the real run, '48 scripts/ file(s) - no entry guard outside the baseline'), check-control-bytes, check-doc-fence-languages, check-doc-component-types, check-doc-links, check-phantom-dependencies, check-lint-coverage, check-type-check-coverage, check-changeset-presence ('2 source file(s) of 2 released package(s) changed, and this change declares 1 changeset(s)'), check-changeset-no-major, check-changeset-fixed, check-cross-repo-closer-outcome. || GHOST-ASSERTION GUARD, two layers. (a) The test file copied onto unmodified origin/main (faa863dce) in a throwaway worktree: FAIL, exit 1, \"Cannot find module '../../packages/plugin-grid/scripts/build-css.mjs'\" — on main there is no build step to assert about. (b) Because an import-level failure only proves the subject is absent, every assertion group was driven red individually by MUTATING THE FIX on the branch. Baseline first (14 passed, exit 0), then: drop @reference + empty the builder's MUST_SURVIVE -> 4 failed (card coverage, themed emitted, theme-token resolution, control); make the subtraction skip rules whose selector contains 'flex' -> 2 failed, the degenerate control in both packages, \"expected [ 'flex' ] to deeply equal []\"; remove the ./style.css export -> 1 failed (wiring); drop source(none) -> 1 failed (byte-identical-from-any-cwd); inject ':root{--color-ghost-4929}' -> 1 failed (utilities-only). Dropping @reference alone makes the BUILD throw first ('removed 3 utility(ies) that only this build can produce'). Each mutation was proved on disk by grep count + blob hash before the run; each restore by `git checkout HEAD -- <abs path>`, a hash back to the HEAD blob and a clean `git diff HEAD`; the driver carried a trap restoring every touched path. || DEGENERATE CONTROL: flex, text-sm, rounded-md, bg-background, sr-only are asserted PRESENT in the pre-subtraction compile and ABSENT from the published sheet, so the absence cannot be explained by the plugin never using the class. || TWO NEGATIVE RESULTS REPORTED RATHER THAN BURIED. (1) A sixth mutation — swapping the narrow entry for a plain `@import 'tailwindcss'` — left the suite GREEN. Not a ghost: the subtraction removes the duplicated preflight/theme too and the artifact comes out the same size (11.26 kB vs 11.41 kB). The narrow entry stays because it makes a :root theme block impossible to emit at all, and such a block loaded after the base sheet would override a consumer's own token overrides. My in-source comments had claimed the wide entry would ship ~170 kB of duplication; the measurement disproved it and commit 2 corrects the comments. (2) The FIRST run of that mutation matrix was discarded: every leg exited 1 through `--reporter=basic`, which vitest 4 does not have, so five legs read as five confirmations while measuring nothing. The matrix was re-run with a baseline row so a non-zero exit means an assertion. || NOT MEASURED LOCALLY, named rather than glossed: `check:doc-snippets` exits 2 'PRECONDITION NOT MET' (it needs most packages built; this worktree was not) and `check:readme-exports` fails on an unbuilt worktree for ~20 other packages ('type entry not on disk — run pnpm build first') with ZERO findings against the two packages touched here. CI runs both after a build.",
      "open_questions": [],
      "out_of_scope_findings": [
        "filed as #6404: objectui run's temporary app (packages/cli/src/utils/app-generator.ts) still restates the components @theme token-for-token and scans node_modules/@object-ui/*/dist; the new sheets make it only PARTLY removable, because the other @object-ui/plugin-* packages still publish no stylesheet — dropping the restatement today would move the #4929 defect rather than fix it. Reported per the dispatch order, not touched.",
        "filed as #6405: packages/fields/scripts/build-css.mjs now duplicates the logic in the shared scripts/build-plugin-stylesheet.mjs; collapsing it needs byte-for-byte verification of a published artifact, which is its own change.",
        "not filed, recorded in the PR body: plugin-kanban's sheet keeps a 2.7 kB @layer properties block because 74 of its 75 --tw-* declarations are byte-identical to components' and the 75th (--tw-scroll-snap-strictness, from snap-mandatory) is not, so rule-level subtraction cannot drop it. Idempotent; left as-is rather than moving the subtraction to declaration level."
      ]
    }

    Generated by Claude Code

  5. os-support-ai commented on Aug 25, 2026

    @os-support-ai
    Collaborator

    ACCEPT — enqueued. PM session session_011SfZeFWrhGLHmfq61xbz4q, domain:ui execution seat. 24 ✅ / 3 skipped / 0 ❌ / 0 pending, clean. Reviewed against the tree, not the report.

    ⭐⭐ Two negative results reported rather than buried — the best thing in this run

    1. A mutation that left the suite GREEN, disclosed as such. Swapping the narrow @reference entry for a plain @import 'tailwindcss' did not turn anything red. The dev reported it instead of quietly dropping the leg, explained why it is not a ghost (the subtraction removes the duplicated preflight/theme anyway, so the artifact lands at 11.26 kB vs 11.41 kB), and kept the narrow entry for a different, measured reason: it makes a :root theme block impossible to emit at all, and such a block loaded after the base sheet would override a consumer's own token overrides.

    ⭐ And it corrected its own source comments in a second commit: they had claimed the wide entry would ship ~170 kB of duplication. Its own measurement disproved its own claim, and it changed the claim rather than the measurement.

    2. ⭐ The first mutation matrix was discarded as a blind instrument. Every leg exited 1 through --reporter=basic, which vitest 4 does not have — "so five legs read as five confirmations while measuring nothing." Re-run with a baseline row, so a non-zero exit means an assertion rather than a broken invocation.

    Five false confirmations that all pointed the way the dev wanted. That is precisely the trap a ghost-assertion guard exists for, applied one level up — to the harness rather than the assertions — and caught by the dev on its own work.

    The three CI failures, verified fixed the right way

    gate how it was fixed verified
    check:entry-guard isEntrypoint(import.meta.url) in the per-package wrappers ✅ and ⛔ the SHRINK-ONLY baseline was never touched — check:check-entry-guard reports "48 scripts/ file(s) — no entry guard outside the baseline"
    turbo-build-inputs added the script to turbo.json build inputs ✅ — and ⭐ the dev also added $TURBO_ROOT$/scripts/invoked-as.mjs, the transitive dependency my message did not mention. A hash that covers the script but not what the script imports is still stale-able.
    package-files-exist the pinned exports list grew to four ✅ with the three-things treatment written into the test file, quoting the 2026-08-17 ruling verbatim

    ⭐ That pin comment does something better than what I asked for: it records which direction is dangerous. Growing is expected as further plugins adopt the step; "shrinking it, by contrast, means a package stopped producing a sheet it still promises: that is objectui#4059's defect returning." A pinned list that explains its own asymmetry will not be misread by the next person who has to move it.

    ⭐ The guard mechanism is also placed correctly: isEntrypoint sits in the wrappers, which are the entrypoints; the shared scripts/build-plugin-stylesheet.mjs has no guard because it is a library. My own check initially grepped the shared module and found nothing — the design is right, my probe was aimed one file over.

    The work itself

    Both packages gain src/index.css in the @reference narrow shape (with the same header-comment discipline fields uses), a scripts/build-css.mjs step and the "./style.css" export. 16.30 kB and 11.41 kB — against 172/164 kB for the unsubtracted compile, so the subtraction is doing real work. Build observed with both sheets deleted first, so emission is measured rather than assumed.

    Re-measured on the merged tree by a compiled-diff method rather than the card's literal grep: 128 plugin-only classes absent from both published sheets, 25 themed — all 21 the card listed still hold, plus 4 the grep method could not see. No premise fork.

    Degenerate control: flex, text-sm, rounded-md, bg-background, sr-only asserted present in the pre-subtraction compile and absent from the published sheet — so absence cannot be explained by the plugin never using the class.

    Two unbuilt-worktree gate limitations (check:doc-snippets, check:readme-exports) named rather than glossed, with zero findings against the two touched packages. content/docs/releases/ clean; changeset present.

    ⚠️ Filed by this run: #6404 (objectui run's temp app is only partly de-duplicable today — the other plugins still publish no sheet, so removing the restatement now would move the defect rather than fix it) and #6405 (fields' build-css now duplicates the shared module). Both ungraded — triage's.


    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

domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatpm:dispatched

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions