Repository navigation
Commit d3fd297
feat(scripts): judge JSON doc fences through the strict authoring face (objectui#5250 slice B) (#12127)
Part of #5250
Clause-②: no
Slice B of the objectui#5250 programme: the JSON-fence gate the
maintainer ruling (comment 5534418546, 2026-09-04, decision batch #25,
option 2) orders "on top of" the strict authoring face, plus the docs
repairs it cannot land without. Slice A (objectui#11069, objectui#12054,
objectui#12050) moved `objectui validate` and `objectui check` onto
`StrictAnyComponentSchema`. This PR runs that same face over every
json/jsonc node document in `content/docs`. The rendering face's
`.passthrough()` is not touched, and nothing under `packages/` changes.
This card stays open: slice C (the non-docs class (i) documents) is a
separate PR.
## What changed
1. **New gate `scripts/check-doc-json-fences.mjs`** (`pnpm
check:doc-json`), a sibling script rather than a branch inside the
ts/tsx gate, because its only precondition is a built
`@object-ui/types`, not the 34-package closure the ts/tsx gate needs.
- Verdict: `StrictAnyComponentSchema.safeParse(document).success`, read
from the BUILT `@object-ui/types` `./zod` entry. That is the module
`@object-ui/cli` imports for `validateAuthoredDocument`, whose whole
body is the same call. So the docs gate and `objectui validate` cannot
disagree about a document. A test runs the CLI's own door over the
corpus and asserts identical verdicts.
- Population: the measurement script's node-document predicate
(json/jsonc fence, parses, plain object, string `type`, `type` names an
arm of `AnyComponentSchema`), with two corrections, both toward reading
more. A `jsonc` fence is read with `jsonc-parser` and the options
`objectui check` uses, because the measurement's `JSON.parse` dropped
every commented jsonc fence. A fence inside a blockquote is read, with
the ts/tsx gate's quote rule.
- Not judged, but counted per bucket and printed: not JSON, not an
object, no string `type`, a `type` no node schema declares (with the
types). `--list` prints every fence with its bucket.
- Fragments: a deliberate fragment carries a `doc-json: fragment` marker
(the MDX-comment form in `.mdx`, the HTML-comment form in `.md`) on the
line above its fence. The fragment design follows objectui#5138: an
explicit marker, never a silent skip. Every run re-judges the
declaration: the fence must be a node document the strict face REFUSES.
A marker on an accepted fence, or on a non-document, is stale. A
dangling marker, a short reason, or a verification claim (the ts/tsx
gate's `VERIFICATION_CLAIM`) is a finding. The namespace is `doc-json:`,
not `doc-snippet:`, because the ts/tsx gate reports its own marker above
any non-ts fence as stale.
- Controls, before any verdict: RESOLUTION (the face resolves inside
`packages/types/dist/`), REFUSES (one undeclared key: strict refuses it,
tolerant accepts it), ACCEPTS (the declared-only twin is accepted), and
ANTI-IDLE (at least one judged document, and the anchor page
`content/docs/blocks/forms.mdx` contributes one).
- Exit codes: the sibling gates' three: 0 clean, 1 findings, 2 could not
run.
- Scan surface: `content/docs` only, which is the population the
measurement priced. The header and every summary say that pages outside
it are not read.
2. **Wiring:** the last step of
`.github/workflows/doc-snippet-types.yml`, after the `--build-filter`
build. That closure already carries `@object-ui/types...`, so no build
step is added. One `package.json` script line.
3. **Docs repairs:** 14 pages under `content/docs`, re-derived on the
dispatch base. Each refused fence is repaired or declared, as tabled
below.
4. **Edits outside the claim's file surface,** each forced by an
existing pin or by a statement this change makes false (deviations,
declared):
- `content/docs/guide/ci-cd-pipeline.md`: the command-parity sweep in
`scripts/__tests__/ci-cd-pipeline-doc.test.ts` requires the workflow's
new command to be named on the page. Its table row also said "two gates,
one job", which is now three.
- `scripts/check-doc-component-types.mjs`: eight `DOC_TYPE_EXEMPTIONS`
entries pointed at `type` literals that only the removed snippets
spelled. That gate reports such entries as stale, so they are deleted
with their sites, each with a one-line note.
- `scripts/check-doc-snippet-types.mjs`: four more exports
(`quoteDepthOf`, `stripQuotePrefix`, `isBlankAtDepth` and
`MIN_REASON_LENGTH`), so that both gates read quoted fences and fragment
reasons by one rule. Also its header item "schema-key validity ... left
unruled on purpose", which this PR makes false for json fences, now
points at the sibling.
## Population on the dispatch base 29b9949
- **Measurement predicate** (JSON.parse, no blockquotes): 231 json/jsonc
fences, 185 node documents, 26 refused. Of those, 13 are strict-only and
13 are red on both faces; 8 are refused naming no key. These are the
same numbers the measurement report recorded at `47b1f0b`.
- **This gate's predicate:** 233 fences, 191 node documents, 29 refused.
Two more come from quoted fences in `api/schema-reference.md`, both
accepted. Four more come from commented jsonc fences:
`fields/location.mdx` is accepted, and three are refused (listed below).
- **After the repairs** (head of this PR): 227 fences, 185 judged, 179
accepted, 6 declared fragments (each confirmed refused), 0 refused. Not
judged: 32 with no string `type` and 10 with a `type` no node schema
declares.
Instrument: the gate itself, plus a throwaway copy of the measurement
predicate for the base reading.
## H3: classification of the red-on-both documents
Cited by page and section heading, never by line, per AGENTS.md section
5 rule #11.
| page, section | type | classification | disposition |
|:--|:--|:--|:--|
| `api/schema-reference.md`, DetailSchema | `detail` | teaching error:
field `type: "email"` is outside the detail field vocabulary | repaired:
`text` |
| `components/basic/icon.mdx`, "`name` is identity, not the glyph" |
`icon` | deliberate refused example: the page states it is refused |
declared |
| `components/index.md`, Example: Button | `button` | teaching error:
`size: "md"` is not a declared size | repaired: `default` |
| `guide/dashboard-filters.md`, Step 3 (`optionsFrom`) | `select`
(collision) | not a node: a `globalFilters[]` member whose `type` is the
filter's input type | declared |
| `guide/expressions.md`, Index in Loops | `list` | teaching error: no
item template exists, and `items` must be an array | section rewritten
(see below) |
| `guide/expressions.md`, Dependent Fields | `form` | teaching error: a
form takes `fields`, and select options are `{label, value}` objects |
repaired |
| `guide/schema-playground.md`, Form | `form` | teaching error: string
options, field `defaultValue`, and form `title` | repaired |
| `guide/schema-playground.md`, Grid | `grid` | teaching error: a data
table taught under the layout `grid` type | rewritten as `data-table` |
| `guide/schema-playground.md`, Composing Schemas | `page` | teaching
error: grid `gap: "md"` | repaired: `4` |
| `utilities/runner.mdx`, 1. Plugin Development | `page` | deliberate:
the child is the reader's own plugin type | declared |
| `utilities/runner.mdx`, 3. Integration Testing | `div` | deliberate: a
type-only skeleton | declared |
| `utilities/vscode-extension.mdx`, Snippets, `objectui-form` | `form` |
teaching error: a snippet the extension never shipped, and `onSubmit` is
a runtime slot | section rewritten from the shipped snippets |
| `utilities/vscode-extension.mdx`, Snippets, `objectui-grid` |
`data-table` | teaching error: a snippet the extension never shipped |
section rewritten |
| `guide/expressions.md`, Scoped Data (jsonc, newly read) | `list` |
teaching error: as Index in Loops | section rewritten |
| `utilities/runner.mdx`, Add Custom Schemas (jsonc, newly read) |
`page` | deliberate: the reader's `my-component` | declared |
None of the 15 is exempted silently. Five are declared with a written
reason, and the gate confirms each declared fence is refused. The sixth
declaration is the Validation fence in the next table.
## Strict-only pairs: per-pair disposition and runtime evidence
**Instrument:** a throwaway dom-project test, never committed. It
renders the node through the real `SchemaRenderer` and the real registry
(`packages/components/src/renderers`), once with the key and once
without it, and compares the normalised `document.body` HTML.
**Controls:** each control changes the output, so the instrument can see
a read key: `button.label`, `empty.title`, `input.inputType`, and
`form.defaultValues`.
| page, section | key | disposition | runtime reading |
|:--|:--|:--|:--|
| `blocks/authentication.mdx` Wire Up Submission; `blocks/ecommerce.mdx`
Add to Cart Action; `blocks/forms.mdx` Add Submit Action |
`button.action` | replaced: an `action:button` node with
`properties.{label, actionType: "api", target, method}` (AGENTS.md
section 5 rule #4; the same form as `guide/schema-rendering.md`'s api
example; `executeAPI` reads `target` and `method`) | identical HTML |
| `blocks/dashboard.mdx` Add Actions | `button.action` (navigate) |
replaced: `action:button`, `actionType: "url"`, `target` (`executeUrl`
reads `target`) | identical HTML |
| `blocks/marketing.mdx` Add Analytics | `button.action` (analytics) |
subsection removed: no `analytics` executor exists (the built-ins are
script, url, modal, flow, api, form and navigation) | identical HTML |
| `blocks/dashboard.mdx` Add Real Data; `blocks/ecommerce.mdx` Add
Product Data | `card.dataSource` | replaced: a data-bound block
(`element:number` and `object-grid` with the per-element `dataSource`
binding, verbatim from `guide/data-source.md` and
`utilities/data-objectstack.mdx`). `SchemaRenderer` strips `dataSource`
from the props it spreads | identical HTML |
| `guide/expressions.md` Empty State; `guide/schema-rendering.md` Empty
States | `empty.message` | replaced: `title`, which the empty renderer
reads (control: `title` changes the heading) | differs only by a stray
`message` DOM attribute from the props spread |
| `guide/expressions.md` Dynamic Validation (now Validation) |
`input.validations` | replaced: `inputType: "email"` (control: it
changes the input's `type`) | identical HTML |
| `guide/schema-playground.md` Card | `card.icon` | removed | differs
only by a stray `icon` DOM attribute |
| `utilities/runner.mdx` Dashboard Example | `card.value`, `card.change`
(x3) | replaced: `children` (a text node) and `description` | differs
only by stray `value` / `change` DOM attributes |
| `utilities/vscode-extension.mdx` Snippets, `objectui-app` |
`app.pages` | section rewritten from
`packages/vscode-extension/snippets/objectui.json` (the real `oui-*`
prefixes) | identical HTML |
| `utilities/vscode-extension.mdx` Validation (jsonc, newly read) |
`button.invalidProp` | rewritten: the extension reports no unknown
property (its JSON schema is `additionalProperties: true`, and
`SchemaValidator` checks only `inputType`, `variant` and labels). The
fence now shows the `variant` warning it really emits, and is declared
as a deliberately refused node | not applicable |
**Red-on-both repairs, probed the same way:**
- `list.itemTemplate`: differs only by an `itemtemplate` DOM attribute.
- `form.title`: differs only by the HTML `title` attribute on the `form`
element, so the page now says a form draws no heading.
- `grid` `title` / `rows` / `actions` / `pagination`: identical.
- `data-table` `dataSource` and `columns[].key` / `title`: identical.
- `form.onSubmit` (object): identical.
- `form` `fields[].defaultValue` on a select field: identical. It was
replaced by the form's `defaultValues`, which the control shows the form
reads.
## Proofs
- **Reverse leg.** With the 14 repaired pages reverted to `29b99490`
(`git checkout 29b9949 -- PAGES`), the gate exits 1 with 29 findings.
Restored with `git checkout HEAD -- PAGES`; `git diff HEAD` is empty
afterwards.
- **Ablation 1.** Prediction, written before the run: exit 2, REFUSES.
`judge` was pointed at `AnyComponentSchema` through objectstack's
`scripts/ablation-replace.mjs` (anchor 1 -> 0, blob changed). Result:
exit 2, "REFUSES: the strict face accepted a document carrying an
undeclared key". Restored: blob == HEAD, `git diff HEAD` empty.
- **Ablation 2.** Prediction: exit 2, anti-idle. `JSON_FENCE_LANGUAGES`
was emptied of `json`/`jsonc`. Result: exit 2, 0 judged documents,
anchor-unread. Restored: blob == HEAD. The run also showed that the
summary line still printed "anchor ... read", which the last commit
corrects.
## Gates (HEAD `3c0d3123`)
| command | exit |
|:--|:--|
| `node scripts/check-doc-json-fences.mjs` | 0 |
| locked `pnpm type-check:scripts` plus `vitest run` over the gate's
tests and every test that reads an edited page or the workflow (40
files) | 0, "Test Files 40 passed (40)", "Tests 1926 passed (1926)" |
| `pnpm check:doc-types` | 0 |
| `pnpm check:doc-fences` | 0 |
| `pnpm docs:check-links` | 0, "Links are valid across 17 scan roots" |
| `pnpm check:new-line-citations` | 0, "0 new citation(s)" (one fixture
address declared) |
| `pnpm check:control-bytes` | 0 |
| `node scripts/check-doc-expression-carriage.mjs` | 0 (report-only) |
| `pnpm check:test-path-roots` | 0 |
| `pnpm lint:root` | 0, 0 errors, 34 warnings, none in a touched file |
| `node scripts/check-changeset-presence.mjs` | 0, "no changeset is
owed", so none is added |
| `node scripts/check-governed-queue-guard.mjs --test` over the 21 paths
| 0, NOT GOVERNED |
| `pnpm check:doc-snippets` / `pnpm check:doc-examples` | NOT MEASURED:
they need the 34-package build. No ts/tsx fence changed; the ts/tsx
gate's edit is an export list and one header item, and its own test file
is in the green run above. Declared to CI |
## Acceptance notes
- **Class (ii) finding, not repaired here.** `form`
`fields[].defaultValue` is read at runtime on a TEXT field: the probe
renders `value="zzz"` with it and nothing without it. It is inert on a
select field, which is the case this PR repaired. The strict face
refuses it, so `objectui validate` tells an author to remove a key that
works on text fields. Reported in the card report; no declaration is
made here.
- **Scan surface.** Pages outside `content/docs` are not read. At this
base the strict face refuses four json node documents there: two in the
root `README.md`, one in `packages/plugin-dashboard/README.md`, and one
in a dated audit under `docs/audits/`. Widening the surface needs those
repaired or ledgered first. Reported, not done here.
- **Earlier leads.** `guide/dashboard-filters.md` was listed as a class
(i) carrier in the earlier reports. It is not a node at all: its `type:
"select"` is a filter input type that collides with a node type.
The session that produced this change is
`https://claude.ai/code/session_01DwLS3LzXyNmyTunMGbpc8W`.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01DwLS3LzXyNmyTunMGbpc8W)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 802f0bc commit d3fd297
22 files changed
Lines changed: 1218 additions & 219 deletions
File tree
- .github/workflows
- content/docs
- api
- blocks
- components
- basic
- guide
- utilities
- scripts
- __tests__
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
185 | 185 | | |
186 | 186 | | |
187 | 187 | | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
710 | 710 | | |
711 | 711 | | |
712 | 712 | | |
713 | | - | |
| 713 | + | |
714 | 714 | | |
715 | 715 | | |
716 | 716 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
54 | 54 | | |
55 | 55 | | |
56 | 56 | | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
57 | 61 | | |
58 | 62 | | |
59 | | - | |
60 | | - | |
61 | | - | |
62 | | - | |
63 | | - | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
64 | 68 | | |
65 | 69 | | |
66 | 70 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
33 | 33 | | |
34 | 34 | | |
35 | 35 | | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
36 | 41 | | |
37 | 42 | | |
38 | | - | |
39 | | - | |
40 | | - | |
41 | | - | |
42 | | - | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
43 | 46 | | |
44 | 47 | | |
45 | 48 | | |
| |||
55 | 58 | | |
56 | 59 | | |
57 | 60 | | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
58 | 64 | | |
59 | 65 | | |
60 | | - | |
61 | | - | |
62 | | - | |
63 | | - | |
64 | | - | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
65 | 71 | | |
66 | 72 | | |
67 | 73 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
39 | 39 | | |
40 | 40 | | |
41 | 41 | | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
42 | 46 | | |
43 | 47 | | |
44 | | - | |
45 | | - | |
46 | | - | |
47 | | - | |
48 | | - | |
| 48 | + | |
| 49 | + | |
49 | 50 | | |
50 | 51 | | |
51 | 52 | | |
52 | 53 | | |
53 | 54 | | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
54 | 59 | | |
55 | 60 | | |
56 | | - | |
57 | | - | |
58 | | - | |
59 | | - | |
60 | | - | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
61 | 66 | | |
62 | 67 | | |
63 | 68 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
93 | 93 | | |
94 | 94 | | |
95 | 95 | | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
96 | 100 | | |
97 | 101 | | |
98 | | - | |
99 | | - | |
100 | | - | |
101 | | - | |
102 | | - | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
103 | 107 | | |
104 | 108 | | |
105 | 109 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
46 | 46 | | |
47 | 47 | | |
48 | 48 | | |
49 | | - | |
50 | | - | |
51 | | - | |
52 | | - | |
53 | | - | |
54 | | - | |
55 | | - | |
56 | | - | |
57 | | - | |
58 | | - | |
59 | | - | |
60 | | - | |
61 | | - | |
62 | 49 | | |
63 | 50 | | |
64 | 51 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
34 | 34 | | |
35 | 35 | | |
36 | 36 | | |
| 37 | + | |
37 | 38 | | |
38 | 39 | | |
39 | 40 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
128 | 128 | | |
129 | 129 | | |
130 | 130 | | |
131 | | - | |
| 131 | + | |
132 | 132 | | |
133 | 133 | | |
134 | 134 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
34 | 34 | | |
35 | 35 | | |
36 | 36 | | |
37 | | - | |
| 37 | + | |
38 | 38 | | |
39 | 39 | | |
40 | 40 | | |
| |||
1159 | 1159 | | |
1160 | 1160 | | |
1161 | 1161 | | |
1162 | | - | |
| 1162 | + | |
1163 | 1163 | | |
1164 | 1164 | | |
1165 | 1165 | | |
| |||
1174 | 1174 | | |
1175 | 1175 | | |
1176 | 1176 | | |
| 1177 | + | |
1177 | 1178 | | |
1178 | 1179 | | |
1179 | 1180 | | |
| |||
1211 | 1212 | | |
1212 | 1213 | | |
1213 | 1214 | | |
| 1215 | + | |
| 1216 | + | |
| 1217 | + | |
| 1218 | + | |
| 1219 | + | |
| 1220 | + | |
| 1221 | + | |
| 1222 | + | |
| 1223 | + | |
| 1224 | + | |
| 1225 | + | |
| 1226 | + | |
| 1227 | + | |
| 1228 | + | |
| 1229 | + | |
| 1230 | + | |
| 1231 | + | |
| 1232 | + | |
| 1233 | + | |
| 1234 | + | |
| 1235 | + | |
| 1236 | + | |
| 1237 | + | |
| 1238 | + | |
| 1239 | + | |
| 1240 | + | |
| 1241 | + | |
| 1242 | + | |
| 1243 | + | |
| 1244 | + | |
| 1245 | + | |
| 1246 | + | |
| 1247 | + | |
| 1248 | + | |
| 1249 | + | |
| 1250 | + | |
| 1251 | + | |
1214 | 1252 | | |
1215 | 1253 | | |
1216 | 1254 | | |
| |||
0 commit comments