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
21 changes: 21 additions & 0 deletions .changeset/modal-actions-are-client-only.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
---
'@objectstack/example-todo': patch
---

**[#3959] `app-todo`'s `defer_task` / `set_reminder` are `type: 'script'`, not `type: 'modal'`.**

Both declared `type: 'modal'` with a `target` naming a modal page that does not
exist (`defer_task_modal`, `set_reminder_modal`), while their handlers sat
registered under `deferTask` / `setReminder` — keys no declaration could
address. A `modal` action has no server dispatch (`headlessActionTypeError`
rejects it over REST), so neither handler had ever executed: the example
shipped business logic that could not run, and ADR-0110 D5's boot inventory
flagged both on its first pass.

Both already declared the `params` their handlers read, so they were always
"collect input, then run server-side" actions — which is `type: 'script'` with
`params`. The runner collects the same dialog and the handler now actually runs.

The action-type table in `content/docs/ui/actions.mdx` said `modal` meant
"collect input, then submit to a handler", contradicting the same page's own
REST table (`modal` → 400, nothing for the server to run). Corrected.
2 changes: 1 addition & 1 deletion content/docs/ui/actions.mdx
Original file line numberDiff line numberDiff line change
Expand Up@@ -19,7 +19,7 @@ The types you'll actually use:
| `script` *(default)* | Run server-side logic | Inline `body` **or** a handler registered via `target` |
| `flow` | Launch a flow (e.g. a screen-flow wizard) | `target` names the flow |
| `url` | Navigate / open a link | `target` is the URL (`${ctx.record.id}` interpolation supported) |
| `modal` | Open a modal page to collect input, then submit to a handler | `target` names the modal page |
| `modal` | Open a modal page — client-side only, no server dispatch | `target` names the modal page (to collect input *and* run logic, use `script` + `params`) |
| `api` | Call an HTTP endpoint directly | `target` is the endpoint; `method` / `bodyShape` / `bodyExtra` shape the request |
| `form` | Open a form view, prefilled with the current record | `target` names the FormView; routed to `/forms/:target?recordId=…` |

Expand Down
23 changes: 17 additions & 6 deletions examples/app-todo/src/actions/task.actions.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -36,14 +36,25 @@ export const StartTaskAction = defineAction({
},
});

/** Defer Task */
/**
* Defer Task — collect input, then run server-side.
*
* [ADR-0110] This is `type: 'script'` with `params`, NOT `type: 'modal'`. A
* `modal` action's `target` names a page to OPEN and has no server dispatch
* (`headlessActionTypeError` rejects it), so the old
* `type: 'modal', target: 'defer_task_modal'` pointed at a page that does not
* exist while `deferTask` sat registered under a key no declaration could
* address — business logic that had never once executed (#3959). `script` +
* `params` is the supported shape: the runner collects the same dialog, then
* the handler runs with those values.
*/
export const DeferTaskAction = defineAction({
name: 'defer_task',
label: 'Defer Task',
objectName: 'todo_task',
icon: 'clock',
type: 'modal',
target: 'defer_task_modal',
type: 'script',
target: 'deferTask',
locations: ['record_header'],
params: [
{
Expand All@@ -63,14 +74,14 @@ export const DeferTaskAction = defineAction({
refreshAfter: true,
});

/** Set Reminder */
/** Set Reminder — collect input, then run server-side (see DeferTaskAction). */
export const SetReminderAction = defineAction({
name: 'set_reminder',
label: 'Set Reminder',
objectName: 'todo_task',
icon: 'bell',
type: 'modal',
target: 'set_reminder_modal',
type: 'script',
target: 'setReminder',
locations: ['record_header', 'list_item'],
params: [
{
Expand Down
4 changes: 2 additions & 2 deletions examples/app-todo/src/actions/task.handlers.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -82,7 +82,7 @@ export async function deleteCompletedTasks(ctx: ActionContext): Promise<void> {
}
}

/** Defer a task by updating its due date (modal form submission handler) */
/** Defer a task by updating its due date (params collected by the action dialog) */
export async function deferTask(ctx: ActionContext): Promise<void> {
const { record, engine, params } = ctx;
await engine.update('todo_task', record.id as string, {
Expand All@@ -92,7 +92,7 @@ export async function deferTask(ctx: ActionContext): Promise<void> {
});
}

/** Set a reminder on a task (modal form submission handler) */
/** Set a reminder on a task (params collected by the action dialog) */
export async function setReminder(ctx: ActionContext): Promise<void> {
const { record, engine, params } = ctx;
await engine.update('todo_task', record.id as string, {
Expand Down
Loading