Skip to content

fix(studio): hold a Rollup field until it is set up, and offer only the child fields the spec accepts for its function - #27720

Merged
objectstack-fleet[bot] merged 5 commits into
mainfrom
claude/project-thread-g43cz2
Oct 29, 2026
Merged

objectstack-fleet[bot] merged 5 commits into
mainfrom
claude/project-thread-g43cz2

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Bill · project thread

维护者速读

  • 改了什么:Studio 数据页新加「汇总(Rollup)」字段时,在子对象、汇总方式、(sum/avg/min/max 时的)汇总子字段配齐之前不再自动保存草稿,页面上出现一行提示点名字段和还缺的那一项,带「Show me」;「汇总的子字段」下拉只列出 spec 规则接受的子字段(选 Sum 不再出现文本、审计、lookup、主从字段)。
  • 为什么改:以前一点 Create 就把 { type: 'summary' }、没有 summaryOperations 的字段写进草稿,服务端照收,这个字段处处读 0;子字段下拉不按汇总方式过滤,作者能选出运行时拒收的组合。
  • 风险与回滚:只改前端的拦截判定与下拉候选,判定全部问 spec(checkFieldCompleteness、FieldSchema、isAggregateCompatibleWithFieldType),不另存类型清单;最坏情况是多拦一次(提示行 + Show me 可解)。回滚即 revert 本 PR。
  • 先看:ui/packages/app-shell/src/views/studio-design/metadataError.ts(summaryHeldInput / summaryHeldEdit)、ui/packages/app-shell/src/views/metadata-admin/inspectors/ObjectFieldInspector.tsx(aggregandOptions)、DataPillar.summaryHeld-27461.test.tsx。
  • 两分钟验证:pnpm --dir ui exec vitest run packages/app-shell/src/views/studio-design/metadataError.summaryHeld-27461.test.ts packages/app-shell/src/views/metadata-admin/inspectors/ObjectFieldInspector.summaryAggregand-27461.test.tsx packages/app-shell/src/views/studio-design/DataPillar.summaryHeld-27461.test.tsx,预期 3 个文件 13 条全部通过。
  • 需要你做的:无,席位落地。

Fixes #27461

Clause-②: no
Metadata-protocol: none

What changes

Held half (card steps 2 to 4). The Data pillar's autosave already holds an edit the object write guard would refuse (a choice field before its options, a relationship before its target). The guard lets a roll-up through at every stage, and the server saves a summary with no summaryOperations (it reads 0 everywhere), so the card's draft PUT went out the moment the field was created. objectHeldEdit now also holds the first named summary field that is still being set up, once the guard lets the fields through and before an unfinished action:

  • The judges are the spec's own, never a list kept here: its completeness rule field/summary-without-operations (checkFieldCompleteness, @objectstack/spec/kernel) for a roll-up with no summaryOperations, and FieldSchema for what a started one still lacks. Unfinished means every issue FieldSchema names under summaryOperations is the child object, the function, or the child field (which the schema requires exactly when summaryFunctionReadsField says the function reads one), each still absent. Any other issue (an unknown function, say) is finished but wrong: the save is sent and its refusal shows as before.
  • The held line names the field by its label and the input it needs, in the inspector's order, with the inspector's own label (Child object, Aggregation, Child field to aggregate). "Show me" reopens the field. The Studio Publish refusal and the held-edit report read the same objHeld, unchanged.
  • One new clause, engine.studio.held.fieldNeedsInput (en, zh): the existing needsOptions / needsTarget / step / action / widget clauses do not name a field input.

Picker half (card steps 5 to 7). "Child field to aggregate" now offers only the child fields isAggregateCompatibleWithFieldType (@objectstack/spec/data, the aggregate × field-type table the dataset compiler and the engine's aggregate door read) accepts for the chosen function. Before a function is picked, it offers a field some value-reading summary function accepts. The filter rows keep offering every child field: a filter reads any.

Tests

  • New metadataError.summaryHeld-27461.test.ts (8 cases): each held case is paired with the spec's verdict on the same field (the completeness rule fires, or FieldSchema refuses at that path); controls: a count without a field and a sum with one are accepted by the spec and not held, an unknown function is sent, and a choice field the guard refuses is named first.
  • New ObjectFieldInspector.summaryAggregand-27461.test.tsx (3 cases): the card's child object (text, textarea, datetime, lookup, master_detail, number, percent, boolean, formula); for each of sum, avg, min, max the picker offers exactly what the rule accepts, and before a function, the union.
  • New DataPillar.summaryHeld-27461.test.tsx (2 cases): the card's path (Form tab, Add field, Label "Child Count", Type Rollup, Create) sends no PUT past the debounce and names the child object; after the child object it names the aggregation and still sends nothing; picking Count sends one PUT with { object, function: 'count' } and clears the line. "Show me" reopens the closed field.

Readings below are on the final head 6f387b30 (this branch merged with origin/main at e35db835) unless a line says otherwise.

Ablations (one per half, ablation-replace, restored blob == HEAD)
  • metadataError.ts: return summaryHeldEdit(body, locale) ?? actionHeldEdit(body, locale); replaced by return actionHeldEdit(body, locale); (the behaviour on main) → 7 failed / 10 across the two held suites (the 5 held predicate cases and both pillar cases; the 3 controls stay green); restored, git diff HEAD empty.
  • ObjectFieldInspector.tsx: the picker's options={aggregandOptions} replaced by the unfiltered aggregateOptions (the behaviour on main) → 3 failed / 3; restored, git diff HEAD empty.
  • app-shell tests import src directly, so no dist build sits between the mutation and the run.
Affected suites (pre-merge head bfe95a6): 200 files / 2360 tests passed

Every app-shell test file naming ObjectFieldInspector, metadataError, objectHeldEdit or DataPillar, plus the whole packages/i18n/ directory, under the verification lock: Test Files 200 passed (200) · Tests 2360 passed | 13 skipped (2373), lock VERDICT command-exit 0. The rest of the ui tree is left to CI Test UI.

After merging origin/main
pnpm install --frozen-lockfile :: exit 0
pnpm --filter '@object-ui/app-shell^...' build :: exit 0
pnpm --filter @object-ui/app-shell run type-check :: exit 0
pnpm --filter @object-ui/i18n run type-check :: exit 0
vitest: the three new suites, DataPillar.heldIncomplete-11786, metadataError.heldEdit-11786, ObjectFieldInspector.test :: Test Files 6 passed (6) · Tests 80 passed (80)
Derived gates (dispatch-gates --commands on this diff)
node scripts/check-adr-0087-registration.mjs --base origin/main :: exit 0
node scripts/check-adr-0087-registration.mjs --self-test :: exit 0
node scripts/check-changeset-no-major.mjs --base origin/main :: exit 0
node scripts/check-closing-keyword-parity.mjs :: exit 0
node scripts/check-closing-keyword-parity.mjs --self-test :: exit 0
node scripts/check-comment-mask-corpus.mjs :: exit 0
node scripts/check-empty-changeset.mjs --base origin/main :: exit 0
node scripts/check-empty-changeset.mjs --self-test :: exit 0
node scripts/check-sdui-manifest.mjs :: exit 0
node scripts/check-sdui-manifest.mjs --self-test :: exit 0
node scripts/pm/release-rehearsal-clone.mjs --self-test :: exit 0
node scripts/release-pending-publish.mjs --self-test :: exit 0
pnpm --dir ui exec vitest run scripts/ :: exit 0
pnpm --filter @objectstack/spec run check:duration-unit-keys :: exit 0
pnpm check:changeset-gate-self-tests :: exit 0
pnpm check:closed-ceremony-text :: exit 0
pnpm check:cross-package-test-inputs :: exit 0
pnpm check:dispatcher-error-vocabulary :: exit 0
pnpm check:driver-memory-census :: exit 0
pnpm check:engine-double-contract :: exit 0
pnpm check:gitlink-declared :: exit 0
pnpm check:nul-bytes :: exit 0
pnpm check:pm-changeset-deadline-census :: exit 0
pnpm check:published-files :: exit 0
pnpm check:query-options-erasure :: exit 0
pnpm check:refd-timer-probe :: exit 0
pnpm check:skill-identifier-liveness :: exit 0
pnpm check:test-source-alias :: exit 0
pnpm check:tier-file-adoption :: exit 0
pnpm check:type-check-coverage :: exit 0
pnpm check:type-check-debt :: exit 0
pnpm check:undeclared-dep-imports :: exit 0
pnpm check:watch-hint-literal :: exit 0
pnpm check:where-matcher :: exit 0

dispatch-gates --ran: 34 derived, 34 run, 0 NOT-MEASURED, 0 UNRUN. Also node scripts/check-new-cross-file-line-citations.mjs (ui): 0 new citations, exit 0. Families CI runs beyond the derived set are read from this PR's check-runs.

Acceptance notes

  1. Booleans are offered for Sum, Avg, Min and Max. The card expected "only numeric child fields"; the triage ruled the picker reads the spec's rule and keeps no second list, and that rule accepts the boolean class in all four rows (booleans aggregate as numbers on every backend). Min and Max also accept the temporal class, and Sum refuses percent while Avg accepts it. The picker follows the rule as written.
  2. No inspector hint for a held roll-up. A held choice or relationship field also shows a hint under its input in the inspector (designer.field.hint.addOption / pickTarget); a held roll-up shows the held line only. The acceptance asks for the held line and "Show me", which this delivers; an inspector hint would be a new string beside a different input per stage, left out of this card.
  3. A field already stored on a roll-up stays as it is. If a stored summaryOperations.field names a child field the rule refuses for the function (for example after switching Sum to Max), the picker no longer lists it but the stored value is not rewritten; the server's diagnostics remain the judge of a finished roll-up.
  4. Attribution line 2 links this card thread, the block the harness requires, rather than the seat thread link the dispatch order quoted.

Verification

Run the three new suites (command in 维护者速读). In a running Console: Studio, Data, a parent object, Form, Add field, Label "Child Count", Type Rollup, Create: no draft PUT goes out and the line "Not saved yet: Child object on the field “Child Count” needs a value. …" appears; set Child object and Aggregation = Sum, then open "Child field to aggregate": only the child's number (and boolean) fields are listed.


Generated by Claude Code

…d picker by the spec

The Data pillar holds a summary field until the spec's judges accept its
roll-up (the field/summary-without-operations completeness rule and
FieldSchema), naming the missing input on the held line. The "Child field
to aggregate" picker offers only the child fields
isAggregateCompatibleWithFieldType accepts for the chosen function.

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

# Conflicts:
#	ui/packages/i18n/src/namespaces/studio/en.ts
#	ui/packages/i18n/src/namespaces/studio/zh.ts
@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation tests tooling labels Oct 29, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:ui#3 · os-bill · session_01Utp6CaJoinvGQSbHeDaWBe

席位核对

ACCEPT · head 6fc5d579a94b5d768de7a37e3d53c58e9a919e39 · 2026-10-29T21:51Z

  • Shape: draft → main, Fixes #27461 on its own line. Clause-②: no and Metadata-protocol: none match the claim; @object-ui/app-shell is private and @object-ui/i18n gains one held-clause key (engine.studio.held.fieldNeedsInput, en + zh). No contract review owed.
  • Premise, measured: held half, objectHeldEdit (metadataError.ts) now holds a summary field that is still being set up, judged by the spec itself (checkFieldCompleteness's field/summary-without-operations, and FieldSchema issues under summaryOperations that are only the object, function or child field still empty); any other issue is sent so its refusal shows. The held line names the field and the input it needs, "Show me" opens it, Publish reads the same objHeld. Picker half, the child field to aggregate is filtered through the spec's isAggregateCompatibleWithFieldType for the chosen function (the union of value-reading functions before one is picked); no second list. Diff read on 6f387b30; the new head adds only a merge of main.
  • Seat note: an object that already carries a summary field with no summaryOperations (saved through the old path) is now held when opened, naming that field with "Show me". That is the spec's own verdict (the field is inert) and the ruling direction for held edits; not a regression.
  • Gates (check-runs on the head): 44 success, 11 skipped, 0 failure. check-governed-merges --pr 27720: NOT governed, 494 changed lines.
  • Acceptance notes, accepted: booleans are offered for sum / avg / min / max because the spec accepts them (triage: the spec decides); a held roll-up has no in-inspector hint yet. Deviation accepted: the attribution line links the card thread.
  • Landing: non-governed, all checks green, mergeable_state clean ⇒ the seat lands it (pr_ready + automerge_enable).

Generated by Claude Code

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 tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Studio: adding a Rollup field autosaves it unconfigured, and the Sum/Avg child-field picker offers text and lookup fields

1 participant