Skip to content

fix(vscode): 片段展开出来的 metadata 现在过得了 spec,并加了一条常驻 gate 让它别再过期 (#4917) - #5031

Merged
xuyushun441-sys merged 1 commit into
mainfrom
claude/issue-4917-vscode-view-snippet
Aug 4, 2026
Merged

fix(vscode): 片段展开出来的 metadata 现在过得了 spec,并加了一条常驻 gate 让它别再过期 (#4917)#5031
xuyushun441-sys merged 1 commit into
mainfrom
claude/issue-4917-vscode-view-snippet

Conversation

@xuyushun441-sys

Copy link
Copy Markdown
Contributor

Fixes #4917

先核实:议题点的两个键确实被拒,但坏掉的不止一个片段

对着本分支基线 bf1edefViewSchema 实测,议题描述属实:

[unrecognized_keys] path=["list"]  Unrecognized key(s) on this list view: `defaultSort`, `pageSize`.

但议题只实测了 os-view-grid。按派发要求把全部 8 个片段都展开过了一遍 safeParse,结果是 5 个坏的,不是 1 个:

片段 被拒的东西 现在的 canonical 写法
os-view-grid list.defaultSortlist.pageSize;外加 container 上的 type / objectName —— 后者正是 ViewSchema 自己的 guidance 点名的「把扁平 view 写在 container 位置」 defineView({ object, list: { …, sort: [{ field, order }], pagination: { pageSize } } })
os-flow 节点的 name / next(键是 label + 一个 edges 数组),以及顶层 trigger defineFlow,对象绑定放在 START 节点的 config: { objectName, triggerType },edges 显式声明
os-agent tools —— 协议 17 已退役(#3894) skills: []
os-stack manifest 缺必填的 idtype { id, namespace, version, type, name, engines }
os-field-lookup reference: { object, labelField } —— reference 是个裸对象名 reference: 'target_object' + displayField

排序 / 分页现在声明在哪是读 schema 定的,没猜:ListViewSchema.sort{ field, order }[](注意是 order,不是片段原来写的 direction),pageSizePaginationConfigSchema 里,挂 list.paginationlist.defaultSortListViewSchema 上从来没声明过 —— 议题的判断是对的,不是改拼写能解决的,是键的位置本来就错。

一个议题没提、但更早就断的东西:import 那一行

5 个整文件片段全部写着 import { Data } from '@objectstack/spec'(以及 { UI } / { Automation } / { AI })。这些根命名空间 re-export 早就被删了(Node ESM 下不可 tree-shake,packages/spec/src/index.ts 里有整段说明,还配了 no-restricted-imports 规则)。也就是说:片段的第一行就解析不了,根本轮不到 metadata 形状出问题。

改成从 subpath 导入,并且一律走该 domain 的校验工厂ObjectSchema.create / defineView / defineFlow / defineAgent / defineStack)—— 这是 examples 里的既有写法,也是 eslint 里 issue #2035 那条规则写明的理由:工厂在 .parse() 时就校验,而且是 value import,断了会硬报错,不会静默降级成 any

这一条超出了议题字面范围。判断:它跟议题是同一份文件、同一类「下游未跟随」缺陷,而且不修的话「修好的片段」依然一展开就报错,交不出去。所以修了,并在这里显式说明,而不是另开一单拖着。

结构性问题的回答:之前没有任何东西在 CI 里 parse 过片段产物

全仓搜 snippets/objectstack.json 只有三处命中:片段文件自己、package.json 的 contributes 声明、README 的表格。没有测试、没有脚本、没有 CI job 读过它。所以 #4001 关掉两个键的时候,不可能有任何东西变红 —— 下一批严格化会以完全相同的方式再打坏一个片段。

本 PR 加的常驻校验(packages/vscode-objectstack/test/snippets.test.ts + test/snippet-harness.ts),接进该包自己的 pnpm test,跑一次 2.5 秒:

  1. 展开占位符。 ${n:default}default,${n|a,b|} → 第一个选项(VS Code 预选的那个),${n}pn,$0 → 空。这四种形式都是全定义的,所以「作者拿到的是什么」是一个确定的字符串;遇到表以外的形式($TM_FILENAME、transform、嵌套占位)直接抛,而不是悄悄展开成没人会看到的文本 —— 这就是派发单里问的「占位符导致展开不平凡」的处理方式:不放弃,但把模型不了的形式变成红。
  2. 用真 spec 求值。 ts.transpileModule 转译 + require 走真正的 @objectstack/spec(不 stub —— stub 正是让 gate 停止跟踪它所守护的契约的办法)。工厂调用被录下来,拿到作者写的那个字面量(pre-parse、pre-defaults)。
  3. 过 schema。 用运行时用的同一个 schema safeParse 那个字面量。
  4. 查 import 面。 每个具名 import 绑定必须还在目标模块的导出面上 —— 这一项要读入口 .d.ts,因为 Data 是个类型命名空间,任何运行时检查都看不见它消失。这就是抓住上面那半个 bug 的那条。

双向证明

把片段文件换回本 PR 之前的版本再跑,11 条判红:

× [os-object]      expands to metadata Data.ObjectSchema accepts
× [os-object]      imports only bindings @objectstack/spec still exports
× [os-field-lookup] expands to metadata Data.FieldSchema accepts
× [os-view-grid]   expands to metadata UI.ViewSchema accepts
× [os-view-grid]   imports only bindings @objectstack/spec still exports
× [os-flow]        expands to metadata Automation.FlowSchema accepts
× [os-flow]        imports only bindings @objectstack/spec still exports
× [os-stack]       expands to metadata ObjectStackDefinitionSchema accepts
× [os-agent]       expands to metadata AI.AgentSchema accepts
× [os-agent]       imports only bindings @objectstack/spec still exports
× os-stack stamps the CURRENT protocol major in engines.protocol

 Tests  11 failed | 10 passed (21)

报错文案例:

`Data` is not exported by '@objectstack/spec'. Root namespace re-exports
(Data / UI / AI / Automation …) were removed as untree-shakeable —
import from the subpath instead (see packages/spec/src/index.ts).

修好之后 21/21 绿

另外三条负控制常驻在测试文件里,所以「红」这件事本身也不依赖手动复现:重新引入 #4001 关掉的那两个键必须被拒、引入一个 spec 不再导出的绑定必须被查出、模型不了的占位符形式必须抛。

两条结构性防护

  • plan 表全覆盖:每个 shipped 片段都必须在测试的 PLANS 里有条目,否则那条断言直接红 —— 新片段没法绕过 gate 落地。
  • engines.protocol 锁步:os-stack 里那个 ^17 是硬编码的字面量,正是本议题这一类会烂掉的东西。scripts/sync-template-versions.mjs 只管 create-objectstack 的模板,不知道这个文件,所以锁步断言写在这条 gate 里,读 spec 自己的 package.json 大版本。大版本一升,这里就红。

改动范围

只动 VS Code 扩展包 + 一个 changeset。没有碰 lint.yml、没有碰根 package.json、没有碰任何共享脚本或配置 —— 根 pnpm test / pnpm typecheck 就是 turbo run test|typecheck,该包新加的 test 脚本自动被 turbo 收进去(test 任务本来就 dependsOn: ["^build"],spec 会先构建),CI 不需要任何改动。pnpm-lock.yaml 的变动仅来自该包新增的 devDependencies。

测试放在自己的 tsconfig project(tsconfig.test.json):扩展本体必须是 CommonJS(VS Code host 要求),gate 是 ESM(要 import.meta.url 定位 shipped 的片段文件),两者没法共用一份 compilerOptionstypecheck 脚本串了两次 tsc,所以测试没有被排除在 tsc --noEmit 之外(AGENTS.md 那条)。check:type-check-coverage 已验证仍然 OK。

验证

pnpm --filter objectstack-vscode test        →  21 passed (21)
pnpm --filter objectstack-vscode typecheck   →  clean (tsc --noEmit && tsc -p ./tsconfig.test.json)
pnpm --filter objectstack-vscode build       →  clean(dist/ 只有 extension.*,测试不进产物)
pnpm exec eslint --no-inline-config packages/vscode-objectstack  →  0 errors / 0 warnings(含 test/ 与 vitest.config.ts)
turbo run test typecheck --filter=objectstack-vscode             →  3 successful, 3 total
pnpm check:type-check-coverage / check:published-files / check:doc-authoring / check:release-notes / check:nul-bytes  →  全绿

顺手记的一个旁支(未修,已另开)

#5028 —— 扩展的 contributes.jsonValidation 指向 ./schemas/objectstack.schema.json,这个文件在仓库里不存在,也没有任何脚本生成它;README 却写着 "Validates objectstack.json files against the bundled schema"。同一类 declared ≠ enforced,但落点和修法都不同(要决定扩展到底要不要 bundle 一份 JSON Schema),不塞进本 PR。已按 Prime Directive #10 未认领立单。


Generated by Claude Code

… that keeps them there (#4917)

`snippets/objectstack.json` is a metadata PRODUCER — whatever `os-view-grid`
expands to is the first `.view.ts` an author (human or AI) ever writes — and
nothing in this repo had ever parsed that output. So the snippets drifted out
of the spec in silence. An audit of all eight found five broken:

- os-view-grid: `list.defaultSort` / `list.pageSize` (never declared on
  ListViewSchema), plus `type` / `objectName` on the CONTAINER, which is the
  flat-view-where-a-container-goes mistake ViewSchema's own guidance names.
  Now `defineView({ object, list: { sort: [{field, order}], pagination:
  { pageSize } } })`.
- os-flow: node `name` / `next` and a top-level `trigger` block. Now
  `defineFlow` with the binding on the START node's config and explicit edges.
- os-agent: `tools`, removed in protocol 17 (#3894). Now `skills`.
- os-stack: manifest missing the required `id` / `type`.
- os-field-lookup: `reference: { object, labelField }`; `reference` is a plain
  object name, the label field is `displayField`.

Separately all five module snippets imported `{ Data }` / `{ UI }` /
`{ Automation }` / `{ AI }` from the package root. Those namespace re-exports
were removed as untree-shakeable (packages/spec/src/index.ts), so the first
line of every scaffold did not resolve. They now import from the subpath and
author through the domain's validating factory (ObjectSchema.create,
defineView, defineFlow, defineAgent, defineStack) — which parses at authoring
time and, as a value import, fails loudly instead of degrading to `any`
(issue #2035's rationale, applied to the scaffolds themselves).

The recurrence is the actual fix. os-view-grid broke because #4001 closed
ListViewSchema and no gate anywhere could see a snippet body; the next
strictness batch would have broken another one identically. The package now
has a `test` script that expands every snippet, evaluates it against the real
@objectstack/spec, and safeParses the authored literal with the schema the
runtime uses. Three independent failure modes — the expansion does not
evaluate, the literal does not parse, an import names a binding the spec no
longer exports — plus a negative control asserting the pre-fix shape is still
rejected, a plan table that fails when a snippet arrives ungated, and a
lockstep assertion on the engines.protocol major so that stamp cannot rot.

Tests live in their own tsconfig project (the extension is CommonJS for the
VS Code host; the gate is ESM) and are wired into `typecheck`, so they are not
hidden from `tsc --noEmit`.

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

vercel Bot commented Aug 4, 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 4, 2026 12:31am

Request Review

@github-actions github-actions Bot added documentation Improvements or additions to documentation dependencies Pull requests that update a dependency file tests tooling size/l labels Aug 4, 2026
@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): objectstack-vscode.

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

  • content/docs/plugins/packages.mdx (via objectstack-vscode)

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dependencies Pull requests that update a dependency file documentation Improvements or additions to documentation size/l tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

VS Code 扩展的 os-view-grid 片段生成的 view 现在 parse 不过:list.defaultSort / list.pageSize 已被 #4001 关掉

2 participants