Skip to content
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
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

Closed

Description

@yinlianghui

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:

"regenerate": "python3 ../../scripts/regenerate-catalog-index.py"

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:

  • plugin-dashboard/filtered-dashboard-dataset-widgets
    • title "Filtered Dashboard — Dataset + Inline Widgets" becomes "Filtered Dashboard Dataset Widgets"
    • 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.


Generated by Claude Code

Activity

  1. added theissue type on Aug 14, 2026
  2. self-assigned this
    on Aug 14, 2026
  3. yinlianghui commented on Aug 14, 2026

    @yinlianghui
    ContributorAuthor

    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).


    Generated by Claude Code

  4. yinlianghui commented on Aug 14, 2026

    @yinlianghui
    ContributorAuthor

    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).


    Generated by Claude Code

  5. yinlianghui commented on Aug 14, 2026

    @yinlianghui
    ContributorAuthor
    {
      "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.


    Generated by Claude Code


    Generated by Claude Code

  6. yinlianghui commented on Aug 14, 2026

    @yinlianghui
    ContributorAuthor

    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.


    Generated by Claude Code

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

Metadata

Metadata

Assignees

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions