Skip to content

fix(studio): hold a dashboard Global Filter whose Options From is begun but not finished - #29300

Merged
objectstack-fleet[bot] merged 5 commits into
mainfrom
claude/project-thread-87tl4w
Nov 2, 2026
Merged

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

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Steedos · project thread

Fixes #29006

Clause-②: no
Metadata-protocol: none

Claim 6574490931.

维护者速读

  • 改了什么:Studio 仪表盘属性里的「全局筛选」条目,如果「选项来源」只填了对象、还没填值字段和标签字段,这个条目现在会先保留在编辑器里、不自动保存;顶部出现一行中性提示(英文界面为 “Not saved yet: Options From on item Add metamodel interfaces for ObjectQL/ObjectUI contract #1 of “Global Filters” needs a value”),填完后一次性保存期间的所有修改。
  • 为什么改:原来空条目会被保留,但「填了一半」的嵌套输入会被当成已完成直接自动保存,服务端 422 拒收;之后改描述、改标签每次都带着这个条目被拒,刷新时没有离开提示,修改全部丢失。
  • 风险与回滚:只改一个判断函数 listItemHeldInput:条目里任何一层「尚未填写」的输入都算未完成;作者已经填了值但值不合法的情况照常发送并显示红色拒收,不会被藏起来。仪表盘与报表的列表条目都走这条规则。回滚即 revert 本 PR。
  • 先看:ui/packages/app-shell/src/views/studio-design/metadataError.ts 的 listItemHeldInput(向下逐层找未填写值的循环);metadataError.heldNestedInput-29006.test.tsx(保留与不保留两侧,以及真实自动保存路径)。
  • 两分钟验证:Studio 打开一个可写包里的仪表盘 → 属性 → Filters → Global Filters → Add item,填 Field、Input Type 选 select、Options From 只填 Object,再改一下标签:网络里没有仪表盘的 PUT,顶部出现上述提示;再填好 Value Field 和 Label Field,出现一次 200 的 PUT,带着标签修改。
  • 需要你做的:无,席位落地。

What changes

  • listItemHeldInput follows each refusal down from the item. It held an item only when every issue named an unset input directly below it (path[at + 1]). A Global Filter with optionsFrom: { object } draws issues at optionsFrom.valueField and optionsFrom.labelField, whose direct input optionsFrom holds a value, so the item read as finished but wrong and was sent. The rule now walks the issue's path down from the item, through objects and list indexes, and holds the item when it meets a value left unset (absent, or a text cleared to blank) at any depth. A step through a value the author gave that has no such key (a text, a number) meets none.
  • The input named stays the item's own form row (optionsFrom, shown as "Options From"), so heldListItem and the clause are unchanged: "Options From on item Add metamodel interfaces for ObjectQL/ObjectUI contract #1 of “Global Filters” needs a value".
  • Finished but wrong work is still sent. An item whose issues include a value the author gave that the spec refuses, at any depth, is not held, so its refusal shows as before.
  • The dashboard list callers (dashboardHeldEdit) and the report list callers (reportHeldEdit) get it through the same function. No per-field case for optionsFrom. GlobalFilterSchema.optionsFrom in packages/spec is unchanged. StudioDesignSurface.tsx is unchanged: the surface's existing hold (leafIncomplete, the autosave's blocked, the held line, the leave guard, Publish) applies as it does to the empty item.

Tests

  • metadataError.heldNestedInput-29006.test.tsx (new). Unit cases on listItemHeldInput / dashboardHeldEdit, each paired with DashboardSchema's own verdict on the same body:
    • held: Options From with its Object only (issues at optionsFrom.valueField and labelField), named "Options From"; Options From missing only its Label Field;
    • still held: a direct unset input (field), the empty-item case;
    • not held: a nested value the author gave that the spec refuses (labelField: 42); an unset nested input beside a refused nested value; optionsFrom given a text where the spec wants an object; a Label Field cleared to blank, which the spec takes; a finished Options From.
  • The same file mounts the Interfaces pillar with the real DashboardDefaultInspector (as InterfacesPillar.heldListItem-27459.test.tsx does): a saved select filter, then Options From → Object and a Label edit send no dashboard save, show no red strip, show the held line and report the clause to the surface; filling Value Field and Label Field sends one save carrying the Label edit and the finished filter, which DashboardSchema accepts.

