Skip to content

fix(studio): an item just added to a dashboard list property is held until it is filled in, not autosaved and refused (#27459) - #27784

Merged
objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/project-thread-0gr0r6
Oct 29, 2026
Merged

objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/project-thread-0gr0r6

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Bill · project thread

维护者速读

  • 改了什么:Studio 界面支柱里,仪表盘属性面板的列表(「全局筛选」、页眉「操作按钮」)点「Add item」后,新项在必填项填好之前不再自动保存草稿;画布上方出现一行中性提示,点名列表、第几项和还缺的输入(如 “Field on item Add metamodel interfaces for ObjectQL/ObjectUI contract #1 of “Global Filters” needs a value”),带「Show me」打开仪表盘属性并定位到该列表;填好后下一次自动保存把期间所有改动一起发出。
  • 为什么改:以前空项立刻被 PUT 成 globalFilters[N] = {},草稿门 422;之后每次保存都带着这个空项继续 422,所以在这之后改的 Label 等都丢了(重载后消失)。
  • 风险与回滚:只改前端的拦截判定,判定问 spec 自己的 DashboardSchema,不另存必填清单;某项只在「所有报错都落在它还没填的输入上」时才拦,填了错值的照常发送并显示红色拒绝条。最坏情况是多拦一次(提示行 + Show me 可解)。回滚即 revert 本 PR。
  • 先看:ui/packages/app-shell/src/views/studio-design/metadataError.ts(listItemHeldInput / heldListItem / dashboardHeldEdit)、StudioDesignSurface.tsx(StudioHeldNotice 泛型化与界面支柱的 Show me 分支)、InterfacesPillar.heldListItem-27459.test.tsx。
  • 两分钟验证:pnpm --dir ui exec vitest run packages/app-shell/src/views/studio-design/metadataError.heldListItem-27459.test.ts packages/app-shell/src/views/studio-design/InterfacesPillar.heldListItem-27459.test.tsx,预期 2 个文件 19 条全部通过。
  • 需要你做的:无,席位落地。

Fixes #27459

Clause-②: no
Metadata-protocol: none

What changes

The hold. The Interfaces pillar's leaf autosave already holds a dashboard whose new widget still waits for its dataset or measures (dashboardHeldEdit, judged by the spec's DashboardWidgetSchema). It now also holds a dashboard with an item of one of its own list properties that is merely unfinished:

  • listItemHeldInput(body, schema, skip) asks the leaf's spec schema (DashboardSchema here) of the body the autosave would send. Each issue is placed on the item of the outermost list its path enters. An item is held only when every issue placed on it names an input directly below it that the item leaves unset (absent, or a text cleared to an empty string). An issue on the item itself, or on an input holding a value, makes it finished but wrong: it is sent, and the refusal it draws shows as before. The widgets list is skipped, so the widget rule keeps its own hold and is asked first.
  • No required-input list lives in the Studio: the spec decides what an item needs.
  • The held line names the input and the list the way the properties panel shows them (the form row's own label, translated, or its prettified key) and the item by the #n its row shows. One new clause, engine.studio.held.listItemNeedsInput (en, zh): the existing widget, step, action and field clauses name no list item.
  • StudioHeld gains a Target type parameter (default MetadataSelection, so the other pillars are unchanged) and an optional input. A list item's target is null, the leaf's own properties, which no selection opens. Its "Show me" is always offered and goes through the pillar's existing showLeafRefusal (clears the selection, opens the properties slot, expands a collapsed inspector, focuses the list by its label).
  • prettify moves from SchemaForm.tsx to field-source-label.ts as prettifyFieldName (no runtime imports there), so the held line can name a row exactly as SchemaForm does without importing the form component. SchemaForm imports it under its old local name; nothing else changes there.

The Publish refusal and the surface's leave guard read the same reported clause (useHeldEditReport), unchanged.

Tests

  • New metadataError.heldListItem-27459.test.ts (15 cases). Every held case is paired with the spec's verdict on the same dashboard (it refuses exactly the unset inputs): Global Filters' new item (the repeater's all-undefined row, and the stored {} the card saw), header Actions' new item (held for Label, then for Action URL), an Action URL cleared to blank, zh-CN naming, and the widget rule asked first. Controls: a Field filled is accepted and not held; a Field cleared to blank is one the spec takes, so not held; a header action refused for the URL it holds is sent; an item with an unset Field and a wrong type is sent; a list holding a non-item value is sent; a dashboard the spec accepts holds nothing.
  • New InterfacesPillar.heldListItem-27459.test.tsx (4 cases), the real DashboardDefaultInspector over a recording metadata client: the card's path (Filters → Global Filters → Add item, then a Label edit) sends no PUT past the debounce, shows no red strip, and names the Field; "Show me" focuses a control in the Global Filters row; filling the Field sends one save carrying the item and the Label edit kept meanwhile, which DashboardSchema accepts, and clears the line; control: a Label edit alone saves at once.

Readings below are on the final head 7ff9cb5c (this branch merged with origin/main) unless a line says otherwise.

Ablation (ablation-replace, restored blob == HEAD)
  • metadataError.ts: in dashboardHeldEdit, return item ? heldListItem( replaced by return item && !"ABLATED_27459" ? heldListItem( (no list item is ever held, the behaviour on main), on pre-merge head 62dac23e → 9 failed / 19 across the two new suites: the 6 held predicate cases and the 3 pillar cases that need a hold; the 9 predicate controls and the pillar's "Label edit alone saves at once" control stay green. Restored: blob after restore == blob at HEAD (e52f543c2055), 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 62dac23): 220 files / 2807 tests passed

Every ui test file naming metadataError, SchemaForm, field-source-label, StudioHeldNotice, DashboardDefaultInspector, InterfacesPillar, engine.studio.held or metadata-form-i18n (130 files across app-shell, core, data-objectstack and the console), plus the whole packages/i18n/ directory, under the verification lock: Test Files 220 passed (220) · Tests 2807 passed | 13 skipped (2820), lock VERDICT command-exit 0. The rest of the ui tree is left to CI Test UI.

After merging origin/main (head 7ff9cb5)

The merge conflicted only beside a sibling card's new held clause (engine.studio.held.fieldNeedsInput in en / zh) and its new spec imports in metadataError.ts; both sides kept.

pnpm --filter '@object-ui/app-shell^...' build :: exit 0
pnpm --filter @object-ui/app-shell --filter @object-ui/i18n run type-check :: exit 0
vitest: every studio-design *held* suite (19 files, the two new ones included) + DashboardDefaultInspector.test :: Test Files 20 passed (20) · Tests 143 passed (143)

Acceptance notes

  • The report half of the card's step 7 is not in this PR. The card names the same class on a report's Properties panel (Order and joined-blocks "Add item", a chart type chosen before its axes). This PR holds the dashboard path the card's title, reproduction and clause name (studio-authoring.dashboard-widget-and-filter-types acceptance[4]). listItemHeldInput takes the schema as an argument, so the report's list items need only ReportSchema and the report form at the pillar's leaf gate; a chart type set before its axes is not a list item and needs its own rule. Named in the report for the seat to carry as a follow-up card before this one closes.
  • Header Actions: the spec judges an action's URL only once the action has its label. An action with a bad URL and no label is therefore held for its label first, and its URL refusal shows after the label is given. That is the spec's issue order, not a second validator.
  • "Show me" does not expand a collapsed form section. The Filters section of the dashboard form is collapsed by default; when the author has collapsed it again, "Show me" opens the dashboard properties but finds no Global Filters label to focus. The line still names the list.
  • A Field cleared to an empty string is accepted by the installed spec (GlobalFilterSchema.field is a plain string), so it is sent, not held: the hold is never stricter than the contract.
Derived gates (dispatch-gates --commands on this diff, head 7ff9cb5): 34 / 34 exit 0
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 families accounted for, 34 run, 0 NOT-MEASURED. node scripts/check-sdui-manifest.mjs (the ui card gate) is among them. CI runs the type-check lanes, the artifact-roster and wide-population families and Test UI.


Generated by Claude Code

… of autosaving it

"Add item" under a dashboard's Global Filters or header Actions appends an
item with every input unset; the draft door refused it with a 422 and every
later save of the same body was refused for it too. The Interfaces pillar now
holds such an item the way it holds a widget waiting for its binding: judged
by the spec's own DashboardSchema, unsent while every issue on the item is an
input it leaves unset, with a held line naming it and "Show me" opening the
dashboard's properties.

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

Copy link
Copy Markdown
Contributor Author

domain:ui#3 · os-bill · session_01Utp6CaJoinvGQSbHeDaWBe

席位核对

ACCEPT · head 7ff9cb5c · 2026-10-29T23:22Z


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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Studio: "Add item" on a dashboard list property autosaves an empty item (422), and every later edit is refused and lost until reload

1 participant