Repository navigation
17 个包各自带 vitest.config.ts 与根 vitest.config.mts 行为分叉:先回答「哪些包真的需要本地配置」 #3240
Description
Activity
xuyushun441-sys commented
on Aug 3, 2026 ContributorAuthorMore actions补一条测量,供排期时估算爆炸半径。
#3219修的那种整模块vi.mock('@object-ui/react', …)写法,在本仓共有 30 个测试文件(含刚修好的plugin-calendar/src/registration.test.tsx):$ grep -rln "vi.mock('@object-ui/react'" packages/ --include=*.test.tsx --include=*.test.ts | wc -l 30分布(部分):
plugin-kanban、plugin-detail×7、plugin-view×3、plugin-gantt、app-shell×多个、plugin-calendar。这不等于 29 个文件现在都是红的 —— 是否炸取决于三件事同时成立:该文件所在配置路径、有没有 setupFiles 预热了消费者模块、以及它的传递依赖恰好用到了 mock 里没列的导出。
plugin-calendar只是第一个三者凑齐的。但每新增一个@object-ui/react导出、或某个包的 setup 名单一变,都可能把其中任意一个翻红,且翻红方式仍然是"一条路径红、另一条绿"。对本 issue 的两点含义:
- 选项 A/B(统一或强制继承根配置)的收益比看起来大 —— 它让这 30 个文件至少在两条路径下结论一致,红要一起红;
- 但配置统一并不能单独解决整模块 mock 的脆弱性:即使只剩一套配置,整模块 mock 仍会在下一个新增导出时失效,只是不再"一路红一路绿"而已。真正的根治要两步:配置统一 + 把这 30 处改成
importOriginal部分 mock(或加一条 lint 规则禁止对 workspace 包做整模块 mock)。
#3219用的断言写法可以直接复用为模板(比较导出集合、不点名具体导出):const actual = await vi.importActual< typeof ObjectUIReact >('@object-ui/react'); const missing = Object.keys(actual).filter((name) => !(name in ObjectUIReact)).sort(); expect(missing).toEqual([]);
不过 30 处逐个加断言是选项 C 的成本形态(线性、且只在踩中后才发现);更划算的大概率是一条 lint 规则。未认领,仍待维护者定方向。
Generated by Claude Code
转决策收件箱:
needs-user-decision(objectui 分片 PM,会话session_01GTRjn8xBqp75dk7kFupVRt)。要拍板的一句话:17 个包的本地
vitest.config.ts是全删统一走根配置(方案 A),还是保留但强制继承根配置(方案 B)?两轴分析:
- 项目长远合理性:A 最彻底——一套配置一个结论,dist/源码解析分叉与 setupFiles 预热差异一次性消失;代价是
pnpm --filter <pkg> test语义要重定义、turbotest的^build依赖要重估。B 保住既有入口但分叉面只是收窄,需要机械检查防漂移——多一个需要长期维护的守卫。 - 防 AI 犯错:两案都远好于现状(现状=同一文件两套结论,
plugin-calendar的 registration.test.tsx 在 main 上就是红的:@object-ui/react的 vi.mock 缺subscribeDataChanges#3219 实测坐实)。A 结构性消灭「在哪跑」这个自由度,AI 无从跑错;B 仍留两个入口,靠守卫兜底。倾向 A,但它是跨 17 包的配置面重构、与在飞单大面积撞文件,须单独排一轮空批次执行——这正是需要你定 appetite 的原因。
过渡期已止血:第 33 单(#3378+#3288)只做「错误调用方式响亮失败 + 文档」,不动配置面,与本决策不冲突、先行落地。
Generated by Claude Code
- 项目长远合理性:A 最彻底——一套配置一个结论,dist/源码解析分叉与 setupFiles 预热差异一次性消失;代价是
xuyushun441-sys commented
on Aug 6, 2026 ContributorAuthorMore actions维护者裁决(2026-08-06,经 PM 会话
session_01MN3ao9kJEtD7PNAKwFVp96转达):A —— 全删 17 个包的本地 vitest.config,统一走根配置。执行约束:- 单独排一轮空批次执行(跨 17 包配置面重构,与任何在飞单不同批);
- 同单重定义
pnpm --filter <pkg> test的语义(对根配置传路径过滤),并重估 turbotest任务的^build依赖; - 逐包确认没有依赖本地配置的特殊 environment/setup,有则迁进根配置的 projects;
- 30 处整模块
vi.mock('@object-ui/react', …)的脆弱性是独立问题:配置统一后补一条 lint 规则(禁止对 workspace 包整模块 mock / 强制importOriginal),单独立单,⛔ 不塞进本单。
摘
needs-user-decision,回pm:queue;已在飞的止血单(#3378+#3288,响亮失败+文档)先行落地,与本裁决不冲突。
Generated by Claude Code
补一份实测清单,供本单排期时把「重定义包级
test脚本语义」这一项的面算准 —— 来自 #3746(runner 包级入口 4 红)的落地,不改本单方向,也不请求提前执行。测到的形状:被劫持的不是 1 个包,是 8 个
#3746 正文推断「runner 的特殊之处只是它目录里有一个
vite.config.ts可被 vitest 误取」。在origin/main@6402e253f上按谓词扫全仓(packages/apps/examples里有test脚本的 39 个包),有vite.config.*、没有 vitest 配置、脚本是 barevitest run的共 8 个:packages/plugin-ai test: vitest run --passWithNoTests packages/plugin-chatbot test: vitest run packages/plugin-designer test: vitest run packages/plugin-editor test: vitest run packages/plugin-markdown test: vitest run packages/plugin-report test: vitest run packages/plugin-tree test: vitest run packages/runner test: vitest run <- #3746 已修这 8 个的包级入口都不会走到本仓的 guard(
scripts/vitest-invocation-guard.mjs只在仓根vitest.config.mts顶部被调用),因为 vitest 先把同目录那份 Vite 应用配置当成自己的配置读了进去 —— 那里面没有test块,于是没有 DOM 环境、没有 setup。runner 因此拿到 4 个ReferenceError: window is not defined;其余 7 个是否同红取决于各自测试是否碰 DOM,未逐个实测,不声称。对照面(仅供本单核对):17 份包级 vitest 配置里 6 份是继承仓根的(
components/core/fields/plugin-dashboard/react/types),它们的包级入口被 guard 明确拒绝(实测pnpm --filter @object-ui/react test→package-cwd,报错正文完整);另 11 份是独立defineConfig(plugin-calendar/plugin-charts/plugin-detail/plugin-form/plugin-gantt/plugin-grid/plugin-kanban/plugin-list/plugin-map/plugin-timeline/plugin-view),它们同样不加载仓根配置,所以也不在 guard 底下。也就是说本单执行时,「不受 guard 保护的包级入口」是 8 + 11 = 19 个,而不只是配置分叉那 17 个。#3746 的落地形状,供本单参考(并非既成事实的扩面)
runner 一条脚本改成本单裁决 A 已经定下的那种语义 —— 对仓根配置传路径过滤:
- "test": "vitest run" + "test": "vitest run --root ../.. packages/runner/"
即 AGENTS.md §怎么跑测试 与 guard 报错正文都指定的写法。效果:包级入口 3 文件 / 13 用例全绿,与仓根入口同一份配置、计数一致;
vitest list --filesOnly显示 runner 的 3 个文件各自只在一个 project 注册([unit]x2、[dom]x1),仓根 projects 机制不受影响;--root指回仓根后 guard 仍在链路上(--转发照样被拒)。没有新增第 18 份包级 vitest 配置 —— 本单要删的东西没有变多。细节:
--passWithNoTests(plugin-ai)在改造后要复核还需不需要 —— 仓根配置的passWithNoTests是由命令行是否点名文件派生的(passWithNoTests: !cliHasTestFilters(process.argv)),而改造后的脚本恰恰点名了路径,于是零匹配会变成非零退出。这对本单是好事(写错的过滤面不再绿着退出),但会让「本来就没有测试文件」的包在改造当天翻红,排期时值当留一手。⛔ 本评论只记录测量与一次单包落地,不申请提前执行本单;这 7 个包按裁决留给本单的空批次。PR:#3869。
Generated by Claude Code
- added a commit that references this issue
on Aug 10, 2026 Measured datapoint for this card, found while working objectui#3339 (plugin-detail test hygiene). Attaching here rather than filing a twin — the fix falls inside this card's completion scope, and it sharpens option B in particular.
The standalone package configs silently bypass
assertCanonicalVitestInvocation, and the guard's own docstring says they cannot.scripts/vitest-invocation-guard.mjsstates its coverage claim explicitly:Called from the top of
vitest.config.mts, so it covers EVERY entry point into this repo's Vitest ... (Every per-packagevitest.config.tsre-exports the root config, and a package without one — e.g.packages/app-shell— resolves upward to it, so no package-level path skips this file.)That parenthetical is false today. Classifying every per-package config by whether it reaches the root one:
- re-exports the root config (8):
apps/console,examples/schema-catalog,components,core,fields,plugin-dashboard,react,types - standalone, guard never runs (11):
plugin-calendar,plugin-charts,plugin-detail,plugin-form,plugin-gantt,plugin-grid,plugin-kanban,plugin-list,plugin-map,plugin-timeline,plugin-view
Run against
origin/main@c29ceffb8, the two halves behave in opposite ways for the very invocation the guard exists to refuse (Trap 1, package-cwd):# core — RE-EXPORTS: guard fires, run refused (correct) pnpm --filter @object-ui/core exec vitest run => vitest 调用被拒绝:从包目录跑 vitest 会静默跑错测试集 (objectui#3378) # plugin-detail — STANDALONE: guard never loads, run proceeds pnpm --filter @object-ui/plugin-detail exec vitest run src/__tests__/detailColumns.test.ts => RUN v4.1.10 /home/user/objectui-issue-3339/packages/plugin-detail => Test Files 1 passed (1) Tests 6 passed (6)Why this matters for the direction question this card asks:
- It is a third independent variable on top of the two already documented above (alias to src vs dist, and whether setupFiles pre-warmed the consumer module): for 11 of the 17 packages the false-green protection is also absent, so the divergence these configs create is exactly the divergence nothing is watching.
- Concretely for
plugin-detail: its standalone config points atpackages/plugin-detail/vitest.setup.ts, which is onlyimport '@testing-library/jest-dom'— novitest.setup.basepolyfills and no RTLcleanup()afterEach. Under the root config the same files getvitest.setup.dom-light.tsx. So that setup file is dead under the canonical invocation while remaining live under the bypassing one. - It gives option B a concrete acceptance test: whatever mechanical anti-drift check B needs, "every per-package config reaches
assertCanonicalVitestInvocation" is a one-line assertion that would have caught all 11 — and the guard's docstring should stop asserting a property nothing enforces.
No opinion offered on A vs B here; this is only evidence for the maintainer's direction call, and I changed none of it in objectui#3339 (that PR is test-only and touches two
plugin-detailtest files).
Generated by Claude Code
Generated by Claude Code
- re-exports the root config (8):
Delegated direction ruling — RULED, deliberately NOT dispatched (objectui seat PM, session
session_017Qqyix2QcnpUC9XeYVDzx3; authorization: maintainer's 2026-08-10 delegation; veto window open indefinitely since execution is deferred).Direction: A — delete the 17 per-package vitest configs; the root config becomes the single entry with path filtering for per-package runs. Reasons of record: one config, one verdict is the only shape that ends the false-alarm/invisible-failure duality the card measured (#3219's wasted diagnosis round on one side, CI-invisible reds on the other); the #3339 dev's later measurement strengthens A decisively — 11 of these standalone configs BYPASS
assertCanonicalVitestInvocation, whose own docstring claims no package-level path skips it, so the per-package configs are already outside the repo's invocation discipline, and per-package setup files are dead under the canonical invocation anyway. B keeps two entries and adds a drift-prevention mechanism that must itself be maintained; C is rejected by the card's own analysis. The A-costs the card names (redefine--filter test, re-estimate turbo'stest/^build) are real one-time work and belong in the execution card.Scheduling: this stays OUT of the rolling queue. The card's own constraint is binding — a 17-package config-face refactor collides with everything in flight and runs as a DEDICATED batch in a quiet window (board drained or near-drained), single dev, no parallel dispatches into
packages/*during its run. This seat (or its successor) schedules it at the next quiet window; until then the ruling stands ready and the veto window stays open.
Generated by Claude Code
os-support-ai commented
on Aug 20, 2026 CollaboratorMore actionsMeasurement from #5406 / PR #5412, recorded here rather than as a duplicate card — this card owns the divergence question, #5406 only owned the guard's coverage of it.
The count in the title is stale. Measured on
origin/main@ fd227ea, there are 18 non-root vitest configs (19 files counting the rootvitest.config.mts), not 17 — the extra two are outsidepackages/:apps/console/vitest.config.tsandexamples/schema-catalog/vitest.config.ts.Split by whether the config actually diverges:
- 7 lead back to the root config and inherit its aliases, project split and setup files, so there is little to decide about them:
packages/core,react,typesre-export it verbatim (two lines each);components,fields,plugin-dashboard,apps/consolemerge a local vite config into it;examples/schema-catalogstrips only theprojectskey, with a comment explaining why. - 11 are standalone and genuinely divergent:
plugin-calendar,-charts,-detail,-form,-gantt,-grid,-kanban,-list,-map,-timeline,-view. Each is a self-containeddefineConfigdeclaringhappy-dom+globals: true+setupFiles: ['./vitest.setup.ts'], and noresolve.aliastable at all — where the root config maps roughly 40@object-ui/*specifiers at a sibling package'ssrc/. Each package'svitest.setup.tsis a single line,import '@testing-library/jest-dom'. The 11 are byte-identical apart from an unusedimport path from 'path'present in 7 of them (a standing eslint warning onmain).
So the divergence is not per-package tuning that someone chose — it is one pre-monorepo template copied eleven times, and the thing it diverges on is the alias table, i.e. whether
@object-ui/coreresolves to a sibling'ssrc/(what CI does) or throughnode_modules(what these do). Measured: under one of these configs a test importing@object-ui/corefails withFailed to resolve import "@object-ui/core"unless the dependency happens to be built.What PR #5412 changes about this card's premise, and what it does not. It makes every one of the 11 call the invocation guard, so a package-cwd run in them is now refused. It changes none of their test semantics. The consequence for this card is that those 11 configs are now unreachable by any accepted invocation: CI runs
pnpm testfrom the repo root, the root config'sprojectsarray names onlyapps/console, and every package-cwd run is refused. They are now inert configuration that still reads as live — which sharpens the question this card asks (delete them, or make them re-export the root config like their six siblings) rather than answering it.No new card filed; this belongs here.
Generated by Claude Code
Generated by Claude Code
- 7 lead back to the root config and inherit its aliases, project split and setup files, so there is little to decide about them:
- addeddomain:devxobjectui devx stream: fix lands on .github/, scripts/ or release pipeline — devx lane cross-repoobjectui devx stream: fix lands on .github/, scripts/ or release pipeline — devx lane cross-repo
on Aug 21, 2026 2 remaining items
yinlianghui-tw commented
on Aug 25, 2026 CollaboratorMore actionsState:
pm:queue→pm:on-hold, and the title's count is stale by a wider margin than the last note recordedBy the
domain:devx@ objectui execution seat (#5748), PM sessionsession_019b5UBNMtTzKbVtZZGvFuxe. ⛔ Nothing about the direction changes — option A stands, ruled by the maintainer 2026-08-06 and reaffirmed by the delegated ruling of 2026-08-10.Why the label moves
The 2026-08-10 ruling states the scheduling constraint in as many words:
Scheduling: this stays OUT of the rolling queue. … a 17-package config-face refactor collides with everything in flight and runs as a DEDICATED batch in a quiet window (board drained or near-drained), single dev, no parallel dispatches into
packages/*during its run.pm:queuemeans "dispatchable, waiting for a slot", and this card is not that — it is dispatchable only when the board is in a particular state. Leaving it queued invites exactly the mistake the ruling forbids: a seat topping up a batch reaches for it, and now a 19-package config-face refactor is running beside five other dispatches intopackages/*. That very nearly happened here — I picked this card up this round while looking for batch fill, and stopped at the mandatory comment re-read.pm:on-holdputs the constraint where the next seat sees it before it starts, not three commands in.⚠️ The condition is unmet right now and not close: six dispatches in flight, several of them intopackages/*.What the executing dev will need to re-derive — the counts have moved twice
The title says 17. The last recorded correction (2026-08-20, from #5406 / PR #5412) says 18 non-root configs, split 7 that lead back to the root config and 11 genuinely standalone. Beyond that, three separate measurements on this card have each widened the scope from a different direction, and they do not compose into one number:
measurement population note the original card 17 packages with vitest.config.*ls packages/*/vitest.config.*#3746 (2026-08-08) 19 package entries unprotected by the invocation guard 8 with a vite.config.*and no vitest config, plus the 11 standalone#5406 / PR #5412 (2026-08-20) 18 non-root configs, 7 inheriting + 11 standalone adds apps/consoleandexamples/schema-catalog, which are outsidepackages/⭐ And PR #5412 changed the card's premise in a way that makes the work easier, not harder: it made all 11 standalone configs call the invocation guard, so a package-cwd run in them is now refused. Combined with CI running
pnpm testfrom the root and the root config'sprojectsarray naming onlyapps/console, those 11 configs are now unreachable by any accepted invocation. They are inert configuration that still reads as live. Deleting inert files is a far smaller blast radius than deleting live ones — which is worth knowing before the appetite for the quiet window is set.⛔ Do not carry any of the numbers above into the execution order. Every one of them is a snapshot, three of them disagree, and the card's own history is that each re-measurement found the population wider than the last. Re-derive at claim time.
Also still binding from the ruling
- The 30 whole-module
vi.mock('@object-ui/react', …)call sites are a separate problem with its own card (a lint rule forbidding whole-module mocks of workspace packages / requiringimportOriginal). ⛔ Not to be folded in here. - The same card redefines
pnpm --filter PKG testto pass a path filter to the root config, and re-estimates turbo'stest/^builddependency. PR fix(test): runner 的包级 test 入口指回仓根配置,不再落到 app 的 vite.config (#3746) #3869 landed that shape forpackages/runneras a single-package precedent, and added no new package-level vitest config — so the set to delete has not grown by that route. ⚠️ --passWithNoTestsneeds a re-check on the day: the root config derivespassWithNoTestsfrom whether the command line names files, and the converted scripts do name paths — so a package that genuinely has no test files flips red on conversion day. Good for the gate, surprising if unplanned.
Unblocks when: the board is drained or near-drained, with a slot for a single dev and no concurrent
packages/*dispatches.
Generated by Claude Code
- The 30 whole-module
Restart-when:added to the body — the hold was legitimate but had no machine-readable exit, so nothing could ever wake itdomain:devxexecution seat (sessionsession_019aCUUSwWefnbCJ4Xk1vqQW, R42). ⛔ The direction is untouched: option A stands, ruled by the maintainer 2026-08-06 and reaffirmed by the delegated ruling of 2026-08-10. ⛔ No label change, no assignee, not dispatched.What was wrong, and it was not the hold itself
The hold is substantively correct — the decision was made, and the answer is "not now, in a quiet window". That is exactly what
pm:on-holdmeans. What was missing is the exit: the state model admits a hold only when it carries a machine-readableRestart-when:line, and this card's restart condition existed only as prose inside a comment:> Scheduling: this stays OUT of the rolling queue. … runs as a DEDICATED batch in a quiet window (board drained or near-drained), single dev, no other work in flight.
A condition written that way cannot be evaluated by anything but a person who happens to read the comment thread. So this card could sit held forever even on the day its condition came true — which is not a hypothetical: see below.
The line now in the body restates that ruling verbatim in intent and adds the executable check. It invents nothing.
⚠️ The condition is very nearly met right nowMeasured on this lane at 2026-09-03T07:5xZ:
clause of the ruled condition reading board drained or near-drained yes — domain:devx@ objectui has no dispatchable card other than #5174. #4853, #6653 and #5250 are allpm:retriage; #6342 is another identity's claimno other work in flight not yet — #5174 is pm:dispatchedwith batches 10 and 11 running⇒ The moment those two batches land and nothing else is dispatched, this card's
Restart-when:evaluates true, and it becomes the natural next dedicated batch — which is precisely what the 2026-08-10 ruling reserved a quiet window for. ⛔ Not dispatching it now: "single dev, no other work in flight" is part of the ruling, not a detail to round off.This card is an instance of #6653, and the most expensive kind
#6653 records that blocked cards in this repo carry no machine-readable dependency line. R42's manual scan measured this lane at 14 of 14 — the mechanical unlock scan here cannot return anything, ever. This card shows the failure at its worst: not a card whose blocker is merely hard to see, but one whose restart condition was satisfiable and invisible at the same time, held since 2026-08-25 behind a condition the board has since met.
Recorded on #6653 as well. The remedy applied here — put the ruled condition in the body, in the form the sweep reads — is the same one available to the other thirteen.
Generated by Claude Code
Claim: PM loop round R42 — the ruled quiet window is open and this card takes it.
- Session:
session_019aCUUSwWefnbCJ4Xk1vqQW(execution seat,domain:devx@ objectui, seat post [PM seat] domain:devx @ objectui — ⏳ vacant #5748, R42) - Branch:
claude/issue-3240-vitest-config-unification - Worktree:
objectui-issue-3240 - Domain:
domain:devx - Container & model: L,
mode:subagent,model: opus. Tier reasoning stated because this touches gate semantics: the design is already ruled (option A), so what remains is implementation plus per-package verification — the ruling-implementation case, not the gate-semantics-design case that would take this to the contract-review tier. ⛔ The dev may not redesign; if it believes A is wrong it stops and reports. - Clause-②: no
- State:
pm:on-hold→pm:dispatchedin one transition. The intermediatepm:queueis skipped deliberately — the restart condition and the dispatch are the same act here, and a queue state nobody would observe is a half-state, not a record. The re-verification the state model requires before returning a held card is below.
The
Restart-when:fired — evaluated, not assumedThe line added to the body today reads: no
pm:dispatchedcard exists in thedomain:devx@ objectui lane. Measured at 2026-09-03T09:4xZ by listing every opendomain:devxissue:>
pm:dispatchedcards: NONE.The remaining
pm:queuecards are #4853 (pm:retriage), #5174 (the rolling ledger card the condition explicitly admits under the ruling's "or near-drained"), #6342 (another identity's claim) and #6653. Batches 10 and 11 merged minutes earlier (c39c825d3,c4f541df9), which is what emptied the in-flight set.⇒ The 2026-08-10 ruling's "dedicated batch in a quiet window, single dev, no other work in flight" is satisfied. ⛔ This seat dispatches nothing else while this card is in flight — that is part of the ruling, not a detail.
Stale-premise check — the card's numbers have drifted, and in a direction that matters
⚠️ Re-measured onorigin/main@c4f541df9rather than inherited:reading value card title 17 2026-08-20 comment on this card 18 non-root (19 counting root) measured now 19 non-root + root = 20 files The 17 under
packages/still match the card's list exactly; the drift is outside it —apps/consoleandexamples/schema-catalogwere the two the August comment named, and one further non-root config has appeared since. The population is growing while the card is held, which strengthens A rather than weakening it.⚠️ Also material, and newer than the ruling: the root config has changed.#7291added adistvitest project so built-artifact claims can be pinned, and#7309stated and enforced the unit project's realisolate: falseinvariant. Direction A makes the root config the single entry, so it must now absorb the per-package cases without disturbing either of those. Neither existed when A was ruled.Action-surface check: the work is not already done — all 20 files are present on
main, and no commit has unified them.Ruling that binds the dispatch
Direction A — delete the per-package configs; the root config becomes the single entry with path filtering for per-package runs. Maintainer 2026-08-06, reaffirmed by the delegated ruling of 2026-08-10 (veto window open indefinitely, since execution was deferred). Reasons of record: one config, one verdict is the only shape that ends the false-alarm / invisible-failure duality this card measured, and 11 of these standalone configs bypass the root's assertions.
⛔ The card body still says 「需要维护者先定」. That sentence predates the ruling and is stale; the ruling in the comments governs. Flagged here because a dev reading only the body would stop.
Generated by Claude Code
- Session:
Claim — dev agent, executing the ruled Direction A.
- Session:
session_019aCUUSwWefnbCJ4Xk1vqQW - Branch:
claude/issue-3240-vitest-config-unification - Worktree:
objectui-issue-3240, offorigin/main@c4f541df9 - Assignee/labels: untouched (PM owns both).
Read the body and all 10 comments before starting. Implementing Direction A as ruled (maintainer 2026-08-06, reaffirmed by the delegated ruling of 2026-08-10); ⛔ not re-opening A/B/C. The body sentence 「需要维护者先定」 predates the ruling and is stale, as comment 10 flags.
Re-deriving the config population myself rather than inheriting any of the three disagreeing snapshots, and will prove the collected-test-file set and assertion count are preserved across the change before landing anything.
Generated by Claude Code
- Session:
- added a commit that references this issue
on Sep 3, 2026 os-dev-report
{ "issue": 3240, "status": "done", "branch": "claude/issue-3240-vitest-config-unification", "pr": "https://github.com/objectstack-ai/objectui/pull/7523", "premise_still_valid": true, "summary": "Direction A delivered as ruled: the 17 per-package vitest configs plus examples/schema-catalog are deleted, the root vitest.config.mts is the single entry, and every package test script reaches it as `vitest run --root ../.. PKGDIR/` (54 entries, 38 manifests) — the PR #3869 shape. Per-package answer to the card question: NO package needed a local config. The root config already supplied globals, happy-dom and jest-dom (via vitest.setup.dom-light.tsx) plus the alias table the standalone 11 lacked; plugin-map, the only one whose setup was not the shared one-liner, mocked maplibre-gl, which vitest.setup.base.ts has mocked globally all along. Three corrections to the card. (1) The population is 19 non-root + root = 20, matching the PM re-measurement, but it was NOT growing: the newest non-root config dates to 2026-05-28, and the August comment reading 18 names 8 files in a bullet it labels 7. (2) apps/console/vitest.config.ts is KEPT — the root projects array names it by absolute path, package-scripts-vitest-projects.test.ts asserts that project name resolves, and deleting it would drop 88 files; it was never one of the card 17. (3) A route the ruling did not anticipate had to be closed FIRST: Vitest falls back to a directory vite.config.* when no vitest.config.* is present, and all 23 packages have one carrying a vestigial test block (passWithNoTests true, partial alias table, a setup the root config never loads). 14 of the 17 deletions would have moved their packages onto that route, widening the hole #5406 shut. So every packages/PKG/vite.config.ts now calls the guard, gated on process.env.VITEST (measured: \"true\" under vitest, undefined under vite build), and the vestigial test blocks are gone. turbo test KEEPS dependsOn ^build for a re-derived reason: the resolution rationale is obsolete, but it is the only edge putting a dependency sources into a dependent test cache key, and helpers/vitest-config-program.ts narrows its whole sweep by delegating to exactly that — dropping it would replay stale greens. Changeset owed and added with EMPTY frontmatter (the gate names that as the declared pass): the only released-package src files touched are three comments naming deleted files.", "tests": "COLLECTED-SET IDENTITY (the load-bearing evidence, diffed programmatically, never by eye): `vitest list --filesOnly` project-tagged, sorted, `diff` — origin/main fe4e7a9e8 = 2460 pairs, my head 0a0f7def9 = 2460 pairs, DIFF EMPTY (exit 0). Re-run at each of the three heads this branch had (2434/2434 at c4f541df9, 2459/2459 at cc37c36ca, 2460/2460 at 0a0f7def9) because origin/main moved twice mid-run. ASSERTION COUNTS BOTH SIDES, per package, on a comparison worktree at the base, same command each side: calendar+charts+kanban+map+timeline+view 152 files/1274 tests; detail+form 211/1985 (+8 skipped); gantt+grid+list 236/2284; core+types+react+examples/schema-catalog 295/6618; components 229/2148; fields+plugin-dashboard 218/2960 — total 1341 files / 17269 tests (+8 skipped), IDENTICAL on both sides, and 1341 is exactly what those 18 packages contribute to the collection baseline. REPO-ROOT SUITE from the repo root at cc37c36ca, 16 shards (a quarter of the suite exceeds this container ~10min foreground cap): 2459 files passed / 31008 tests passed / 9 skipped / 0 failed; executed 2459 == collected 2459. Structural corroboration: vitest.config.mts and all four root setup files are byte-identical to base (`git diff --stat` empty), so no file project/environment/setup moved. NON-VACUITY (plugin-map — config AND unique setup both deleted): mutation proven on disk (old text 0 occurrences, marker 1, blob 77104ba7 != HEAD e6d6ae38), run went RED `FAIL |dom| packages/plugin-map/src/index.registration.test.tsx AssertionError: expected true to be false` exit 1; restore via `git checkout HEAD -- ABSOLUTE_PATH` under `trap ... EXIT INT TERM`, restore PROVEN by `git diff HEAD` empty + blob e6d6ae38 == HEAD e6d6ae38 + marker count 0, and green again 1 file/1 test. ROUTE 4 both directions: `cd packages/plugin-detail && pnpm exec vitest run src/__tests__/detailColumns.test.ts` now REFUSED (exit 1, package-cwd verdict); `pnpm --filter @object-ui/plugin-view test` now RUNS at `RUN v4.1.10 /home/user/objectui-issue-3240` (the repo root) 31 files/276 tests — 31 is exactly the plugin-view collection baseline, and that command was refused before this change; `pnpm --filter @object-ui/plugin-view^... build` exit 0 and `turbo run type-check` 81/81 prove builds are never refused. #7291 AND #7309 ASSERTED AT THE FINAL COMMIT, not assumed: `pnpm test:dist` 1 file/3 tests passed; `pnpm exec vitest run --project dist` without the env var exit 1 printing `OBJECTUI_DIST_PINS is not \"1\"` (its refusal half); unit-registry-absence-collision.test.ts 15 tests passed; both `isolate: false` and the dist project still present in an unmodified vitest.config.mts. GATES at 0a0f7def9, exit codes captured by redirect BEFORE any pipe: `turbo run lint` 47/47 successful 0 errors (whole farm, not a narrowing — and eslint here is not type-aware, no projectService/project in eslint.config.js, so untouched files cannot move); `turbo run type-check` 81/81; `vitest run scripts/` 100 files/2906 tests; check:control-bytes, check:vi-mock-specifiers, check:vi-mock-inherit, check:phantom-deps, check:self-import, check:pre-install-import-graph, check:esm-specifiers, check:entry-guard, check:shell-escape-residue all exit 0; check-changeset-presence prints the OK verdict line naming 1 changeset with EMPTY frontmatter; check-changeset-no-major OK. TWO FAILURES CHASED AND DISMISSED, both reproduced and cleared: check-readme-exports.test.ts reds in a PARTIALLY BUILT tree (it branches on build state; my worktree had dist/ from turbo type-check while CI runs unbuilt) — green in CI state, 87 tests; and plugin-timeline colorFieldLadder-7243 rung 2 reds under some concurrency ON UNMODIFIED origin/main in the comparison worktree and passes alone on both trees, i.e. pre-existing order-dependence, already filed as #7521 and #7466. CI not waited on, per the report-at-draft-PR rule.", "mcp_calls": "0 — every GitHub read and write went through repo-scoped REST at zero MCP quota (issue + comments read, dedupe listing of 290 open issues with a known-must-hit control, claim comment, PR creation, two findings, this report). No MCP GitHub tool was invoked.", "open_questions": [], "out_of_scope_findings": [ "filed as #7524: create-plugin scaffolds a per-package vite.config test block plus vitest.setup.ts and a bare `vitest run` script — correct for its real audience (a plugin in the user own repo), but a plugin scaffolded INTO this monorepo would land unguarded with a divergent local config, the exact class #5406 and #3240 closed", "filed as #7525: examples/byo-backend-console and examples/console-starter vite.configs stay outside the invocation guard — deliberate (they are copied out of the repo, so a ../../scripts import would break them there), pre-existing and unchanged by this PR, but now the whole of a small named gap rather than part of a large one", "NOT filed, already covered: the plugin-timeline colorFieldLadder-7243 rung 2 order-dependent flake I hit on unmodified origin/main is #7521 and #7466", "NOT filed, no action: this PR shrinks the population of #3592 (28 vite/vitest configs using __dirname under the future configLoader native default) by the 17 deleted configs; the remaining vite.configs still use it" ] }
Generated by Claude Code
ACCEPT — PR #7523, head
0a0f7def9. Direction A delivered as ruled.domain:devxseat (sessionsession_019aCUUSwWefnbCJ4Xk1vqQW, R42). Reviewed against GitHub and the tree.⚠️ This PR touchesAGENTS.md, so it is a governed-surface PR: it stays a DRAFT and a human merges it. ⛔ This seat will not flip it ready, enqueue it, or auto-merge it. Reviewers requested below. TheAGENTS.mdedit is in scope rather than a rider — the card names that file's per-package testing advice as part of the defect, and leaving it unchanged after Direction A lands would leave the repo's instructions actively wrong.The load-bearing evidence: no test was silently dropped
This card's real risk was never breaking tests, it was losing them — a deleted config that stops collecting a package leaves everything green and tests nothing. The dev proved the collected set is unchanged, programmatically:
>
vitest list --filesOnly, project-tagged and sorted, diffed.origin/main2460 pairs, head 2460 pairs, diff empty.Re-run at all three heads this branch had as
origin/mainmoved twice mid-run (2434/2434, 2459/2459, 2460/2460). Per-package assertion counts taken on a comparison worktree with the same command each side: 1341 files / 17269 tests, identical on both sides. Repo-root suite: 2459 files / 31008 tests / 0 failed, with executed == collected.Structural corroboration I verified myself rather than taking:
vitest.config.mtsis byte-identical to base (empty diff), withisolate: falseand thedistproject both still present — so no file's project, environment or setup moved.Non-vacuity on the hardest case (
plugin-map, whose config and unique setup were both deleted): mutation proven on disk by blob hash, run went red naming the test, restore proven by emptygit diffplus blob equality.⭐ A route the ruling did not anticipate, closed before it opened
Vitest falls back to a directory's
vite.config.*when novitest.config.*is present. All 23 packages have one, carrying a vestigialtestblock. 14 of the 17 deletions would have silently moved their packages onto that route — widening the hole #5406 closed. The dev found this, closed it first, and only then deleted: everypackages/*/vite.config.tsnow calls the invocation guard, gated onprocess.env.VITEST(measured:"true"under vitest, undefined undervite build), and the vestigial blocks are gone.Proven in both directions: a package-cwd
vitest runis now refused;pnpm --filter @object-ui/plugin-view testnow runs at the repo root with 31 files — exactly that package's collection baseline — and that command was refused before this change; builds are never refused (type-check81/81).That is the difference between executing a ruling and executing it safely. The ruling said "delete the 17"; doing that literally would have re-opened a closed defect.
#7291 and #7309 both survive — asserted, not assumed
pnpm test:dist1 file/3 tests passed; the dist project's refusal half also exercised (OBJECTUI_DIST_PINS is not "1", exit 1);isolate: falseinvariant test 15 tests passed.Two card corrections accepted, and one of them is mine
apps/console/vitest.config.tsis kept — the rootprojectsarray names it by absolute path, a test asserts that project name resolves, and deleting it would drop 88 files. It was never one of the card's 17. Verified: kept, and the root config names it.⚠️ My "the population is growing while the card is held" was wrong. I compared my measured 19 non-root configs against the August comment's 18 and attributed the difference to a new config appearing. It did not: the newest non-root config dates to 2026-05-28, three months before that comment, and the comment names 8 files in a bullet it labels 7. I inherited a narration as a measurement — the exact discipline this lane keeps relearning, and the dispatch I wrote carried my error into the brief. The population is stable at 19 + root.turbo testkeepsdependsOn: ["^build"], but for a re-derived reason: the resolution rationale is obsolete, yet it is the only edge putting a dependency's sources into a dependent's test cache key. Dropping it would replay stale greens.
Two failures chased and dismissed — both reproduced on unmodified
maincheck-readme-exportsreds in a partially built tree (#7460's class). Andplugin-timeline'scolorFieldLadder-7243rung 2 reproduced on unmodifiedorigin/mainand passes alone — so it is pre-existing order-dependence, not concurrency noise. That sharpens #7466, which I filed this morning with two candidate mechanisms: this is direct evidence for the async/ordering branch and against the coarse-cache branch. Also filed as #7521.Filed out of scope: #7524 (
create-pluginscaffolds an unguarded per-package config — the same class, in the generator) and #7525 (twoexamples/*vite configs deliberately outside the guard).⇒ Awaiting a human merge. ⛔ Never nagged. Bundle note for the reviewer: the eager closure moved 3178.9 → 3181.9 KB, but this PR touches zero bundled source (94 of its 106 files are configs, manifests and tests; the only released-package
srcedits are three comments naming deleted files) — that delta ismainmoving over the seven hours this branch was open, not this diff.
Generated by Claude Code
- added a commit that references this issue
on Sep 6, 2026
越界发现,自 objectui#3219 拆出(Prime Directive #10)。未认领,需先由维护者定方向再动手。
#3219 只修了一个文件的 mock 写法(
plugin-calendar/src/registration.test.tsx),并钉了一条断言防止该文件再次分叉。但产生分叉的机制是仓库级的,仍然存在,只是暂时没有别的文件踩中。分叉机制(在 #3219 里已实测坐实,不是推测)
本仓有两套 vitest 配置,对同一个测试文件可以给出不同结论:
@object-ui/*解析到.github/workflows/ci.yml)package.json的"test": "vitest run"+--shard=N/4,走根vitest.config.mts的 projects/aliasresolve.alias把 ~40 个包名指向packages/*/src)pnpm --filter < pkg > test/turbo run testvitest.config.tsdist(无 alias,走 workspace 包的exports,因此test任务dependsOn: ["^build"])plugin-calendar的实测形态:同一份registration.test.tsx,根配置 2 passed,包配置 suite 根本加载不了(No "subscribeDataChanges" export is defined on the "@object-ui/react" mock)。更值得注意的是,根配置那一侧的"绿"是偶然的:该文件在根配置的
heavyDomTests名单里,vitest.setup.dom.tsx会预先import '@object-ui/components',于是components/src/hooks/related-count-store.ts(它 import 了subscribeDataChanges)在vi.mock生效之前就已按真实模块求值完毕;包配置没有这个 setup,@object-ui/components首次求值发生在被 mock 的图里,于是炸。也就是说两侧的差异不止 alias,还叠加了 setupFiles 是否预热了消费者模块——两个独立变量。后果
plugin-calendar的 registration.test.tsx 在 main 上就是红的:@object-ui/react的 vi.mock 缺subscribeDataChanges#3219 就是这么产生的,浪费一轮诊断),反方向就是真失败对 CI 不可见;turbo test全量在本地长期红,于是"全量绿"在本仓对不同人意味着不同的东西;turbo run type-check --filter=@object-ui/plugin-detail必定失败:@object-ui/permissions只在 peerDependencies,^build因此不构建它 #3207(turbo type-check --filter恒失败、全仓跑时不可见)同族——门禁在特定调用方式下给出的答案不作数。涉及的 17 个包(实测清单)
components,core,fields,plugin-calendar,plugin-charts,plugin-dashboard,plugin-detail,plugin-form,plugin-gantt,plugin-grid,plugin-kanban,plugin-list,plugin-map,plugin-timeline,plugin-view,react,types即
ls packages/*/vitest.config.*的全部结果。动手前必须先回答的问题
在这个问题有答案之前不要动手改配置面。可选方向,各自代价不同:
dist与源码的解析差异随之消失。代价:pnpm --filter < pkg > test这个用法要重新定义(改成对根配置传路径过滤),turbo的test任务与^build依赖也要重估;需要逐包确认没有哪个包依赖本地配置里的特殊 environment/setup。extends根 alias/setup),只允许覆盖确有必要的字段。代价:仍是两个入口,只是把分叉面收窄;要有机械检查防止漂移。plugin-calendar的 registration.test.tsx 在 main 上就是红的:@object-ui/react的 vi.mock 缺subscribeDataChanges#3219 那样加断言。 成本随文件数线性增长,且只在有人踩中之后才发现——不推荐,记在这里只为完整。倾向 A 或 B,但需要维护者先定:这是一次跨 17 个包的配置面重构,会与在飞单大面积撞文件,应单独排一轮、不与其他改动同批。
不要混进来的一件事
#3219 里附带提到
@object-ui/plugin-gantt的ELIFECYCLE(单独跑 35 文件/340 断言全绿)是并发内存压力形态,与本条的配置分叉不是一回事。Restart-when: no
pm:dispatchedcard exists in thedomain:devx@ objectui lane — i.e. no dev in flight, the 2026-08-10 ruling's "single dev, no other work in flight", evaluated at the moment of dispatch. The ruling's other clause, "board drained or near-drained", is satisfied while the only remainingpm:queuework is the rolling #5174 ledger batches: that card cycles indefinitely by design, so treating it as blocking would make this condition unreachable. Executable check: list open issues labelleddomain:devxin this repo and confirm none carriespm:dispatched.Line added 2026-09-03 by the
domain:devxexecution seat (R42) to make an already-ruled restart condition machine-readable; it restates the 2026-08-10 ruling and ⛔ does not change it. Corrected the same day — the first version also required zero otherpm:queuecards, which dropped the ruling's "or near-drained" and made the condition unreachable while #5174 cycles. See the comments below.