Repository navigation
fix(app-shell): Studio refusals read as an author sentence, with the raw text under Details (objectui#11785) - #11848
Merged
objectstack-fleet[bot] merged 6 commits intoOct 8, 2026
Conversation
…raw text under Details (objectui#11785) Studio's save strips printed formatMetadataError(e) verbatim: the object write guard's developer prose, and raw server issue paths. Each strip now shows a StudioRefusal: a sentence naming the input (a field's label, a step and its inspector input, a navigation item), a "Show me" button that opens it, and the raw text inside a closed "Details" disclosure. The write guard's message names no class, package or tracker id, and is the same at every door, so Studio recognises its refusal by re-running the guard on the body it sent instead of parsing prose. Claude-Session: https://claude.ai/code/session_01CGZy1BGCjdN5cXqL9cnvB8 Co-authored-by: Claude <noreply@anthropic.com>
…pin (objectui#11785) Claude-Session: https://claude.ai/code/session_01CGZy1BGCjdN5cXqL9cnvB8 Co-authored-by: Claude <noreply@anthropic.com>
…symbol, and declare the release (objectui#11785) `check:spec-symbols` read the module-local `Doc` alias as a hand-written copy of a spec export; it is renamed `SentBody`. Adds the patch changeset for @object-ui/app-shell and @object-ui/data-objectstack. Claude-Session: https://claude.ai/code/session_01CGZy1BGCjdN5cXqL9cnvB8 Co-authored-by: Claude <noreply@anthropic.com>
Contributor
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
… the guard's new first line (objectui#11785) The write guard no longer names its door. Both `// throws:` quotes in the README began "MetadataClient.save refused this object metadata write:"; they now begin "The object was not saved:", as the guard throws for those two example bodies. No other README text changes. Claude-Session: https://claude.ai/code/session_01CGZy1BGCjdN5cXqL9cnvB8 Co-authored-by: Claude <noreply@anthropic.com>
Contributor
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
objectstack-fleet
Bot
deleted the
claude/issue-11785-author-refusal-text
branch
October 8, 2026 01:51
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #11785
Clause-②: no
What this changes
Studio's save strips printed
formatMetadataError(e)verbatim. Authors read the object write guard's developer prose (a class name, tracker ids, wire paths) and the server's raw issue paths (nodes.2.config.title — …,navigation.1.objectName — …). Each strip on the Data, Automations and Interfaces pages now shows aStudioRefusalwith three parts:formatMetadataError(e), what the strip printed before, so nothing is lost.A path Studio cannot place on the open page keeps its line under Details, and the sentence says the draft was refused.
The guard (
assertObjectMetadataWritable) changes message text only. It no longer prints the door's label (MetadataClient.save,MetadataService,importObjectDraft),@objectstack/specor tracker ids. Its meaning, the field and type it names, and the remedy are kept. Its signature, error type and exports are unchanged. The third parameter is now spelled_writer, because the root tsconfig hasnoUnusedParameters. The data-objectstack README's two// throws:quotes now begin with the new first line, "The object was not saved: the field".How Studio recognises the guard's refusal without parsing prose
The guard throws a plain
Errorbefore any request, and a typed refusal would be a new export (a Clause-② stop). Instead, the message is now a function of the body alone (it names no door), and Studio keeps the body it sent. It re-runs the guard on that body.ecounts as the guard's refusal only when it has no status, carries no issues, and its message is byte-identical to the guard's. The field is the first one the guard refuses on its own, checked by the same equality. Anything else falls back to the failure as it was. The client door test pins the premise: the door refuses in the guard's own words.Measured before acting (dispatch Zone 2)
metadata-client.objectWriteGuard.test.ts, whose door-name pin is inverted here; plugin-designer'sMetadataFieldsPage.storedChoiceWithoutOptions-11253banner test, which matches the phraseis a radio with no options, a clause kept verbatim; and the data-objectstack README (see Acceptance notes).MetadataError.issues(path, message, code) reaches every Studiocatch. It was flattened atsetError(formatMetadataError(e)). The strips now hold the view model instead of a string.fields.NAME…(record, or array by index or name)nodes.N.config.KEY/nodes.N.KEY…navigation.N.KEY(top level)The index used for
navigation.Nis the index in the list that was SENT, mapped back to the editor's entry by object orid. objectui#11776's save leaves unbound entries out, so the two indexes differ.Unmapped remainder, kept under Details with the generic sentence:
edges.*,variables.*and other flow-level keys;navigation.N.children…(the Studio nav inspector edits top-level entries only);fields(validations.*,name, root);Pins
metadataError.authorRefusal-11785.test.ts(view model): the guard's choice and relationship refusals become sentences. Its controls cover the same words with a server status, the guard's words about a body that was not sent, and a transport failure. It also coversnodes.2.config.title→ node 2's Title, the unmapped path under Details, several issues, a nav sent-index differing from the editor index, nested nav, zh, and the CONTROL thatformatMetadataError(every other caller) is unchanged.DataPillar.authorRefusal-11785.test.tsx(real door, transport double): switching a field to Picklist shows the author sentence, with noMetadataClient, tracker id, backtick or API name in the visible message. The raw text sits in a closeddetailselement. "Show me" reopens the field.AutomationsPillar.authorRefusal-11785.test.tsx(real FlowPreview / FlowInspector): a 422 atnodes.2.config.titlenames "Title on the step “Notify approver”", with the raw line under Details. "Show me" opens that node, and an unmappedvariables.0.namestays under Details.Ablation (one run, committed implementation, restore proven):
objectSaveRefusal(e, body, locale)in the Data page's save catch was replaced withplainRefusal(e), i.e. renderformatMetadataError(e)verbatim again.ablation-replace.mjsreported anchor 1 → 0 and the blob changed. Both Data pins went red (2 failed / 2). The first failure: expected "Changes not saved: the field “New field” needs at least one option…", received "The object was not saved: the fieldfield_2is aselectwith no options…". The restore left the blob equal to HEAD with an emptygit diff HEAD.Tests and gates
All runs are on the final commit
36165fc(this branch merged withmainat455c646, never rebased), under the container's shared verify lock, from the worktree root.turbo run build --filter=@object-ui/app-shell^...(28 of 28 tasks). Type-check:pnpm --filter @object-ui/data-objectstack type-checkexits 0;pnpm --filter @object-ui/app-shell type-check(tsc --noEmit && tsc -p tsconfig.test.json, which includessrc/**/*.test.ts(x)) exits 0.pnpm exec vitest runon the union below: 228 files, 2608 tests passed, exit 0.packages/data-objectstack/in full.packages/app-shell/src/views/studio-design/in full.StudioDesignSurface,metadataError, the guard, or reads the designer table (derived bygit grep, 33 files).MetadataFieldsPage*suites (the non-StudioformatMetadataErrorcallers, as the control).StudioDesignSurface.tsxhas 13 warnings against 14 on the base (16cda0b), so none is new. The fullpnpm lintis CI's.Docalias, renamedSentBody, now green;scripts/check-changeset-presence.mjs.index.tson the base tree findsflow-node-config.ts,navItemLabel.tsandi18n.tsalready IN the barrel's closure. Control:console/home/QuickActions.tsxreads OUT, confirmed by hand. So the new imports add no module to it.Patch round on head
00304bc(README only;mainstill455c646, already merged), under the shared lock where noted:turbo run build $(node scripts/check-doc-snippet-types.mjs --build-filter) --concurrency=2: exit 0 (locked).pnpm --filter @object-ui/data-objectstack type-check(tsc --noEmit): exit 0 (locked).pnpm check:doc-snippets(its corpus is everyREADME.mdunderpackages/): exit 0, "Every covered documentation snippet compiles against the built types." (locked).pnpm check:readme-exports: exit 0, "check-readme-exports: OK (43 tracked README(s) under packages/ …)". It was run after that build; the run before the build was a PREREQUISITE failure (nodist), not a finding.pnpm check:changeset-claimsexit 0,pnpm check:pending-changeset-literalsexit 0,pnpm check:doc-typesexit 0,pnpm check:doc-fencesexit 0,pnpm check:control-bytesexit 0,node scripts/check-changeset-presence.mjsexit 0.36165fc, whose union is above.Acceptance notes
6050143821, answer A):packages/data-objectstack/README.mdis published (fileslists it). Its two// throws:comment lines quoted the guard's old first line, "MetadataClient.save refused this object metadata write: the field". Both now read "The object was not saved: the field". That is the guard's first line as measured by running it on the README's two example bodies. The second line of each quote already matched and is unchanged, and no other README text changed (commit00304bc).ResourceEditPage(metadata-admin) is not restructured. It already maps a 422's issues to labelled inline errors, and it shows the guard's refusal aserr.message, which is now free of class names and tracker ids.formatPublishFailures). No strip renders them, and the card measured no publish case. Converting them is a separate surface.CreateItemDialog, off-surface) printerror.messageonly. Creates keep the plain text there.ObjectFieldInspector.tsxand the flow inspectors are another seat's files or off-surface, so the strip's sentence plus "Show me" is the attachment.translateValidationMessagemaps a bareRequiredto "A name is required." whatever the path. The sentence passes the server's message through it, as ResourceEditPage does, and inherits that. It was not reached at a public door in this run.Session:
https://claude.ai/code/session_01CGZy1BGCjdN5cXqL9cnvB8Generated by Claude Code