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
2 changes: 1 addition & 1 deletion ROADMAP.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -81,7 +81,7 @@ Full adoption of Cloud namespace, contracts/integration/security/studio modules,

### CRM Example Metadata Enrichment ✅

Enriched all 8 CRM object definitions (`account`, `contact`, `opportunity`, `product`, `order`, `user`, `project_task`, `event`) to exercise the full `@objectstack/spec` feature set. Added `description` to all objects; field enrichments (`required`, `searchable`, `unique`, `defaultValue`, `helpText`, `placeholder`, `readonly`); diverse field types (`richtext`, `phone`, `avatar`, `color`, `multi-select`); 30+ new fields (tags, linkedin, expected_revenue, shipping_address, etc.); 2+ list views per object with sort/filter; select options with colors across all objects; updated seed data leveraging new fields.
Enriched all 8 CRM object definitions (`account`, `contact`, `opportunity`, `product`, `order`, `user`, `project_task`, `event`) to exercise the full `@objectstack/spec` feature set. Added `description` to all objects; field enrichments (`required`, `searchable`, `unique`, `defaultValue`, `readonly`); diverse field types (`richtext`, `phone`, `avatar`, `color`, `multi-select`); 30+ new fields (tags, linkedin, expected_revenue, shipping_address, etc.); 2+ list views per object with sort/filter; select options with colors across all objects; updated seed data leveraging new fields. Added 21 object-level actions across all 8 objects (Send Email, Change Status, Assign Owner, Mark as Won/Lost, Generate Invoice, Reset Password, etc.) with `params`, `confirmText`, `variant`, `locations`, and `refreshAfter`. Added sections-based `form_layout` for all 8 objects organizing fields into logical groups (e.g., Basic Info, Financials, Address, Timeline) with `columns`, `collapsible`, and field grouping.

### Architecture

Expand Down
244 changes: 34 additions & 210 deletions examples/crm/objectstack.config.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -8,6 +8,22 @@ import { OrderObject } from './src/objects/order.object';
import { UserObject } from './src/objects/user.object';
import { ProjectObject } from './src/objects/project.object';
import { EventObject } from './src/objects/event.object';
import { AccountView } from './src/views/account.view';
import { ContactView } from './src/views/contact.view';
import { OpportunityView } from './src/views/opportunity.view';
import { ProductView } from './src/views/product.view';
import { OrderView } from './src/views/order.view';
import { UserView } from './src/views/user.view';
import { EventView } from './src/views/event.view';
import { ProjectView } from './src/views/project.view';
import { AccountActions } from './src/actions/account.actions';
import { ContactActions } from './src/actions/contact.actions';
import { OpportunityActions } from './src/actions/opportunity.actions';
import { ProductActions } from './src/actions/product.actions';
import { OrderActions } from './src/actions/order.actions';
import { UserActions } from './src/actions/user.actions';
import { ProjectActions } from './src/actions/project.actions';
import { EventActions } from './src/actions/event.actions';

