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
12 changes: 12 additions & 0 deletions .changeset/action-undoable.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
"@objectstack/spec": minor
---

feat(action): `undoable` flag on the UI Action schema

Single-record update actions can declare `undoable: true`. The runtime captures
the record's prior field values and offers an "Undo" affordance on the success
toast (backed by the client UndoManager). Pairs with the objectui runtime that
honours it. Also documents that conditional `visible` / `disabled` CEL
predicates are evaluated by the action renderers (used here to hide an action
when it no longer applies, e.g. Convert Lead on an already-converted lead).
5 changes: 5 additions & 0 deletions examples/app-crm/src/actions/convert-lead.action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,9 @@ export const ConvertLeadAction: UI.Action = {
target: 'crm_convert_lead_wizard',
locations: ['list_item', 'record_header', 'record_more'],
recordIdParam: 'recordId',
// Conditional visibility (CEL): hide the action once the lead is already
// converted — so the button disappears rather than the user clicking it and
// hitting the flow's "already converted" guard screen. The flow keeps that
// guard as a server-side backstop.
visible: 'record.status != "converted"',
};
1 change: 1 addition & 0 deletions examples/app-crm/src/actions/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.

export { ConvertLeadAction } from './convert-lead.action.js';
export { ParkLeadAction } from './park-lead.action.js';
31 changes: 31 additions & 0 deletions examples/app-crm/src/actions/park-lead.action.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.

import type { UI } from '@objectstack/spec';

/**
* Row-level action on crm_lead — reassign the lead's owner.
*
* Demonstrates the `undoable` action affordance: it's a single-record field
* update (`assigned_to`), so the runtime captures the prior owner and the
* success toast offers an "Undo" that restores it (backed by the client
* UndoManager — Ctrl+Z works too). Prompts for the new owner via one param
* (pre-filled with "Triage Queue").
*/
export const ParkLeadAction: UI.Action = {
name: 'crm_park_lead',
label: 'Reassign Lead',
description: 'Reassign this lead to a new owner (undoable).',
icon: 'UserPlus',
objectName: 'crm_lead',
// `type: 'api'` with a non-URL target routes to the console runtime's generic
// `dataSource.update` path (the row id comes from the list_item row record).
type: 'api',
target: 'crm_lead',
// List-row only: the apiHandler needs the row record to capture prior values.
locations: ['list_item'],
params: [
{ field: 'assigned_to', label: 'Reassign to', defaultValue: 'Triage Queue', required: true },
],
undoable: true,
successMessage: 'Lead reassigned.',
};
4 changes: 4 additions & 0 deletions packages/spec/src/ui/action.zod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,10 @@ export const ActionSchema = lazySchema(() => z.object({
// set a friendly failure toast instead of surfacing the raw error string.
errorMessage: I18nLabelSchema.optional().describe('Error message to show when the action fails (overrides the raw error).'),
refreshAfter: z.boolean().default(false).describe('Refresh view after execution'),
// Single-record update actions only. When true, the runtime captures the
// record's prior field values and offers an "Undo" affordance on the success
// toast (backed by the client UndoManager) to restore them.
undoable: z.boolean().optional().describe('Offer an Undo affordance after this single-record update action succeeds.'),

/**
* Result Dialog — describe how to render the API response on success.
Expand Down
Loading