Skip to content

Add action handler examples, modal cross-reference validation, and action.mdx doc sync#837

Merged
hotlong merged 7 commits intomainfrom
copilot/add-handler-implementation-examples
Mar 1, 2026
Merged

Add action handler examples, modal cross-reference validation, and action.mdx doc sync#837
hotlong merged 7 commits intomainfrom
copilot/add-handler-implementation-examples

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Mar 1, 2026

Follow-up to #833: fills three gaps — dangling handler references in examples, stale docs, and missing modal target validation.

Modal cross-reference validation (stack.zod.ts)

  • validateCrossReferences() now checks action.type="modal" targets against the pages[] collection, mirroring the existing flowflows[] pattern
  • Skips when pageNames.size === 0 (plugin may provide them)
// Validate modal-type actions reference a defined page
if (action.type === 'modal' && action.target && pageNames.size > 0 && !pageNames.has(action.target)) {
  errors.push(
    `Action '${action.name}' references page '${action.target}' (via modal target) which is not defined in pages.`,
  );
}

Example handler implementations (18/18 actions covered)

Complete handler coverage across both example apps with full registration lifecycle:

  • app-todo (8/8 actions): task.handlers.ts — 6 script handlers (completeTask, startTask, cloneTask, massCompleteTasks, deleteCompletedTasks, exportTasksToCSV) + 2 modal handlers (deferTask, setReminder)
  • app-crm (10/10 actions): handlers/ directory with lead (convertLead, addToCampaign), opportunity (cloneRecord, massUpdateStage), case (escalateCase, closeCase), contact (markAsPrimaryContact, sendEmail), and global (exportToCSV, logCall) handlers
  • register-handlers.ts for both apps — demonstrates the full engine.registerAction() registration lifecycle with architecture diagrams showing metadata → handler → registration flow
  • objectstack.config.ts updated in both apps with onEnable lifecycle hook that wires handler registration at runtime
  • Action barrels (index.ts) export metadata definitions only (safe for Object.values()defineStack()); handlers are in separate paths

Documentation (action.mdx)

  • execute field marked deprecated with migration callout
  • Added target binding rules table (required vs optional per action type)
  • Added cross-reference validation section
  • Added platform comparison table (Salesforce, ServiceNow, Power Platform)

Tests

  • 3 new tests: detect undefined page, pass with defined page, skip when no pages defined
  • Full suite: 6557/6557 passing
Original prompt

This section details on the original issue you should resolve

<issue_title>补充Action示例Handler实现、.mdx文档同步和Modal交叉引用校验(833后续)</issue_title>
<issue_description># 续上 #833

尚待完善细节(合并为一新issue可子任务化执行):

1. 示例层:Handler注册/实现

  • 在examples/app-todo和app-crm的所有action handler相关actions,补充实际的handler函数实现与注册。避免悬空字符串引用,比如completeTask/startTask/cloneTask等应有示例方法体。

2. 文档层:参考文档与schema注释同步

  • 重新生成并发布含target新规范的.mdx文档;execute字段明确为deprecated,示例全改target并与代码一致。
  • 在文档/平台对标部分补充与Salesforce、ServiceNow、Power Platform 的action绑定机制对比。

3. 校验层扩展:Modal类型引用合规性

  • 参考action→flow的交叉引用校验,增加action.type="modal"时target(弹框名)的存在性检查;若modal声明表或规范未完善,优先给出后续设计方案/位置。

</issue_description>

Comments on the Issue (you are @copilot in this section)


💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.

@vercel
Copy link
Copy Markdown

vercel bot commented Mar 1, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
objectstack-play Ready Ready Preview, Comment Mar 1, 2026 8:29am
spec Ready Ready Preview, Comment Mar 1, 2026 8:29am

Request Review

Copilot AI and others added 4 commits March 1, 2026 07:54
…xistence check

