Uh oh!
There was an error while loading. Please reload this page.
refactor(10 packages)!: burn ledger batch 7 — 14 symbols stop wearing spec names - #3224
Conversation
…-charts,plugin-form,providers,runner,sdui-parser)!: burn ledger batch 7 — 14 symbols stop wearing spec names objectui#3161, objectstack#4115 ledger batch 7 — the long tail, one or two entries per package. All ten packages leave the ledger (17 -> 3 collisions, 11 -> 1 packages; what remains is batch 8's @object-ui/types trio, untouched). Verdicts, per SYMBOL rather than per cluster: @object-ui/fields - isFileIdToken -> re-export. The local copy was character-for-character identical to the spec's function while its comment claimed to "mirror" it, so every behaviour test passed against it and only reference identity could tell the two apart (objectui#3003, re-proved on isAggregatedViewContainer in objectui#3169). The regex is a wire decision: widen it server-side while a copy here keeps the old bound and every new id reads as "not a reference". - FieldWidgetProps -> FieldWidgetComponentProps (spec's is the DECLARED widget-plugin props contract). The two disagree on the validation-message key — spec `error`, objectui `errorMessage` — filed as objectui#3222; the `[key: string]: any` that hid it is objectui#3221. @object-ui/layout - PageHeaderProps -> PageHeaderComponentProps, the name app-shell already chose for the same split in objectui#3169, reused rather than re-invented. - Page -> PageNodeRenderer. The spec's Page is the authored page DOCUMENT; this renders types' PageNodeSchema, the SDUI NODE that objectui#3074 had already renamed off PageSchema. Unregistered and `& any`: objectui#3223. @object-ui/plugin-detail - FeedFilterMode -> re-export (a hand copy of the spec enum, in a file that already imported FeedItemType from the same spec subpath). - ObjectFieldLike -> ObjectDefFieldLike. The spec's is the i18n duck type translateObject walks, and it ends in `[key: string]: any` — deriving would trade a precise three-key layout contract for a bag (objectstack#4075 from the spec's side). @object-ui/plugin-grid - ColumnSummaryConfig -> ColumnSummarySetting, derived as NonNullable<ListColumn['summary']>. The spec owns that name for the OBJECT form ALONE, so the local declaration wore it for a strictly WIDER type: code written here accepted 'sum', the same name imported from the spec rejects it. The eleven-member union beside it is the spec's ColumnSummary enum now instead of a hand copy under a comment promising lockstep, which turns the total Record<ColumnSummaryType, string> label map into the thing that reports a divergence. - isMultiValueField -> hasMultiValueShape, delegating to the spec function it used to shadow. The two had different PRECONDITIONS (the spec's requires a def with a `type`; this one is called with `objectFields?.[name]`), which is worse than a plain fork: an import swapped between the modules keeps compiling and changes behaviour only on the missing-field path. @object-ui/collaboration - RealtimeConfig -> RealtimeSubscriptionConfig (spec's is the app-level realtime declaration; this is one browser socket's dial settings). Zero shared declared keys — measured against the spec's own schema shape at runtime, because the spec declares that schema as a PASSTHROUGH object and a type-level key comparison against a bag answers nothing. @object-ui/plugin-charts - ChartConfig -> ChartContainerConfig. The one collision in this batch that had already produced ambiguity in read code: AdvancedChartImpl declared `config?: ChartConfig` meaning the shadcn per-series style map, lines from comments reading "Spec `ChartConfig.yAxis`" meaning the authored document. @object-ui/plugin-form - FormSection -> FormSectionContainer. Deliberately NOT batch 6's ListView exemption: ListViewProps.schema takes the spec-derived metadata as one prop, so that renderer restates nothing, while FormSectionContainerProps takes the same metadata FLATTENED into individual props — a rival vocabulary. `columns` stays `1|2|3|4` rather than binding the spec key, which accepts the string forms and normalises them in its own pipe; the pin fails if that changes. @object-ui/providers - Theme -> ThemePreference, derived from the spec's ThemeMode input plus the legacy 'system' spelling. Neither of the two shapes the triage note predicted: it was Theme['mode'] wearing Theme's name, i.e. a string union standing where the spec has a whole document. ThemeMode, the spec's own name for it, is a spec export too — the objectui#3169 rule again. @object-ui/runner - App -> RunnerApp (spec owns App for the authored application metadata AND the App.create() builder). A default export, so no consumer churn. @object-ui/sdui-parser - ValidationResult -> ManifestValidationResult, per the <what was validated>Validation<Error|Result> convention registered on objectstack#4115; core took SchemaNodeValidationResult in batch 4. Renames are by identifier boundary, per package, no global replace; not one string literal changed (the `form-section` CSS class, component keys, error names). Docs that named the renamed public types are updated with them (content/docs, skills/objectui). Verification: `--ledger` regenerated and set-diffed — removed is exactly these 14, added empty, @object-ui/types byte-identical. Guard green; turbo type-check 78/78 (full repo); full suite 834 files / 9746 tests passed, 0 failed; ESLint on changed files 0 errors. plugin-detail/plugin-grid/plugin-form gained a tsconfig.typetests.json chained off type-check (their test trees are still in TEST_DEBT, so nothing would have compiled the new pins — objectui#3181); collaboration gained a tsconfig.test.json with its first test file. Five mutation directions, all red: re-forking isFileIdToken byte-for-byte (guard by name+file, and the reference-identity assertion — while all 20+ behaviour assertions in file-value.test.ts stayed green); restoring the original hand-written Theme (guard); re-forking the derived ColumnSummaryType with one member dropped (the runtime spec-vocabulary test names the missing member, and the typetests pin fails); leaving a burned name in DEBT (rotting ratchet); unchaining a tsconfig.typetests.json (type-check coverage ratchet). A sixth records a known hole: renaming ThemePreference back to `Theme` while KEEPING the derivation passes the guard — derivation is derivation, batch 5's ConflictResolutionStrategy trap — and is caught only by the tripwire test, which is why every rename here has one. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PRJtkgUAaVG11FsJQbvZWA
The latest updates on your projects. Learn more about Vercel for GitHub. |
✅ Console Performance Budget
📦 Bundle Size Report
Size Limits
|
Uh oh!
There was an error while loading. Please reload this page.
os-zhuang
commented
Aug 2, 2026
PM 验收:通过,已放行合并队列四条门我独立核过,不是采信 PR 描述: 1. 台账 set-diff — 2. 一条新 3. 线上契约未被改名波及(批次 6 4. CI — 15 项 13 绿 2 skipped,零红; 顺带裁一下 open question A(spec 依赖边):按 A,接受
与批次 5 在 若某个包是按策略刻意保持 spec-free,在 objectstack#4115 指出具体是哪个,我改派回滚那一个——package.json 级改动,回退成本接近零,不值得为它挡住这批。 本批捎带的三个发现已分别处置
Generated by Claude Code |
Fixes#3161. objectstack#4115 台账燃尽第 7/8 批(长尾:9 包 14 符号)。
台账 17 条 / 11 包 → 3 条 / 1 包。十个包整包退出台账,只剩批次 8(objectui#3162,等 objectstack#4171)的
@object-ui/types三条,逐字节未动。判决一览(逐符号,不按簇)
fieldsisFileIdTokenfieldsFieldWidgetPropsFieldWidgetComponentPropslayoutPageHeaderPropsPageHeaderComponentPropspage:header节点(zod schema)layoutPagePageNodeRenderername/label/regions)plugin-detailFeedFilterModeplugin-detailObjectFieldLikeObjectDefFieldLiketranslateObject的鸭子类型(带[key: string]: any)plugin-gridColumnSummaryConfigColumnSummarySetting+ 派生ListColumn.summary的对象形态,本地那个是整个联合plugin-gridisMultiValueFieldhasMultiValueShape+ 委托type必填的分类函数collaborationRealtimeConfigRealtimeSubscriptionConfigtransport/subscriptions[])plugin-chartsChartConfigChartContainerConfigtype/xAxis/series)plugin-formFormSectionFormSectionContainername/pane/fields)providersThemeThemePreference+ 派生colors/typography)runnerAppRunnerAppApp.create()构造器sdui-parserValidationResultManifestValidationResult{valid, errors?, warnings?})跨包定名照批次 3/4 的判决执行,未另起炉灶:
PageHeaderComponentProps沿用 app-shell(objectui#3169),ManifestValidationResult沿用< 被校验的东西 >Validation< Error|Result >约定(objectstack#4115 评论、@object-ui/core批次 4)。值得写进方法学的四件事
一、
Theme的预判两个方向都不对 —— 它既不是「已解析的主题」也不是「授权 JSON」跟踪评论建议「先问 providers 的
Theme是运行时已解析的主题还是授权 JSON;两种情况都不需要新方言名」。实测是第三种:它是Theme['mode']顶着Theme的名字——一个字符串联合,站在 spec 一整份文档的位置上。这比一份漂了的副本更误导:下一个 session 读到@object-ui/providers的Theme会得出「ObjectStack 的主题是个字符串」的结论。而 spec 自己给这个概念的名字
ThemeMode也是 spec 导出(又一次印证批次 3 的「新名字必须先跑守卫」——显而易见的名字恰恰最容易已被占用),何况本地还多一个'system'(pre-spec 拼写,用户 localStorage 里存着的)。所以落在ThemePreference,并且派生:三个真实模式取自 spec 枚举,'system'显式留作本地成员。二、
ColumnSummaryConfig是一种新形态:顶着 spec 的名字,却是一个更宽的类型spec 的
ColumnSummaryConfig只是ListColumn.summary的对象形态({type, field?}),简写形态'sum'在 spec 里叫ColumnSummary,两者的并集才是summary接受的东西。本地那个声明顶着ColumnSummaryConfig的名字装了整个并集——同一个名字,一边接受'sum',另一边拒绝。没有任何测试能发现它,因为两边各自都是「对的」,只是在说不同的事。改名为
ColumnSummarySetting,并直接取NonNullable< ListColumn['summary'] >——由 spec 自己定义它接受几种形态。顺带把旁边那份守卫看不见的手抄件也拆了:十一个成员的聚合联合原本是手写的,注释承诺「与 spec 枚举保持同步」而没有任何东西执行这句承诺;现在它就是 spec 的枚举,于是TYPE_LABELS(一张Record< ColumnSummaryType, string >全键映射)变成了报告差异的地方——spec 新增一个聚合,得到的是一条指名道姓的编译错误,而不是一个渲染成空白的页脚单元格。三、又一次「注释声称是镜像,实际是逐字副本」——而这次副本的内容是一个线上决定
fields的isFileIdToken与 spec 的实现逐字符相同(/^[A-Za-z0-9_-]{1,64}$/),注释写着 "Mirrors the platform'sisFileIdToken"。与批次 3 的isAggregatedViewContainer同型:任何值比较、任何行为断言都会全绿,只有引用同一性能区分。测试里的闸门因此是expect(isFileIdToken).toBe(specIsFileIdToken)。为什么这一份特别值得共享而不是复制:那个正则是线上契约。服务端哪天放宽它(比如 id 超过 64 字符),本地副本仍按老边界回答「这不是一个引用」,于是 widget 会朝一个期待引用的后端提交 legacy inline blob——症状是缩略图坏掉,离一个正则十万八千里。
四、批次 6 的第一条方法学在这批双向各命中一次
Page(layout)与App(runner)「预计走改名或 ALLOW」——实测走改名成立,但理由与预判不同。isFileIdToken/isMultiValueField「若语义一致则 re-export」——一个 re-export、一个不能。isMultiValueField的本地版本与 spec 版本前置条件不同:spec 的要求type必填,本地那个的调用点是objectFields?.[p.name],例行传undefined。共享一个名字、却有不同的前置条件,比一份普通的 fork 更危险——两个模块之间调错 import 仍然能编译,只在「字段不存在」这条路径上改变行为。所以改名hasMultiValueShape,并把分类本身委托给 spec 的函数(原来是拿MULTI_OPTION_TYPES/MULTI_CAPABLE_TYPES自己重推那两行——一个自称 "consumed instead of restated" 的 helper 最后还是把规则 restate 了)。另外:
ChartConfig是本批唯一一个已经在读的代码里造成歧义的碰撞AdvancedChartImpl.tsx里config?: ChartConfig指的是 shadcn 的按序列样式表,而同一个文件上下几行的注释写着 "SpecChartConfig.yAxis"、"SpecChartConfig.showLegend" 指的是 spec 的图表文档。一个标识符、两个概念、一个文件。改名之后那些注释才第一次是对的。派生用的是哪一侧
ThemePreference与ColumnSummary*都取授权侧。ThemeModeSchema通过 schema 自己的_zod载体读(与packages/react/src/spec-input.ts、批次 6 的ViewTab同一手法),@object-ui/providers因此不需要 zod 依赖。批次 6 的第 3 条点名
FormSection/ChartConfig/RealtimeConfig是同一陷阱的候选——三条实测都不适用,理由各不相同:ChartConfig/RealtimeConfig与 spec 的同名导出根本是两个概念(没有可派生的东西);FormSection确实带两个.default(),但拦住绑定的不是它们,而是columns(见下)。为什么
FormSection走改名而不是批次 6 给ListView的 ALLOW判据不是「组件一律豁免」(批次 5 把
Field改成了FieldContainer),而是:这份声明会不会被下一个 session 读作 spec 那个概念的权威形状。ListViewProps.schema把 spec 派生的元数据整个当作一个 prop 收下,所以那个渲染器什么形状都没声明;FormSectionContainerProps则是把同一份元数据摊平成一个个 prop(label/description/collapsible/collapsed/columns),少了fields、name、pane、visibleWhen,多了渲染专用开关——这正是一份对手形状。而且这个词在本仓自己的文档里已经归 spec 那个概念了(plugin-form/README.md与插件文档都写 "specFormSection.pane",@object-ui/types把 spec 的类型 re-export 成SpecFormSection),文件头还写着 "Aligns with @objectstack/spec FormSection"——守卫文件头所说的「给下一个 session 埋的前提」。columns刻意不绑定 spec 的键:spec 的授权侧接受'1' | 1 | '2' | 2 | …并在自己的 pipe 里把字符串规约掉,而这里的gridCols按数字索引——绑过来会让组件收到'2'然后静默渲染成一列。规约属于解析授权元数据的接缝,不属于容器的 props。测试把这条钉住了:spec 哪天退掉字符串形态,钉扎报红,这个键就可以绑了。顺带记录的、未修的东西(都已单独立单)
FieldWidgetComponentProps的[key: string]: any。这是本批最锋利的一处:索引签名让这个类型对每个未声明的键都回答any,所以 spec 声明的required/error在本地「已经存在」——读它们合法、类型any、运行时恒undefined。一个声称拥有全部键的类型永远不可能被报告「缺了某个键」(objectstack#4075 的机制)。**parity 测试对这个符号原理上无效,守卫是唯一探测器。**测试里三条钉扎就是为了在FieldWidgetComponentProps的[key: string]: any让每个 widget prop 检查失效 #3221 删掉索引签名那天报红。error,objectui 的 widget 全部读errorMessage。照着 spec 写的第三方 widget 放进来不显示校验消息,且无人报错。方向需要拍板(objectui 跟随 / spec 跟随 / 按 ADR-0049 退掉这份没人执行的 schema),所以只记录。layout的PageNodeRenderer没有被任何地方注册也没有调用点(page键归@object-ui/components的PageRenderer),props 还是… & any——与any求交会吸收整个类型,是批次 6 记录的三种「注销编译期检查」写法之外的第四种。删还是接线,需要知道意图的人拍板。RealtimeConfigSchema是 passthrough(z.core.$loose),所以keyof SpecRealtimeConfig含string,任何K extends keyof Spec探针一律回答 true——批次 5 方法学第 7 条在 spec 侧的又一个实例(同批ObjectFieldLike是同类)。collaboration 的「两边零共享键」因此改成对着 spec 自己的 schema shape 在运行时比对,并用Record< keyof Local, true >让 tsc 保证本地键列表穷尽。未提 issue:这可能是 spec 为 transport 前向兼容的有意选择。验证
11 declared dialects, 3 untriaged collisions in 1 packages)。--ledger重生成后逐条 set-diff:removed恰为本批 14 条、added为空、其余包(@object-ui/types,批次 8)逐字节不变。turbo type-check78/78(全仓,含apps/console)。三个仍在TEST_DEBT的包(plugin-detail/plugin-grid/plugin-form)新增tsconfig.typetests.json并串进type-check,否则本 PR 的编译期钉扎不会被任何tsc读到——objectui#3181 / build(types): type-check this package's tests, so the spec-derivation guards actually run (objectstack#4074) #3009 那个形态;collaboration本来一个测试文件都没有,随首个测试补上tsconfig.test.json。check:type-check-coverage绿(22/37 compile their tests, 9 with a narrow type-assertion project)。vitest run834 文件通过 / 1 skipped,9746 用例通过 / 25 skipped / 0 失败。十个受影响包单独复跑 185 文件 / 1805 用例全绿。新增 10 个 tripwire 测试文件、30 条断言。.changeset/long-tail-spec-symbol-burn-down.md,十个包major(公开导出改名)。变异测试:五向报红,外加一个记录在案的洞
isFileIdToken改回逐字符相同的手抄件file-value.ts:67);引用同一性断言红(expected [Function isFileIdToken] to be [Function isFileIdToken]);而file-value.test.ts的 20+ 条行为断言全绿——这正是这条闸门存在的理由Theme还原成原来的手写联合ColumnSummaryType改回手抄件并漏掉一个成员count_unique(「spec 接受这个 summary,而渲染器没有对应的 label/聚合——授权列会通过校验然后渲染出一个空白页脚单元格」);typetests 钉扎同时红lists 1 symbol in DEBT that no longer collide)tsconfig.typetests.json从type-check里摘掉第六向是一个已知的洞,值得记在方法学里:把
ThemePreference改回叫Theme但保留派生——守卫放行(派生就是派生),只有 tripwire 测试报红(TS2305: Module '"../types"' has no exported member 'ThemePreference'+Equal钉扎)。这是批次 5ConflictResolutionStrategy那条结论的第二个活样本:「派生」不等于「派生自正确的东西」——顶着 spec 名字派生出另一个 spec 概念,守卫看不出来。所以本批每一次改名都配了钉住新名字的测试,而不是只靠守卫。施工规约
改名一律按标识符边界替换,逐包范围内进行,没有全局替换;字符串字面量一个没动(
form-sectionCSS 类、page:header组件键、错误name等)——批次 6DataApiValidationError那条线上契约规则,git diff里可核对。runner的App是 default export,main.tsx用什么本地名绑定与它无关,所以这次改名的消费方成本是零。文档随公开 API 一起改:
content/docs/guide/plugin-development.md、content/docs/layout/page-header.mdx、skills/objectui/guides/{plugin-development,architecture}.md里import { type FieldWidgetProps } from '@object-ui/fields'这类示例,不改就会教下一个插件作者去 import 一个不存在的名字。指向 spec 那个同名导出的地方(packages/types/src/widget.ts的注释、.github/prompts/component.prompt.md的 spec 示例)一律未动——它们说的是 spec 的符号,依然正确。依赖变动:
@objectstack/spec在@object-ui/fields与@object-ui/providers从devDependencies移入dependencies(前者 re-export 一个运行时函数,后者的公开.d.ts引用了 spec)。layout/runner/sdui-parser/collaboration新增 spec 的 devDependency,仅供 tripwire 测试使用。与批次 5 在@object-ui/components上的同一处置一致——若某个包本意保持 spec-free,请指出。Generated by Claude Code