Repository navigation
Commit 4a9fe31
fix(app-shell): Studio refusals read as an author sentence, with the raw text under Details (objectui#11785) (#11848)
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 a `StudioRefusal` with three
parts:
- **One sentence** that names the input in the editor's own terms. On
the Data page that is the field's label ("Changes not saved: the field
“Status” needs at least one option. Add an option, or change the field
to a type that is not a choice."). On the Automations page it is the
step's label and the inspector input ("Changes not saved. Check Title on
the step “Notify approver” — Required text value"). On the Interfaces
page it is the navigation item and its input.
- **A "Show me" button** that opens that field, step or navigation item,
when the path maps to one.
- **A closed "Details" disclosure** holding the raw text. That text is
exactly `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/spec` or 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 has
`noUnusedParameters`. 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 `Error` before 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. `e` counts 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)
- **H1, readers of the guard's text.** These read it: the guard's own
tests; `metadata-client.objectWriteGuard.test.ts`, whose door-name pin
is inverted here; plugin-designer's
`MetadataFieldsPage.storedChoiceWithoutOptions-11253` banner test, which
matches the phrase `is a radio with no options`, a clause kept verbatim;
and the data-objectstack README (see Acceptance notes).
- **H2, does structure survive?** Yes. `MetadataError.issues` (path,
message, code) reaches every Studio `catch`. It was flattened at
`setError(formatMetadataError(e))`. The strips now hold the view model
instead of a string.
- **H3, what maps.**
| Issue path | Mapped to | Opens |
|---|---|---|
| `fields.NAME…` (record, or array by index or name) | the field, by its
label | the field inspector |
| `nodes.N.config.KEY` / `nodes.N.KEY…` | step N by its label, plus the
input label from the node's field table | the node inspector |
| `navigation.N.KEY` (top level) | the navigation item, plus its Label /
Link-to-object input | nav editing on that entry |
The index used for `navigation.N` is the index in the list that was
SENT, mapped back to the editor's entry by object or `id`.
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;
- nested `navigation.N.children…` (the Studio nav inspector edits
top-level entries only);
- object keys outside `fields` (`validations.*`, `name`, root);
- every Interfaces leaf path (page blocks, dashboard widgets).
## 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 covers
`nodes.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 that `formatMetadataError` (every
other caller) is unchanged.
- `DataPillar.authorRefusal-11785.test.tsx` (real door, transport
double): switching a field to Picklist shows the author sentence, with
no `MetadataClient`, tracker id, backtick or API name in the visible
message. The raw text sits in a closed `details` element. "Show me"
reopens the field.
- `AutomationsPillar.authorRefusal-11785.test.tsx` (real FlowPreview /
FlowInspector): a 422 at `nodes.2.config.title` names "Title on the step
“Notify approver”", with the raw line under Details. "Show me" opens
that node, and an unmapped `variables.0.name` stays under Details.
**Ablation (one run, committed implementation, restore proven):**
`objectSaveRefusal(e, body, locale)` in the Data page's save catch was
replaced with `plainRefusal(e)`, i.e. render `formatMetadataError(e)`
verbatim again. `ablation-replace.mjs` reported 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 field `field_2` is a
`select` with no options…". The restore left the blob equal to HEAD with
an empty `git diff HEAD`.
## Tests and gates
All runs are on the final commit `36165fc` (this branch merged with
`main` at `455c646`, never rebased), under the container's shared verify
lock, from the worktree root.
- **Build:** `turbo run build --filter=@object-ui/app-shell^...` (28 of
28 tasks). Type-check: `pnpm --filter @object-ui/data-objectstack
type-check` exits 0; `pnpm --filter @object-ui/app-shell type-check`
(`tsc --noEmit && tsc -p tsconfig.test.json`, which includes
`src/**/*.test.ts(x)`) exits 0.
- **Tests:** `pnpm exec vitest run` on the union below: **228 files,
2608 tests passed**, exit 0.
- `packages/data-objectstack/` in full.
- `packages/app-shell/src/views/studio-design/` in full.
- Every app-shell and console test that imports `StudioDesignSurface`,
`metadataError`, the guard, or reads the designer table (derived by `git
grep`, 33 files).
- The write-doors and i18n script tests.
- All 11 plugin-designer `MetadataFieldsPage*` suites (the non-Studio
`formatMetadataError` callers, as the control).
- **Declared narrowing:** app-shell's full suite is not run locally and
is left to CI. The derived set covers direct importers of the changed
modules. It is blind to a test that reaches them only transitively.
- **ESLint** on the 9 touched source and test files: 0 errors.
`StudioDesignSurface.tsx` has 13 warnings against 14 on the base
(`16cda0b`), so none is new. The full `pnpm lint` is CI's.
- **Gates:** all exit 0.
- control-bytes, test-path-roots, changeset-claims,
pending-changeset-literals;
- i18n-keys, i18n-drift, i18n-dead-keys, i18n-designer-parity ("Every en
row has a zh row, and every shared row carries the same placeholders.");
- metadata-write-doors ("3 reach assertObjectMetadataWritable");
- esm-specifiers, phantom-deps, vi-mock-inherit, vi-mock-override-shape;
- spec-symbols: red once on a module-local `Doc` alias, renamed
`SentBody`, now green;
- new-line-citations, designer-field-key-parity, and
`scripts/check-changeset-presence.mjs`.
- **NOT MEASURED:** check:eager-closure, check:doc-examples and
check:doc-snippets. All three report PREREQUISITE NOT MET because they
need a built console or a doc build. As a stand-in for eager-closure, a
static-import crawl of app-shell's `index.ts` on the base tree finds
`flow-node-config.ts`, `navItemLabel.ts` and `i18n.ts` already IN the
barrel's closure. Control: `console/home/QuickActions.tsx` reads OUT,
confirmed by hand. So the new imports add no module to it.
**Patch round on head `00304bc`** (README only; `main` still `455c646`,
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 every `README.md` under
`packages/`): 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 (no `dist`), not a
finding.
- `pnpm check:changeset-claims` exit 0, `pnpm
check:pending-changeset-literals` exit 0, `pnpm check:doc-types` exit 0,
`pnpm check:doc-fences` exit 0, `pnpm check:control-bytes` exit 0, `node
scripts/check-changeset-presence.mjs` exit 0.
- The code is unchanged since `36165fc`, whose union is above.
## Acceptance notes
- **README, done in this PR (claim amendment comment `6050143821`,
answer A):** `packages/data-objectstack/README.md` is published (`files`
lists 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 (commit `00304bc`).
- `ResourceEditPage` (metadata-admin) is not restructured. It already
maps a 422's issues to labelled inline errors, and it shows the guard's
refusal as `err.message`, which is now free of class names and tracker
ids.
- Publish failures stay toasts (`formatPublishFailures`). No strip
renders them, and the card measured no publish case. Converting them is
a separate surface.
- Create dialogs (`CreateItemDialog`, off-surface) print `error.message`
only. Creates keep the plain text there.
- Inline errors inside the inspectors were not added.
`ObjectFieldInspector.tsx` and the flow inspectors are another seat's
files or off-surface, so the strip's sentence plus "Show me" is the
attachment.
- Observation, not filed: `translateValidationMessage` maps a bare
`Required` to "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.
- objectui#11786 (the autosave refusing intermediate states) is not
addressed here and remains open.
Session: `https://claude.ai/code/session_01CGZy1BGCjdN5cXqL9cnvB8`
---
_Generated by [Claude Code](https://claude.ai/code)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 59ce048 commit 4a9fe31
11 files changed
Lines changed: 1142 additions & 79 deletions
File tree
- .changeset
- packages
- app-shell/src/views
- metadata-admin
- studio-design
- data-objectstack
- src
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
2758 | 2758 | | |
2759 | 2759 | | |
2760 | 2760 | | |
| 2761 | + | |
| 2762 | + | |
| 2763 | + | |
| 2764 | + | |
| 2765 | + | |
| 2766 | + | |
| 2767 | + | |
| 2768 | + | |
| 2769 | + | |
| 2770 | + | |
| 2771 | + | |
| 2772 | + | |
| 2773 | + | |
| 2774 | + | |
| 2775 | + | |
| 2776 | + | |
| 2777 | + | |
2761 | 2778 | | |
2762 | 2779 | | |
2763 | 2780 | | |
| |||
5714 | 5731 | | |
5715 | 5732 | | |
5716 | 5733 | | |
| 5734 | + | |
| 5735 | + | |
| 5736 | + | |
| 5737 | + | |
| 5738 | + | |
| 5739 | + | |
| 5740 | + | |
| 5741 | + | |
| 5742 | + | |
| 5743 | + | |
| 5744 | + | |
| 5745 | + | |
| 5746 | + | |
| 5747 | + | |
5717 | 5748 | | |
5718 | 5749 | | |
5719 | 5750 | | |
| |||
Lines changed: 182 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
Lines changed: 158 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
0 commit comments