export default defineStack({
objects: [
Expand All@@ -21,218 +37,26 @@ export default defineStack({
EventObject
],
views: [
// --- Account Views ---
{
listViews: {
all_accounts: {
name: 'all_accounts',
label: 'All Accounts',
type: 'grid',
data: { provider: 'object', object: 'account' },
columns: ['name', 'industry', 'type', 'annual_revenue', 'rating', 'owner'],
sort: [{ field: 'name', order: 'asc' }],
},
active_accounts: {
name: 'active_accounts',
label: 'Active Customers',
type: 'grid',
data: { provider: 'object', object: 'account' },
columns: ['name', 'industry', 'annual_revenue', 'phone', 'owner'],
filter: ['type', '=', 'Customer'],
sort: [{ field: 'annual_revenue', order: 'desc' }],
},
},
},
// --- Contact Views ---
{
listViews: {
all_contacts: {
name: 'all_contacts',
label: 'All Contacts',
type: 'grid',
data: { provider: 'object', object: 'contact' },
columns: ['name', 'email', 'phone', 'title', 'account', 'status', 'priority'],
sort: [{ field: 'name', order: 'asc' }],
},
active_contacts: {
name: 'active_contacts',
label: 'Active Contacts',
type: 'grid',
data: { provider: 'object', object: 'contact' },
columns: ['name', 'email', 'title', 'account', 'status'],
filter: ['is_active', '=', true],
},
},
},
// --- Opportunity Views ---
{
listViews: {
all: {
name: 'all',
label: 'All Opportunities',
type: 'grid',
data: { provider: 'object', object: 'opportunity' },
columns: ['name', 'amount', 'stage', 'close_date', 'probability', 'forecast_category'],
sort: [{ field: 'close_date', order: 'asc' }],
},
pipeline: {
name: 'pipeline',
label: 'Pipeline',
type: 'kanban',
data: { provider: 'object', object: 'opportunity' },
columns: ['name', 'amount', 'close_date', 'probability'],
kanban: {
groupByField: 'stage',
columns: ['name', 'amount', 'close_date'],
},
},
closing_this_month: {
name: 'closing_this_month',
label: 'Closing This Month',
type: 'grid',
data: { provider: 'object', object: 'opportunity' },
columns: ['name', 'amount', 'stage', 'close_date', 'probability'],
sort: [{ field: 'close_date', order: 'asc' }],
},
},
},
// --- Product Views ---
{
listViews: {
all_products: {
name: 'all_products',
label: 'All Products',
type: 'grid',
data: { provider: 'object', object: 'product' },
columns: ['name', 'sku', 'category', 'price', 'stock', 'is_active'],
sort: [{ field: 'name', order: 'asc' }],
},
active_products: {
name: 'active_products',
label: 'Active Products',
type: 'grid',
data: { provider: 'object', object: 'product' },
columns: ['name', 'sku', 'category', 'price', 'stock', 'tags'],
filter: ['is_active', '=', true],
},
},
},
// --- Order Views ---
{
listViews: {
all_orders: {
name: 'all_orders',
label: 'All Orders',
type: 'grid',
data: { provider: 'object', object: 'order' },
columns: ['name', 'customer', 'amount', 'status', 'order_date', 'payment_method'],
sort: [{ field: 'order_date', order: 'desc' }],
},
pending_orders: {
name: 'pending_orders',
label: 'Pending Orders',
type: 'grid',
data: { provider: 'object', object: 'order' },
columns: ['name', 'customer', 'amount', 'order_date'],
filter: ['status', '=', 'pending'],
},
},
},
// --- User Views ---
{
listViews: {
all_users: {
name: 'all_users',
label: 'All Users',
type: 'grid',
data: { provider: 'object', object: 'user' },
columns: ['name', 'email', 'role', 'department', 'active'],
sort: [{ field: 'name', order: 'asc' }],
},
active_users: {
name: 'active_users',
label: 'Active Users',
type: 'grid',
data: { provider: 'object', object: 'user' },
columns: ['name', 'email', 'role', 'title'],
filter: ['active', '=', true],
},
},
},
// --- Event Views ---
{
listViews: {
all_events: {
name: 'all_events',
label: 'All Events',
type: 'grid',
data: { provider: 'object', object: 'event' },
columns: ['subject', 'start', 'end', 'location', 'type', 'status'],
sort: [{ field: 'start', order: 'asc' }],
},
calendar: {
name: 'calendar',
label: 'Calendar',
type: 'calendar',
data: { provider: 'object', object: 'event' },
columns: ['subject', 'start', 'end', 'type'],
calendar: {
startDateField: 'start',
endDateField: 'end',
titleField: 'subject',
},
},
upcoming_meetings: {
name: 'upcoming_meetings',
label: 'Upcoming Meetings',
type: 'grid',
data: { provider: 'object', object: 'event' },
columns: ['subject', 'start', 'location', 'organizer'],
filter: ['type', '=', 'meeting'],
sort: [{ field: 'start', order: 'asc' }],
},
},
},
// --- Project Task Views ---
{
listViews: {
all_tasks: {
name: 'all_tasks',
label: 'All Tasks',
type: 'grid',
data: { provider: 'object', object: 'project_task' },
columns: ['name', 'status', 'priority', 'start_date', 'end_date', 'progress', 'assignee'],
sort: [{ field: 'start_date', order: 'asc' }],
},
board: {
name: 'board',
label: 'Board',
type: 'kanban',
data: { provider: 'object', object: 'project_task' },
columns: ['name', 'priority', 'start_date', 'end_date'],
kanban: {
groupByField: 'status',
columns: ['name', 'priority', 'start_date', 'end_date'],
},
},
gantt: {
name: 'gantt',
label: 'Gantt',
type: 'gantt',
data: { provider: 'object', object: 'project_task' },
columns: ['name', 'start_date', 'end_date', 'progress', 'status'],
},
timeline: {
name: 'timeline',
label: 'Timeline',
type: 'timeline',
data: { provider: 'object', object: 'project_task' },
columns: ['name', 'start_date', 'status'],
},
},
},
AccountView,
ContactView,
OpportunityView,
ProductView,
OrderView,
UserView,
EventView,
ProjectView,
],
reports: [],
actions: [
...AccountActions,
...ContactActions,
...OpportunityActions,
...ProductActions,
...OrderActions,
...UserActions,
...ProjectActions,
...EventActions,
],
pages: [
{
name: 'crm_help',
Expand Down
37 changes: 37 additions & 0 deletions examples/crm/src/actions/account.actions.ts
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
export const AccountActions = [
{
name: 'account_send_email',
label: 'Send Email',
icon: 'mail',
type: 'api' as const,
locations: ['record_header' as const, 'list_item' as const],
params: [
{ name: 'to', label: 'To Email', type: 'email' as const, required: true },
{ name: 'subject', label: 'Subject', type: 'text' as const, required: true },
Comment on lines +3 to +10

CopilotAIFeb 20, 2026

Copy link

Choose a reason for hiding this comment

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

These actions are declared as type: 'api' but don't provide endpoint/api/target. In the current ActionRunner implementation, type: 'api' without an endpoint returns No API endpoint provided, so clicking these will always fail. Consider either supplying a dummy endpoint (if you want to exercise API execution) or switching these example actions to a non-network type (e.g. script) so Console verification can reach success/refresh flows.

Copilot uses AI. Check for mistakes.
{ name: 'body', label: 'Message', type: 'textarea' as const },
],
Comment on lines +7 to +12

CopilotAIFeb 20, 2026

Copy link

Choose a reason for hiding this comment

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

This file uses params: [...] as parameter definitions. In the current ActionRunner, parameter collection is triggered by actionParams (definitions), while params is treated as runtime values/body for API calls. As-is, the param dialog won’t open and executeAPI() will stringify the array into the request body. If the goal is to exercise param collection in Console, align the property name with what the runner expects (or update the Console mapping to translate paramsactionParams).

Copilot uses AI. Check for mistakes.
successMessage: 'Email sent successfully',
},
{
name: 'account_assign_owner',
label: 'Assign Owner',
icon: 'user-plus',
type: 'api' as const,
locations: ['record_header' as const, 'list_item' as const],
params: [
{ name: 'owner_id', label: 'New Owner', type: 'lookup' as const, required: true },
],
refreshAfter: true,
successMessage: 'Owner assigned successfully',
},
{
name: 'account_merge',
label: 'Merge Accounts',
icon: 'git-merge',
type: 'api' as const,
locations: ['record_more' as const],
confirmText: 'Are you sure you want to merge these accounts? This action cannot be undone.',
variant: 'danger' as const,
refreshAfter: true,
},
];
37 changes: 37 additions & 0 deletions examples/crm/src/actions/contact.actions.ts
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
export const ContactActions = [
{
name: 'contact_send_email',
label: 'Send Email',
icon: 'mail',
type: 'api' as const,
locations: ['record_header' as const, 'list_item' as const],
params: [
{ name: 'subject', label: 'Subject', type: 'text' as const, required: true },
{ name: 'body', label: 'Message', type: 'textarea' as const },
],
successMessage: 'Email sent successfully',
Comment on lines +7 to +12

CopilotAIFeb 20, 2026

Copy link

Choose a reason for hiding this comment

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

These actions use params: [...] as parameter definitions. In ActionRunner, param collection is driven by actionParams (definitions) and params is treated as runtime values/body. If you want the Console to pop a parameter dialog, either export definitions under actionParams or add a mapping layer that translates spec params into actionParams before execution.

Copilot uses AI. Check for mistakes.
},
{
name: 'contact_convert_to_customer',
label: 'Convert to Customer',
icon: 'user-check',
type: 'api' as const,
locations: ['record_header' as const],
confirmText: 'Convert this contact to a customer?',
refreshAfter: true,
successMessage: 'Contact converted to customer',
},
{
name: 'contact_log_call',
label: 'Log a Call',
icon: 'phone',
type: 'api' as const,
locations: ['record_header' as const, 'list_item' as const],
Comment on lines +2 to +29

CopilotAIFeb 20, 2026

Copy link

Choose a reason for hiding this comment

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

All actions in this file are type: 'api' but don’t specify endpoint/api/target. With the current ActionRunner, executing these will always fail with No API endpoint provided. Add an endpoint (even a mock URL) or change the action type so they can be executed during Console verification.

Copilot uses AI. Check for mistakes.
params: [
{ name: 'call_subject', label: 'Subject', type: 'text' as const, required: true },
{ name: 'call_notes', label: 'Notes', type: 'textarea' as const },
{ name: 'call_duration', label: 'Duration (min)', type: 'number' as const },
],
successMessage: 'Call logged successfully',
},
];
38 changes: 38 additions & 0 deletions examples/crm/src/actions/event.actions.ts
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
export const EventActions = [
{
name: 'event_send_invitation',
label: 'Send Invitation',
icon: 'send',
type: 'api' as const,
locations: ['record_header' as const],
params: [
{ name: 'message', label: 'Optional Message', type: 'textarea' as const },
],
successMessage: 'Invitation sent to all participants',
},
Comment on lines +7 to +12

CopilotAIFeb 20, 2026

Copy link

Choose a reason for hiding this comment

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

params: [...] is used as parameter definitions here, but ActionRunner’s param collection is keyed off actionParams and treats params as runtime values. To actually exercise param collection in Console, align the schema (or translate spec params → runner actionParams).

Copilot uses AI. Check for mistakes.
{
name: 'event_mark_completed',
label: 'Mark as Completed',
icon: 'check-circle',
type: 'api' as const,
locations: ['record_header' as const],
confirmText: 'Mark this event as completed?',
refreshAfter: true,
successMessage: 'Event marked as completed',
},
{
name: 'event_cancel',
label: 'Cancel Event',
icon: 'x-circle',
type: 'api' as const,
locations: ['record_more' as const],
variant: 'danger' as const,
params: [
{ name: 'cancel_reason', label: 'Cancellation Reason', type: 'text' as const },
{ name: 'notify_participants', label: 'Notify Participants', type: 'boolean' as const },
],
confirmText: 'Are you sure you want to cancel this event?',
refreshAfter: true,
successMessage: 'Event cancelled',
},
Comment on lines +2 to +37

CopilotAIFeb 20, 2026

Copy link

Choose a reason for hiding this comment

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

These actions are type: 'api' without any endpoint/api/target. With the current ActionRunner, executing them always fails (No API endpoint provided). Provide endpoints or use a non-API type so the actions can succeed during Console verification.

Copilot uses AI. Check for mistakes.
];
Loading