Skip to content

feat(fields): Setup's notification topic and channel fields pick from the served list and still take a typed value - #31069

Draft
objectstack-fleet[bot] wants to merge 9 commits into
mainfrom
claude/project-thread-sotebb
Draft

objectstack-fleet[bot] wants to merge 9 commits into
mainfrom
claude/project-thread-sotebb

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Nov 7, 2026 •

Copy link
Copy Markdown
Contributor

Requested by Tesla · project thread

Fixes #31062
Part of #30208

Clause-②: yes (widening) — five new fields.preferenceOption.* keys in every published @object-ui/i18n pack, and field:notification-topic / field:notification-channel join @object-ui/fields' registered widget keys (RegisteredFieldWidgetType); nothing is narrowed.
Metadata-protocol: none

维护者速读

  • 改了什么:Setup 的通知偏好(主题、渠道)和通知模板(主题)不再是纯文本框,改成下拉选择器,列表来自服务端 preference-options 接口,* 排第一;仍可直接输入列表外的值。
  • 为什么改:管理员手敲主题/渠道容易打错(如 invoices.paid、emial),通知就会静默不发或发错渠道;按裁决 E([Decision] 通知偏好 topic/channel: pickers, refuse unmatched values, or free text as ruled on #25346? #30527)给出可选列表并保留自由输入。
  • 风险与回滚:只影响声明了 notification-topic / notification-channel 的三个字段;接口失败或返回空列表时退回原来的文本框;回滚即 revert 本 PR。
  • 先看:ui/packages/fields/src/widgets/RegistryRefField.tsx(填充分支 PreferenceOptionField)、ui/packages/fields/src/widgets/preferenceOptions.ts(读取与共享请求)、ui/packages/fields/src/index.tsx(注册)。
  • 两分钟验证:打开 Setup → Notification Preferences → New,点 Topic,应看到 "All topics (*)" 排第一的列表;在搜索框输入 invoices.paid 点 "Use …",字段显示 "invoices.paid (not in the list)";Channel 列表里 push 显示 "(not configured)" 且可选。
  • 需要你做的:无,席位落地。
  • 席位意见:ui 席 2 对照 head 43dd3c69 的 diff 复核:24 个文件、+776 / −28,无受治理路径。设置里通知偏好与模板的「主题」「渠道」字段改为可填写的下拉:列表来自 GET /api/v1/messaging/preference-options(经宿主已认证的 fetch,整页共用一次请求),* 排在最前;手输的值原样保存,不在列表中的值保留并标注;未配置的渠道照列、标注、可选;请求失败或列表为空时回到原来的文本框。新增五条文案进所有语言包。Clause-② 改为 yes (widening),席位 TIER_DEFAULT 复核 PASS(6700168365)。CI 全绿,同意落地。

What changes

sys_notification_preference.topic / .channel and sys_notification_template.topic declare widget: 'notification-topic' / 'notification-channel' since #31004; nothing registered them, so they rendered as text boxes. This PR registers both as field:WIDGET keys in @object-ui/fields, so the record form picks them up with no per-page code.

Home: option (a), a fillable arm on RegistryRefField. It registers through fieldWidgetMap, the field widget registry the record form already consults for FieldSchema.widget, beside position-ref / permission-ref; option (b) would live in app-shell beside FlowReferenceField, which only the flow inspector reads, and would need its own route into the record form. The arm reuses the registry picker's four-state catalog machinery (IDLE / LOADING / FAILED / LOADED, generalised from useRegistryCatalog to useCatalog(reader, origin, list) with one module-level reader per arm), and differs from the registry arm only where an open list must:

  • The list comes from GET /api/v1/messaging/preference-options through the host's authenticated fetch (SchemaRendererContext.apiFetch, the console's existing createAuthenticatedFetch path; no new fetch helper). One request per fetch function for the page, shared by the topic and the channel field; a failed read is not kept. Listed in the server's order, * first, shown as "All topics ()" / "All channels ()".
  • No list (no host fetch, a failed or malformed answer, or an empty list): the plain text box, with no notice. The registry arm's failure banner is not shown here.
  • A typed value: the picker's search box offers a "Use "TEXT"" row that commits the text exactly as typed. A stored value the list lacks stays its own option, marked "TEXT (not in the list)" (data-not-in-list), on the edit and the read face. Nothing is refused or rewritten.
  • A channel the server marks not configured is listed as "push (not configured)" (data-not-configured) and is selectable.
  • Strings: five fields.preferenceOption.* keys in every console pack; zh-Hant regenerated from zh-Hans with gen:zh-hant.
  • Registration ripple, the position-ref (objectui#7611) ripple: FIELD_WIDGET_LABELLING, the bare-name skip list, INLINE_EXCLUDED_FIELD_TYPES and the two fields widget sweeps; under the seat's claim amendments 6699576193 and 6699989026, two widgetHintOnly rows in app-shell paramValueShape.ts, the two names in plugin-detail's inlineEditTypeCoverage excluded bucket, and ui/packages/cli/src/utils/known-schema-types.ts regenerated by node ui/scripts/regenerate-known-schema-types.mjs (two lines, no hand edit). The de quote census moves 108 → 109 for the new „{{value}}“ verwenden row.

Out of scope, unchanged: the template's channel (channel_renders_template, no widget, still a text box), quiet hours, row 5 of #30208, any server change.

Tests

ui/packages/fields/src/widgets/__tests__/RegistryRefField.preferenceOptions-31062.test.tsx, 14 cases:

  • the list renders from the route (* first, the door's order, one shared read for two fields), and both keys resolve through field:WIDGET in the registry;
  • the fallback to the text box when the route answers 500, when the fetch rejects, when the list is empty, and when no host fetch exists (nothing read);
  • a value outside the list is committed as typed and shown marked, on the edit and the read face;
  • a "not configured" channel is shown labelled and is selectable.

Ablation (scripts/ablation-replace.mjs, src import, no build in the path): deleting the 'notification-topic' loader from fieldWidgetMap turns "notification-topic is registered and lists the door" red (expected undefined to be defined, 1 failed / 12 passed); restore verified blob == HEAD.

Acceptance notes

  • Clause-② is copied from the claim (no). The PR adds five keys to every published @object-ui/i18n pack and two registered widget keys; some recent i18n-key PRs declared that yes (widening) and some no, so the seat may want to amend.
  • Open PR feat(spec,i18n): register the delete_package platform tool and label it in every pack (cloud#3656) #31037 also edits all ten hand-written ui/packages/i18n/src/locales/*.ts packs, in a different block; whichever lands second takes a textual merge.
  • pnpm check:eager-closure needs a Console build and is CI's: the arm adds code to @object-ui/fields, which rides the eager ui-components chunk.

Verification

Readings name their tree. Gates and type-check at da4d45a2; the last commit 1fc7c752 adds only the regenerated known-schema-types.ts, and the ui scripts/ suite was re-run on it. Re-deriving at 1fc7c752 gives the same 38 families.

Live before, on main 23cf11a (showcase backend --fresh, Console dev server, admin sign-in)
  • Setup → Notification Preferences → New: Topic and Channel are INPUT type=text, prefilled *. Notification Templates → New: Topic and Channel are INPUT type=text. No request to the options route.
  • GET /api/v1/messaging/preference-options (signed in): 200, 28 topics (* first, then approval.*, auth.*, collab.mention, the showcase flow topics such as invoice.signoff, project.digest, task.assigned, and notify), 5 channels: *, inbox, email, sms, and push with configured: false. Anonymous: 401.
Live after, on this branch (same backend)
  • Notification Preferences → New: Topic trigger reads "All topics ()", its list has 28 options with "All topics ()" first; Channel lists All channels (*), inbox, email, sms, push (not configured).
  • Typed invoices.paid in the topic search and picked "Use …": the trigger reads "invoices.paid (not in the list)" with data-not-in-list=true. Picked push (not configured). Save, then GET /api/v1/data/sys_notification_preference reads back topic: invoices.paid, channel: push.
  • Notification Templates → New: Topic is the picker; Channel stays INPUT type=text.
  • The options route answered 200 once per page load.
Tests
  • pnpm --dir ui exec vitest run packages/fields/src/widgets/__tests__/RegistryRefField.preferenceOptions-31062.test.tsx: 14 passed.
  • packages/fields/ whole, packages/i18n/, packages/components/src/renderers/form/, the plugin-form position-ref form test, the app-shell form-type guards and the cli ratchet: 8 reds before the ripple rows (all named above), then green on the rerun of every red file (361 + 367 + 48 tests).
  • pnpm --filter @object-ui/fields --filter @object-ui/i18n --filter @object-ui/app-shell run type-check after a dts build of their closure: exit 0.
Derived gates (dispatch-gates --commands, 38 families; every line exit 0)

Reconciled with dispatch-gates.mjs --ran: 38 derived, 38 run, 0 NOT-MEASURED. Record: /mnt/project-files/ui-seat-2/r2026-11-06/31062/gates-ran-1fc7c752.list. Includes node scripts/check-sdui-manifest.mjs (exit 0), pnpm check:nul-bytes, pnpm check:cross-package-test-inputs, pnpm check:type-check-coverage, pnpm check:type-check-debt, pnpm check:undeclared-dep-imports, the changeset gates, and pnpm --dir ui exec vitest run scripts/ (170 passed, 2 skipped).

… their labelling and sweep rows

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L9qibzyvUbUU6pC3x3Amrx
…ys in the form-type drift guards

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L9qibzyvUbUU6pC3x3Amrx
@github-actions github-actions Bot added size/l documentation Improvements or additions to documentation tests tooling Fix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface labels Nov 7, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:ui#2 · os-tesla · session_01L9qibw7DekEe3wx5f3zphf

Contract review

Served-tier: TIER_DEFAULT
Head-sha: 43dd3c69585773fbb98d2240396aec8b0dc09d34
Local-runs: none

Owed by the Clause-②: yes (widening) declaration alone: no packages/spec/src file, no governed path, no narrowing. The face is the declaration (--face declaration). The head moved from 1fc7c752 to 43dd3c69 by a one-line changeset edit only, which is that declaration.

① Derived judgments

  • @object-ui/fields .: fieldWidgetMap gains notification-topic and notification-channel, so RegisteredFieldWidgetType widens by those two literals. FIELD_WIDGET_LABELLING gains them as 'control', and FIELD_TYPES_SKIP_FALLBACK gains them. That is right: the metadata on main already declares widget: 'notification-topic' / 'notification-channel' (feat(service-messaging): serve the notification topics and channels a preference or template can name #31004), and FieldSchema.widget is a free string, so no authorable value changes. Only the renderer that answers it changes.
  • INLINE_EXCLUDED_FIELD_TYPES (re-exported through ./FieldEditWidget.js) gains the same two names. That is right and matches position-ref / permission-ref: a picker that reads a host route is not a grid-cell editor.
  • RegistryRefField.tsx and preferenceOptions.ts are not re-exported from .. PreferenceOptionField, NotificationTopicField, NotificationChannelField and loadPreferenceOptions are reachable only through the lazy loader, so they are not published surface.
  • @object-ui/i18n: five new fields.preferenceOption.* keys in every built-in pack, with zh-Hant regenerated, and the same values as the fields defaults table. This is a widening; nothing is removed or renamed.
  • Cross-package drift rows: paramValueShape.ts adds two widgetHintOnly rows, known-schema-types.ts is regenerated with two lines, and the plugin-detail inline-coverage pin adds two names. All three follow FORM_FIELD_TYPES and match the position-ref ripple. No accept set narrows.

② Semver level

@object-ui/fields patch and @object-ui/i18n patch, with Clause-②: yes (widening) and Metadata-protocol: none in both the changeset and the PR body. That is right: a non-protocol widening ships patch (AGENTS.md post-task 3). No BREAKING banner is owed. Precedent: #30854 and #30428 declare new pack keys the same way.

③ Boundary flags

Implemented-by: claude/project-thread-sotebb
Reviewed-by: session_01L9qibw7DekEe3wx5f3zphf

VERDICT: PASS

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:ui#2 · os-tesla · session_01L9qibw7DekEe3wx5f3zphf

席位核对 · ACCEPT · 2026-11-07T01:32Z · head 43dd3c69585773fbb98d2240396aec8b0dc09d34

Seat domain:ui#2 reviewed the diff of its own dispatch (claim 6699169269 on #31062, amendments 6699576193 and 6699989026, card thread session_01L9qibzyvUbUU6pC3x3Amrx, report 6700092020). The same seat dispatched and reviews; the code is the thread's.

  1. Shape: 24 files, +776 / -28. Option (a): a fillable arm of RegistryRefField. useRegistryCatalog is generalised into useCatalog(reader, origin, list), and the registry arm's behaviour is unchanged.
    • preferenceOptions.ts reads GET /api/v1/messaging/preference-options through SchemaRendererContext.apiFetch only, as the order rules. There is one request per fetch function, shared through a WeakMap. A failed read is not cached, and a malformed answer counts as a failure.
    • PreferenceOptionField behaves as follows:
      • while loading: a disabled input;
      • after a failure, an empty list, or no host fetch: the plain text box, with no notice;
      • after a load: a Popover/Command picker in the server's order, * first and shown as "All topics (*)".
      • A "Use …" row commits the typed text verbatim. A stored value missing from the list stays as its own option, marked "not in the list". A not-configured channel is listed, labelled, and selectable.
    • fieldWidgetMap, FIELD_WIDGET_LABELLING, FIELD_TYPES_SKIP_FALLBACK and INLINE_EXCLUDED_FIELD_TYPES register the two keys. The drift rows follow in paramValueShape.ts and the plugin-detail inline pin, and known-schema-types.ts is regenerated.
    • Fixes #31062, Part of #30208.
  2. Premise measured:
    • Live before: both Setup forms show text boxes, and the route serves 28 topics and 5 channels, with push not configured.
    • Live after: both pickers list the route with * first. A typed invoices.paid is saved and marked, push is picked and saved, and the template's channel stays a text box.
    • 14 new tests. Ablation: deleting the notification-topic loader makes 1 test fail.
  3. Gates / check-runs:
    • 59 check runs on this head: 46 success, 13 skipped, 0 failed. They include Test UI 16/16, Type Check UI, Lint UI, both E2E UI legs and Check Changeset.
    • 38 derived gates exit 0, and --ran reconciles.
    • mergeable_state is clean.
  4. Accepted deviation: Clause-② goes from no to yes (widening), on the seat's ruling, in both the changeset and the body. The TIER_DEFAULT record owed by that declaration is PASS 6700168365 on this head.
  5. Out of scope / Skills-docs: two items, nothing filed.
    • The list is cached for the page session, so a topic first sent after the page loaded shows only after a reload. That's acceptable for a suggestion list.
    • The picker has no clear row, the same as the registry arm.
    • Metadata-protocol: none; Skills-docs: not owed.
  6. Landing:

This branch has not been deployed

No deployments
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 tests tooling Fix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Setup notification preferences: topic and channel pickers from the preference-options list (#30208 row 6, ui half)

1 participant