Skip to content

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

Merged
objectstack-fleet[bot] merged 9 commits into
mainfrom
claude/project-thread-sotebb
Nov 7, 2026
Merged

objectstack-fleet[bot] merged 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:

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review November 7, 2026 01:34
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Nov 7, 2026
Merged via the queue into main with commit 3f61e4f Nov 7, 2026
64 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/project-thread-sotebb branch November 7, 2026 01:51
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