Follows the same pattern as flow validation: when pages are defined,
modal-type action targets are validated against the page names collection.
When no pages are defined, validation is skipped (plugin may provide them).

Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
Provides actual function bodies for previously dangling target string
references: completeTask, startTask, cloneTask, massCompleteTasks,
deleteCompletedTasks, exportTasksToCSV (todo); convertLead, addToCampaign,
cloneRecord, massUpdateStage, escalateCase, closeCase, markAsPrimaryContact,
exportToCSV (crm).

Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
…s, platform comparison, and cross-reference validation section

Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
Copilot AI changed the title [WIP] Add handler implementation examples and synchronize documentation Add action handler examples, modal cross-reference validation, and action.mdx doc sync Mar 1, 2026
@hotlong
Copy link
Copy Markdown
Contributor

hotlong commented Mar 1, 2026

@copilot 仔细的评估现有的crm系统元数据,和objectstack runtime自己能够完成的完整的action注册和处理使用的所有的流程吗?仔细评估是否已经完成了任务中的所有要求。

Copilot AI and others added 2 commits March 1, 2026 08:18
…ifecycle, and config wiring

- Add deferTask/setReminder handlers (todo modal actions)
- Add sendEmail/logCall handlers (CRM modal actions)
- Create register-handlers.ts for both apps showing engine.registerAction() lifecycle
- Wire onEnable lifecycle in objectstack.config.ts for runtime registration
- Update action barrel JSDoc to reference handler paths

Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
…ation

- Replace `as string ?? value` with `? String(x) : default` pattern
- Replace `Function` type with `(...args: unknown[]) => unknown`
- Remove `as any` casts in config lifecycle hooks

Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
Copy link
Copy Markdown
Contributor Author

Copilot AI commented Mar 1, 2026

@copilot 仔细的评估现有的crm系统元数据,和objectstack runtime自己能够完成的完整的action注册和处理使用的所有的流程吗?仔细评估是否已经完成了任务中�...

经过仔细评估,发现了之前实现中的几个关键缺口,已在 b8659bc9ac5165 中修复:

缺口分析:

  1. 缺少 modal 类型 action 的 handler — 之前只覆盖了 script 类型,modal 类型的 deferTask、setReminder(todo)和 sendEmail、logCall(CRM)的服务端表单处理逻辑缺失。现已全部补齐。

  2. 缺少 handler 注册流程 — handler 函数文件存在但完全是"孤岛"代码,没有示例展示如何通过 engine.registerAction() 注册到运行时。现已新增 register-handlers.ts(含完整架构图),并在 objectstack.config.ts 中通过 onEnable 生命周期钩子演示完整的注册闭环。

  3. Action barrel 混合导出问题 — 最初错误地将 handler 函数混入 barrel,会导致 Object.values() 传入 defineStack() 时 Zod 校验失败。已修正为 barrel 只导出 Action 元数据定义。

当前覆盖率(18/18 actions):

应用 script modal flow 合计
Todo 6 ✅ 2 ✅ 8/8
CRM 3 ✅ 6 ✅ 1 ✅ 10/10

完整生命周期: *.actions.ts(声明式元数据)→ *.handlers.ts(函数实现)→ register-handlers.tsengine.registerAction() 注册)→ objectstack.config.ts#onEnable(运行时入口)

@hotlong hotlong marked this pull request as ready for review March 1, 2026 08:34
Copilot AI review requested due to automatic review settings March 1, 2026 08:34
@hotlong hotlong merged commit 4e8b2e3 into main Mar 1, 2026
4 checks passed
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds spec-level validation for modal action targets, fills in missing example action handler implementations/registration wiring, and synchronizes action reference docs with the current target/execute rules.

Changes:

  • Extend defineStack() cross-reference checks to validate action.type = 'modal' targets against pages[], with tests.
  • Add complete action handler implementations + onEnable registration wiring for both example apps.
  • Update content/docs/references/ui/action.mdx to document target binding rules and execute deprecation.

Reviewed changes