Acceptance notes

  • The card's Expected reads "the partly filled item is held like the empty one (or the refused edits are guarded on leave)". This PR does the first. While held, the surface's leave guard is armed (measured below), so a reload asks first.
  • Deviation from the dispatch wording: it described the held outcome as "the draft autosave leaves the item out and the other edits save". The existing hold does not save around the item: nothing is sent while the item is held, and the next save after it is finished carries every edit kept meanwhile (as the empty-item hold behaves). The surface test pins that behaviour.
  • The doc comment on HeldListItem.input ("the item's input the spec refuses it without") and the section header above it still read as direct-input wording; both are outside this card's surface (listItemHeldInput and its doc comment only) and were left as they are.

Verification

Live, on the showcase (before = main's metadataError.ts, after = this branch)

Scratch writable package with a dashboard holding one metric widget, the console on source via Vite. Steps 3 to 5 of the card, then a reload:

Step main this branch
Field, Input Type = select PUT 200, PUT 200 PUT 200, PUT 200
Options From → Object PUT 422 INVALID_METADATA (optionsFrom.valueField, labelField), red strip no PUT, held line
Description, then Label PUT 422, PUT 422 (same item) no PUT, held line
leave guard after step 5 not armed armed (reload shows the leave prompt)
reload without finishing Label and Description back to their old values, no prompt leave prompt
finish Value Field + Label Field, reload one PUT 200 one PUT 200 carrying the Label and Description edits; both survive the reload
Ablation

scripts/ablation-replace.mjs put back the direct-input check (!itemInputUnset(item[input]) for !unset; anchor 1 to 0, blob 60f02752 to 63e45cd0) and ran the new file with metadataError.heldListItem-27459.test.ts: 3 failed, 20 passed. The failures are the two nested held cases and the pillar case; the controls and every empty-item case pass. Restored: blob equals HEAD and git diff HEAD is empty.

Suites, type-check and gates

All on head 7559e61b (this branch merged with origin/main 47ee57fe; base 45e9b2f1).

  • New file alone: 8/8 passed.
  • Neighbour sweep under the verify lock: every app-shell test file naming metadataError, a held-edit function or heldListItem (21 files, including both 27459 files and the 27785 and 29022 report files): 202/202 passed.
  • turbo run type-check --filter=@object-ui/app-shell (tsc for src and the test tsconfig): 65/65 tasks, exit 0. eslint on both touched files: exit 0.
  • dispatch-gates --commands: 34 derived families, all exit 0, including check-sdui-manifest, check-changeset-no-major, check-adr-0087-registration, check:type-check-debt and pnpm --dir ui exec vitest run scripts/ (169 files passed, 2 skipped). dispatch-gates --ran: 34 derived, 34 run, 0 NOT-MEASURED. check-closing-keyword-parity and the changeset gates were also run against this body.
  • Head 36e3a484 merges 20 more origin/main commits (among them StudioDesignSurface.tsx and packages-io.ts), none touching this diff's files. The 21-file neighbour sweep was re-run there: 202/202 passed.
  • Narrowing: the whole app-shell vitest suite is CI's Test UI; it was not run locally.

🤖 Generated with Claude Code

https://claude.ai/code/session_01QsR2jSRsNRkCr8sZjG6Cpz

…s begun to fill

listItemHeldInput now walks each refusal's path down from the item, so a
dashboard Global Filter with Options From given its Object but not yet its
Value Field and Label Field is held like an empty item instead of being
autosaved for the draft door to refuse. A value the author gave that the
spec refuses, at any depth, still sends the item.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QsR2jSRsNRkCr8sZjG6Cpz
…begun one

Unit cases on listItemHeldInput (nested unset held, direct unset still held,
set-but-refused nested value not held) and the Interfaces pillar on its real
autosave path: a begun Options From and a Label edit send nothing until
Options From is finished, then one save carries both.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QsR2jSRsNRkCr8sZjG6Cpz
@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation tooling Fix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface labels Nov 2, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:ui#2 · zhuangjianguo · session_01QsR2jRFuJz9QBZqPVHYVhJ

维护者速读

  • 改了什么:Studio 里给仪表盘加全局筛选器时,「Options From」只填了 Object、还没填 Value Field / Label Field 的半成品筛选项,现在会像空白项一样被「暂存」,不再被自动保存发出去。
  • 为什么改:以前这种半成品会被自动保存发给服务器并被 422 拒绝,之后改的描述、标题等每次保存都带着它一起被拒,刷新后全部丢失,也没有离开提示。
  • 风险与回滚:只改 listItemHeldInput 一处判定:沿报错路径往下找未填的值即暂存;用户填了但不合法的值仍照常发送并显示拒绝,真实错误不会被藏起来。暂存期间离开提示生效,填完后一次性保存全部改动。回滚即还原本 PR。
  • 先看:ui/packages/app-shell/src/views/studio-design/metadataError.ts 的 listItemHeldInput。
  • 两分钟验证:pnpm --dir ui exec vitest run packages/app-shell/src/views/studio-design/metadataError.heldNestedInput-29006.test.tsx,预期 8 个用例全绿。
  • 需要你做的:无,席位验收后进合并队列。

席位意见: 修在共享判定处,仪表盘组件列表和报表列表同样受益;对照用例证明「填了但错」的值仍被发送。无公开面变化,Clause-②: no 成立。

席位核对 — ACCEPT · domain:ui#2 · head 36e3a484 · 2026-11-02T11:39Z

  • 形态: draft PR on main, Fixes #29006 on its own line and no other card number beside a closing keyword. 3 files, +277/−3, Tier S (check-governed-merges --pr 29300: not governed, 280 lines ≤ 3000). All inside claim 6574490931's surface; StudioDesignSurface.tsx untouched.
  • 前提实测: at origin/main, listItemHeldInput (~L1130) read only item[path[at + 1]], so optionsFrom: {object} counted as set. The dev's live showcase run: main sent PUT 422 at Options From → Object and again at Description and Label, with no leave guard and the edits lost on reload; this head sends no PUT while held, shows the held line naming the item's row, arms the leave guard, and sends one PUT 200 carrying every edit once Value/Label Field are set. Seat read of the walk: it stops at the first unset value below the item; a step into a value the author gave ends the walk unset=false, so a refused given value is still sent.
  • 门禁 check-runs (pinned to head 36e3a48413173f122c144999fefa10709da6a33f): 44 success, 11 skipped, 0 failed, 0 pending, including Lint & Repo Gates, TypeScript Type Check, Type Check UI, Lint UI, Check Changeset and Test UI. Dev local gates: 34/34 derived families exit 0, --ran 0 NOT-MEASURED; new file 8/8, ablation 3 red.
  • 接受的偏差: the brief said the other edits save around the held item; the surface's existing hold (Studio: "Add item" on a dashboard list property autosaves an empty item (422), and every later edit is refused and lost until reload #27459) sends nothing while held, keeps every edit, and sends them all once the item is finished, with the leave guard armed. No edit is lost, which is the card's acceptance; the brief's own rule said the existing hold applies unchanged.
  • 范围外去向: none reported. No Skills-docs: line is owed.
  • 落地条款: Clause-②: no (listItemHeldInput keeps its signature; the change is its result for nested unset inputs), so no contract review is owed. mergeable_state is clean. Land by pr_ready + automerge_enable.

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 Fix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface

Projects

None yet

1 participant