Skip to content

fix(app-shell): pending changes folds platform-written system fields and derived keys into one collapsed line - #25984

Merged
objectstack-fleet[bot] merged 7 commits into
mainfrom
claude/project-thread-icjdxh
Oct 26, 2026
Merged

objectstack-fleet[bot] merged 7 commits into
mainfrom
claude/project-thread-icjdxh

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Steve · project thread

Fixes #25890

Clause-②: no
Metadata-protocol: none

What changes

Studio's pending-changes drill-in (DraftChangesPanel) listed a new object's platform-injected system columns (created_at, created_by, owner_id, updated_at, updated_by) as fields the author added, and its derived nameField under "Also changed", next to the one field the author actually typed. Following the triage ruling ("Pending changes lists what the author changed. Platform-provisioned system fields and derived keys belong in a collapsed 'generated' group, not in the author's diff."):

  • System fields fold. In computeChangeDetail, a field this publish adds or removes, where every definition the two bodies carry has system: true, moves to a new generated list. system: true is the spec's own mark (FieldSchema.system: "Auto-injected system/audit field … Tools that surface system fields separately from author-declared business fields should branch on this flag"). No name list is kept in the panel. The read localises the injected columns' labels, so byte-identity against injectedSystemColumnDefs cannot answer this on a served body; the mark can.
  • Derived keys fold. A top-level nameField goes to generated when its value equals the @objectstack/spec/data provisionPrimary derivation over the body without the pointer. That is the same test the registry's write-side strip (stripProvisionedPrimaryFrom) uses. A pointer naming a field the derivation would not pick is the author's choice and stays an author row.
  • What stays an author row. A system field whose definition differs between the published and draft bodies (for example, the author relabelled owner_id) stays an author row: the platform injects the same definition on both reads, so a difference is an edit. The objectui#13125 rule (system AND hidden, e.g. __search) still drops those rows outright, unchanged.
  • The fold renders as one collapsed line, "Generated by the platform (N)" (「平台生成(N)」), with a toggle that shows those rows. It stays collapsed in the confirm posture too.
  • A new item's keys are headed "Includes:" (「包含:」) instead of "Also changed:" (「其他变更:」). Every key of an item that does not exist yet is part of it, not some other change.
  • Two new i18n keys (preview.changes.detailNewKeys, preview.changes.generatedGroup) are added to all ten packs, because the key-parity test requires every pack.
  • scripts/sdui-manifest.record.json: the registry-source hash is re-recorded by node scripts/gen-sdui-manifest-node.mjs. The manifest bytes did not change (sha256 1f6e1ccb925a…, 106 components).

The card's repro (new object 「报修工单」, OWD private) now expands to + 名称 name, then Includes: + label + name + sharingModel, then one collapsed Generated by the platform (6) line (the five system columns plus nameField).

Acceptance notes

  • Deviation from the dispatch's acceptance wording ("only the authored field(s) plus one folded system-fields line"). The new object's authored top-level keys label, name and sharingModel are still listed, under "Includes:". The ruling folds platform-provisioned fields and derived keys, and those three are authored in the create dialog. A first cut dropped name/label for new items (the row already shows them), but that reversed seven existing pins in four files (DraftChangesPanel.test.tsx "classifies a NEW item", noChangeAndKeySplit-11807, posture-12517, protectionEnvelope-12594), which the dispatch asked to keep green. So it was reverted. If the maintainer wants a new item's name/label hidden as well, that is a follow-up with those pins rewritten deliberately.
  • One existing pin updated on purpose. In DraftChangesPanel.fieldRows-13125.test.tsx, owner_id and created_at were "listed" controls ("this card does not widen the rule"). They now assert placement in the generated group, and that the drop rule still leaves them in the detail behind the toggle.
  • The panel only sees served bodies, so an author who declares a column that the platform also injects, with system: true on it, reads as platform-written when added. That is the spec's stated meaning of the mark.

Tests

