Skip to content

fix(examples,scripts): regenerating the schema catalog stops discarding curated metadata - #4637

Merged
yinlianghui merged 1 commit into
mainfrom
claude/issue-4633-catalog-regenerate
Aug 14, 2026
Merged

fix(examples,scripts): regenerating the schema catalog stops discarding curated metadata#4637
yinlianghui merged 1 commit into
mainfrom
claude/issue-4633-catalog-regenerate

Conversation

@yinlianghui

Copy link
Copy Markdown
Collaborator

Fixes #4633

The defect, re-measured against current origin/main

The card measured at 69eb6b22b; three merges have landed on this surface since
(665661a, 69eb6b2, e028dfc). Re-measured at 5bf5d2cb6premise still valid,
identical shape and identical counts
:

$ git status --short           # clean tree
$ python3 scripts/regenerate-catalog-index.py
Wrote examples/schema-catalog/src/index.ts with 423 entries.
$ git diff --stat
 examples/schema-catalog/src/index.ts | 58 ++++++++++++++++++------------------
 1 file changed, 29 insertions(+), 29 deletions(-)

Both mechanisms the card names were confirmed in the script before choosing a fix:

  • title was derived from the filename and description: "" written unconditionally,
    so ten entries lost curated metadata (the card measured six plugin-dashboard
    entries; the full set is those six plus the four auth entries, which survived only
    because the script carried a hard-coded HANDCRAFTED table for them).
  • Ordering sorted Path objects, i.e. the filename including .json. '-' < '.',
    so filtered-dashboard.json sorted after filtered-dashboard-dataset-widgets.json.

A third source of nondeterminism was found that the card does not mention: the script
read /tmp/all-entries.txt as a best-effort metadata source. Output that depends on a
machine-local absolute path outside the repo cannot satisfy "reproducible from its
generator" — with that file present, the same tree regenerates to different bytes.

The fix — shape (2), via a sidecar rather than the entry JSON

The card offered two shapes and preferred (2), "removing the class rather than the
instance". This PR takes (2), but puts the curated block in a sidecar,
examples/schema-catalog/src/catalog-meta.json, rather than inside each entry JSON.
The reason is an explicit in-repo invariant — examples/schema-catalog/src/types.ts:

Metadata describing an example. Kept separate from the schema JSON so the raw
schemas remain copy-pasteable into user projects.

Those entry JSONs are real ObjectUI schemas that the docs site renders and that users
copy-paste; a meta key inside them would ride along into every copy and add a
non-schema key to a live schema surface. The sidecar gets shape (2)'s durability —
curated strings live somewhere the generator only ever reads, so regeneration
cannot destroy them — without contaminating the schemas. index.ts becomes a
genuinely derived artifact, and it now carries a GENERATED FILE — DO NOT EDIT BY HAND
banner pointing authors at the sidecar.

Shape (1) (preserve by parsing the previously generated index.ts) was rejected: it
makes the generated artifact its own input, so the file can never be rebuilt from
scratch, and it keeps authors editing a generated file — which is the very thing that
made the loss possible.

Also in scope, both required for "reproducible from its generator":

  • entries sort by (category, slug), not by filename;
  • the /tmp/all-entries.txt read is gone.

Verification evidence

(a) regenerate on a clean tree produces zero diff

$ git status --short                       # clean, at HEAD of this branch
$ python3 scripts/regenerate-catalog-index.py
Wrote examples/schema-catalog/src/index.ts with 423 entries (10 carrying curated metadata from examples/schema-catalog/src/catalog-meta.json).
$ git diff --stat
                                           # empty — zero diff

Byte-level idempotence, two consecutive runs:

$ md5sum examples/schema-catalog/src/index.ts
480ae21abd4d445dc0bea3857a5fe440  examples/schema-catalog/src/index.ts
$ python3 scripts/regenerate-catalog-index.py >/dev/null && md5sum examples/schema-catalog/src/index.ts
480ae21abd4d445dc0bea3857a5fe440  examples/schema-catalog/src/index.ts

