Repository navigation
Commit 3518664
fix(app-shell): the notify recipients editor writes and keeps { emailField } (#25996)
<!-- claude-projects-attribution: {"github_login":"os-steve"} -->
_Requested by **Steve** · [project
thread](https://claude.ai/code/project/chan_01FdL1XeUyvVNxLreh3F4Qi9?thread=cmsg_01FdL1XeUyvVNxLreh3F4Qi9S2Diy7UYLduR4oipBE7yEa)_
Fixes #25900
Clause-②: no
Metadata-protocol: none
## What changes
The Studio notify node's recipients editor (`FlowRecipientsField`) now
knows the spec's fourth recipient form, the email-field recipient `{
emailField: 'FIELD' }` (`NotifyEmailFieldRecipientSchema`, the whole
`recipients` value, on a node whose only channel is `email`).
- **Data loss closed.** A stored `recipients: { emailField }` read as no
recipients (`readRecipients` kept only strings and CEL envelopes), so
the first edit of the node replaced it. It now reads as one Email field
row, and an unedited row is written back as the object it was read from.
- **An Email field row.** Picks an `email`-typed field of the trigger
object (the suggestions are filtered by field type) and stores `{
emailField: 'FIELD' }` as the whole value. It is offered in the type
select only while it is the sole row, and "Add recipient" is disabled
beside it, so the editor never writes it as a list entry (which the spec
refuses).
- **Channels in the same patch.** The commit that first stores an
email-field recipient also writes `channels: ['email']` when the node
does not already name `email` alone (either spelling, string or list,
counts). A blur that changes nothing writes no channel. A stored `{
emailField }` on a node naming other channels shows a note and a
one-click "Send by email only" fix.
- **Record field row on a node that sends email.** Where `channels`
include `email`, the Record field picker no longer suggests
`email`-typed fields: the runtime refuses the address that row's
`record.FIELD` envelope evaluates to unless a user holds it
(service-automation notify node, `screenComputedAddresses`). A stored
Record field row over such a field keeps its value and says the address
must belong to a user.
## Where the fix lives, and one file beyond the card's list
- `FlowRecipientsField.tsx` — the row kind, read/write, notes.
- `FlowReferenceField.tsx` — `ResolvedRef` gains optional
`onlyFieldTypes` / `exceptFieldTypes` for the `object-field` kind;
`resolveObjectName` is exported so the recipients field reads the
trigger object's field types.
- `FlowNodeConfigField.tsx` — `onCommit` takes an optional `alongside`
map of sibling keys and the recipients case forwards it.
- `FlowNodeInspector.tsx` (declared to the seat as a file-surface
addition) — `setField` applies `alongside` to the same node before the
one patch. The write is the inspector's: two sequential `setField` calls
each start from the node the render closed over, so the second would
drop the first key.
- `i18n.ts` — en and zh strings for the new type, placeholder and notes.
## Tests
New file `FlowRecipientsField.emailField-25900.test.tsx`, 11 tests:
stored `{ emailField }` round-trip and its row, Add disabled beside it,
the Email field picker's suggestions and its commit (accepted by the
installed `NotifyConfigSchema`, with `channels`), no channel write when
the node already names email alone, not offered beside another row, the
stored-wrong-channels note and fix, a non-email field named, the Record
field filter on email and non-email nodes, and the inspector writing
`recipients` and `channels` in exactly one patch.
- Before the fix (the new file against the unmodified editor): 10
failed, 1 passed (11). The data-loss pins failed on `expected [] to
deeply equal [ { kind: 'emailField', … } ]` and `expected null not to be
null`.
- After, at `e25d3a6e`: `FlowRecipientsField*` 2 files, 28/28 passed;
the whole `metadata-admin/` directory 597 files, 6561 passed, 1 skipped.
- Ablation (committed fix, `scripts/ablation-replace.mjs`, mutation
landed `anchor 1 -> 0`, restored `blob == HEAD`, `git diff HEAD` empty):
dropping the email-field arm from the `readRecipients` filter turns
exactly the 5 stored-value tests red (11 → 6 passed), the write and
filter tests stay green.
- `pnpm --filter @object-ui/app-shell run type-check`: exit 0 (the test
program lists the new test file).
## Gates
`node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack` at `e25d3a6e` derived 34 commands; all 34
exit 0, and `--ran` reconciles 34 derived, 34 run, 0 NOT-MEASURED (a
derived zero, each line carrying its exit code). `check:type-check-debt`
first returned queue-timeout (exit 3, nothing measured) and was re-run
under the lock: OK. Type Check UI, Lint UI and Build & E2E UI are CI's.
## Acceptance notes
- **Channels editor reads a string as empty (not fixed here).** The
notify node's Channels field is the offline `stringList` editor, which
reads a stored `channels: 'email'` string as an empty list; an edit
there then writes a list without `email`. This PR writes `channels` as a
list so the field shows it; a stored string spelling stays affected.
Reported to the seat for filing.
- Lint on the touched files: 0 errors. Four new
`react-refresh/only-export-components` warnings come from the new helper
exports (`isEmailFieldRecipient`, `readChannels`, `isEmailOnlyChannels`,
`resolveObjectName`), the same pattern as the helpers these files
already export.
- A stored list that contains an `{ emailField }` entry (which the spec
refuses) is now kept as read instead of dropped; the spec's refusal
names it.
Session: `https://claude.ai/code/session_01VrsYtPiyNCyFG3fUsVvNEQ`
---
_Generated by [Claude
Code](https://claude.ai/code/session_01VrsYtPiyNCyFG3fUsVvNEQ)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent f20a491 commit 3518664
7 files changed
Lines changed: 541 additions & 28 deletions
File tree
- .changeset
- ui/packages/app-shell/src/views/metadata-admin
- inspectors
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1330 | 1330 | | |
1331 | 1331 | | |
1332 | 1332 | | |
| 1333 | + | |
| 1334 | + | |
| 1335 | + | |
| 1336 | + | |
| 1337 | + | |
| 1338 | + | |
| 1339 | + | |
1333 | 1340 | | |
1334 | 1341 | | |
1335 | 1342 | | |
| |||
5013 | 5020 | | |
5014 | 5021 | | |
5015 | 5022 | | |
| 5023 | + | |
| 5024 | + | |
| 5025 | + | |
| 5026 | + | |
| 5027 | + | |
| 5028 | + | |
| 5029 | + | |
5016 | 5030 | | |
5017 | 5031 | | |
5018 | 5032 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
430 | 430 | | |
431 | 431 | | |
432 | 432 | | |
433 | | - | |
| 433 | + | |
| 434 | + | |
| 435 | + | |
| 436 | + | |
| 437 | + | |
| 438 | + | |
| 439 | + | |
434 | 440 | | |
435 | 441 | | |
436 | 442 | | |
| |||
651 | 657 | | |
652 | 658 | | |
653 | 659 | | |
654 | | - | |
| 660 | + | |
655 | 661 | | |
656 | 662 | | |
657 | 663 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
481 | 481 | | |
482 | 482 | | |
483 | 483 | | |
484 | | - | |
| 484 | + | |
485 | 485 | | |
486 | 486 | | |
487 | 487 | | |
| |||
544 | 544 | | |
545 | 545 | | |
546 | 546 | | |
| 547 | + | |
| 548 | + | |
| 549 | + | |
| 550 | + | |
| 551 | + | |
| 552 | + | |
| 553 | + | |
547 | 554 | | |
548 | 555 | | |
549 | 556 | | |
| |||
805 | 812 | | |
806 | 813 | | |
807 | 814 | | |
808 | | - | |
| 815 | + | |
809 | 816 | | |
810 | 817 | | |
811 | 818 | | |
| |||
0 commit comments