Skip to content

Commit cbc844e

Browse files
fix(vscode): snippets expand to metadata the spec accepts, and a gate that keeps them there (#4917) (#5031)
`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`. Claude-Session: https://claude.ai/code/session_018iARDqtrhQgz6fVHDeDkbQ Co-authored-by: Claude <noreply@anthropic.com>
1 parent 3120fe1 commit cbc844e

9 files changed

Lines changed: 670 additions & 55 deletions

File tree

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
---
2+
"objectstack-vscode": patch
3+
---
4+
5+
fix(vscode): every contributed snippet expands to metadata the spec accepts — and a gate that keeps it that way (#4917)
6+
7+
The extension's snippets are a metadata **producer**: whatever `os-view-grid`
8+
expands to is the first `.view.ts` an author (human or AI) ever writes. Nothing
9+
in this repo has ever parsed that output, so the snippets drifted out of the
10+
spec in silence. An audit of all eight found **five** broken against
11+
`@objectstack/spec` 17:
12+
13+
| snippet | what was rejected | canonical form now |
14+
|---|---|---|
15+
| `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 | `defineView({ object, list: { …, sort: [{ field, order }], pagination: { pageSize } } })` |
16+
| `os-flow` | node `name` / `next` (the keys are `label` + an `edges` array), and a top-level `trigger` block | `defineFlow` with the object binding on the START node's `config: { objectName, triggerType }` and an explicit `edges: []` |
17+
| `os-agent` | `tools` — removed in protocol 17 (#3894) | `skills: []` |
18+
| `os-stack` | `manifest` missing the required `id` and `type` | `{ id, namespace, version, type, name, engines }` |
19+
| `os-field-lookup` | `reference: { object, labelField }``reference` is a plain object name | `reference: 'target_object'` + `displayField` |
20+
21+
Separately, **all five** module snippets imported `{ Data }` / `{ UI }` /
22+
`{ Automation }` / `{ AI }` from the package root. Those namespace re-exports
23+
were removed for being untree-shakeable (see `packages/spec/src/index.ts`), so
24+
the very first line of each scaffold did not resolve. They now import from the
25+
subpath and author through the domain's validating factory — `ObjectSchema.create`,
26+
`defineView`, `defineFlow`, `defineAgent`, `defineStack` — which parses at
27+
authoring time and, being a *value* import, fails loudly instead of degrading
28+
to `any` (issue #2035's rationale, applied to the scaffolds themselves).
29+
30+
**The recurrence is what actually got fixed.** `os-view-grid` broke because
31+
#4001 closed `ListViewSchema` for unknown keys and no gate anywhere could see a
32+
snippet body; the next strictness batch would have broken another one the same
33+
way. The package now has a `test` script that expands every snippet, evaluates
34+
it against the real spec, and `safeParse`s the authored literal with the schema
35+
the runtime uses. Three independent failure modes are covered — the expansion
36+
does not evaluate, the literal does not parse, or an import names a binding the
37+
spec no longer exports — with a negative control asserting the pre-fix shape is
38+
still rejected, a plan table that fails when a snippet arrives ungated, and a
39+
lockstep check on the `engines.protocol` major so that stamp cannot rot either.
40+
41+
No authoring change is required of anyone: this only replaces snippet output
42+
that never validated.

packages/vscode-objectstack/README.md

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,30 @@
1515

1616
## Snippets
1717

18-
| Prefix | Description |
19-
|--------|-------------|
20-
| `os-object` | Define a new business object |
21-
| `os-field-text` | Add a text field |
22-
| `os-field-select` | Add a select (picklist) field |
23-
| `os-field-lookup` | Add a lookup (reference) field |
24-
| `os-view-grid` | Define a grid list view |
25-
| `os-flow` | Define an automation flow |
26-
| `os-stack` | Full `defineStack` boilerplate |
27-
| `os-agent` | Define an AI agent |
18+
Every snippet scaffolds through the spec's own authoring factory
19+
(`ObjectSchema.create`, `defineView`, `defineFlow`, `defineAgent`,
20+
`defineStack`), so what you tab out of the IDE validates against
21+
`@objectstack/spec` the moment it runs — never a bare `: Type` literal that
22+
type-checks over a shape nothing ever parses.
23+
24+
| Prefix | Scaffolds | Validated by |
25+
|--------|-----------|--------------|
26+
| `os-object` | A new business object | `ObjectSchema.create` |
27+
| `os-field-text` | A text field (paste inside `fields: { … }`) | `FieldSchema` |
28+
| `os-field-select` | A select (picklist) field | `FieldSchema` |
29+
| `os-field-lookup` | A lookup (reference) field | `FieldSchema` |
30+
| `os-view-grid` | A grid list view container | `defineView` |
31+
| `os-flow` | A record-change automation flow | `defineFlow` |
32+
| `os-stack` | Full `defineStack` boilerplate | `defineStack` |
33+
| `os-agent` | An AI agent | `defineAgent` |
34+
35+
That claim is enforced, not advertised: `pnpm test` in this package expands
36+
every snippet, evaluates it against the real `@objectstack/spec`, and
37+
`safeParse`s the authored literal with the same schema the runtime uses — plus
38+
a check that each import binding still exists on the spec's export surface. A
39+
snippet that goes stale (as `os-view-grid` did when `ListViewSchema` closed
40+
`defaultSort` / `pageSize`) fails CI instead of shipping. See
41+
`test/snippets.test.ts`.
2842

2943
## Installation
3044

@@ -67,6 +81,9 @@ npm run build
6781
# Watch for changes
6882
npm run watch
6983

84+
# Verify every contributed snippet still parses against @objectstack/spec
85+
npm test
86+
7087
# Package as .vsix
7188
npm run package
7289
```

packages/vscode-objectstack/package.json

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,12 +57,16 @@
5757
"build": "tsc -p ./tsconfig.json",
5858
"watch": "tsc -watch -p ./tsconfig.json",
5959
"package": "vsce package",
60-
"typecheck": "tsc --noEmit"
60+
"test": "vitest run",
61+
"typecheck": "tsc --noEmit && tsc -p ./tsconfig.test.json"
6162
},
6263
"devDependencies": {
64+
"@objectstack/spec": "workspace:*",
65+
"@types/node": "^26.1.2",
6366
"@types/vscode": "^1.125.0",
6467
"@vscode/vsce": "^3.9.2",
65-
"typescript": "^6.0.3"
68+
"typescript": "^6.0.3",
69+
"vitest": "^4.1.10"
6670
},
6771
"keywords": [
6872
"objectstack",

packages/vscode-objectstack/snippets/objectstack.json

Lines changed: 41 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@
33
"prefix": "os-object",
44
"description": "Define a new ObjectStack business object",
55
"body": [
6-
"import { Data } from '@objectstack/spec';",
6+
"import { ObjectSchema } from '@objectstack/spec/data';",
77
"",
8-
"const ${1:myObject}: Data.Object = {",
8+
"export const ${1:MyObject} = ObjectSchema.create({",
99
" name: '${2:my_object}',",
1010
" label: '${3:My Object}',",
1111
" pluralLabel: '${3:My Object}s',",
@@ -18,9 +18,7 @@
1818
" },",
1919
" $0",
2020
" },",
21-
"};",
22-
"",
23-
"export default ${1:myObject};"
21+
"});"
2422
]
2523
},
2624
"ObjectStack: Text Field": {
@@ -59,66 +57,65 @@
5957
" type: 'lookup',",
6058
" label: '${2:Related Object}',",
6159
" required: ${3|false,true|},",
62-
" reference: {",
63-
" object: '${4:target_object}',",
64-
" labelField: '${5:name}',",
65-
" },",
60+
" reference: '${4:target_object}',",
61+
" displayField: '${5:name}',",
6662
"},"
6763
]
6864
},
6965
"ObjectStack: Grid List View": {
7066
"prefix": "os-view-grid",
7167
"description": "Define a grid list view for an object",
7268
"body": [
73-
"import { UI } from '@objectstack/spec';",
69+
"import { defineView } from '@objectstack/spec';",
7470
"",
75-
"const ${1:myObject}ListView: UI.View = {",
76-
" name: '${2:my_object}_list',",
77-
" label: '${3:My Object} List',",
78-
" type: 'list',",
79-
" objectName: '${2:my_object}',",
71+
"export const ${1:MyObject}Views = defineView({",
72+
" object: '${2:my_object}',",
8073
" list: {",
74+
" label: '${3:My Object} List',",
8175
" type: 'grid',",
76+
" data: { provider: 'object', object: '${2:my_object}' },",
8277
" columns: [",
8378
" { field: 'name', width: 200 },",
8479
" $0",
8580
" ],",
86-
" defaultSort: { field: 'name', direction: 'asc' },",
87-
" pageSize: 25,",
81+
" sort: [{ field: 'name', order: 'asc' }],",
82+
" pagination: { pageSize: 25 },",
8883
" },",
89-
"};",
90-
"",
91-
"export default ${1:myObject}ListView;"
84+
"});"
9285
]
9386
},
9487
"ObjectStack: Automation Flow": {
9588
"prefix": "os-flow",
96-
"description": "Define an automation flow",
89+
"description": "Define a record-change automation flow",
9790
"body": [
98-
"import { Automation } from '@objectstack/spec';",
91+
"import { defineFlow } from '@objectstack/spec';",
9992
"",
100-
"const ${1:myFlow}: Automation.Flow = {",
93+
"export const ${1:MyFlow} = defineFlow({",
10194
" name: '${2:my_flow}',",
10295
" label: '${3:My Flow}',",
103-
" type: '${4|autolaunched,screen,schedule|}',",
96+
" type: 'record_change',",
10497
" status: 'draft',",
105-
" trigger: {",
106-
" type: 'record_change',",
107-
" object: '${5:my_object}',",
108-
" events: ['after_insert', 'after_update'],",
109-
" },",
11098
" nodes: [",
11199
" {",
112100
" id: 'start',",
113101
" type: 'start',",
114-
" name: 'Start',",
115-
" next: '${6:end}',",
102+
" label: 'Start',",
103+
" config: {",
104+
" objectName: '${4:my_object}',",
105+
" triggerType: '${5|record-after-write,record-after-insert,record-after-update,record-after-delete|}',",
106+
" },",
116107
" },",
117108
" $0",
109+
" {",
110+
" id: 'end',",
111+
" type: 'end',",
112+
" label: 'End',",
113+
" },",
118114
" ],",
119-
"};",
120-
"",
121-
"export default ${1:myFlow};"
115+
" edges: [",
116+
" { id: 'e1', source: 'start', target: 'end' },",
117+
" ],",
118+
"});"
122119
]
123120
},
124121
"ObjectStack: defineStack Boilerplate": {
@@ -129,9 +126,12 @@
129126
"",
130127
"export default defineStack({",
131128
" manifest: {",
132-
" name: '${1:my_app}',",
133-
" version: '${2:0.1.0}',",
134-
" label: '${3:My Application}',",
129+
" id: '${1:com.example.my_app}',",
130+
" namespace: '${2:my_app}',",
131+
" version: '${3:0.1.0}',",
132+
" type: 'app',",
133+
" name: '${4:My Application}',",
134+
" engines: { protocol: '^17' },",
135135
" },",
136136
" objects: [",
137137
" $0",
@@ -145,9 +145,9 @@
145145
"prefix": "os-agent",
146146
"description": "Define an AI agent",
147147
"body": [
148-
"import { AI } from '@objectstack/spec';",
148+
"import { defineAgent } from '@objectstack/spec';",
149149
"",
150-
"const ${1:myAgent}: AI.Agent = {",
150+
"export const ${1:MyAgent} = defineAgent({",
151151
" name: '${2:my_agent}',",
152152
" label: '${3:My Agent}',",
153153
" role: '${4:Assistant}',",
@@ -156,12 +156,10 @@
156156
" provider: '${6|openai,anthropic,google|}',",
157157
" model: '${7:gpt-4o}',",
158158
" },",
159-
" tools: [",
159+
" skills: [",
160160
" $0",
161161
" ],",
162-
"};",
163-
"",
164-
"export default ${1:myAgent};"
162+
"});"
165163
]
166164
}
167165
}

0 commit comments

Comments
 (0)