(b) regenerating after adding a new entry preserves every curated string

$ echo '{ "type": "dashboard", "title": "Brand New" }' > examples/schema-catalog/src/schemas/plugin-dashboard/brand-new-entry.json
$ python3 scripts/regenerate-catalog-index.py
Wrote examples/schema-catalog/src/index.ts with 424 entries (10 carrying curated metadata from examples/schema-catalog/src/catalog-meta.json).
$ git diff examples/schema-catalog/src/index.ts

The whole diff is the new entry — one import line and one nine-line registry block:

+import plugin_dashboard_brand_new_entry from './schemas/plugin-dashboard/brand-new-entry.json' with { type: 'json' };
...
+  'plugin-dashboard/brand-new-entry': {
+    id: 'plugin-dashboard/brand-new-entry',
+    meta: {
+      title: "Brand New Entry",
+      description: "",
+      category: 'plugin-dashboard',
+    },
+    schema: plugin_dashboard_brand_new_entry,
+  },

Nothing else moved. On origin/main the same two commands emit 29 extra destructive
lines alongside that block — that is the whole bug.

No curated metadata was lost in the migration

The sidecar was extracted mechanically, then the result was proved equal to the
pre-change file. All 423 registry entries were parsed out of index.ts before and
after and compared field by field:

entries before/after: 423 423
id set identical: True
entries whose meta changed: 0
entries whose registry position changed: 7
  ['plugin-dashboard/support-dashboard', 'plugin-dashboard/filtered-dashboard',
   'plugin-dashboard/filtered-dashboard-dataset-widgets',
   'plugin-dashboard/filtered-dashboard-date-presets',
   'plugin-dashboard/filtered-dashboard-dynamic-options',
   'plugin-dashboard/filtered-dashboard-filter-types',
   'plugin-dashboard/filtered-dashboard-target-widgets']

Zero title/description/tags changed across all 423 entries. The only
substantive change to index.ts in this PR is that plugin-dashboard/support-dashboard
moves to its sorted position: the checked-in registry body had it hand-inserted before
filtered-dashboard, so the file's own import block and registry body were already in
different orders. Both are now canonically (category, slug)-sorted. The gallery on
/docs/guide/schema-catalog renders in registry order, so one card within the
plugin-dashboard group moves; no card gains, loses or changes a string.

Reverse verification — direction predicted before running, both legs RED

Predicted RED for both, and both were measured RED (each restored from the commit
afterwards; the generator's blob hash was checked equal to HEAD's to prove identity):

  1. Restore the filename-derived title and unconditional empty description →
    the checked-in index.ts matches its generator and adding an entry leaves existing curated metadata byte-identical both fail, the second naming the exact
    curated string that would be discarded.
  2. Restore the sort over the filename →
    orders entries by (category, slug), not by filename and the --check
    assertion both fail, the diff naming filtered-dashboard moving after its
    filtered-dashboard-* siblings.

Gate added

scripts/__tests__/catalog-index-regenerable-4633.test.ts (6 tests). It runs
regenerate --check against the real tree, so a hand-edit to the generated file — or a
curated title typed there instead of into the sidecar — fails in CI rather than silently
at the next author's regeneration. The other five run in a hermetic mkdtemp sandbox
and prove the gate can actually fail: adding an entry preserves curated strings
byte-for-byte, ordering is (category, slug), --check goes red on a hand-edit, and
curated metadata for an id with no entry on disk is refused rather than silently ignored.
It shells out to python3 rather than re-implementing the output format in TypeScript,
so there stays exactly one source of truth for the format.