Copilot reviewed 16 out of 16 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
packages/spec/src/stack.zod.ts Adds modal→pages cross-reference validation alongside existing flow validation.
packages/spec/src/stack.test.ts Adds unit tests covering modal target validation pass/fail/skip conditions.
examples/app-todo/src/actions/task.handlers.ts Implements Todo action handlers (script + modal-related handlers).
examples/app-todo/src/actions/register-handlers.ts Demonstrates runtime handler registration via engine.registerAction().
examples/app-todo/src/actions/index.ts Clarifies action barrel export intent (metadata-only).
examples/app-todo/objectstack.config.ts Wires onEnable hook to register handlers at runtime.
examples/app-crm/src/actions/register-handlers.ts Registers CRM handlers across domains via engine.registerAction().
examples/app-crm/src/actions/index.ts Clarifies action barrel export intent (metadata-only).
examples/app-crm/src/actions/handlers/opportunity.handlers.ts Implements opportunity handlers.
examples/app-crm/src/actions/handlers/lead.handlers.ts Implements lead handlers.
examples/app-crm/src/actions/handlers/index.ts Barrel re-export for CRM handler implementations.
examples/app-crm/src/actions/handlers/global.handlers.ts Implements global handlers.
examples/app-crm/src/actions/handlers/contact.handlers.ts Implements contact handlers.
examples/app-crm/src/actions/handlers/case.handlers.ts Implements case handlers.
examples/app-crm/objectstack.config.ts Wires onEnable hook to register handlers at runtime.
content/docs/references/ui/action.mdx Documents target rules, execute deprecation, and cross-ref validation behavior.

Comment on lines +78 to +82
// ConvertLeadAction (type: flow) — also has server-side handler for
// programmatic conversion outside the screen flow.
engine.registerAction('lead', 'convertLead', convertLead);
// CreateCampaignAction (type: modal) — processes campaign assignment form
engine.registerAction('lead', 'addToCampaign', addToCampaign);
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ConvertLeadAction is declared as type: 'flow' with target: 'lead_conversion', but this registers a handler under 'convertLead'. If target is the resolvable identifier for flow actions (and defineStack now validates flow targets against flows[]), registering 'convertLead' here likely won't be invoked by the action. If you want a programmatic server-side conversion entrypoint in addition to the flow, consider modeling it as a separate script action (with its own target) rather than registering a handler for the flow action under a different name.

