Skip to content

fix(spec)!: composeStacksi18n 退出 last-wins,与其余非数组顶层键对齐 (#5051) - #6240

Merged
qq9340100 merged 1 commit into
mainfrom
claude/issue-5051-compose-stacks-i18n-merge
Aug 7, 2026
Merged

fix(spec)!: composeStacksi18n 退出 last-wins,与其余非数组顶层键对齐 (#5051)#6240
qq9340100 merged 1 commit into
mainfrom
claude/issue-5051-compose-stacks-i18n-merge

Conversation

@qq9340100

Copy link
Copy Markdown
Collaborator

Fixes #5051

背景与裁决口径

#5005composeStacks 的非数组顶层键统一成「同值放行 / 冲突报错带处方」,维护者 2026-08-04 裁决点名否掉了两种形状:⛔ last-wins(先声明的栈被后组合者无声覆盖)、⛔ deep-merge(造出两位作者都没写过的第三种值)。

i18n 是唯一逃过那一轮的键 —— #5005 的主题是被丢掉的键,而 i18n 本来就有一个能工作(只是无声)的策略,所以被显式留在范围外。于是它成了整个顶层键面上仅剩的一个 last-wins

  • 2026-08-06 维护者裁决:取 A(对齐),B(保留 last-wins + warn)/ C(维持现状写进 docblock)否决;ComposeStacksOptionsSchema 不加旋钮
  • 2026-08-07 窗口改判:target:v18target:v17(已决定的协议变更一律落 v17)。

本 PR 按裁决原文与既有单值键(api / server / runtimeModule)的实现执行,没有第三种语义:两栈各带部分 i18n 子键、彼此不同,判为冲突报错,而不是把两侧并起来(那正是被否掉的 deep-merge)。

改了什么

packages/spec/src/stack.zod.ts:

  1. COMPOSE_KEY_DISPOSITIONS.i18n'i18n' 改为 'single',ComposeDisposition 联合类型去掉 'i18n' 分支;
  2. composeStacks 里那段专用的 last-wins 循环删除 —— i18n 随之落到既有的单值路径 composeSingleValue,步骤编号顺延(6 步变 5 步);
  3. 处置表仍是 Record< keyof ObjectStackDefinition, ComposeDisposition >,总覆盖不变(新增顶层键不声明合成语义仍然 tsc 不过);
  4. 单值冲突的错误信息补上 i18n 专属的危害面(丢掉的那一侧的 translations 语言包会去寻址组合后不再承认的 locale),点名冲突键 / 两个来源栈 / 两条出路的处方结构不变。

行为激活面(FROM → TO)

场景 FROM TO
只有一个栈声明 i18n 保留该声明 不变
多个栈声明相同i18n 保留 不变(同值放行)
多个栈声明不同i18n 靠后的栈胜出,靠前那个栈的声明被静默丢弃 抛错,点名 i18n 与两个来源栈(无 manifest 时用 stack #N)

被静默丢弃的那一侧不再消失 —— 它以一条点名两个来源栈的错误出现,由作者决定保留哪一个。changeset 按此定级 major 并写明迁移(改一致 / 只在应当拥有它的栈里声明)。

仓内实测

examples/app-crm(supportedLocales: ['en','zh-CN'])与 examples/app-todo(['en','zh-CN','ja-JP'])正是会冲突的一对:defaultLocale 一致,locale 集合不一致。content/docs/getting-started/examples.mdx 的组合示例用的就是这两个 app,因此补了一段说明(单值键既不覆盖也不合并 + 这一对需要先对齐)。除此之外无运行时调用点组合带 i18n 的多个栈。

测试

新增 packages/spec/src/compose-stacks-i18n-merge.test.ts(14 例):

  • 冲突面:报错并点名两个栈与处方、顺序无关、三栈报首个分歧对、无 manifest 时用 stack #N、strict defineStack 路径同样报错、部分键 vs 部分键不深合并、examples 实测形状(同 defaultLocale 不同 locale 集合)。
  • 控制面:仅单方声明(两个位置各一次)、同值放行(结构相等而非引用相等)、显式 i18n: undefined 视同未声明、无人声明则结果里没有该键、零 console.warn(证明它命中了已声明规则而不是默认路径)、单栈短路不受影响。

按裁决反转的既有用例:compose-stacks-key-loss.test.ts#5005 钉现状的 i18n 控制用例(该 describe 改名,i18n 覆盖迁到新文件),compose-stacks.test.tsshould use last i18n config (last-wins) 改为断言冲突报错并补一条同值放行。

反向验证(方向事先声明为「红」)

把删掉的 last-wins 循环装回去:9 条冲突用例全红,其余 71 条保持绿 —— 后者是控制而非证据,因为 last-wins 与单值语义在「没有分歧」时本就一致。实测丢失侧:装回后 composeStacks([a, b]).i18n{"defaultLocale":"zh-CN","supportedLocales":["zh-CN"]},栈 #0 的 en 声明不在结果里。脚手架已移除(git grep 零残留)。

命令与凭据

pnpm --filter @objectstack/spec test        → 331 files / 8433 tests passed
pnpm --filter @objectstack/spec typecheck   → tsc --noEmit 0;check:test-typecheck OK
pnpm --filter @objectstack/spec check:docs  → 232 generated files in sync
pnpm --filter @objectstack/spec check:authorable-surface → 通过(1622 schemas)
npx eslint --no-inline-config <改动文件>     → exit 0
node scripts/check-nul-bytes.mjs            → OK(5924 文件,无控制字节)

check:generated 只在 api-surface/ 一项报 stale,原因是本 worktree 从未 build 过 dist(check:api-surface 直报 Could not resolve module symbol … Is the package built?);该产物只记导出名("composeStacks (function)"),本 PR 零导出增删改,故判定不涉及、不手改。以 CI 的 TypeScript Type Check 逐 job 结论为准。

边界

⛔ 只动 i18n 这一个键的合成路径;⛔ 未碰 content/docs/releases/;⛔ 未碰 .zod.ts 的别名区(#6083 在飞);⛔ ComposeStacksOptionsSchema 未加旋钮。


Generated by Claude Code

#5005 把 `composeStacks` 的非数组顶层键统一成「同值放行 / 冲突报错带处方」,
维护者 2026-08-04 裁决点名否掉 last-wins(先声明的栈被后组合者无声覆盖)与
deep-merge(造出两位作者都没写过的第三种值)。`i18n` 是唯一逃过那一轮的键 ——
#5005 的主题是**被丢掉**的键,而 `i18n` 本来就有一个能工作(只是无声)的策略。
于是它成了整个顶层键面上仅剩的一个 last-wins。2026-08-06 裁决取 A(对齐),
2026-08-07 窗口由 v18 拉入 v17。

实施:`i18n` 的处置从 `'i18n'` 改为 `'single'`,`composeStacks` 里那段专用的
last-wins 循环删除(该键随之落到既有的单值路径 `composeSingleValue`),
`ComposeDisposition` 联合类型去掉 `'i18n'` 分支 —— 处置表仍是
`Record< keyof ObjectStackDefinition, ComposeDisposition >`,总覆盖不变。
`ComposeStacksOptionsSchema` 按 #5005 裁决不加旋钮。

为什么本地化配置不能择一:一个栈的 `translations` 语言包是照着它自己声明的
`supportedLocales` 写的。让附加包的 `i18n` 胜出,等于让基础栈的语言包去寻址一组
组合后的应用不再承认的 locale,运行时只表现为「查不到翻译」。单值冲突的错误信息
补上了这条 i18n 专属的危害,处方(改一致 / 只在应当拥有它的栈里声明)不变。

测试:新增 `compose-stacks-i18n-merge.test.ts`(14 例)—— 冲突报错并点名两个栈、
顺序无关、三栈报首个分歧对、无 manifest 时用 `stack #N`、strict `defineStack`
路径同样报错、部分键 vs 部分键**不**深合并;控制面为单方声明/同值放行/显式
`undefined` 视同未声明/零 warn/单栈短路。#5005 钉现状的控制用例与
`compose-stacks.test.ts` 的 last-wins 用例按裁决反转。

反向验证(方向事先声明为「红」):把删掉的 last-wins 循环装回去,9 条冲突用例
全红(其余 71 条控制用例保持绿,因为 last-wins 与单值语义在「没有分歧」时本就
一致);实测丢失侧 —— 装回后 `composeStacks([a,b]).i18n` 得
`{"defaultLocale":"zh-CN",…}`,栈 #0 的 `en` 声明不在结果里。

仓内实测:`examples/app-crm`(`['en','zh-CN']`)与 `examples/app-todo`
(`['en','zh-CN','ja-JP']`)正是会冲突的一对,组合文档据此补了一段说明。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011M7UwH25Unfi73UHim7ajY
@vercel

vercel Bot commented Aug 7, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectstack Ignored Ignored Aug 7, 2026 11:40am

Request Review

@github-actions github-actions Bot added the size/m label Aug 7, 2026
@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/spec.

112 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/ai/agents.mdx (via @objectstack/spec)
  • content/docs/ai/skills-reference.mdx (via @objectstack/spec)
  • content/docs/ai/skills.mdx (via @objectstack/spec)
  • content/docs/api/client-sdk.mdx (via @objectstack/spec)
  • content/docs/api/environment-routing.mdx (via @objectstack/spec)
  • content/docs/api/error-catalog.mdx (via @objectstack/spec)
  • content/docs/api/error-handling-client.mdx (via @objectstack/spec)
  • content/docs/api/error-handling-server.mdx (via @objectstack/spec)
  • content/docs/api/index.mdx (via @objectstack/spec)
  • content/docs/automation/approvals.mdx (via @objectstack/spec)
  • content/docs/automation/connectors.mdx (via @objectstack/spec)
  • content/docs/automation/flows.mdx (via @objectstack/spec)
  • content/docs/automation/hook-bodies.mdx (via packages/spec)
  • content/docs/automation/hooks.mdx (via @objectstack/spec)
  • content/docs/automation/index.mdx (via @objectstack/spec)
  • content/docs/automation/webhooks.mdx (via @objectstack/spec)
  • content/docs/automation/workflows.mdx (via @objectstack/spec)
  • content/docs/concepts/architecture.mdx (via @objectstack/spec)
  • content/docs/concepts/design-principles.mdx (via packages/spec)
  • content/docs/concepts/index.mdx (via @objectstack/spec)
  • content/docs/concepts/metadata-driven.mdx (via @objectstack/spec)
  • content/docs/concepts/metadata-lifecycle.mdx (via packages/spec)
  • content/docs/concepts/north-star.mdx (via @objectstack/spec)
  • content/docs/data-modeling/analytics.mdx (via @objectstack/spec)
  • content/docs/data-modeling/drivers.mdx (via @objectstack/spec)
  • content/docs/data-modeling/external-datasources.mdx (via @objectstack/spec)
  • content/docs/data-modeling/field-types.mdx (via @objectstack/spec)
  • content/docs/data-modeling/fields.mdx (via @objectstack/spec)
  • content/docs/data-modeling/formulas.mdx (via @objectstack/spec)
  • content/docs/data-modeling/index.mdx (via @objectstack/spec)
  • content/docs/data-modeling/objects.mdx (via @objectstack/spec)
  • content/docs/data-modeling/queries.mdx (via @objectstack/spec)
  • content/docs/data-modeling/schema-design.mdx (via @objectstack/spec)
  • content/docs/data-modeling/seed-data.mdx (via @objectstack/spec)
  • content/docs/data-modeling/validation-rules.mdx (via @objectstack/spec)
  • content/docs/data-modeling/validation.mdx (via @objectstack/spec)
  • content/docs/deployment/cli.mdx (via @objectstack/spec)
  • content/docs/deployment/tenancy-modes.mdx (via @objectstack/spec)
  • content/docs/deployment/troubleshooting.mdx (via @objectstack/spec)
  • content/docs/deployment/validating-metadata.mdx (via @objectstack/spec)
  • content/docs/getting-started/build-with-claude-code.mdx (via @objectstack/spec)
  • content/docs/getting-started/common-patterns.mdx (via @objectstack/spec)
  • content/docs/getting-started/examples.mdx (via @objectstack/spec)
  • content/docs/getting-started/quick-reference.mdx (via @objectstack/spec)
  • content/docs/getting-started/quick-start.mdx (via @objectstack/spec)
  • content/docs/getting-started/your-first-project.mdx (via @objectstack/spec)
  • content/docs/kernel/cluster.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/auth-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/cache-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/data-engine.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/index.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/metadata-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/storage-service.mdx (via @objectstack/spec)
  • content/docs/kernel/index.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/data-service.mdx (via @objectstack/spec)
  • content/docs/kernel/runtime-services/email-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/examples.mdx (via @objectstack/spec)
  • content/docs/kernel/runtime-services/index.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/queue-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/sharing-service.mdx (via @objectstack/spec)
  • content/docs/kernel/runtime-services/sms-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/storage-service.mdx (via @objectstack/spec)
  • content/docs/kernel/services-checklist.mdx (via @objectstack/spec)
  • content/docs/kernel/services.mdx (via @objectstack/spec)
  • content/docs/permissions/authorization.mdx (via @objectstack/spec)
  • content/docs/permissions/permission-sets.mdx (via @objectstack/spec)
  • content/docs/permissions/permissions-matrix.mdx (via @objectstack/spec)
  • content/docs/permissions/positions.mdx (via @objectstack/spec)
  • content/docs/permissions/rls.mdx (via @objectstack/spec)
  • content/docs/permissions/sharing-rules.mdx (via @objectstack/spec)
  • content/docs/plugins/adding-a-metadata-type.mdx (via @objectstack/spec)
  • content/docs/plugins/development.mdx (via @objectstack/spec)
  • content/docs/plugins/index.mdx (via @objectstack/spec)
  • content/docs/plugins/packages.mdx (via @objectstack/spec)
  • content/docs/protocol/backward-compatibility.mdx (via @objectstack/spec)
  • content/docs/protocol/diagram.mdx (via packages/spec)
  • content/docs/protocol/kernel/config-resolution.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/http-protocol.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/i18n-standard.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/index.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/lifecycle.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/plugin-spec.mdx (via @objectstack/spec)
  • content/docs/protocol/knowledge.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/index.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/query-syntax.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/schema.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/security.mdx (via packages/spec)
  • content/docs/protocol/objectql/state-machine.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/actions.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/concept.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/index.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/layout-dsl.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/record-alert.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/widget-contract.mdx (via @objectstack/spec)
  • content/docs/releases/implementation-status.mdx (via @objectstack/spec)
  • content/docs/releases/index.mdx (via @objectstack/spec)
  • content/docs/releases/v12.mdx (via @objectstack/spec)
  • content/docs/releases/v13.mdx (via @objectstack/spec)
  • content/docs/releases/v16.mdx (via @objectstack/spec)
  • content/docs/releases/v17.mdx (via @objectstack/spec)
  • content/docs/releases/v9.mdx (via @objectstack/spec)
  • content/docs/ui/actions.mdx (via @objectstack/spec)
  • content/docs/ui/apps.mdx (via @objectstack/spec)
  • content/docs/ui/create-vs-edit-form.mdx (via @objectstack/spec)
  • content/docs/ui/dashboards.mdx (via @objectstack/spec)
  • content/docs/ui/field-grouping-and-order.mdx (via @objectstack/spec)
  • content/docs/ui/forms.mdx (via @objectstack/spec)
  • content/docs/ui/index.mdx (via @objectstack/spec)
  • content/docs/ui/public-data-collection.mdx (via @objectstack/spec)
  • content/docs/ui/setup-app.mdx (via @objectstack/spec)
  • content/docs/ui/translations.mdx (via @objectstack/spec)
  • content/docs/ui/views.mdx (via @objectstack/spec)

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

@github-actions github-actions Bot added documentation Improvements or additions to documentation tests tooling labels Aug 7, 2026
@qq9340100
qq9340100 marked this pull request as ready for review August 7, 2026 12:00
@qq9340100
qq9340100 added this pull request to the merge queue Aug 7, 2026
Merged via the queue into main with commit 8140915 Aug 7, 2026
25 checks passed
@qq9340100
qq9340100 deleted the claude/issue-5051-compose-stacks-i18n-merge branch August 7, 2026 12:15
os-zhuang added a commit that referenced this pull request Aug 7, 2026
#6240 改到 packages/spec/src/stack.zod.ts 的模块 JSDoc(正是本 PR 渲染的区域),
故按 #4675 再走一轮:本提交只合并,下一步整体重跑 gen:docs 验证。
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

composeStacksi18n 仍是 last-wins —— #5005 裁决否掉的那个形状,只剩这一个键还在用

2 participants