From 13d48054776063fd0aaed9a0999478f2dfe4eb60 Mon Sep 17 00:00:00 2001 From: Jack Zhuang <277994282+os-zhuang@users.noreply.github.com> Date: Mon, 22 Jun 2026 11:48:07 +0800 Subject: [PATCH] fix(showcase): register Mark Done action handler via inline body MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The `showcase_mark_done` action was declared `type: 'script'` but carried neither a `body` nor a `target` handler, so AppPlugin never registered an engine handler for it. Clicking "Mark Done" therefore hit `POST /actions/showcase_task/showcase_mark_done`, fell through to the `'*'` wildcard lookup, and returned `Action 'showcase_mark_done' on object '*' not found` — surfaced in the UI as an error. Add an L2 sandboxed `body` that flips the dedicated `done` flag and `progress` to 100. It deliberately avoids writing `status: 'done'`: the `task_status_flow` state-machine only permits `in_review -> done`, so a direct status write would be rejected from Backlog/To Do/In Progress rows. Using the `done` boolean lets the action succeed from any state. Verified in-browser (green "Task marked done." toast, no errors) and via API regression on an In Progress task; `pnpm verify` green (typecheck + 22 tests). Co-Authored-By: Claude Opus 4.8 --- examples/app-showcase/src/actions/index.ts | 26 +++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/examples/app-showcase/src/actions/index.ts b/examples/app-showcase/src/actions/index.ts index efc2cd4e3e..2f760a55af 100644 --- a/examples/app-showcase/src/actions/index.ts +++ b/examples/app-showcase/src/actions/index.ts @@ -10,13 +10,37 @@ const task = 'showcase_task'; * record header/more, related list, global nav). */ -/** script — inline handler, shown on each row and the record header. */ +/** + * script — inline sandboxed handler, shown on each row and the record header. + * + * The `body` (L2 sandboxed JS) is what makes this action *executable*: AppPlugin + * walks the bundle's actions on bind and only registers an engine handler for + * those carrying a `body` (or `target` -> bundle function). Without it the + * runtime has nothing to invoke and `POST /actions/showcase_task/showcase_mark_done` + * fails with "Action ... not found". + * + * It flips the dedicated `done` flag and `progress` rather than the `status` + * select on purpose: `status` is governed by the `task_status_flow` + * state-machine (only `in_review -> done` is a legal direct jump), so writing + * `status: 'done'` from a Backlog/To Do/In Progress row would be rejected. The + * `done` boolean is the completion flag that works from any state. + */ export const MarkDoneAction = defineAction({ name: 'showcase_mark_done', label: 'Mark Done', icon: 'check', objectName: task, type: 'script', + body: { + language: 'js', + source: + "var id = ctx.recordId || (ctx.record && ctx.record.id) || input.recordId;" + + "if (!id) throw new Error('No record to mark done');" + + "await ctx.api.object('showcase_task').update({ id: id, done: true, progress: 100 });" + + "return { ok: true, id: id };", + capabilities: ['api.write'], + }, + successMessage: 'Task marked done.', // `record_section` so the Task Detail page's `record:quick_actions` bar // (which names this action) resolves it — the engine location-filters even // explicitly-named actions, mirroring the platform's own sys-user pages.