At head f96a8519:

  • pnpm exec vitest run --maxWorkers=2 packages/i18n/ packages/app-shell/src/preview/ (from ui/): Test Files 120 passed (120), Tests 1585 passed | 13 skipped.
  • New DraftChangesPanel.generatedGroup-25890.test.tsx (9 tests). It pins the card's repro using the spec's real injectedSystemColumnDefs with zh-localised labels, plus four controls: a system field the author changed, an author-chosen nameField, a system field added on an update, and an audit-shaped field without the mark.
  • Ablation, once. I disabled the fold predicate through scripts/ablation-replace.mjs (anchor 1 to 0, blob 6c92a1c5… to 805a4a90…). The new suite went 4 failed | 5 passed. The file was restored to its HEAD blob 6c92a1c5…. The suite resolves DraftChangesPanel from source through the ui vitest alias, so no dist rebuild was involved.
  • pnpm --filter @object-ui/i18n --filter @object-ui/app-shell run type-check (both tsc --noEmit and tsconfig.test.json): Done / Done.
  • node scripts/check-i18n-call-site-keys.mjs (ui): exit 0. eslint on the touched files: 0 errors. The 6 warnings are all in code this PR does not change.

Gates

node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack at f96a8519 derived 46 families. All 46 ran with exit 0. --ran reconciliation: "46 derived famil(ies) accounted for — 46 run, 0 NOT-MEASURED (a DERIVED zero …)". At the earlier head 6577bce6, check-sdui-manifest was red because the ui source hash had moved. The regeneration commit fixed it, and the check is green at f96a8519. CI families are left to the PR's check-runs.

Out of bounds and untouched: ui/.github/** and the root .github/workflows/ci.yml.


Generated by Claude Code

…and derived keys into one collapsed group

A new object's drill-in listed the injected audit columns and owner_id as
fields the author added, and its own name / label / derived nameField under
"Also changed". System fields (system: true) the publish adds or removes and
a nameField equal to the registry's provisionPrimary derivation now fold into
a collapsed "Generated by the platform (N)" line; a new item's name and
label (shown on its row) are no longer listed as keys.

Claude-Session: https://claude.ai/code/session_01P4aqt32BH9VTnLdDgkBUMx
Co-authored-by: Claude <noreply@anthropic.com>
…bject repro; keep name/label rows

Claude-Session: https://claude.ai/code/session_01P4aqt32BH9VTnLdDgkBUMx
Co-authored-by: Claude <noreply@anthropic.com>
…nges edit (manifest bytes unchanged)

Claude-Session: https://claude.ai/code/session_01P4aqt32BH9VTnLdDgkBUMx
Co-authored-by: Claude <noreply@anthropic.com>
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对 · ACCEPT · seat domain:ui#1 · 2026-10-26T08:20Z

Head f96a85198f53 (read from the PR object).

  • Shape. Draft on main; Fixes #25890; Clause-②: no, Metadata-protocol: none; changeset .changeset/25890-pending-changes-generated-group.md in the root .changeset/ (patch, app-shell + i18n).
  • Checks pinned to the head. 61 named check runs, latest per name: 54 success, 7 skipped, 0 failing, 0 pending. Lint & Repo Gates, TypeScript Type Check, Test UI 16/16, Type Check UI all success.
  • Governed surface. check-governed-merges.mjs --pr 25984: 0 of 15 paths governed.
  • Changeset against the diff. A field added or removed whose every definition carries system: true moves to the collapsed generated group; a system field CHANGED between bodies stays an author row; nameField is generated only when provisionPrimary(body without the pointer, { synthesize: false }) derives the same name, so the derivation is the registry's own and not restated; a new item's keys are headed "Includes:"; the hidden injected columns stay dropped by the existing isStudioHiddenSystemField rule. Each sentence matches the code.
  • Deviations accepted. (1) label / name / sharingModel stay under "Includes:" for a new object: they are the author's keys of a new item, and moving them would rewrite seven existing pins. The seat agrees with the dev's recommendation; no follow-up card. (2) The objectui#13125 pin's owner_id / created_at controls now assert "not dropped, in the generated group", which is the card's intent, not a widened rule. (3) Ten-pack i18n rows, required by the parity test. (4) scripts/sdui-manifest.record.json hash re-recorded, required by check-sdui-manifest.

⚠️ Merge order: #25978 (queued) also appends to the ten locale packs and moves the same manifest-record line. If this PR conflicts after it lands, the card thread merges origin/main and regenerates the record.

Landing: ready + auto-merge through the relay.

…-icjdxh

# Conflicts:
#	scripts/sdui-manifest.record.json
…anifest bytes unchanged)

Claude-Session: https://claude.ai/code/session_01P4aqt32BH9VTnLdDgkBUMx
Co-authored-by: Claude <noreply@anthropic.com>
…-icjdxh

# Conflicts:
#	scripts/sdui-manifest.record.json
…anifest bytes unchanged)

Claude-Session: https://claude.ai/code/session_01P4aqt32BH9VTnLdDgkBUMx
Co-authored-by: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m tooling

Projects

None yet

2 participants