Repository navigation
fix(app-shell): editing an action in Studio no longer stops the object from saving - #26827
Conversation
…t from saving The action inspector writes the spec's nested `ai` block instead of the flat `aiExposed` / `aiDescription` it refuses, clears the keys a Type switch makes refused, and edits Body Shape as Flat or Wrap(key). The Data pillar holds an action that is only unfinished, and places a refused one on the action, with "Show me" opening it on the Actions view. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QsR2jSHgVcT66bTgBM8Yq3
Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QsR2jSHgVcT66bTgBM8Yq3
席位核对ACCEPT · seat
Landing: no contract review is owed (no published schema, no governed text, |
⛔ merge queue 构建失败 — 先分诊,再决定要不要重排队列构建 40562512172 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集), 分类: 失败的 job(日志抽取,best effort):
跨 PR 相同签名(24h,按失败测试文件聚合):
历史信号:
分诊清单:
Generated by Claude Code · merge-queue-triage workflow (#4859) |
Requested by Steedos · project thread
Fixes #26728
Fixes #26736
Clause-②: no
Metadata-protocol: none
维护者速读
ai块;切换「类型」时清掉新类型拒收的键(如 API 动作上的脚本body);「请求体形态」改成「平铺 / 包裹(键)」控件。数据支柱对「只是还没填完」的动作暂存不发送并说明缺什么,对服务端拒收的保存在提示条里点名到具体动作,「定位」直接打开它。ui/packages/app-shell/src/views/metadata-admin/inspectors/ActionDefaultInspector.tsx的patchAi/commitType/TYPE_SCOPED_KEYS;ui/packages/app-shell/src/views/studio-design/metadataError.ts的actionHeldInput(暂存判断,以规范的ActionSchema为准);StudioDesignSurface.tsx的showObjTarget。cd ui && pnpm exec vitest run packages/app-shell/src/views/studio-design/DataPillar.actionRefusal-26736.test.tsx,预期 2 个用例全绿;或在 Studio 里勾选「开放给 AI 智能体」再取消,对象照常保存,不再出现 422。What changes
Three controls in Studio's action inspector wrote an action
@objectstack/specrefuses, and the Data pillar then sent it, so every later save of the object answered 422 until a reload:aiExposed/aiDescription(refused by name)ai: { exposed, description }, merged into anyaiblock the draft holds; the next write clears the flat keys an older draft carries; unticking drops a description the spec would refuse as too short (its box hides while unexposed)type, keepingbody,onSuccess,outcomeMessages,undoable: truethe new type refusestype, plusundefinedfor each of those keys the draft holds and the new type refuses (TYPE_SCOPED_KEYS); everything the new type accepts,targetincluded, is kept{"wrap":"payload"}became a string; a stored{ wrap }displayed aspayload)'flat', or{ wrap: KEY }once a key is typed (an empty key is never written; the spec would accept it and nest under"")The refusal surface the inspector reports through (
studio-design/metadataError.ts, the Data pillar inStudioDesignSurface.tsx, andObjectActionsPanel.tsx):objectHeldEditnow also asksactionHeldInputof each action: the spec'sActionSchemajudges the action, and when every issue is an input the inspector offers that has no value yet (a script source, a script's body when it has neither body nor target, thetargeta type requires, an exposed action's AI description absent or still too short) the object is held unsent, with the neutral line "Not saved yet: API endpoint on the action “Send” needs a value". Anything else is sent and its refusal shown.objectSaveRefusalgainsobjectActionLocator: an issue atactions.INDEX…names the action by its label, and the input when the key under it is one the inspector edits. "Show me" switches to the Actions view and selects that action (ObjectActionsPanel's newfocusprop), from any view.ActionPreviewread the flataiExposedfor its AI pill; it now readsai.exposed, since the inspector no longer writes the flat key (bounded in-place fix, on the claim's amended surface). Newstudionamespace strings, en and zh: the Body Shape control,engine.studio.refusal.action/actionInput,engine.studio.held.actionNeedsInput. Nopackages/spec/**, server or new metadata key.Why
One visible checkbox, or an ordinary Type switch, stopped the whole object from saving, with nothing on screen saying which action or why; a reload discarded the edits. The spec already defines every shape written here, so the defect was the designer's.
Tests
ActionDefaultInspector.aiBlock-26728.test.tsx(ask 1): the real inspector, drafts parsed withActionSchemaafter a JSON round trip. A tick writes{ ai: { exposed: true } }and the only refusal isai.description; a 40-character description parses and reads back; unticking parses with and without a description; a draft carrying the flat keys parses after the control's next write; otheraikeys are kept;AI_DESCRIPTION_MINis the spec's minimum both sides.ActionDefaultInspector.typeSwitch-26736.test.tsx(asks 2, 3): script with a body → API drops the body, and the draft parses once the endpoint is typed (and after a Method edit); every type-scoped key is cleared,undoable: falsekept; a switch with nothing to clear writes the type alone and keepstarget. Body Shape: a stored{ wrap: 'payload' }reads back as Wrap /payload; Wrap writes nothing until a key, then{ wrap: KEY }; clearing the key takes the shape off; Flat writes'flat'; the old JSON text is refusedinvalid_union. Parity: eachTYPE_SCOPED_KEYSentry parsed on everyActionType, refused exactly off its listed types.metadataError.actionHeld-26736.test.ts(asks 2, 4): seven held cases, each paired with the spec's own refusal; four finished-but-wrong cases not held; the object-level hold names the action and selects it; field holds still come first; refusals atactions.N.bodyandactions.Nare placed on the action; field paths unchanged; an unplaceable path stays unlocated.DataPillar.actionRefusal-26736.test.tsx(asks 1, 2, 4): the real Data pillar, real inspector, realMetadataClient, a transport that parses each action withActionSchemaand answers 422 withactions.N…issues. A refused save names the action; "Show me" from the Form view opens it on Actions; tick + untick sends an accepted save and the strip clears, no reload. Script → API is held (no PUT, held line names the endpoint on the action), and typing it sends an accepted save.automationPreviews.i18n-10862.test.tsxandinspectorStrings.hookAction.i18n-10586.test.tsxnow carryai: { exposed }instead of the flat keys.node scripts/ablation-replace.mjson committed head27b93089, each restored to the HEAD blob withgit diff HEADempty: A1patchAiwritesaiExposed→ aiBlock 4 failed; A2 Type switch clears nothing → typeSwitch 2 failed + DataPillar held case failed; A3 no action hold → metadataError 1 + DataPillar 1 failed; A4 no action locator → metadataError 2 + DataPillar refusal case failed. Control after restore: Tests 36 passed (36).Acceptance notes
aistays editable under "More fields" (not curated), socategory,paramHints,outputSchemaandrequiresConfirmationremain reachable;exposedanddescriptionare therefore editable in both places, writing one block.target. Moving between two target types (a URL to a flow) keeps the old value in the new type's picker, where an unknown name shows as(not found); the spec judges it on the new type.StudioDesignSurface.tsxedit is about 20 lines in the Data pillar; Studio Automations: with the right panel hidden, selecting a node shows its configuration nowhere — the config drawer only reopens on mobile #26737 edits the same file's flow fold, so whichever lands second mergesmain.Verification
All readings at head
27b93089(basec8583f80), underscripts/pm/os-verify-lock.shwhere heavy.Touched packages: @object-ui/app-shell, @object-ui/i18n
pnpm exec turbo run build --filter=@objectstack/formula --filter=@objectstack/sdui-parser --filter=@objectstack/lint --filter=@objectstack/client --concurrency=2, 36 tasks, VERDICT command-exit 0.cd ui && pnpm exec turbo run type-check --filter=@object-ui/app-shell --filter=@object-ui/i18n --concurrency=2, 65 tasks; both packages runtsc --noEmit && tsc -p tsconfig.test.json(the test config covers the new tests); VERDICT command-exit 0.ActionDefaultInspector*,metadataError*,DataPillar*,ObjectActionsPanel*file and the two moved fixtures: Test Files 41 passed (41), Tests 322 passed (322). The rest of app-shell is left to CI's Test UI shards.pnpm --dir ui exec vitest run scripts/(reads the i18n catalogues): Test Files 178 passed, 2 skipped (180); Tests 5065 passed, 2 skipped; VERDICT command-exit 0.Derived gates
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack: 34 commands, each run with its exit code captured before any pipe: 34 exit 0. PR-body gates read this body throughGITHUB_EVENT_PATH/PR_BODY.dispatch-gates --ran: 34 derived, 34 run, 0 NOT-MEASURED, 0 UNRUN.