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.

scripts/__tests__/plugin-published-stylesheet.test.ts pins the two plugin sheets but not @object-ui/fields, the one that is actually published #6438

Closed

Description

@os-warren

What was observed

scripts/__tests__/plugin-published-stylesheet.test.ts runs its five per-package
assertions — the themed utilities survive, the subtraction really ran, utilities only
(no preflight, no theme block), the same bytes from any working directory, and the
exports/build-step declaration — over exactly two subjects:

const SUBJECTS = [
  { name: 'plugin-grid', mod: gridStylesheet },
  { name: 'plugin-kanban', mod: kanbanStylesheet },
] as const;

@object-ui/fields is not one of them, and it is the package whose stylesheet has
actually been shipping to consumers since #4059 — the very defect the test's own header
cites as the reason the shape exists. Its dist/index.css is covered by the four
write-time assertions inside the builder (which run only at build time) and by nothing in
the test suite.

Why it is newly cheap to close

Before #6405 fields ran its own copy of the builder and exposed no module surface, so it
could not be a subject without being rewritten first. It now runs the same shared
createPluginStylesheetBuilder and exports the same shape the two plugins do —
PACKAGE_ROOT, PACKAGE_NAME, MUST_SURVIVE, CLASS_CEILING, builder,
buildOptions — behind the same isEntrypoint guard. Adding it to SUBJECTS is
mechanical; the only genuinely new content is a CARD_THEMED entry naming the fields
utilities that resolve unpublished @theme tokens (bg-primary/20, hover:bg-accent/30,
ring-destructive/50 are the ones #4059 measured), derived independently rather than read
back off MUST_SURVIVE, per the note the test already makes about why it names its own
list.

One caveat worth checking rather than assuming: fields passes a per-package header, so
whichever assertions read the emitted banner have to take it from buildOptions rather
than from the shared default.

Not done in #6405

That card's acceptance gate was byte identity of the emitted sheet, and its scope was the
re-point. Widening a pin test to a third subject is a different change with a different
review; filed rather than ridden along.

Filed unassigned, as an observation from the #6405 implementation.


Generated by Claude Code

Activity

  1. claude commented on Aug 31, 2026

    @claude
    Contributor

    分诊解锁 · pm:blocked → pm:queue —— 阻塞已过期

    本轮「objectui 停放面」复核:本卡的 Blocked-by: 目标已全部关闭,阻塞比阻塞方活得久。

    Blocked-by: 目标 直读结果
    objectstack-ai/objectui#6405 closed

    ⇒ 声明的前置条件已兑现 ⇒ 解除,回 pm:queue。

    ⚠️ 关于本次读数的来源,如实声明

    check-half-states.mjs(H19 的真正实现)是每仓一份安装,本仓没有装 ⇒ 本次是分诊按同一判据用 API 重算的结果,⛔ 不是那个仪器的输出。若将来本仓装上 sweep 且结论不一致,以 sweep 为准。目标状态是逐个直读的,不是推断的。

    ⚠️ 本席只解除了「机器可读的那条阻塞线」

    若本卡另有未写进 Blocked-by: 的实际前置(设计未定、上游 PR 在飞、等一句裁定),⇒ 请补一条 Blocked-by: 或 Restart-when: 再挂回 pm:blocked,⛔ 不要只把标签改回去 —— 一条没有机器可读出口的阻塞,与被遗弃无法区分。

    ⚠️ 本仓停放面同批读数:pm:on-hold 67 · pm:blocked 44,其中 26 张 on-hold 没有可点火的 Restart-when:。本卡不在那 26 张内。


    Generated by Claude Code

  2. zhuangjianguo commented on Aug 31, 2026

    @zhuangjianguo
    Collaborator

    认领 · PM 派发 R39

    domain:devx @ objectui PM 席位认领本卡并派发。

    • session: 86aefd79-6ef3-5a63-98f9-701da03c59ab
    • branch: claude/issue-6438-fields-stylesheet-pin
    • 状态: pm:queue → pm:dispatched

    解锁条件已兑现,且我直读复核过(当前 main = e33b44796)

    08-31 的解锁评论要求:「re-verify on the merged ref that fields exports the shared shape」。我做了这次复核,结论是前提成立:

    packages/fields/scripts/build-css.mjs(⚠️ 不是 build-stylesheet.mjs —— 见下):

    卡断言 fields 会导出的 直读结果
    createPluginStylesheetBuilder 共享构建器 ✅ 第 94 行 import
    PACKAGE_ROOT / PACKAGE_NAME ✅ 第 96 / 97 行
    MUST_SURVIVE / CLASS_CEILING ✅ 第 114 / 132 行
    builder / buildOptions ✅ 第 161 / 163 行
    isEntrypoint 守卫 ✅ 第 171 行

    ⇒ 与 plugin-grid / plugin-kanban 的形状逐项一致。SUBJECTS(测试第 91 行)今天仍是两个主体。

    ⭐ 卡的那条 caveat 是真的,我验证了它

    卡说:「fields passes a per-package header, so whichever assertions read the emitted banner have to take it from buildOptions rather than from the shared default」。

    成立:fields 第 147 行 export const HEADER = [...],第 168 行 header: HEADER 进入 buildOptions;而 plugin-grid 的 buildOptions(第 62 行)没有 header 键。⇒ ⛔ 任何从共享默认值读 banner 的断言,加入 fields 后会在 fields 这一行错误地红/绿。请从 buildOptions 取。

    ⚠️ 一次我自己的错误读数,公开记下,因为它正是本卡要防的那个形状

    我第一次探测时查的是 packages/fields/scripts/build-stylesheet.mjs —— 该路径不存在,返回零命中。⛔ 若我就此上报,我会得出「fields 没有接入共享构建器、本卡前提已失效」这个完全相反的结论。

    救回它的是控制项:我同时查了 packages/fields 下的文件总数(214,非零 ⇒ 探测面是活的),并改用 git grep -l createPluginStylesheetBuilder 反查,才拿到真实路径 build-css.mjs。

    ⇒ ⭐ 这条对取卡者是直接相关的,不是题外话:本卡的全部内容就是「一个断言跑在它看不见的主体上」。请在你自己的每一次零命中读数上都带一个必须命中的控制项。

    派发要点(分诊的,加我一条)

    • ⭐ CARD_THEMED 的 fields 条目独立推导,⛔ 不要从 MUST_SURVIVE 回读 —— 测试自己已经写明了它为什么另立一份列表。@object-ui/fields declares a ./style.css export but nothing builds dist/index.css for it — the subpath quick-start teaches looks unresolvable #4059 测得的 bg-primary/20、hover:bg-accent/30、ring-destructive/50 是起点语料,不是答案。
    • ⭐ banner 断言从 buildOptions 取(见上)。
    • ⛔ PM 追加:五条 per-package 断言加到第三个主体上之后,请证明它们对 fields 确实在起作用 —— 用一次 ablation:破坏 fields 的某一条前提、证明对应断言变红、还原、按 blob hash 证明还原。⛔ 「加进去之后套件是绿的」不构成证据:本卡记录的缺陷正是一个绿色的、什么都没看的断言。

    Generated by Claude Code

  3. zhuangjianguo commented on Aug 31, 2026

    @zhuangjianguo
    Collaborator

    os-dev-report

    {
      "issue": 6438,
      "branch": "claude/issue-6438-fields-stylesheet-pin",
      "pr_url": "https://github.com/objectstack-ai/objectui/pull/7043",
      "commit": "1219b8d88",
      "status": "ACCEPT_REQUESTED",
      "premise_still_valid": true,
      "files_changed": [
        "scripts/__tests__/plugin-published-stylesheet.test.ts",
        ".changeset/spotty-pins-cover-fields.md"
      ],
      "card_themed_entry": [
        "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"
      ],
      "how_it_was_derived": "Independently from the emitted sheet, NOT read off MUST_SURVIVE. Method: read the 35 --color-* tokens from the @theme block in packages/components/src/index.css (the unpublished source), build fields' sheet through the shared builder with write:false, parse the emitted css, and keep every surviving class whose OWN declarations resolve one of those tokens. 17 of 163 surviving classes qualify. Two independent corroborations: (1) packages/fields/scripts/build-css.mjs records from #4059 that '17 of those depend on the ObjectUI @theme block' - same count via a different method; (2) the three utilities the card names (bg-primary/20, hover:bg-accent/30, ring-destructive/50) are 3 of the 17, confirming they were the starting corpus and not the answer; the other 14 are ones a literal-grep method could not see. Only 3 of the 17 are in MUST_SURVIVE, so the list is demonstrably not that list read back.",
      "banner_source_evidence": "Verified the caveat is real and then found it is PREVENTIVE here, not corrective. fields exports HEADER (line 147) and passes header: HEADER into buildOptions (line 168); plugin-grid's buildOptions has no header key and inherits defaultHeader(packageName), which is module-private in scripts/build-plugin-stylesheet.mjs. The emitted css is header + newline + sheet, so a banner assertion reading the shared default would judge fields wrongly. Survey with a control term (grep for HEADER/defaultHeader/buildOptions.header across scripts/__tests__ returned only unrelated hits - shadcn OBJECTUI_HEADER and docs table headers - while the control 'ls scripts/__tests__' returned 94 files, so the surface was live): NO assertion in the suite reads the emitted banner. The one assertion inspecting the whole css string ('carries utilities only') was checked against fields' actual HEADER bytes - none of its three patterns (@layer base, ^:root[,{], --color-x: ) can match it. The test spreads ...mod.buildOptions, so fields' own header flows through; confirmed at runtime that result.css.startsWith(buildOptions.header) is true. No banner assertion was ADDED - see open_questions.",
      "ablation_evidence": "TWO ablations, each proving the mutation reached disk BEFORE measuring and proving the restore by BLOB HASH, never by exit code. Both used an absolute REPO_ROOT and a trap for the restore leg. Implementation was committed FIRST so HEAD was a valid restore reference. (1) THEMED UTILITY: hover:bg-primary/10 is in the derived list and deliberately NOT in MUST_SURVIVE, so the builder's write-time throw could not fire and mask the result; its single usage site in packages/fields/src/widgets/RecordPickerDialog.tsx was retargeted to hover:bg-primary/15. On disk: removed-text occurrences 1 to 0, injected-text 0 to 1, blob 1e9d341d to 3e0a4915. Result: exit 1, 'Tests 1 failed | 19 passed', failure = 'fields > emits the themed utilities only this build can produce', AssertionError: expected [ 'hover:bg-primary/10' ] to deeply equal []. Restore: blob back to 1e9d341dea23f9512740dc5f180274dbc3143605, git diff HEAD empty. (2) MANIFEST: fields' exports['./style.css'] pointed at ./dist/nope.css, which also proves the subject name resolves to packages/fields. On disk: injected 1, original pair 0, blob 5e442f18 to 98eeea6f. Result: exit 1, 'Tests 1 failed | 19 passed', failure = 'fields > declares the export AND the step that produces it'. Restore: blob back to 5e442f18c711442e47be3acf67b5f7b47c4451cd, git diff HEAD empty. In BOTH runs exactly one assertion went red and it was a fields one, with plugin-grid and plugin-kanban still green - the red is subject-specific, not a global break.",
      "union_assertion_finding": "Under ablation 1 the union assertion 'gives every themed utility the card measured a producer' STAYED GREEN. Measured cause: plugin-grid also emits hover:bg-primary/10 (verified directly), so the union was satisfied by another subject while fields' own sheet had genuinely regressed. This is empirical proof that adding fields to SUBJECTS is what buys the coverage - adding it only to CARD_THEMED would have reproduced the very defect this card records.",
      "tests_run": [
        "npx vitest run scripts/__tests__/plugin-published-stylesheet.test.ts --maxWorkers=2 -> exit 0, 'Tests 20 passed (20)' (was 14: 2 subjects x 6 + 2 standalone; now 3 x 6 + 2). Union run on final commit 1219b8d88, tree clean.",
        "pnpm type-check:scripts -> exit 0. Load-bearing: the shared builder's own comment records that this reads the build() options types THROUGH this test file, and fields is the first subject to pass header.",
        "pnpm type-check:coverage -> exit 0, '45/46 via type-check', '41/41 packages compile their tests'",
        "pnpm check:control-bytes -> exit 0, 'OK (scanned 5850 tracked text file(s); skipped 85 binary)'",
        "pnpm changeset:check -> exit 0, 'All workspace packages are in the changeset fixed group', 'No changeset declares a major bump'",
        "node scripts/check-changeset-presence.mjs -> exit 0, 'No source or published contract of a released package changed in this range, so no changeset is owed'; the changeset is an explicit no-release declaration in the empty-frontmatter shape this repo uses for test-only changes (mirrors .changeset/tidy-pumas-repeat.md). No skip-changeset label applied - that label is inert in objectui.",
        "npx eslint --no-inline-config on the changed file -> exit 0, 1 file linted, 0 errors 0 warnings"
      ],
      "verification_narrowing_declared": "Lint was narrowed to the changed file rather than run farm-wide. Three pieces of evidence, not an assumption: (1) file count read from eslint's own --format json output = 1; (2) the changed file is under root lint scope; (3) type-aware linting is NOT enabled in eslint.config.js - grep for projectService/project:/parserOptions returned exit 1 (no match) while the control term 'rules' hit 10 times in the same 14724-byte file, so the query was live - therefore this diff cannot move the lint verdict on any untouched file. CI runs the full farm regardless.",
      "mcp_calls": "6 - one search_issues (dedup, after the REST /search/ endpoint returned 403), one create_pull_request, four issue_write (one create + three updates while recovering a stripped footer). All issue and PR READS went through the zero-quota repo-scoped REST channel, which was probed first and returned HTTP 200.",
      "out_of_scope_findings": [
        "filed as #7044: the published stylesheet banner is pinned by nothing, and cannot be pinned cleanly while the shared builder keeps defaultHeader module-private - an all-subject assertion would need a tolerant consumer-side fallback, which is the contract-first smell; filed unassigned, no label, for PM triage"
      ],
      "open_questions": [
        {
          "question": "Should a banner assertion be added at all, and if so, does the shared builder export defaultHeader to make it clean?",
          "options": [
            "A: leave unpinned for now (what this PR does) and let #7044 carry the decision",
            "B: add the assertion here with a consumer-side fallback re-spelling the default banner in the test",
            "C: export defaultHeader from scripts/build-plugin-stylesheet.mjs, then assert per package"
          ],
          "recommendation": "A here, C as the follow-up. B is the option to avoid: a second hand-maintained copy of the default banner in the test can drift from the real one while staying green - the exact 'assertion that inspects nothing' shape this card records - and it is the consumer-side leniency the contract-first rule says to fix at the producer. C is correct but is a producer-side change to a shared build module with its own review surface, so it was filed rather than ridden along on a test-coverage card."
        },
        {
          "question": "PLATFORM NOTE, no decision needed: the mandated attribution footer is silently STRIPPED from issue bodies written through the MCP issue_write path.",
          "options": [
            "Localized by a controlled test: posting body + footer + 'SENTINEL-END-MARKER' stored the sentinel but removed the footer block from the middle, so it is a targeted strip of the Claude Code attribution, not truncation",
            "The PR body created via create_pull_request KEPT its footer (session-URL form), read back byte-identical to local"
          ],
          "recommendation": "No action needed from the PM. Attribution for #7044 is stated as prose in the body so it survives. Flagging it because a future agent will otherwise read the missing footer as its own omission and retry, as I did twice before testing."
        },
        {
          "question": "Minor discrepancy in the card's own wording, recorded for accuracy.",
          "options": [
            "The card and dispatch both say 'five per-package assertions'; the file actually has SIX its inside describe.each (lines 142, 147, 155, 163, 170, 178) - the card folds in 'resolves those utilities through the unpublished @theme tokens'"
          ],
          "recommendation": "No action. All six now run over fields; the test count 14 -> 20 reflects six, not five. Noted so the arithmetic in the PR body is not read as a mistake."
        }
      ]
    }

    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:devxobjectui devx stream: fix lands on .github/, scripts/ or release pipeline — devx lane cross-repopm:dispatched

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions