Repository navigation
fix(studio): stop offering the dead field.externalId toggle, and keep a stored value through every save - #27315
Conversation
The field form row, the designer drawer switch and the field inspector checkbox are gone; a stored externalId: true is carried through every Studio save untouched. Drops the i18n strings only the toggle used. Claude-Session: https://claude.ai/code/session_01WGufzg4R646jZ5Ky7hqq9L Co-authored-by: Claude <noreply@anthropic.com>
…ut field.externalId; changesets Claude-Session: https://claude.ai/code/session_01WGufzg4R646jZ5Ky7hqq9L Co-authored-by: Claude <noreply@anthropic.com>
…eaf count 695 -> 694 Claude-Session: https://claude.ai/code/session_01WGufzg4R646jZ5Ky7hqq9L Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 2 package(s): 5 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
What this run could not see
Coarse fallback — 165 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin f2811273f2af8bc76fefa8465c140f615729f2d2 && git checkout f2811273f2af8bc76fefa8465c140f615729f2d2
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin ff26bc4c2c4b4299c1e7acbf822774eb61b248b5 0b08d30643b4630798a30d0142a5bb5b563db300 && git checkout -B drift-repro ff26bc4c2c4b4299c1e7acbf822774eb61b248b5 && git merge --no-ff 0b08d30643b4630798a30d0142a5bb5b563db300
node scripts/docs-audit/affected-docs.mjs --json ff26bc4c2c4b4299c1e7acbf822774eb61b248b5
|
…surface offers the key Claude-Session: https://claude.ai/code/session_01WGufzg4R646jZ5Ky7hqq9L Co-authored-by: Claude <noreply@anthropic.com>
|
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U Contract-review-claim: Triage seat, taking Generated by Claude Code |
Contract reviewServed-tier: Inputs read: card #26674 body and all 4 comments (triage 6442569651, claim 6471969865, os-dev-report 6474555034, claim amendment 6474597348); the ruling the triage comment cites, the #26598 contract-review records (should-fix 2 = this card); PR #27315 body, 32-file list, net diff vs ① Derived judgments
② Semver levelFour changesets, each Clause-②: ③ Boundary flags
④ Design judgment
Break attempts, each read in the live tree at the head:
Must-fix: none. Should-fix (do not decide the verdict):
Implemented-by: VERDICT: PASS Generated by Claude Code |
维护者速读 (席位终稿)
席位意见:接受。分诊契约复核 PASS(6485032650,就在当前 head 上):接受集不变,表单与 Zod 对账的省略行是必需的,三个 Studio 保存面和通用元数据编辑器都会保留已存的值。 席位核对 · ACCEPT ·
|
Requested by Yibo Wang · project thread
Fixes #26674
Claim: 6471969865
Clause-②: no
Metadata-protocol: none
Studio stops offering the key; the protocol keeps parsing it. This is not a retirement: no tombstone, no narrowing, no schema, type or export change.
FieldSchemaacceptsexternalIdexactly as before, and a field that already carriesexternalId: truekeeps it through every Studio save.维护者速读
externalId: true的字段,在 Studio 里编辑保存后原样保留。field.externalId已判为 dead,任何 upsert、导入、种子都不按它匹配。开关让人以为重新导入会按它更新而不是新增,结果静默产生重复记录,os lint还会对表单自己给出的值报警告。ui/packages/plugin-designer/src/FieldDesigner.tsx(更新路径不再写externalId)、ui/packages/app-shell/src/views/metadata-admin/inspectors/ObjectFieldInspector.tsx(删复选框)、packages/spec/src/data/field.form.ts(删表单行)。cd ui && pnpm exec vitest run packages/plugin-designer/src/MetadataFieldsPage.externalIdRoundTrip-26674.test.tsx,预期 2 个用例全绿:编辑保存后 PUT 里两个字段的externalId仍是true。What changes
Measured first on
origin/main2302b92b: three Studio surfaces offered the toggle.packages/spec/src/data/field.form.ts, Advanced section)externalId, help text "Mark as external ID for upsert operations", repeated in all four*.metadata-forms.generated.tsbundles (en "External Id" / zh-CN "外部 ID" / ja-JP / es-ES)node scripts/check-i18n-bundles.mjs --write --filter=platform-objects(16 lines removed, nothing else moved)ui/packages/plugin-designer/src/FieldDesigner.tsx, Advanced section; used byMetadataFieldsPage.tsx)externalIdboolean control; update path wroteexternalId: data.externalId === true...editingFieldcarries a stored value through; a created field no longer carriesexternalId: falseui/packages/app-shell/.../inspectors/ObjectFieldInspector.tsx, Advanced section)External IDcheckboxpatchDefspreads the whole stored definition, so the key survives every editappDesigner.fieldDesigner.externalIdremoved from all tenui/packages/i18n/src/locales/*.tspacks and from the plugin-designer fallback table (useDesignerTranslation.ts);designer.field.externalIdremoved from the Studio namespace (en,zh).MetadataFieldsPage.tsxis unchanged. ItstoDesignerField/fromDesignerFieldpair already carriesexternalIdboth ways; with the drawer now preserving it, the round-trip holds, and a test pins it end to end.externalIdis recorded as a deliberate root omission of thefieldform, with its reason (gradeddead, the schema still parses it, the seed dataset'sexternalIdand an import mapping'supsertKeyare the real match keys).@objectstack/spec,@objectstack/platform-objects,@object-ui/plugin-designer,@object-ui/i18n.@object-ui/app-shellis private, so it gets none.packages/spec/liveness/field.json, rowexternalId, claim amendment 6474597348): the evidence said Studio's inspector and designer edit the key, which this PR makes false. It now says the protocol still parses the key, no Studio surface offers it (form row, designer drawer, inspector), and theui/mentions only carry a stored value through a save. Prose only: the grade staysdead,verifiedAtandauthorHintare unchanged.skills/**,content/docs/**,.claude/**or other register path is touched.Tests
packages/spec/src/data/field-form-external-id-not-offered.test.ts(new, 4 cases): the field form declares noexternalIdrow at any depth (with a positive control that the walk reachesstorage); no help text still calls a field flag an upsert key;FieldSchemastill parses an authoredexternalId: true; the liveness rowfield.externalIdstill readsdead.ui/packages/plugin-designer/src/__tests__/FieldDesigner.externalIdNotOffered-26674.test.tsx(new, 4 cases): noexternalIdcontrol in the create or edit drawer (each paired with thetrackHistorysibling as positive control); a field withexternalId: truekeeps it through the drawer's edit-and-save; a created field carries noexternalIdkey.ui/packages/plugin-designer/src/MetadataFieldsPage.externalIdRoundTrip-26674.test.tsx(new, 2 cases): the REALFieldDesignerover a realMetadataClientand a fetch double; after an edit-and-save the PUT keepsexternalId: trueon the edited field and on an untouched one, a field without the flag gains none, and every PUT field parses throughFieldSchema.ui/packages/app-shell/src/views/metadata-admin/inspectors/ObjectFieldInspector.externalIdNotOffered-26674.test.tsx(new, 3 cases): noExternal IDcontrol (with its Advanced siblings rendering), a stored value does not resurrect it, and a label edit keepsexternalId: truein the saved fields map.metadata-form-zod-reconciliation.test.tsgains thefieldroot omission row;object-lifecycle-panel-echo-decisions.test.tstranslated-leaf positive control 695 → 694 (one row label fewer, per locale);ObjectFieldInspector.retiredIndexed.test.tsxdropsExternal IDfrom its list of Advanced siblings.Acceptance notes
DesignerFieldDefinition.externalId(@object-ui/types), its Zod mirror andMetadataService's field summary are unchanged on purpose: they carry the stored value, which is what keeps it from being stripped.content/docs/**andskills/**already describe the field flag as "Not read" and teach the seed dataset'sexternalId, so nothing there became false.Verification
All readings at head
b357ce35unless stated; local runs underscripts/pm/os-verify-lock.sh.Round r1 (head
0b08d306, the liveness evidence prose only):pnpm --filter @objectstack/spec check:livenessexit 0 (80 dead, 1219 classified);pnpm --filter @objectstack/spec test749 passed + 1 skipped of 750 files, 22379 passed, 0 failed.Package tests and type-checks
pnpm --filter @objectstack/spec testat5570dc7a: 749/750 files, 22380 passed, 1 failed (metadata-form-zod-reconciliation, the expected root-omission ripple). Fixed inb357ce35; that file, the new spec test andmetadata-form-declared-rows.pin.test.tsthen 3/3 files, 91/91 tests.pnpm --filter @objectstack/spec typecheck: exit 0.pnpm --filter @objectstack/platform-objects testat5570dc7a: 95/96 files, 1360 passed, 1 failed (the 695 leaf-count pin). Fixed inb357ce35; that file 35/35.pnpm --filter @objectstack/platform-objects typecheck: exit 0.cd ui && pnpm exec vitest run packages/plugin-designer/ packages/i18n/: 139/139 files, 1833 passed, 13 skipped.ObjectFieldInspector(the full app-shell suite is CI's Test UI): 40/40 files, 459/459 tests.pnpm exec turbo run type-check --filter=@object-ui/plugin-designer --filter=@object-ui/i18n --filter=@object-ui/app-shell: 66/66 tasks, each of the three type-checks executed (cache miss).pnpm --dir ui run check:designer-field-key-parity,check:i18n-keys,check:i18n-drift: exit 0 each (drift: 1 key removed, 0 values changed).node scripts/check-i18n-bundles.mjs(pnpm check:i18n): OK, 10 packages in sync.Ablation (the update-path change)
node scripts/ablation-replace.mjsputexternalId: data.externalId === trueback intoFieldDesigner.handleFormSuccess's update path (anchor 1 → 0, bloba0985e17→6a7c1f3e). The two round-trip cases went red, exactly those two:FieldDesigner … keeps externalId: true through the update pathandMetadataFieldsPage … keeps externalId: true on the edited field and on an untouched one(expected false to be true); 4 other cases stayed green. Restored: blob equals HEAD,git diff HEADempty.Derived gates
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack: 85 families. Reconciliation with--ran: 85 derived, 85 run, 0 NOT-MEASURED, 0 UNRUN; every recorded exit is 0. On the first pass nine families ran while a turbo build was rewritingpackages/spec/dist(exit 3 / timeout) and were re-run on a settled tree, exit 0 each.Then all 85 re-run once more, serially, on the final head
b357ce35with nothing else running: 85/85 exit 0;--ranagain 85 derived, 85 run, 0 NOT-MEASURED, 0 UNRUN (dispatch-gates --ranexit 0).Generated by Claude Code