Local verification (all at 7db3160, this branch's HEAD)

Command Result
pnpm exec vitest run examples/schema-catalog scripts/__tests__/catalog-index-regenerable-4633.test.ts scripts/__tests__/turbo-build-inputs.test.ts scripts/__tests__/check-type-check-coverage.test.ts 11 files, 1641 tests passed
pnpm --filter @object-ui/example-schema-catalog type-check pass
pnpm --filter @object-ui/example-schema-catalog lint pass, 0 errors (2 pre-existing warnings, unchanged from main)
pnpm type-check:scripts pass
node scripts/check-control-bytes.mjs pass (4176 files)
node scripts/check-changeset-presence.mjs pass — no changeset owed
pnpm --filter '@object-ui/example-schema-catalog^...' build pass

The vitest run includes the catalog package's own eight suites — among them the smoke
test that mounts every one of the 423 examples through SchemaRenderer, which is what
covers the ordering change and the new banner against the docs-site gallery.

Changeset

None, and none is owed. @object-ui/example-* is in the ignore list of
.changeset/config.json, and scripts/ is not a workspace package, so nothing this PR
touches is under the src/ of a released package. node scripts/check-changeset-presence.mjs
confirms mechanically:

Compared the working tree with 5bf5d2cb6 (merge-base with origin/main): 6 file(s) changed,
0 of them under the src/ of a package the release covers, 0 under a package changesets
ignores, 0 changeset(s) added.
No source of a released package changed in this range, so no changeset is owed.

File surface note

The dispatch named scripts/regenerate-catalog-index.py + examples/schema-catalog/src/index.ts
(+ entry JSON files if shape 2 were chosen). Four files outside that list are touched, all
inside the same package and all required by the fix — flagged here rather than shipped quietly:

  • examples/schema-catalog/src/catalog-meta.jsonnew; the sidecar that replaces the
    entry-JSON meta block shape 2 would have used.
  • examples/schema-catalog/README.md — its "Adding an example" section said "Register it
    in src/index.ts with metadata (title, description, tags)"
    , i.e. it documented the
    destructive workflow as the correct one. Leaving it would ship a fix whose own docs
    instruct people to do the thing it prevents.
  • examples/schema-catalog/package.json — adds regenerate:check alongside regenerate.
  • scripts/__tests__/catalog-index-regenerable-4633.test.tsnew; the gate.

Generated by Claude Code

…ng curated metadata

`scripts/regenerate-catalog-index.py` derived every `title` from the filename
and wrote `description: ""` unconditionally, so running the documented
`regenerate` command on an untouched tree reported success while producing a
29-insertion diff that discarded hand-written metadata for ten entries — six of
them `plugin-dashboard` entries whose strings are user-visible on
/docs/guide/schema-catalog. The checked-in index.ts was not reproducible from
its own generator, and the generator won whenever it ran.

Curated title/description/tags now live in
`examples/schema-catalog/src/catalog-meta.json`, which the generator only ever
reads, so index.ts becomes a genuinely derived artifact. Ordering is by
(category, slug) rather than by filename, and the machine-local
/tmp/all-entries.txt input is gone — both were sources of nondeterminism.

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

vercel Bot commented Aug 14, 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)
objectui Ignored Ignored Aug 14, 2026 10:57am

Request Review

Copy link
Copy Markdown
Collaborator Author

CI note: the Test (shard 3/4) failure on head 7db3160 is not this PR's — it is quick-reference-current-release-4143.test.ts, red on the base branch itself (the 17.5.0 release commit left QUICK_REFERENCE.md at 17.4.0). Repair filed and dispatched as #4642. This PR re-runs once main recovers; all other gates here are green or converging.


Generated by Claude Code

@yinlianghui
yinlianghui marked this pull request as ready for review August 14, 2026 15:19
@yinlianghui
yinlianghui added this pull request to the merge queue Aug 14, 2026
Merged via the queue into main with commit 1111fa1 Aug 14, 2026
18 of 19 checks passed
@yinlianghui
yinlianghui deleted the claude/issue-4633-catalog-regenerate branch August 14, 2026 15:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

examples/schema-catalog pnpm regenerate is destructive: it blanks six plugin-dashboard entries' curated titles and descriptions

2 participants