Skip to content

fix(studio): a Data rail switch sends the pending edit first; a broken CEL formula stays held after the inspector closes and keeps its returnType (#27511, #27485) - #27617

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

objectstack-fleet[bot] merged 8 commits into
mainfrom
claude/project-thread-ufaqzz

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Oct 29, 2026 •

Copy link
Copy Markdown
Contributor

domain:ui#3 · os-bill · session_01Utp6CaJoinvGQSbHeDaWBe

Requested by Bill · project thread

维护者速读

Fixes #27511
Fixes #27485

Clause-②: no
Metadata-protocol: none

What changes

#27511, the rail switch inside the autosave delay. The Data pillar's rail now opens an object through one openObject gate. With an edit pending inside the 1.5 s debounce, it calls the autosave's existing flush and opens the next object once that save has landed; a save already in flight is waited out the same way. An edit that cannot be sent (its save was refused, or Studio holds it as unfinished) goes through the existing confirmDropUnsavedWork question; declining keeps the open object and its edit. The requestedSurface deep-link path uses the same gate. No second autosave path: the timer, the target guard and the single-flight of useDraftAutoSave are untouched (only its comment now names the caller that flushes).

#27485, returnType. The field inspector stamped returnType from the formula's inferred type on every edit, and a source that does not parse infers unknown, so each broken keystroke cleared the stored type. settleFormulaType now settles the stamp only once the editor's lint verdict and inference verdict are both in for the same source: a source with a lint error keeps the stored type; a clean source stamps its proven type, or clears it when none is proven, as before. The order of the two async callbacks does not matter.

#27485, switch arm. A switch over a held edit (a CEL error) never flushes; it asks through the same gate as above.

#27485, close arm (own commit 73b0ad8). The inspector's CEL count used to be keyed to the open field selection, so closing the inspector (Close, Escape, another field card, another tab) zeroed it and the autosave sent the broken expression. The pillar now keeps each inspector verdict with a stamp of the field's CEL-bearing properties and the field list (fieldCelStamp). A kept verdict holds the save while the draft still carries that exact text, and ends when that text is edited, the field is removed, or the object reloads. While it holds, the pillar shows the existing held line with a new clause (engine.studio.held.celInvalid, en + zh) and Show me, which reopens the field; Studio's Publish refusal names it through the existing held-edit report.

Why

Both cards are one defect form: a pending or held Studio draft edit was tied to the current selection (open inspector, object in the rail) rather than to the draft, so changing the selection dropped the edit or released its hold. Triage asked for exactly this: "The rail switch calls useDraftAutoSave's existing flush, or confirms first … with no second autosave path" and "Tie the CEL hold to the draft rather than to the inspector's selection, and preserve returnType."

Tests

  • DataPillar.railSwitchFlush-27511.test.tsx (new, 4): flush then switch sends one save to the old object with the new field and none to the new one; a save in flight is waited out; a refused save asks, a declined ask stays, an agreed ask drops it unsent; a switch with nothing pending opens at once and sends nothing.
  • ObjectFieldInspector.returnTypeHeld-27485.test.tsx (new, 3): the card's repro keeps number; a source that parses again stamps text; a clean source that proves no type still clears it.
  • DataPillar.celGate.test.tsx: new switch-arm case (asks; declined stays; agreed drops unsent); close arm: the hold survives the Close button, Escape, another field card and the Records tab, and the held line names the field; Show me reopens it and a fix releases the hold; removing the field ends the hold. The old case "drops the count when the inspector closes, so the draft cannot wedge unsaved" is removed (see Acceptance notes).
  • StudioDesignSurface.autosaveSwitch-11232.test.tsx: the Data control case now expects the pending edit to go to its own object and still nothing to the newly opened one.

Ablations, each through scripts/ablation-replace.mjs, restored to HEAD with an empty git diff HEAD afterwards:

Ablation Result
Rail onClick back to a bare setCurrent(o) railSwitchFlush 3 of 4 red (nothing-pending case stays green, as it should)
settleFormulaType without its errors > 0 guard returnType repro red; the other two green
confirmDropUnsavedWork line removed from openObject refused-save case and the celGate switch-arm case red
Kept-verdict condition forced false celGate 5 red (four closers and Show me)

Acceptance notes

  1. File surface widened, seat-approved. ui/packages/i18n/src/namespaces/studio/en.ts and zh.ts gain one key, engine.studio.held.celInvalid, for the held clause. The ui seat 3 seat approved this widening at 13:14:22Z on 2026-10-29.

  2. Conflict with objectui#4306 sub-decision A (ruled by the maintainer 2026-10-29T15:17Z: the close arm stays; sub-decision A's reset-on-close mechanism is replaced, its two invariants stand). The base test file restated the ruling as: "the host RESETS its own count when the selection changes or the inspector unmounts (ruling sub-decision A) — writability must never depend on a child's teardown running, or closing the panel on a faulty formula leaves Save wedged shut with no editor on screen to fix it", and pinned it with the case "drops the count when the inspector closes, so the draft cannot wedge unsaved". Studio Data › Form: closing the field inspector releases the CEL error hold and autosaves the broken formula (returnType dropped); switching object discards the held edit silently #27485's triage asks for the opposite at the close: "Tie the CEL hold to the draft rather than to the inspector's selection". This PR follows the card and keeps the ruling's two invariants instead of its mechanism: writability does not depend on a child's teardown (the verdict lives in host state, stamped to the draft), and the draft never wedges with no editor on screen (the held line names the field and Show me reopens it; editing the text, removing the field or reloading ends the hold). objectui#4306 itself was not readable from this session (403), so the quote above is the in-repo restatement. The close arm is its own commit, 73b0ad8, separable: at its parent 19ceb5d the four touched suites pass 25/25, so if the ruling stands, reverting 73b0ad8 leaves Studio object designer: an edit made less than 1.5 s before switching to another object in the rail is silently discarded (the autosave debounce never sends it and no confirm is shown) #27511, returnType and the switch arm intact (Studio Data › Form: closing the field inspector releases the CEL error hold and autosaves the broken formula (returnType dropped); switching object discards the held edit silently #27485 would then stay open for its close arm).
    Ruling-ref: 6500685972

  3. Commit shape deviation. The dispatch asked for one commit per card. A WIP commit holding both cards' code (5002d07) was pushed before the split; with no force push allowed, it stays, and 59a0510 takes the close arm back out so the following commits are one per card (e72223f Studio object designer: an edit made less than 1.5 s before switching to another object in the rail is silently discarded (the autosave debounce never sends it and no confirm is shown) #27511, 19ceb5d Studio Data › Form: closing the field inspector releases the CEL error hold and autosaves the broken formula (returnType dropped); switching object discards the held edit silently #27485 returnType and switch arm, 73b0ad8 Studio Data › Form: closing the field inspector releases the CEL error hold and autosaves the broken formula (returnType dropped); switching object discards the held edit silently #27485 close arm). A merge of main brings in feat(ui): record-mode currency — each record shows its own currency; ISO 4217 code select pre-filled with the organization currency; no cross-currency totals; Studio currency section #27340, which also touches ObjectFieldInspector.tsx in another region; no conflict.

  4. Residual windows (not fixed here). The inspector lints 250 ms after the last keystroke. Escape or a switch inside that window can still read the previous verdict (a stale 0 releases nothing new, but the last keystroke's fault is unjudged). Reopening a held field: the inspector's mount report of 0 releases the hold for about 250 ms until its lint runs. Closing inside the window right after a fix can leave a false hold; reopening the field clears it.

  5. Same defect class, not measured. The panel family's CEL hold (Validations and the other tabs, keyed by tab) has the same selection-keyed shape. Out of scope for both cards; noted, not filed.

  6. CelPredicateField was not touched; the returnType fix is wholly in the inspector.

Verification

On head efb181a (after merging main at 1ed3360):

  • pnpm --dir ui exec vitest run packages/app-shell/src/views/studio-design packages/app-shell/src/views/metadata-admin/inspectors: 460 files, 4610 passed, 1 skipped, 0 failed (under the verification lock).
  • The four touched suites at 19ceb5d (the close arm's parent): 25/25; at head: 30/30.
  • pnpm --filter @object-ui/app-shell type-check: exit 0 (after rebuilding @object-ui/core, whose new measureCellCurrency export feat(ui): record-mode currency — each record shows its own currency; ISO 4217 code select pre-filled with the organization currency; no cross-currency totals; Studio currency section #27340 brought in; the first run read the stale dist). pnpm --filter @object-ui/i18n type-check: exit 0.
  • eslint on the 9 changed TS/TSX files: 0 errors (warnings are pre-existing react-hooks/set-state-in-effect lines elsewhere in the surface).
  • node scripts/pm/dispatch-gates.mjs --ran: 34 derived, 34 run, 0 not measured. 33 exit 0. pnpm --dir ui exec vitest run scripts/ exited 1 on five 15 s timeouts while the 460-file run held the box; alone under the lock it was 1 timeout (check-vi-mock-inherit, a whole-tree sweep at 16 s), and that file alone is 83/83 green. None of the five reads a file this PR touches.
  • node scripts/check-sdui-manifest.mjs: exit 0. node scripts/check-nul-bytes.mjs: exit 0.

Human Minimum

  • 1. Schemas start as Zod; packages/spec holds no runtime logic. (not touched)
  • 2. Every name I added uses the casing for its kind.
  • 3. No formatter ran; eslint on the changed files has 0 errors (the full pnpm lint is left to CI).
  • 4. One issue, one branch off an up-to-date main, one pull request. (two folded cards, one PR, per the dispatch)
  • 5. A change that publishes carries a changeset, and both declaration lines are filled in here and in the changeset.
  • 6. packages/spec touched: its generated artifacts are regenerated and committed. (not touched)
  • 7. No hand edits to content/docs/references/, packages/*/CHANGELOG.md or content/docs/releases/.
  • 8. Title, body and comments are in English. (the maintainer brief is in Chinese by the requester's standing rule)
  • The Before Submitting list in CONTRIBUTING.md is done.

Generated by Claude Code

…ommit

The WIP commit carried both cards' code. The close arm (the CEL verdict
kept with the draft after the inspector closes, its held line and its
clause key) leaves here and lands again as its own commit, so it can be
dropped on its own if the objectui#4306 sub-decision A ruling stands.

Claude-Session: https://claude.ai/code/session_01Utp6CbCrKUjGeTReNEmUJx
Co-authored-by: Claude <noreply@anthropic.com>
…fore it switches objects (#27511)

An edit made less than 1.5 s before a click on another object was dropped:
the autosave's timer sends nothing once the open object has changed. The
rail's switch now sends it with the autosave's own flush and opens the
next object once the save has landed, waits out a save in flight, and asks
before it drops an edit that cannot be sent. Pins the switch in
DataPillar.railSwitchFlush-27511 and moves the objectui#11232 Data control
to the new behaviour.

Claude-Session: https://claude.ai/code/session_01Utp6CbCrKUjGeTReNEmUJx
Co-authored-by: Claude <noreply@anthropic.com>
…ver a held formula asks (#27485)

The inspector settles returnType from the formula editor's lint and
inferred type on one source: a source with a lint error proves no type,
so the stored one stays. The object switch over an edit the CEL verdict
holds asks first (the #27511 switch).

Claude-Session: https://claude.ai/code/session_01Utp6CbCrKUjGeTReNEmUJx
Co-authored-by: Claude <noreply@anthropic.com>
…aft (#27485)

The inspector's CEL verdict was stamped with the selection, so closing it
(Close, Escape, another field, another tab) released the hold and the
autosave sent the broken expression. Each field's last verdict is now kept
with a stamp of the CEL text it judged and holds while the draft carries
that text; the held line names the field with a Show me that reopens it.
Reverses the mechanism of objectui#4306 sub-decision A (reset on close)
while keeping its invariants; separable on purpose.

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

Brings in #27340 (ObjectFieldInspector, studio i18n) before the PR opens; no conflicts.

Claude-Session: https://claude.ai/code/session_01Utp6CbCrKUjGeTReNEmUJx
Co-authored-by: Claude <noreply@anthropic.com>
…d sub-decision A's reset-on-close (#27485)

The maintainer ruled letter A on #27485 (comment 6500685972, 2026-10-29T15:17Z):
the Data pillar keeps the inspector's CEL hold on the draft, and objectui#4306
sub-decision A stands as its two invariants. The suite's header now names that
ruling as what replaced the mechanism. Comment only.

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

Brings in #27591 (ObjectFieldInspector API-name input, card #27517) and the rest of main; no conflicts.

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

Copy link
Copy Markdown
Contributor Author

domain:ui#3 · os-bill · session_01Utp6CaJoinvGQSbHeDaWBe

席位核对

ACCEPT · head d1b296226ea1e426417301781d8c0c46607a206a · 2026-10-29T16:47Z


Generated by Claude Code

Merged via the queue into main with commit aa874c8 Oct 29, 2026
56 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/project-thread-ufaqzz branch October 29, 2026 17:30
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 tooling

Projects

None yet

1 participant