Copilot uses AI. Check for mistakes.
Comment on lines +94 to +95
engine.registerAction('task', 'deferTask', deferTask);
engine.registerAction('task', 'setReminder', setReminder);
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Modal handlers are registered under action names ('deferTask', 'setReminder') that don't match the modal actions' target values in task.actions.ts (e.g. defer_task_modal, set_reminder_modal). If the runtime resolves/executes handlers by Action.target (as described earlier in this file), these registrations will be unreachable. Align the registered actionName with whatever identifier the runtime uses for modal execution (likely the action's target or name), or avoid registering modal handlers here and instead invoke script actions from the modal page/form submit flow.

Suggested change
engine.registerAction('task', 'deferTask', deferTask);
engine.registerAction('task', 'setReminder', setReminder);
engine.registerAction('task', 'defer_task_modal', deferTask);
engine.registerAction('task', 'set_reminder_modal', setReminder);

Copilot uses AI. Check for mistakes.
* Exports action metadata definitions only. Used by `Object.values()` in
* objectstack.config.ts to auto-collect all action declarations for defineStack().
*
* **Handler functions** are exported from `./handlers/` — see register-handlers.ts
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment says handler functions are exported from ./handlers/, but this example app's handlers live in task.handlers.ts (and registration in register-handlers.ts). Updating this path avoids confusing readers following the barrel guidance.

Suggested change
* **Handler functions** are exported from `./handlers/` see register-handlers.ts
* **Handler functions** are exported from `./task.handlers` see register-handlers.ts

Copilot uses AI. Check for mistakes.
Comment on lines +82 to +106
engine.registerAction('lead', 'addToCampaign', addToCampaign);

// ─── Opportunity Domain ────────────────────────────────────────────
// CloneOpportunityAction (type: script)
engine.registerAction('opportunity', 'cloneRecord', cloneRecord);
// MassUpdateStageAction (type: modal) — processes stage selection form
engine.registerAction('opportunity', 'massUpdateStage', massUpdateStage);

// ─── Case Domain ───────────────────────────────────────────────────
// EscalateCaseAction (type: modal) — processes escalation reason form
engine.registerAction('case', 'escalateCase', escalateCase);
// CloseCaseAction (type: modal) — processes resolution form
engine.registerAction('case', 'closeCase', closeCase);

// ─── Contact Domain ────────────────────────────────────────────────
// MarkPrimaryContactAction (type: script)
engine.registerAction('contact', 'markAsPrimaryContact', markAsPrimaryContact);
// SendEmailAction (type: modal) — processes email composer form
engine.registerAction('contact', 'sendEmail', sendEmail);

// ─── Global (cross-domain) ─────────────────────────────────────────
// ExportToCsvAction (type: script) — wildcard '*' applies to all objects
engine.registerAction('*', 'exportToCSV', exportToCSV);
// LogCallAction (type: modal) — processes call log form
engine.registerAction('*', 'logCall', logCall);
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Several registerAction() calls use names like 'addToCampaign', 'massUpdateStage', 'escalateCase', etc., but the corresponding Action metadata for type: 'modal' uses target values like 'add_to_campaign_modal', 'mass_update_stage_modal', 'escalate_case_modal', etc. Unless the runtime executes actions by some identifier other than Action.target, these handlers won't be found when the action runs. Consider registering the handler under the modal action's actual execution key (e.g. action.target or action.name), or model modal submission as a separate script action that the modal/page triggers explicitly.

Suggested change
engine.registerAction('lead', 'addToCampaign', addToCampaign);
// ─── Opportunity Domain ────────────────────────────────────────────
// CloneOpportunityAction (type: script)
engine.registerAction('opportunity', 'cloneRecord', cloneRecord);
// MassUpdateStageAction (type: modal) — processes stage selection form
engine.registerAction('opportunity', 'massUpdateStage', massUpdateStage);
// ─── Case Domain ───────────────────────────────────────────────────
// EscalateCaseAction (type: modal) — processes escalation reason form
engine.registerAction('case', 'escalateCase', escalateCase);
// CloseCaseAction (type: modal) — processes resolution form
engine.registerAction('case', 'closeCase', closeCase);
// ─── Contact Domain ────────────────────────────────────────────────
// MarkPrimaryContactAction (type: script)
engine.registerAction('contact', 'markAsPrimaryContact', markAsPrimaryContact);
// SendEmailAction (type: modal) — processes email composer form
engine.registerAction('contact', 'sendEmail', sendEmail);
// ─── Global (cross-domain) ─────────────────────────────────────────
// ExportToCsvAction (type: script) — wildcard '*' applies to all objects
engine.registerAction('*', 'exportToCSV', exportToCSV);
// LogCallAction (type: modal) — processes call log form
engine.registerAction('*', 'logCall', logCall);
engine.registerAction('lead', 'add_to_campaign_modal', addToCampaign);
// ─── Opportunity Domain ────────────────────────────────────────────
// CloneOpportunityAction (type: script)
engine.registerAction('opportunity', 'cloneRecord', cloneRecord);
// MassUpdateStageAction (type: modal) — processes stage selection form
engine.registerAction('opportunity', 'mass_update_stage_modal', massUpdateStage);
// ─── Case Domain ───────────────────────────────────────────────────
// EscalateCaseAction (type: modal) — processes escalation reason form
engine.registerAction('case', 'escalate_case_modal', escalateCase);
// CloseCaseAction (type: modal) — processes resolution form
engine.registerAction('case', 'close_case_modal', closeCase);
// ─── Contact Domain ────────────────────────────────────────────────
// MarkPrimaryContactAction (type: script)
engine.registerAction('contact', 'markAsPrimaryContact', markAsPrimaryContact);
// SendEmailAction (type: modal) — processes email composer form
engine.registerAction('contact', 'send_email_modal', sendEmail);
// ─── Global (cross-domain) ─────────────────────────────────────────
// ExportToCsvAction (type: script) — wildcard '*' applies to all objects
engine.registerAction('*', 'exportToCSV', exportToCSV);
// LogCallAction (type: modal) — processes call log form
engine.registerAction('*', 'log_call_modal', logCall);

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

补充Action示例Handler实现、.mdx文档同步和Modal交叉引用校验(833后续)

3 participants