fix(spec): 参考文档生成器按 typeof 决定字面量是否加引号,数值字面量不再被记成字符串 (#5729) - #6127
Merged
Conversation
`formatType()` 的两个字面量分支无条件给每个值套上 `'…'`,数值字面量因此 被写成字符串字面量。`FormSection.columns` 声明为 `z.union([z.enum(['1','2','3','4']), z.literal(1) … z.literal(4)])`, `ui/view.mdx` 却渲染成 `Enum<'1' | '2' | '3' | '4'> | '1' | '2' | '3' | '4'` —— 后半段那四个数值字面量与前半段的字符串完全无法区分,页面因此宣称这个键 只收字符串,而 schema 同时接受 `2` 和 `'2'`。 参考页是 AI 作者的权威输入(ADR-0033),字面量联合又是「照抄拼写」的表面: 页面上的引号会被原样抄进 metadata。#5611 已经付过代价——它的 `RecordDetailsProps.sections[].columns` 本打算写成数值字面量联合,照生成的 参考写是硬解析错误,PR 里被迫退回 `z.number().int().min(1).max(4)`。生成器 在反向定义契约。 引号改为按**值**判断而非按节点判断:JSON Schema 逐值声明成员类型,`enum` 可以混装(`z.nativeEnum({A: 1})` 就产出数值 `enum`)。字符串保留引号, `number`/`boolean` 裸渲染,`null` 渲染为关键字。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014wsZeReNTqiceBfLb5Pyf5
`pnpm --filter @objectstack/spec gen:schema && gen:docs` 的产物,无手工编辑。 8 个页面 / 11 行,方向单一:18 处非字符串字面量去掉引号,没有任何字符串 字面量失去引号(逐行机检,详见 PR 正文)。 - api/dispatcher, api/errors —— `success: 'false'` → `false` - data/data-engine —— `sort: Record<string, '1' | '-1'>` → `Record<string, 1 | -1>` - data/driver-nosql —— `projection: Record<string, '0' | '1'>` → `Record<string, 0 | 1>` - data/object —— `systemFields` / `stageField` 的 `'false'` → `false` - security/explain —— `version: '1'` → `1` - ui/dashboard —— `filterBindings` 的 `'false'` → `false` - ui/view —— `columns: … | '1' | '2' | '3' | '4'` → `… | 1 | 2 | 3 | 4` 页面上所有 `Enum<'a' | 'b'>` 形态的字符串枚举一律未变。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014wsZeReNTqiceBfLb5Pyf5
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
Contributor
📓 Docs Drift CheckNo hand-written docs reference the 0 changed package(s). ✅ |
os-zhuang
marked this pull request as ready for review
August 7, 2026 03:01
This was referenced Aug 7, 2026
This was referenced Aug 7, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #5729
问题
packages/spec/scripts/lib/format-type.ts的两个字面量分支无条件加引号,不看值的类型:于是任何「只收数值」的字面量联合都被记成只收字符串,而参考页正是 AI 作者的权威来源(ADR-0033)。
前提复核(vs
origin/main7efa127,已含 #6069 / #6096 / #6100 修复)两条都仍然成立,没有被前面几个 PR 顺手改掉:
format-type.ts:148/:152(改前行号)两个分支确实仍无条件加引号 —— 见上方摘录。content/docs/references/ui/view.mdx:184仍是 issue 报告的那一行:后半段四个就是
FormSectionSchema.columns(packages/spec/src/ui/view.zod.ts:1502-1510)里的四个数值 字面量
z.literal(1..4)。补一条实测(对着真转换器探的,不是猜的)——四种字面量的 JSON Schema 编码:
z.toJSONSchema产出z.literal(2){ type: 'number', const: 2 }z.literal('2'){ type: 'string', const: '2' }z.literal(true){ type: 'boolean', const: true }z.literal(null){ type: 'null', const: null }z.enum(['a','b']){ type: 'string', enum: ['a','b'] }z.nativeEnum({ A: 1, B: 2 }){ type: 'number', enum: [1, 2] }修复
新增
formatLiteral(value),按typeof决定:字符串加引号,number/bigint/boolean裸渲染,null渲染为关键字;复合const(对象/数组,目前没有 schema 产出)退回JSON.stringify,因为旧代码在这种节点上会打印
'[object Object]'。两个分支都改为调用它。为什么逐「值」而不是逐「节点」判断:JSON Schema 逐值声明成员类型,一个
enum数组可以混装类型 ——上表最后一行
z.nativeEnum({ A: 1, B: 2 })就产出数值enum。用节点级的prop.type去判断,混装那一类就会渲染错;逐值判断则顺带就对了。
反向验证(方向在运行前先行判定)
预判:普通方向 —— 把无条件加引号改回去,新增的「非字符串」pin 转红;「字符串保持引号」的 pin
两边都绿(它们是 over-reach guard:一个只会「删引号」而不按 typeof 选择的修法,会把它们转红)。
实测:
5 failed | 30 passed。五个红全部落在断言非字符串字面量的用例上:三个「字符串不变」的用例两边都绿,符合预判。
诚实记录一处偏差:我最初在测试文件的 MEASURED 注释里预判「4 红 / 2 绿」,实跑是 5 红。原因不是
判断方向错了,而是测试结构写坏了 —— 我把「字符串保留引号」的断言和数值断言塞进了同一个
it,它永远在红色兄弟的阴影下运行,绿了也证明不了自己。把三个字符串守卫拆成独立
it后重新测量,才得到上面的5 failed | 30 passed。仓库里那段 MEASURED 注释记的是重测后的真实数字,并写明了这次修正。参考页改动逐页确认
pnpm --filter @objectstack/spec gen:schema && gen:docs的产物,无手工编辑,单独一个 commit。共 8 页 / 11 行:
api/dispatcher.mdxsuccess'false'falseapi/errors.mdxsuccess'false'falsedata/data-engine.mdxsortRecord< string, '1' | '-1' >Record< string, 1 | -1 >data/driver-nosql.mdxoptions.projectionRecord< string, '0' | '1' >Record< string, 0 | 1 >data/driver-nosql.mdxprojectionRecord< string, '0' | '1' >Record< string, 0 | 1 >data/object.mdxsystemFields'false' | {...}false | {...}data/object.mdxstageFieldstring | 'false'string | falsesecurity/explain.mdxversion'1'1ui/dashboard.mdxfilterBindingsRecord< string, string | 'false' >Record< string, string | false >ui/view.mdxcolumnsEnum<'1' | '2' | '3' | '4'> | '1' | '2' | '3' | '4'Enum<'1' | '2' | '3' | '4'> | 1 | 2 | 3 | 4每一处都对着 zod 源核过,确认声明的就是非字符串字面量,例如
api/dispatcher.zod.ts:184 success: z.literal(false)、data/data-engine.zod.ts:43 z.record(z.string(), z.union([z.literal(1), z.literal(-1)]))、security/explain.zod.ts:332 version: z.literal(1).default(1)。方向机检(脚本逐行比对改动前后每行的「带引号字面量多重集」,而不是肉眼看):
也就是说:引号只从非字符串字面量上移除,没有任何字符串字面量失去引号。页面上所有
Enum<'asc' \| 'desc'>、Enum<'1' \| '2' \| '3' \| '4'>这类字符串枚举一律原样保留 ——columns那一格前半段的字符串枚举带引号、后半段的数值裸写,正是这个键真实的契约。验证
format-type.test.ts新增 8 个用例(27 → 35),其中 5 个钉非字符串字面量、3 个守住字符串不变。一处过程记录:先用
OS_SKIP_DTS=1建包时check:generated报api-surface/stale —— 那是gen:api-surface读dist/**/*.d.ts而我跳过了 DTS 所致,不是本改动引起(api-surface/在 git 里未被改动)。改跑完整 build 后复测即
✓ All 10 generated artifacts are up to date。changeset
有 ——
.changeset/docs-gen-numeric-literal-quoting.md,@objectstack/spec: patch。理由:
scripts/不在packages/spec的files白名单里(不随包发布),但本改动改变了对读者可见的产物:8 个参考页的渲染结果变了,而这些页面就是作者(尤其 AI 作者)读的契约。这与 #6058(同样是
format-type.ts改动 + 参考页重新生成)的判例形状完全一致,沿用它的 named patch 写法。与 #5340 的关系
#5340(同文件的 inline enum 宽度问题,
finding挂起)没有被本 PR 实现,也不是搭车项。问:本改动让 #5340 更容易、更难、没必要、还是无影响?
答:基本无影响,边际上略微更容易。#5340 谈的是内联 enum 太宽需要收窄/省略,属于
formatType的布局/长度决策;本改动动的是单个字面量的拼写决策,两者在代码上不重叠(#5340 要改的是 enum 成员
的省略策略,本 PR 只改了成员如何转成字符串)。边际上略微更容易的地方是:成员渲染现在集中在
formatLiteral()一个函数里,#5340 若要做「超过 N 个成员就省略」之类的处理,拿到的是一个已经归一化的成员字符串列表,不必再关心引号。宽度上也有极小的减少(数值成员各少 2 个字符),但那不足以改变 #5340
的结论 —— 它要解决的问题依然存在。
并行冲突提示
并行派发 #5059 也会重新生成
content/docs/references/**。两边代码文件不相交(#5059 改build-docs.ts,本 PR 改format-type.ts),只有生成产物可能撞车;谁后落地谁 merge main 后重新生成一次即可。
Generated by Claude Code