Skip to content

Commit e983465

Browse files
os-zhuangclaude
andauthored
feat(showcase): demonstrate action-param widgets + related-list pagination (#3393)
The v16 sweep (#3358 §4) couldn't exercise three shipped features because the showcase never demonstrated them out of the box — a gap `coverage.ts` doesn't catch (it tracks metadata KINDS, not sub-features like action-param widget types or list pagination). Fill them: - **Action-param widget gallery** (`ActionParamGalleryAction` on Field Zoo): one inline param of every non-trivial type so the ADR-0059 `ActionParamDialog` renders each real field widget — richtext editor, color picker, date picker, select, number, the AutoNumber widget for an `autonumber` param, and the ⚠️ `image`/`file` uploads (multiple/accept/maxSize + the upload guard). No showcase action declared `params` before, so these dialogs were undemonstrated. - **Related-list pagination**: 24 bulk prospects under one account (Northwind) so its Account → Contacts related list exceeds a page and demonstrates server-side `$top`/`$skip` windowing (objectui#2711) on a fresh boot. - Record the sub-feature demonstration in the `action` coverage entry. Verified in the running app: the ParamDialog renders richtext/select/date/color/ autonumber widgets + image/file upload zones (not text boxes); the Northwind Contacts related list holds 26 rows but fetches `?top=5` (windowed, not load-all). `os validate` (9 Actions) + `tsc --noEmit` pass. Follow-up to #3364, from the #3358 sweep §4. Co-authored-by: Jack Zhuang <277994282+os-zhuang@users.noreply.github.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 212b66a commit e983465

3 files changed

Lines changed: 74 additions & 1 deletion

File tree

examples/app-showcase/src/coverage.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,12 @@ export const KIND_COVERAGE: Record<MetadataType, KindCoverage> = {
9191
'revenue-pulse also demonstrates dashboard-level filters (framework#2501): dateRange + globalFilters driving two objects via per-widget filterBindings.',
9292
},
9393
app: { status: 'demonstrated', files: ['src/ui/apps/index.ts'] },
94-
action: { status: 'demonstrated', files: ['src/ui/actions/index.ts'] },
94+
action: {
95+
status: 'demonstrated',
96+
files: ['src/ui/actions/index.ts'],
97+
notes:
98+
'Every ActionType (script/url/flow/modal/api/form). `ActionParamGalleryAction` additionally exercises the ADR-0059 param-dialog widgets: one inline param per non-trivial type (richtext/color/date/select/number/autonumber) plus image/file uploads with multiple/accept/maxSize and the upload guard.',
99+
},
95100
report: { status: 'demonstrated', files: ['src/ui/reports/index.ts'] },
96101
dataset: { status: 'demonstrated', files: ['src/ui/datasets/index.ts'] },
97102

examples/app-showcase/src/data/seed/index.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,18 @@ const contacts = defineSeed(Contact, {
8585
{ name: '张伟', email: 'zhangwei@huaning.example', phone: '+86 21 5550 1111', company: '华宁科技', title: 'Engineering Manager', account: '华宁科技', stage: 'qualified' },
8686
{ name: '王芳', email: 'wangfang@huaning.example', phone: '+86 21 5550 2222', company: '华宁科技', title: 'Procurement Director', account: '华宁科技', stage: 'working' },
8787
{ name: '李雷', email: 'lilei@huaning.example', phone: '+86 21 5550 3333', company: '华宁科技', title: 'IT Specialist', account: '华宁科技', stage: 'new' },
88+
// Bulk prospects under ONE account (Northwind) so its Account → Contacts
89+
// related list exceeds a page and demonstrates server-side related-list
90+
// pagination ($top/$skip, objectui#2711) on a fresh boot — every other
91+
// account keeps a handful for a clean people picker.
92+
...Array.from({ length: 24 }, (_, i) => ({
93+
name: `Prospect ${String(i + 1).padStart(2, '0')}`,
94+
email: `prospect${i + 1}@northwind.example`,
95+
company: 'Northwind',
96+
title: 'Sales Prospect',
97+
account: 'Northwind',
98+
stage: 'new',
99+
})),
88100
],
89101
});
90102

examples/app-showcase/src/ui/actions/index.ts

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { defineAction } from '@objectstack/spec/ui';
44

55
const task = 'showcase_task';
66
const invoice = 'showcase_invoice';
7+
const fieldZoo = 'showcase_field_zoo';
78

89
/**
910
* Action matrix — covers every `ActionType` (script / url / flow / modal /
@@ -172,6 +173,60 @@ export const SubmitForSignoffAction = defineAction({
172173
refreshAfter: true,
173174
});
174175

176+
/**
177+
* script — the **action-param widget gallery** (ADR-0059). One inline param of
178+
* every non-trivial widget type, so the `ActionParamDialog` renders each real
179+
* field widget (not a text box): richtext editor, color picker, date picker,
180+
* select, number, the AutoNumber widget for an `autonumber` param, and — the
181+
* ⚠️ ones — `image`/`file` uploads through the ambient UploadProvider with
182+
* `multiple` / `accept` / `maxSize` honored, and the **upload guard** (Confirm
183+
* stays disabled while a file is still uploading). Lives on Field Zoo, the
184+
* "one specimen of everything" object, next to its every-field-type record.
185+
*
186+
* The body just echoes the received keys — the point is the dialog, not a side
187+
* effect — so it needs no capabilities.
188+
*/
189+
export const ActionParamGalleryAction = defineAction({
190+
name: 'showcase_action_param_gallery',
191+
label: 'Action Param Gallery',
192+
icon: 'sparkles',
193+
objectName: fieldZoo,
194+
type: 'script',
195+
params: [
196+
{ name: 'p_text', type: 'text', label: 'Title', required: true, placeholder: 'A short title' },
197+
{ name: 'p_richtext', type: 'richtext', label: 'Rich note', helpText: 'Renders the rich-text editor, not a plain textarea.' },
198+
{
199+
name: 'p_priority', type: 'select', label: 'Priority', defaultValue: 'normal',
200+
options: [
201+
{ label: 'Low', value: 'low' },
202+
{ label: 'Normal', value: 'normal' },
203+
{ label: 'High', value: 'high' },
204+
],
205+
},
206+
{ name: 'p_date', type: 'date', label: 'Effective date' },
207+
{ name: 'p_color', type: 'color', label: 'Accent color', defaultValue: '#7C3AED' },
208+
// Spec `autonumber` param → the AutoNumber widget (read-only, auto-assigned).
209+
{ name: 'p_reference', type: 'autonumber', label: 'Reference #' },
210+
// ⚠️ image/file uploads: real widget + upload guard + multiple/accept/maxSize.
211+
{ name: 'p_cover', type: 'image', label: 'Cover image', accept: ['image/*'], maxSize: 5 * 1024 * 1024 },
212+
{
213+
name: 'p_attachments', type: 'file', label: 'Attachments', multiple: true,
214+
accept: ['application/pdf', 'image/*'], maxSize: 10 * 1024 * 1024,
215+
helpText: 'Confirm stays disabled while a file is still uploading (ADR-0059 upload guard).',
216+
},
217+
],
218+
body: {
219+
language: 'js',
220+
// No side effect — the value of this action is the dialog's widgets. Echo
221+
// the keys the dialog collected so the result dialog shows something.
222+
source: 'return { ok: true, received: Object.keys(input || {}) };',
223+
capabilities: [],
224+
},
225+
successMessage: 'Params received — every widget type rendered through the shared field-widget map.',
226+
locations: ['record_header', 'list_item'],
227+
refreshAfter: false,
228+
});
229+
175230
export const allActions = [
176231
MarkDoneAction,
177232
OpenDocsAction,
@@ -181,4 +236,5 @@ export const allActions = [
181236
LogTimeAction,
182237
NewTaskAction,
183238
SubmitForSignoffAction,
239+
ActionParamGalleryAction,
184240
];

0 commit comments

Comments
 (0)