Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions .changeset/31062-notification-topic-channel-pickers.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
---
'@object-ui/fields': patch
'@object-ui/i18n': patch
---

feat(fields): Setup's notification topic and channel fields offer the served list and still take any typed value

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

A notification preference's `topic` and `channel`, and a notification template's `topic`, already declare `widget: 'notification-topic'` / `'notification-channel'`. The record form now resolves both: `@object-ui/fields` registers `field:notification-topic` and `field:notification-channel`, a fillable arm of the registry-name picker (`RegistryRefField`).

- **The list.** Read from `GET /api/v1/messaging/preference-options` through the host's authenticated fetch (`SchemaRendererContext.apiFetch`), once per page for every field that draws from it, and listed in the order the server sends: `*` first, shown as "All topics (*)" / "All channels (*)".
- **A typed value.** Typing in the picker's search box offers a row that commits the text exactly as typed. A stored value the list does not hold stays on screen as its own option, marked "not in the list". Nothing is refused or rewritten; the server stores what it is given, as before.
- **A channel that is not configured** is listed, labelled "(not configured)", and can be picked.
- **No list.** When the request fails, or answers an empty list, the field is the plain text box it was before, with no notice.
- **Strings.** Five new `fields.preferenceOption.*` keys in every built-in language pack (`zh-Hant` regenerated from `zh-Hans`).

The template's `channel` field declares no widget and stays a text box.
2 changes: 2 additions & 0 deletions ui/packages/app-shell/src/utils/paramValueShape.ts
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,8 @@ export const PARAM_VALUE_SHAPES: Readonly<Record<string, ParamValueShapeSpec>> =
'recipient-picker': { base: 'string', cardinality: 'scalar', widgetHintOnly: true, note: 'Recipient id string. Widget-hint only — not a declarable param type.' },
'position-ref': { base: 'string', cardinality: 'scalar', widgetHintOnly: true, note: 'Position name string. Widget-hint only — not a declarable param type.' },
'permission-ref': { base: 'string', cardinality: 'scalar', widgetHintOnly: true, note: 'Permission-set name string. Widget-hint only — not a declarable param type.' },
'notification-topic': { base: 'string', cardinality: 'scalar', widgetHintOnly: true, note: 'Notification topic string. Widget-hint only — not a declarable param type.' },
'notification-channel': { base: 'string', cardinality: 'scalar', widgetHintOnly: true, note: 'Notification channel string. Widget-hint only — not a declarable param type.' },
});

/** Concrete `base[]` tag for a base shape. */
Expand Down
2 changes: 2 additions & 0 deletions ui/packages/cli/src/utils/known-schema-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,8 @@ export const KNOWN_SCHEMA_TYPES: readonly string[] = [
'field:markdown',
'field:master_detail',
'field:multiselect',
'field:notification-channel',
'field:notification-topic',
'field:number',
'field:object',
'field:object-ref',
Expand Down
1 change: 1 addition & 0 deletions ui/packages/fields/src/FieldEditWidget.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,7 @@ export const INLINE_EXCLUDED_FIELD_TYPES = new Set<string>([
// sibling fields / a loaded object catalog), not inline in a grid cell.
'object-ref', 'filter-condition', 'recipient-picker',
'position-ref', 'permission-ref',
'notification-topic', 'notification-channel',
]);

/** Field types whose value is chosen in one discrete gesture (no free typing). */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,12 @@ import { QRCodeField } from '../widgets/QRCodeField';
import { ObjectRefField } from '../widgets/ObjectRefField';
import { FilterConditionField } from '../widgets/FilterConditionField';
import { RecipientPickerField } from '../widgets/RecipientPickerField';
import { PositionRefField, PermissionRefField } from '../widgets/RegistryRefField';
import {
PositionRefField,
PermissionRefField,
NotificationTopicField,
NotificationChannelField,
} from '../widgets/RegistryRefField';

/** Same widget map as the #3291 sweep, so the parity check covers the registry. */
const WIDGETS: Record<string, ComponentType<any>> = {
Expand Down Expand Up @@ -188,6 +193,8 @@ const WIDGETS: Record<string, ComponentType<any>> = {
'recipient-picker': RecipientPickerField,
'position-ref': PositionRefField,
'permission-ref': PermissionRefField,
'notification-topic': NotificationTopicField,
'notification-channel': NotificationChannelField,
};

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,12 @@ import { QRCodeField } from '../widgets/QRCodeField';
import { ObjectRefField } from '../widgets/ObjectRefField';
import { FilterConditionField } from '../widgets/FilterConditionField';
import { RecipientPickerField } from '../widgets/RecipientPickerField';
import { PositionRefField, PermissionRefField } from '../widgets/RegistryRefField';
import {
PositionRefField,
PermissionRefField,
NotificationTopicField,
NotificationChannelField,
} from '../widgets/RegistryRefField';

/* ════════════════════════════════════════════════════════════════════════════
* Every registered field widget, both hosts
Expand Down Expand Up @@ -207,6 +212,8 @@ const WIDGETS: Record<string, ComponentType<any>> = {
'recipient-picker': RecipientPickerField,
'position-ref': PositionRefField,
'permission-ref': PermissionRefField,
'notification-topic': NotificationTopicField,
'notification-channel': NotificationChannelField,
};

/** Option widgets render an "unfillable" placeholder unless offered a list. */
Expand Down
12 changes: 12 additions & 0 deletions ui/packages/fields/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4890,6 +4890,14 @@ const fieldWidgetMap = {
// permission-ref → a permission set (sys_user_permission_set.permission_set)
'position-ref': () => import('./widgets/RegistryRefField.js').then(m => ({ default: m.PositionRefField })),
'permission-ref': () => import('./widgets/RegistryRefField.js').then(m => ({ default: m.PermissionRefField })),
// The fillable arm of the same widget (#30208 row 6, ruling E on #30527): a
// notification preference's `topic` / `channel` and a template's `topic`,
// offered from `GET /api/v1/messaging/preference-options` and still taking
// any typed value, which the server stores as written.
// notification-topic → a notification topic, `*` for all
// notification-channel → a notification channel, `*` for all
'notification-topic': () => import('./widgets/RegistryRefField.js').then(m => ({ default: m.NotificationTopicField })),
'notification-channel': () => import('./widgets/RegistryRefField.js').then(m => ({ default: m.NotificationChannelField })),
} satisfies Record<string, FieldWidgetLoader>;

/** The registered field widget keys, as a literal union (objectui#4857). */
Expand Down Expand Up @@ -5072,6 +5080,8 @@ const FIELD_TYPES_SKIP_FALLBACK = new Set([
'recipient-picker',
'position-ref',
'permission-ref',
'notification-topic',
'notification-channel',
// ── Retired bare node keys (objectui#10859 batch 8) ───────────────────────
// Each was registered bare only as this loop's fallback: no other package
// owns the name, no form path reads it, and no document authors it as a
Expand Down Expand Up @@ -5220,6 +5230,8 @@ export const FIELD_WIDGET_LABELLING: Record<
'recipient-picker': 'control',
'position-ref': 'control',
'permission-ref': 'control',
'notification-topic': 'control',
'notification-channel': 'control',
};

export function registerField(fieldType: string): void {
Expand Down
Loading
Loading