You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
Repository navigation
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
examples/schema-catalog pnpm regenerate is destructive: it blanks six plugin-dashboard entries' curated titles and descriptions #4633
Measured while working #4624 (PR #4630). Filed unassigned. Duplicate-searched (keyword + regenerate-catalog-index.py + the index path): no open issue covers it.
The defect
examples/schema-catalog/package.json declares the documented way to rebuild the catalog index:
Run it on a clean origin/main @ 69eb6b22b, touching nothing else, and it reports success:
Wrote examples/schema-catalog/src/index.ts with 423 entries.
while producing a 29-insertion / 29-deletion diff that discards hand-curated metadata. The generator derives title from the filename and writes description: "" unconditionally, so entries whose metadata was written by hand are reverted to defaults. Measured examples:
description "Dashboard filters scoping dataset-bound widgets (via the dataset query's runtimeFilter) alongside an inline widget" becomes ""
plugin-dashboard/filtered-dashboard-date-presets
title "Filtered Dashboard — Date Presets + Custom Range" becomes "Filtered Dashboard Date Presets"
description "Built-in dateRange with presets and allowCustomRange; per-widget date-field override and a dateRange: false opt-out" becomes ""
plugin-dashboard/filtered-dashboard loses its description "Dashboard-level date + region filters driving multiple charts over different objects"
Six plugin-dashboard entries are affected in total, and the entry ordering also changes (filtered-dashboard moves after its own filtered-dashboard-* siblings, because the sort is over the flat filename rather than the path segments).
Why it matters
Anyone adding a catalog entry is expected to run the script — that is what it is for — and doing so silently reverts other people's curated titles and descriptions in the same commit. Those strings are user-visible on /docs/guide/schema-catalog. The loss is easy to miss in review because the diff looks like generated-file churn.
It also means the checked-in index.ts is not reproducible from its generator today: the file and the script disagree, and the script wins whenever it is run.
Fix shape (not prescriptive)
The generator needs to PRESERVE existing title / description for entries already present in index.ts and only synthesize defaults for new ones — or the curated metadata needs to move out of the generated file entirely (a sidecar per entry, or a meta block inside the entry JSON, which the generator would then copy through). The second is the more durable shape, since it removes the class rather than the instance.
Not fixed in PR #4630: that PR changes only entry JSON contents, adds and renames nothing, so it does not need the generator to run, and running it there would have smuggled this loss into an unrelated change.
Triage (whole-repo seat, sole grading producer for this backlog): promoted bare card → dispatch, type Bug — the checked-in index.ts is not reproducible from its own generator, and running the documented regenerate script destroys other authors' curated, user-visible metadata; that violates the generator's implied contract (idempotent over an untouched tree). Duplicate search was already done at filing (keyword + script name + index path, no open cover). Graded and claimed in the same round under priority:p0-free normal ordering because the surface (scripts/regenerate-catalog-index.py + examples/schema-catalog/src/index.ts) is disjoint from the two p0 cards in flight, and the card is fresh, fully-specified, and same-day-churn-sensitive (three merges touched this surface in the last day — waiting invites another silent revert).
Claim: PM loop round 1
Session: session_01RnQd8iMMUwXQEV1crFmQiQ
Branch: claude/issue-4633-catalog-regenerate
Worktree: objectui-issue-4633
Domain: repo:objectui
File surface: scripts/regenerate-catalog-index.py + examples/schema-catalog/src/index.ts (+ entry JSON files only if the chosen fix moves curated metadata into them) (stop on breach; explain in the report)
Container & model: M, mode:subagent, model: opus
Serial constraints cleared: three same-day merges on this surface (665661a, 69eb6b2, e028dfc) — dev must work against post-merge origin/main; no in-flight claim or open dev PR touches examples/schema-catalog or scripts/ (checked origin/main log + open PR list this round); in-flight #8500/#8096 declare packages/app-shell/** only (disjoint).
{
"issue": 4633,
"status": "done",
"branch": "claude/issue-4633-catalog-regenerate",
"pr": "https://github.com/objectstack-ai/objectui/pull/4637",
"premise_still_valid": true,
"summary": "Re-measured against current origin/main (5bf5d2cb6, i.e. after the three same-day merges 665661a / 69eb6b2 / e028dfc): premise unchanged, byte-for-byte the same 29-insertion/29-deletion destructive diff the card reported at 69eb6b22b. Both PM-hypothesised mechanisms confirmed in the script, plus a third the card missed — the generator read /tmp/all-entries.txt, a machine-local path outside the repo, so output was not even a function of the tree. Fix takes the card's preferred shape (2) but puts curated title/description/tags in a SIDECAR (examples/schema-catalog/src/catalog-meta.json) rather than inside the entry JSON: src/types.ts states the invariant 'Metadata ... Kept separate from the schema JSON so the raw schemas remain copy-pasteable into user projects', and those entry JSONs are live ObjectUI schemas the docs site renders and users copy-paste, so a meta block would contaminate them. The sidecar gets shape (2)'s durability (the generator only ever READS it, so regeneration cannot destroy curation) without that cost; index.ts becomes a truly derived artifact and gains a DO-NOT-EDIT banner. Ordering is now by (category, slug) instead of by filename, and the /tmp read is gone. Note for the PM: the actual blast radius was TEN entries, not six — the card's six plugin-dashboard entries plus the four auth entries, which survived only because the script carried a hard-coded HANDCRAFTED table for them; that table is now folded into the sidecar and the special case is gone.",
"tests": "All runs at final commit 7db3160 (`git rev-parse --short HEAD`), tree clean, union re-run AFTER the last commit. (a) Acceptance bar A — `git status --short` empty, then `python3 scripts/regenerate-catalog-index.py` => 'Wrote ... 423 entries (10 carrying curated metadata ...)', `git diff --stat` => EMPTY (zero diff); two consecutive regenerations give identical md5 480ae21abd4d445dc0bea3857a5fe440. (b) Acceptance bar B — added schemas/plugin-dashboard/brand-new-entry.json, regenerated (424 entries), whole diff is one import line + one 9-line registry block for the new entry; every curated string untouched (on origin/main the same two commands emit 29 extra destructive lines). Migration proof: all 423 entries parsed out of index.ts before and after and compared field-by-field => 'id set identical: True / entries whose meta changed: 0'; only 7 registry POSITIONS moved, all because support-dashboard had been hand-inserted out of order (the checked-in file's import block and registry body were already in different orders — both are canonical now). Reverse verification, direction predicted RED before running, both legs measured RED and each restored via `git checkout <branch> -- <path>` from the commit with blob hash proven equal to HEAD's (fbe3e90c61a7fe7ac0393c02aae0db92534c8cdc): leg 1 restore filename-derived title + empty description => 'the checked-in index.ts matches its generator' AND 'adding an entry leaves existing curated metadata byte-identical' fail, the latter naming the exact discarded string; leg 2 restore the filename sort => 'orders entries by (category, slug), not by filename' AND the --check assertion fail, diff naming filtered-dashboard moving after its filtered-dashboard-* siblings. Suites: `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` => 'Test Files 11 passed (11) / Tests 1641 passed (1641)' (includes the catalog's smoke test mounting all 423 examples through SchemaRenderer, which covers the reordering and the new banner for the docs-site gallery). `pnpm --filter @object-ui/example-schema-catalog type-check` pass; `... lint` pass, 0 errors (2 warnings, both pre-existing on main); `pnpm type-check:scripts` pass; `node scripts/check-control-bytes.mjs` pass (4176 files) plus a manual `grep -naP` control-byte self-scan of all six changed files (clean); `node scripts/check-changeset-presence.mjs` pass. Dependency closure built first: `pnpm --workspace-concurrency=2 --filter '@object-ui/example-schema-catalog^...' build` (prefix form = downstream-to-this-package dependencies) pass. No ablation in this change, so no dist rebuild claim to make. Heavy phase serialized on /tmp/os-heavy-verify.lock; three -E 99 queue timeouts were waited out actively in-turn (holder PID 13610, another agent's node run) with type-check/lint/PR-body work done between attempts.",
"open_questions": [],
"out_of_scope_findings": []
}
Two things the PM should know beyond the JSON
1. File-surface note (declared, not quietly shipped). The dispatch named scripts/regenerate-catalog-index.py + examples/schema-catalog/src/index.ts
(+ entry JSONs if shape 2 were chosen). Six files changed. The four beyond that list are
all inside the same package and all entailed by the fix:
examples/schema-catalog/src/catalog-meta.json (new) — the sidecar standing in for the
entry-JSON meta block shape 2 would have used, i.e. the same slot in the design.
scripts/__tests__/catalog-index-regenerable-4633.test.ts (new) — the idempotence gate
the dispatch invited.
examples/schema-catalog/package.json — adds regenerate:check next to regenerate
(pnpm --filter X regenerate --check would let pnpm eat the flag).
examples/schema-catalog/README.md — this one is a judgement call worth your eye. Its
"Adding an example" section instructed authors to "Register it in src/index.ts with
metadata (title, description, tags)" — it documented the destructive workflow as the
correct one, and is arguably how the ten curated entries came to live in a generated
file in the first place. Shipping the fix while leaving the README telling the next
author to do the forbidden thing seemed worse than the surface expansion, so it is
updated. Revert it if you disagree; nothing else depends on it.
2. No changeset, and none is owed — by the repo's mechanical rule, not by diff size. @object-ui/example-* sits in the ignore list of .changeset/config.json and scripts/ is not a workspace package, so nothing here is under the src/ of a released
package. node scripts/check-changeset-presence.mjs says so itself: "6 file(s) changed,
0 of them under the src/ of a package the release covers ... no changeset is owed."
objectui enforces this through that gate rather than through a skip-changeset label, so
no label was applied. Labels currently on the PR — dependencies, examples, tests —
were applied by the auto-labeler, not by me; I have left them alone.
CI status at report time: in_progress (draft PR opened at 7db3160, checks
dispatching). Per the dispatch contract the convergence wait is yours, not mine.
ACCEPT — PR #4637 (reviewer of record: seat session session_01RnQd8iMMUwXQEV1crFmQiQ).
Verified against GitHub, not the report's self-description:
Premise re-measured at current origin/main (5bf5d2cb6, after the three same-day merges): still valid, identical destructive diff. The dev also found a third nondeterminism source the card missed — the generator read /tmp/all-entries.txt, machine-local state outside the repo — now removed.
Fix shape: the card's preferred shape (2) via a sidecar (catalog-meta.json) instead of entry-JSON meta blocks, on the strength of the in-repo invariant in types.ts (schemas stay copy-pasteable). Shape (1) rejected for making the generated file its own input. Reasoning recorded in the script header — accepted.
Acceptance bars: (a) regenerate-on-clean-tree → zero diff, byte-identical md5 across consecutive runs; (b) add-entry regeneration preserves all curated strings — whole diff is the new entry. Migration proof: 423 entries field-compared before/after, 0 meta changes; the 7 position moves are the sort fix (support-dashboard was hand-inserted out of order). Reverse verification predicted-RED on both legs and measured RED.
Blast radius wider than the card: actual affected set was ten entries (six plugin-dashboard + four auth held up only by a hard-coded HANDCRAFTED table, now folded into the sidecar).
File surface: 6 files, 4 beyond the dispatch's named list, all flagged in the report and PR body, all in-package and required (sidecar, gate test, regenerate:check script entry, README that documented the destructive workflow as correct) — accepted as declared increments, not silent scope creep.
Changeset: none owed — @object-ui/example-* is in .changeset/config.json's ignore list, verified mechanically by check-changeset-presence (green in CI).
New gate: catalog-index-regenerable-4633.test.ts runs --check against the real tree + hermetic sandbox proofs that the gate can fail. Strengthening, welcome.
Landing: CI was converging at review time (completed jobs all green). The seat flips ready + arms auto-merge once the Lint / Type Check / Test-shard job conclusions are success on head 7db3160.
Activity
yinlianghui commentedon Aug 14, 2026
Triage (whole-repo seat, sole grading producer for this backlog): promoted bare card → dispatch, type Bug — the checked-in
index.tsis not reproducible from its own generator, and running the documentedregeneratescript destroys other authors' curated, user-visible metadata; that violates the generator's implied contract (idempotent over an untouched tree). Duplicate search was already done at filing (keyword + script name + index path, no open cover). Graded and claimed in the same round underpriority:p0-free normal ordering because the surface (scripts/regenerate-catalog-index.py+examples/schema-catalog/src/index.ts) is disjoint from the two p0 cards in flight, and the card is fresh, fully-specified, and same-day-churn-sensitive (three merges touched this surface in the last day — waiting invites another silent revert).Generated by Claude Code
yinlianghui commentedon Aug 14, 2026
Claim: PM loop round 1
Session:
session_01RnQd8iMMUwXQEV1crFmQiQBranch:
claude/issue-4633-catalog-regenerateWorktree:
objectui-issue-4633Domain:
repo:objectuiFile surface:
scripts/regenerate-catalog-index.py+examples/schema-catalog/src/index.ts(+ entry JSON files only if the chosen fix moves curated metadata into them) (stop on breach; explain in the report)Container & model: M,
mode:subagent,model: opusSerial constraints cleared: three same-day merges on this surface (
665661a,69eb6b2,e028dfc) — dev must work against post-mergeorigin/main; no in-flight claim or open dev PR touchesexamples/schema-catalogorscripts/(checkedorigin/mainlog + open PR list this round); in-flight #8500/#8096 declarepackages/app-shell/**only (disjoint).Generated by Claude Code
yinlianghui commentedon Aug 14, 2026
{ "issue": 4633, "status": "done", "branch": "claude/issue-4633-catalog-regenerate", "pr": "https://github.com/objectstack-ai/objectui/pull/4637", "premise_still_valid": true, "summary": "Re-measured against current origin/main (5bf5d2cb6, i.e. after the three same-day merges 665661a / 69eb6b2 / e028dfc): premise unchanged, byte-for-byte the same 29-insertion/29-deletion destructive diff the card reported at 69eb6b22b. Both PM-hypothesised mechanisms confirmed in the script, plus a third the card missed — the generator read /tmp/all-entries.txt, a machine-local path outside the repo, so output was not even a function of the tree. Fix takes the card's preferred shape (2) but puts curated title/description/tags in a SIDECAR (examples/schema-catalog/src/catalog-meta.json) rather than inside the entry JSON: src/types.ts states the invariant 'Metadata ... Kept separate from the schema JSON so the raw schemas remain copy-pasteable into user projects', and those entry JSONs are live ObjectUI schemas the docs site renders and users copy-paste, so a meta block would contaminate them. The sidecar gets shape (2)'s durability (the generator only ever READS it, so regeneration cannot destroy curation) without that cost; index.ts becomes a truly derived artifact and gains a DO-NOT-EDIT banner. Ordering is now by (category, slug) instead of by filename, and the /tmp read is gone. Note for the PM: the actual blast radius was TEN entries, not six — the card's six plugin-dashboard entries plus the four auth entries, which survived only because the script carried a hard-coded HANDCRAFTED table for them; that table is now folded into the sidecar and the special case is gone.", "tests": "All runs at final commit 7db3160 (`git rev-parse --short HEAD`), tree clean, union re-run AFTER the last commit. (a) Acceptance bar A — `git status --short` empty, then `python3 scripts/regenerate-catalog-index.py` => 'Wrote ... 423 entries (10 carrying curated metadata ...)', `git diff --stat` => EMPTY (zero diff); two consecutive regenerations give identical md5 480ae21abd4d445dc0bea3857a5fe440. (b) Acceptance bar B — added schemas/plugin-dashboard/brand-new-entry.json, regenerated (424 entries), whole diff is one import line + one 9-line registry block for the new entry; every curated string untouched (on origin/main the same two commands emit 29 extra destructive lines). Migration proof: all 423 entries parsed out of index.ts before and after and compared field-by-field => 'id set identical: True / entries whose meta changed: 0'; only 7 registry POSITIONS moved, all because support-dashboard had been hand-inserted out of order (the checked-in file's import block and registry body were already in different orders — both are canonical now). Reverse verification, direction predicted RED before running, both legs measured RED and each restored via `git checkout <branch> -- <path>` from the commit with blob hash proven equal to HEAD's (fbe3e90c61a7fe7ac0393c02aae0db92534c8cdc): leg 1 restore filename-derived title + empty description => 'the checked-in index.ts matches its generator' AND 'adding an entry leaves existing curated metadata byte-identical' fail, the latter naming the exact discarded string; leg 2 restore the filename sort => 'orders entries by (category, slug), not by filename' AND the --check assertion fail, diff naming filtered-dashboard moving after its filtered-dashboard-* siblings. Suites: `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` => 'Test Files 11 passed (11) / Tests 1641 passed (1641)' (includes the catalog's smoke test mounting all 423 examples through SchemaRenderer, which covers the reordering and the new banner for the docs-site gallery). `pnpm --filter @object-ui/example-schema-catalog type-check` pass; `... lint` pass, 0 errors (2 warnings, both pre-existing on main); `pnpm type-check:scripts` pass; `node scripts/check-control-bytes.mjs` pass (4176 files) plus a manual `grep -naP` control-byte self-scan of all six changed files (clean); `node scripts/check-changeset-presence.mjs` pass. Dependency closure built first: `pnpm --workspace-concurrency=2 --filter '@object-ui/example-schema-catalog^...' build` (prefix form = downstream-to-this-package dependencies) pass. No ablation in this change, so no dist rebuild claim to make. Heavy phase serialized on /tmp/os-heavy-verify.lock; three -E 99 queue timeouts were waited out actively in-turn (holder PID 13610, another agent's node run) with type-check/lint/PR-body work done between attempts.", "open_questions": [], "out_of_scope_findings": [] }Two things the PM should know beyond the JSON
1. File-surface note (declared, not quietly shipped). The dispatch named
scripts/regenerate-catalog-index.py+examples/schema-catalog/src/index.ts(+ entry JSONs if shape 2 were chosen). Six files changed. The four beyond that list are
all inside the same package and all entailed by the fix:
examples/schema-catalog/src/catalog-meta.json(new) — the sidecar standing in for theentry-JSON
metablock shape 2 would have used, i.e. the same slot in the design.scripts/__tests__/catalog-index-regenerable-4633.test.ts(new) — the idempotence gatethe dispatch invited.
examples/schema-catalog/package.json— addsregenerate:checknext toregenerate(
pnpm --filter X regenerate --checkwould let pnpm eat the flag).examples/schema-catalog/README.md— this one is a judgement call worth your eye. Its"Adding an example" section instructed authors to "Register it in
src/index.tswithmetadata (title, description, tags)" — it documented the destructive workflow as the
correct one, and is arguably how the ten curated entries came to live in a generated
file in the first place. Shipping the fix while leaving the README telling the next
author to do the forbidden thing seemed worse than the surface expansion, so it is
updated. Revert it if you disagree; nothing else depends on it.
2. No changeset, and none is owed — by the repo's mechanical rule, not by diff size.
@object-ui/example-*sits in theignorelist of.changeset/config.jsonandscripts/is not a workspace package, so nothing here is under thesrc/of a releasedpackage.
node scripts/check-changeset-presence.mjssays so itself: "6 file(s) changed,0 of them under the src/ of a package the release covers ... no changeset is owed."
objectui enforces this through that gate rather than through a
skip-changesetlabel, sono label was applied. Labels currently on the PR —
dependencies,examples,tests—were applied by the auto-labeler, not by me; I have left them alone.
CI status at report time: in_progress (draft PR opened at
7db3160, checksdispatching). Per the dispatch contract the convergence wait is yours, not mine.
Generated by Claude Code
Generated by Claude Code
yinlianghui commentedon Aug 14, 2026
ACCEPT — PR #4637 (reviewer of record: seat session
session_01RnQd8iMMUwXQEV1crFmQiQ).Verified against GitHub, not the report's self-description:
origin/main(5bf5d2cb6, after the three same-day merges): still valid, identical destructive diff. The dev also found a third nondeterminism source the card missed — the generator read/tmp/all-entries.txt, machine-local state outside the repo — now removed.catalog-meta.json) instead of entry-JSONmetablocks, on the strength of the in-repo invariant intypes.ts(schemas stay copy-pasteable). Shape (1) rejected for making the generated file its own input. Reasoning recorded in the script header — accepted.HANDCRAFTEDtable, now folded into the sidecar).regenerate:checkscript entry, README that documented the destructive workflow as correct) — accepted as declared increments, not silent scope creep.@object-ui/example-*is in.changeset/config.json's ignore list, verified mechanically bycheck-changeset-presence(green in CI).catalog-index-regenerable-4633.test.tsruns--checkagainst the real tree + hermetic sandbox proofs that the gate can fail. Strengthening, welcome.Landing: CI was converging at review time (completed jobs all green). The seat flips ready + arms auto-merge once the Lint / Type Check / Test-shard job conclusions are
successon head7db3160.Generated by Claude Code