Repository navigation
Commit 31add5f
fix(app-shell): notify recipients render online and are picked; record field values pick their field; New automation asks for the trigger (objectui#11788) (#11875)
Fixes #11788
Clause-②: no
Studio can now build a "notify the owner when the ticket is done" flow
without editing JSON. This PR closes the three gaps on the card. Starter
templates are not part of it: triage comment 6043731723 leaves them out.
## What changes
1. **Notify recipients render online, and the same fix restores the http
node's Headers and Body.** With a backend, the notify form had no
Recipients field, and the node's `recipients: []` seed sat in Advanced
(JSON). Now a config key that the published descriptor declares, but
that the JSON-Schema mapper cannot type, keeps the offline table's
editor. The editor appears at the key's declared position and carries
the descriptor's own title and description. A key the descriptor does
not declare is never brought back, because the engine rejects undeclared
config keys at `registerFlow()`.
2. **Recipients are picked, not typed.** Each row picks a type and then
a value. The types are a field of the trigger record, a user, a team or
an email address. The picker stores only spellings the platform already
reads (see H2).
3. **Record field values pick their field.** On `create_record` and
`update_record`, the key cell of `config.fields` lists the fields of the
node's `config.objectName`. The key stays free text. The value cell is
untouched and keeps the slot's own dialect, including the text / CEL
toggle the spec's expression ledger declares for `fields.*`.
4. **New automation asks for the trigger.** The dialog offers the Start
node's own trigger field, using its options and labels. It asks for the
object when the Start node would show its Object field for that trigger.
The choice is written where the Start node's field writes it. Left
unset, the flow is created as before.
## Measured first
### H1: why the Recipients field was missing
The PM's candidate was close but not exact: an **untyped** key is
dropped, not an array-typed one.
- **Code path.** The inspector's `fields` memo in
`FlowNodeInspector.tsx` decides the fields of a registry-described node.
It runs `jsonSchemaToFlowFields(configSchemas[type])`, and then
`mergeServerFlowFields` lets the mapped fields replace every
config-rooted field of the offline table.
- **The published descriptor.** The notify `configSchema` declares
`recipients` with a description and **no `type`**. That is objectstack
`registerNotifyNode` in `service-automation` `builtin/notify-node.ts`,
read on objectstack `main` `51290bca`. The contract
(`NotifyConfigSchema.recipients`) takes a string or a string array.
- **What the mapper does.** It skips untyped properties
("unrepresentable, so Advanced"). The merge then dropped the offline
`recipients` row. Array-typed `channels` did map, to `stringList`.
- **Probe on the base tree.** Feeding that descriptor shape to the real
inspector, before the fix:
- offline: Recipients label count 1, and Advanced holds nothing;
- engine: Recipients label count 0, and Advanced holds the `recipients:
[]` JSON.
- **Census of every builtin descriptor `configSchema` in
`service-automation` at `51290bca`.** These are the declared keys the
mapper drops:
- notify: `recipients`, `templateData`, `payload`;
- http: `headers` (an object with neither `properties` nor
`additionalProperties`) and `body` (untyped);
- loop: `body`;
- try_catch: `try`, `catch`.
Of these, only `recipients`, `headers` and `body` have an offline
editor. Those three are what the merge rule restores. The others stay in
Advanced, as before. Keeping the editor for a declared-but-unmapped key
is one rule that closes this whole class, rather than a fix for
`recipients` alone.
### H2: the recipients grammar
- `NotifyConfigSchema.recipients` is a string or a string array. The
notify executor interpolates `{token}` templates in it and hands each
resolved string to the messaging service as an audience spec.
- objectstack `service-messaging` `RecipientResolver` (ADR-0030) reads
these specs:
- a bare user id, or `user:ID`;
- an email-shaped value, resolved to a `sys_user`;
- `role:NAME`;
- `team:ID`, which expands through `sys_team_member.team_id`;
- `owner_of:OBJECT:ID`.
- The picker writes these spellings:
| Type | Stored as |
|---|---|
| Record field | `{record.FIELD}` |
| User | the bare user id (ADR-0030's "explicit ids") |
| Team | `team:ID` |
| Email | the address |
The user and team lookups are the approval picker's own `sys_user.id`
and `sys_team.id` bindings, from the spec's `APPROVER_VALUE_SOURCES`.
- A user can be spelled two ways. Both are documented and resolve to the
same recipient, so the schema does not read two ways that store
different values. The picker writes the bare id and reads both.
- Every other stored value is shown under "Other" and saved byte for
byte. That covers a flow variable such as `{ownerId}`, `{task.owner}`, a
dotted `{record.a.b}`, `role:`, `owner_of:` and `user:ID`.
### H3: the trigger in the New dialog
The H3 premise is only partly true.
- No trigger descriptor registry exists to read. ADR-0041 section 4 is
not implemented on objectstack `main`: `maturity` exists only on
`ActionDescriptorSchema`, no `start` descriptor is registered, and no
route serves trigger descriptors.
- The Start node's trigger picker reads the offline `start` field
`triggerType`, through `fieldsForNodeType('start')`. The flow header
pill already reads it the same way.
- The dialog reads that same field. It writes the shape the Start node's
select writes: `config.triggerType`, plus `config.objectName` when the
trigger watches an object.
### H4: the field source
`useObjectFields(objectName)` in `previews/useObjectFields.ts` is the
source that the reference picker's `object-field` kind already reads.
- The picker is keyed by node type and path (`create_record` and
`update_record`, `config.fields`), the same way `BLOCK_SWITCHES` is
keyed. That way it holds for both descriptor sources. The published
`fields` is `additionalProperties: true`, which the mapper turns into
`keyValue`.
- The value dialect is unchanged. objectui#11824 and
objectstack-ai/objectstack#19939 own any change to it.
## Files and regions beyond the claim's list
The claim listed regions inside these files. The measured cause sits in
neighbouring regions of the same files, named here:
- `inspectors/flow-node-config.ts`:
- the `FlowConfigFieldKind` union gains `recipients`;
- `mergeServerFlowFields` gains the declared-key rule (H1). This is the
class fix, and the http restore comes with it.
- `inspectors/FlowNodeInspector.tsx`: one line in the `fields` memo
passes the declared keys. This is not the ID-field rename path that
objectui#11838 owned (now landed).
- `inspectors/json-schema-to-fields.ts`: `declaredConfigKeys`, the
declared key list with each key's title and description.
- `inspectors/FlowKeyValueField.tsx`: the editor the field-value rows
render through. It gains optional `keyOptions` and `keyHint`. Every
other map renders as before.
- `inspectors/FlowNodeConfigField.tsx`: the `recipients` case, the
`keyValue` case's field-value map wrapper, and the path table.
- `studio-design/StudioDesignSurface.tsx`:
- the Automations pillar's New dialog (`extra` slot), `doCreateFlow`,
and the New button's reset of the two new state values;
- two module-level helpers, `newFlowTriggerFields` and
`withStartTrigger`, just above `AutomationsPillar`;
- two imports.
`CreateItemDialog.tsx` is not edited. Its existing `extra` slot carries
the trigger choice.
- New file: `inspectors/FlowRecipientsField.tsx`. It is the one
module-private picker the claim allows.
- Tests: four new files, plus one updated expectation in
`flow-node-config.test.ts` (the notify recipients kind).
- `.changeset/11788-notify-recipients-picker.md`: an
`@object-ui/app-shell` patch.
i18n: 17 new rows, each in `en` and `zh`, under prefixes no in-flight
card uses:
- `engine.inspector.recipients.*`
- `engine.inspector.fieldMap.*`
- `engine.studio.newAutoTrigger.*`
The notify `recipients` zh help row is also updated. Nothing is added to
the package entry: no export, prop, `@object-ui/types` member or
`packages/i18n` key.
## Tests and gates
The head is `8c156f6`, which merges `origin/main` `b0aa892`.
- **The full set below was run on `3c844f8`,** which merged `4e7dd56`.
- **The last merge brought three PRs.** Of the files this branch
touches, they overlap only `i18n.ts`. On `8c156f6` I re-ran:
- the i18n, citation, control-byte and changeset gates (all exit 0, with
the verdict lines below);
- `pnpm --filter @object-ui/app-shell type-check` (exit 0);
- 8 test files: the four new pins, `flow-node-config.test.ts`,
`notify-node.dogfood.test.tsx`,
`StudioDesignSurface.flowStatusTruth-11779.test.tsx` and
`AutomationsPillar.authorRefusal-11785.test.tsx`. Result: `Test Files 8
passed (8)`, `Tests 94 passed (94)`.
- **CI runs the full set on the head.**
Each command below was run from the repo root, through the container's
verify lock, against `3c844f8`, unless noted.
**Tests**
| Command | Result |
|---|---|
| `pnpm --filter @object-ui/app-shell type-check`, after `turbo run
build --filter=@object-ui/app-shell^...` | exit 0 (`tsc --noEmit` and
`tsc -p tsconfig.test.json`) |
| `pnpm exec vitest run
packages/app-shell/src/views/metadata-admin/inspectors/` plus the 9
other metadata-admin tests that read `flow-node-config` | `Test Files
147 passed (147)`, `Tests 2050 passed, 1 skipped (2051)` |
| `pnpm exec vitest run packages/app-shell/src/views/studio-design/` |
`Test Files 103 passed (103)`, `Tests 648 passed (648)` |
The test type-check program lists all four new test files. That was
counted with `tsc -p tsconfig.test.json --listFilesOnly`: 4 of 4.
**The four new pins**
- `FlowNodeInspector.declaredUnmapped-11788.test.tsx`
- `FlowRecipientsField.test.tsx`
- `FlowNodeInspector.fieldValueMap-11788.test.tsx`
- `StudioDesignSurface.newFlowTrigger-11788.test.tsx`
**Gates (each verdict line as printed)**
- `pnpm check:i18n-designer-parity`: "Every en row has a zh row, and
every shared row carries the same placeholders."
- `pnpm check:i18n-keys`: "Every in-scope call-site key resolves against
the en pack (3300 keys)…"
- `pnpm check:i18n-drift`: "No designer-table en value changed in this
range." It also reports 17 designer keys added.
- `pnpm check:i18n-dead-keys` is a report. None of the 17 new keys is
listed in it.
- `pnpm check:new-line-citations`: "VERDICT
new-cross-file-line-citations: 0 new citation(s)"
- `pnpm check:control-bytes`: "OK"
- `pnpm check:designer-field-key-parity`: "OK"
- `node scripts/check-changeset-presence.mjs`: "13 source file(s) of 1
released package(s) changed, and this change declares 1 changeset(s)"
- `node scripts/check-changeset-no-major.mjs`: "No changeset declares a
`major` bump."
- Also exit 0:
- `check:changeset-claims`, `check:pending-changeset-literals`;
- `check-changeset-fixed`, `check-changeset-overwrite`;
- `check:vi-mock-specifiers`, `check:vi-mock-inherit`,
`check:vi-mock-override-shape`;
- `check:test-path-roots`, `check:unreferenced-sources`,
`check:spec-symbols`;
- `check:esm-specifiers`, `check:phantom-deps`;
- `check:metadata-write-doors`, `check:handler-key-reads`;
- `check-type-check-coverage`, `check-lint-coverage`.
**ESLint on the touched files: 0 errors, 26 warnings**
This is a narrowed lint run, not the repo-wide one.
- The population was every touched `.ts` / `.tsx` file. The `--format
json` report counts 13 files, matching the 13 touched; none was ignored.
- Invariance: `eslint.config.js` enables no type-aware linting (no
`projectService` and no `parserOptions.project`). So this diff cannot
change a verdict on a file it does not touch.
- Warning delta against `main` `4e7dd56`:
- unchanged: `FlowKeyValueField.tsx` 4, `FlowNodeInspector.tsx` 3,
`StudioDesignSurface.tsx` 13;
- new: 6, all in the new `FlowRecipientsField.tsx`. They are
`react-refresh/only-export-components` on its exported grammar helpers,
the same pattern as `FlowKeyValueField.tsx`.
## Ablations (one-time, from committed state)
Each leg below:
1. mutated one committed line with `ablation-replace`, and checked on
disk that the anchor hit once and the blob changed;
2. ran the pin;
3. restored the line, proved by blob equal to HEAD and an empty `git
diff HEAD`.
| Leg | Mutation | Result |
|---|---|---|
| declared keys | `mergeServerFlowFields(serverFields, nodeType, null)`
| `3 failed / 6 passed`. All failures are the engine-source cases
(notify Recipients, http Headers/Body, and the payload control's
recipients half). The offline cases stayed green. |
| field-value map | `fieldValueMapObject` call replaced by `null` | `6
failed / 2 passed`. The `update_record` filter control stayed green. |
| Start trigger | `withStartTrigger` returns the skeleton unchanged | `2
failed / 2 passed`. The "left unset" control stayed green. |
| record-field spelling | `writeRecipient` field case returns the bare
name | `5 failed / 13 passed` |
The restored tree ran the four pins together: `Test Files 4 passed (4)`,
`Tests 39 passed (39)`, and `git diff HEAD` was empty.
## Acceptance notes
- **Gaps against the triage direction:**
- No "role" type is offered. The direction names four types: field,
user, team and email. `role:NAME`, `owner_of:` and flow variables stay
authorable under "Other", saved as typed.
- The Record field list shows every field of the trigger object. The
field catalog carries no reference target, so the list is not narrowed
to user lookups.
- **Producer note, not filed.** The notify descriptor publishes
`recipients` untyped.
- If it is ever typed as a plain string array, the mapper will draw a
`stringList`, and the online form will lose the picker. The merge
restores only unmapped keys.
- A recipient annotation on the descriptor would be the contract-first
way to keep the picker. That is an objectstack contract question this
card does not open.
- Carrier: none.
- **Boundary, not a filing class.** The offline `http_request` Headers
help still says "e.g. Authorization". The published descriptor warns
never to put a credential in headers. Online, the restored editor now
carries the descriptor's warning. The offline text is unchanged.
Carrier: none.
- **Drift, not filed.** The offline notify table has a `url` row, while
the descriptor and contract key is `actionUrl`; `url` is the pre-17
alias the load-time conversion rewrites. The merge rule does not bring
`url` back online, because the descriptor does not declare it. Carrier:
none.
- **Same as the Start node picker.** Choosing `schedule` or
`time_relative` writes `triggerType` alone. `resolveFlowTriggerKind`
answers no kind until `config.schedule` or `config.timeRelative` is set.
The dialog mirrors the Start node picker here and changes neither.
- **Fixed in passing.** The previous `stringList` recipients editor read
a stored single string, for example `recipients: '{record.assignee}'`,
as an empty list. The picker reads it as one row and keeps it a string
while it holds one recipient.
- **Not measured: `check:eager-closure`.** It needs a console production
build, which CI runs. `StudioDesignSurface.tsx` now imports
`ObjectPicker.tsx`. A static-import walk of the base tree read
`ObjectPicker.tsx` as not reachable from the app-shell entry, and its
own imports as already reachable.
- **Not measured: repo-wide lint.** `pnpm lint` is CI's to run. The
local narrowing is given under Tests and gates.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01MgfduSkFrfM3eorB3UGfAU)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 8962734 commit 31add5f
14 files changed
Lines changed: 1568 additions & 51 deletions
File tree
- .changeset
- packages/app-shell/src/views
- metadata-admin
- inspectors
- studio-design
| 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 | |
|---|---|---|---|
| |||
1167 | 1167 | | |
1168 | 1168 | | |
1169 | 1169 | | |
| 1170 | + | |
| 1171 | + | |
| 1172 | + | |
| 1173 | + | |
| 1174 | + | |
| 1175 | + | |
| 1176 | + | |
| 1177 | + | |
| 1178 | + | |
| 1179 | + | |
| 1180 | + | |
| 1181 | + | |
| 1182 | + | |
| 1183 | + | |
| 1184 | + | |
| 1185 | + | |
| 1186 | + | |
| 1187 | + | |
1170 | 1188 | | |
1171 | 1189 | | |
1172 | 1190 | | |
| |||
3221 | 3239 | | |
3222 | 3240 | | |
3223 | 3241 | | |
| 3242 | + | |
| 3243 | + | |
3224 | 3244 | | |
3225 | 3245 | | |
3226 | 3246 | | |
| |||
4284 | 4304 | | |
4285 | 4305 | | |
4286 | 4306 | | |
| 4307 | + | |
| 4308 | + | |
| 4309 | + | |
| 4310 | + | |
| 4311 | + | |
| 4312 | + | |
| 4313 | + | |
| 4314 | + | |
| 4315 | + | |
| 4316 | + | |
| 4317 | + | |
| 4318 | + | |
| 4319 | + | |
| 4320 | + | |
| 4321 | + | |
| 4322 | + | |
| 4323 | + | |
| 4324 | + | |
4287 | 4325 | | |
4288 | 4326 | | |
4289 | 4327 | | |
| |||
6170 | 6208 | | |
6171 | 6209 | | |
6172 | 6210 | | |
| 6211 | + | |
| 6212 | + | |
6173 | 6213 | | |
6174 | 6214 | | |
6175 | 6215 | | |
| |||
6589 | 6629 | | |
6590 | 6630 | | |
6591 | 6631 | | |
6592 | | - | |
| 6632 | + | |
6593 | 6633 | | |
6594 | 6634 | | |
6595 | 6635 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
213 | 213 | | |
214 | 214 | | |
215 | 215 | | |
| 216 | + | |
| 217 | + | |
| 218 | + | |
| 219 | + | |
| 220 | + | |
| 221 | + | |
| 222 | + | |
| 223 | + | |
| 224 | + | |
216 | 225 | | |
217 | 226 | | |
218 | 227 | | |
| |||
245 | 254 | | |
246 | 255 | | |
247 | 256 | | |
| 257 | + | |
| 258 | + | |
248 | 259 | | |
| 260 | + | |
| 261 | + | |
249 | 262 | | |
250 | 263 | | |
251 | 264 | | |
| |||
324 | 337 | | |
325 | 338 | | |
326 | 339 | | |
| 340 | + | |
327 | 341 | | |
328 | 342 | | |
329 | 343 | | |
| |||
404 | 418 | | |
405 | 419 | | |
406 | 420 | | |
| 421 | + | |
| 422 | + | |
| 423 | + | |
| 424 | + | |
| 425 | + | |
| 426 | + | |
| 427 | + | |
| 428 | + | |
| 429 | + | |
| 430 | + | |
407 | 431 | | |
408 | 432 | | |
409 | 433 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
9 | 9 | | |
10 | 10 | | |
11 | 11 | | |
12 | | - | |
| 12 | + | |
| 13 | + | |
13 | 14 | | |
14 | 15 | | |
15 | 16 | | |
| |||
19 | 20 | | |
20 | 21 | | |
21 | 22 | | |
22 | | - | |
| 23 | + | |
23 | 24 | | |
24 | 25 | | |
| 26 | + | |
25 | 27 | | |
26 | 28 | | |
27 | 29 | | |
| |||
49 | 51 | | |
50 | 52 | | |
51 | 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 | + | |
52 | 121 | | |
53 | 122 | | |
54 | 123 | | |
| |||
237 | 306 | | |
238 | 307 | | |
239 | 308 | | |
240 | | - | |
| 309 | + | |
| 310 | + | |
| 311 | + | |
| 312 | + | |
| 313 | + | |
| 314 | + | |
| 315 | + | |
| 316 | + | |
| 317 | + | |
| 318 | + | |
| 319 | + | |
| 320 | + | |
| 321 | + | |
| 322 | + | |
| 323 | + | |
| 324 | + | |
| 325 | + | |
| 326 | + | |
| 327 | + | |
| 328 | + | |
| 329 | + | |
| 330 | + | |
| 331 | + | |
| 332 | + | |
| 333 | + | |
| 334 | + | |
| 335 | + | |
| 336 | + | |
| 337 | + | |
| 338 | + | |
| 339 | + | |
| 340 | + | |
| 341 | + | |
| 342 | + | |
| 343 | + | |
241 | 344 | | |
242 | | - | |
| 345 | + | |
243 | 346 | | |
244 | 347 | | |
245 | 348 | | |
246 | 349 | | |
247 | | - | |
248 | | - | |
249 | | - | |
250 | | - | |
251 | | - | |
252 | | - | |
253 | 350 | | |
254 | | - | |
255 | | - | |
256 | | - | |
257 | | - | |
258 | | - | |
259 | | - | |
260 | | - | |
261 | | - | |
262 | | - | |
263 | | - | |
264 | | - | |
| 351 | + | |
| 352 | + | |
| 353 | + | |
| 354 | + | |
265 | 355 | | |
266 | 356 | | |
267 | | - | |
| 357 | + | |
268 | 358 | | |
269 | | - | |
| 359 | + | |
270 | 360 | | |
271 | 361 | | |
272 | 362 | | |
273 | 363 | | |
| 364 | + | |
| 365 | + | |
| 366 | + | |
274 | 367 | | |
275 | | - | |
276 | | - | |
277 | | - | |
278 | | - | |
279 | 368 | | |
280 | 369 | | |
281 | 370 | | |
| |||
0 commit comments