From dd141b22f4afe84a74a74a52bdbea7c79bc39dc5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 20 Jan 2026 10:37:53 +0000 Subject: [PATCH 1/4] Initial plan From 991ad328bd31f6507a79dbca57317b44bf94da20 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 20 Jan 2026 10:44:54 +0000 Subject: [PATCH 2/4] Fix App branding schema and add new field types to examples Co-authored-by: huangyiirene <7665279+huangyiirene@users.noreply.github.com> --- examples/crm/objectstack.config.ts | 18 +++---- .../crm/src/domains/crm/account.object.ts | 41 +++++++++----- examples/crm/src/domains/crm/case.object.ts | 22 ++++++-- examples/crm/src/domains/crm/lead.object.ts | 53 +++++++++++-------- .../crm/src/domains/crm/opportunity.object.ts | 10 ++++ examples/todo/objectstack.config.ts | 5 ++ examples/todo/src/domains/todo/task.object.ts | 21 +++++++- 7 files changed, 118 insertions(+), 52 deletions(-) diff --git a/examples/crm/objectstack.config.ts b/examples/crm/objectstack.config.ts index 4d45a6ed6..ecd5b06e0 100644 --- a/examples/crm/objectstack.config.ts +++ b/examples/crm/objectstack.config.ts @@ -16,6 +16,7 @@ export default App.create({ label: 'CRM App', description: 'Comprehensive CRM example demonstrating all ObjectStack Protocol features', version: '2.0.0', + icon: 'briefcase', // All objects in the app objects: [ @@ -85,17 +86,10 @@ export default App.create({ // Reports reports: Object.values(CrmReports), - // App-level settings - settings: { - theme: { - primaryColor: '#4169E1', - logo: '/assets/crm-logo.png', - }, - features: { - enableGlobalSearch: true, - enableNotifications: true, - enableMobileApp: true, - enableOfflineMode: true, - } + // App-level branding + branding: { + primaryColor: '#4169E1', + logo: '/assets/crm-logo.png', + favicon: '/assets/crm-favicon.ico', } }); \ No newline at end of file diff --git a/examples/crm/src/domains/crm/account.object.ts b/examples/crm/src/domains/crm/account.object.ts index e011f871c..e0a300b10 100644 --- a/examples/crm/src/domains/crm/account.object.ts +++ b/examples/crm/src/domains/crm/account.object.ts @@ -6,6 +6,7 @@ export const Account = ObjectSchema.create({ pluralLabel: 'Accounts', icon: 'building', description: 'Companies and organizations doing business with us', + nameField: 'name', fields: { // AutoNumber field - Unique account identifier @@ -69,12 +70,18 @@ export const Account = ObjectSchema.create({ label: 'Website', }), - // Address fields - billing_street: Field.textarea({ label: 'Billing Street' }), - billing_city: Field.text({ label: 'Billing City' }), - billing_state: Field.text({ label: 'Billing State/Province' }), - billing_postal_code: Field.text({ label: 'Billing Postal Code' }), - billing_country: Field.text({ label: 'Billing Country' }), + // Structured Address field (new field type) + billing_address: Field.address({ + label: 'Billing Address', + addressFormat: 'international', + }), + + // Office Location (new field type) + office_location: Field.location({ + label: 'Office Location', + displayMap: true, + allowGeocoding: true, + }), // Relationship fields owner: Field.lookup('user', { @@ -104,13 +111,21 @@ export const Account = ObjectSchema.create({ readonly: true, }), - // Formula field - combines first and last name - full_address: Field.formula({ - label: 'Full Billing Address', - expression: 'CONCAT(billing_street, ", ", billing_city, ", ", billing_state, " ", billing_postal_code, ", ", billing_country)', + // Brand color (new field type) + brand_color: Field.color({ + label: 'Brand Color', + colorFormat: 'hex', + presetColors: ['#FF0000', '#00FF00', '#0000FF', '#FFFF00', '#FF00FF', '#00FFFF'], }), }, + // Database indexes for performance + indexes: [ + { fields: ['name'], unique: false }, + { fields: ['owner'], unique: false }, + { fields: ['type', 'is_active'], unique: false }, + ], + // Enable advanced features enable: { trackHistory: true, // Track field changes @@ -176,12 +191,12 @@ export const Account = ObjectSchema.create({ { label: 'Contact Details', columns: 2, - fields: ['phone', 'website'] + fields: ['phone', 'website', 'brand_color'] }, { - label: 'Billing Address', + label: 'Location & Address', columns: 2, - fields: ['billing_street', 'billing_city', 'billing_state', 'billing_postal_code', 'billing_country', 'full_address'] + fields: ['billing_address', 'office_location'] }, { label: 'Additional Information', diff --git a/examples/crm/src/domains/crm/case.object.ts b/examples/crm/src/domains/crm/case.object.ts index 33ce2d13a..b9c31f082 100644 --- a/examples/crm/src/domains/crm/case.object.ts +++ b/examples/crm/src/domains/crm/case.object.ts @@ -133,14 +133,21 @@ export const Case = ObjectSchema.create({ }), // Customer satisfaction - customer_rating: Field.select(['⭐ Very Dissatisfied', '⭐⭐ Dissatisfied', '⭐⭐⭐ Neutral', '⭐⭐⭐⭐ Satisfied', '⭐⭐⭐⭐⭐ Very Satisfied'], { - label: 'Customer Rating', + customer_rating: Field.rating(5, { + label: 'Customer Satisfaction', + description: 'Customer satisfaction rating (1-5 stars)', }), customer_feedback: Field.textarea({ label: 'Customer Feedback', }), + // Customer signature (for case resolution acknowledgment) + customer_signature: Field.signature({ + label: 'Customer Signature', + description: 'Digital signature acknowledging case resolution', + }), + // Internal notes internal_notes: Field.markdown({ label: 'Internal Notes', @@ -155,6 +162,15 @@ export const Case = ObjectSchema.create({ }), }, + // Database indexes for performance + indexes: [ + { fields: ['case_number'], unique: true }, + { fields: ['account'], unique: false }, + { fields: ['owner'], unique: false }, + { fields: ['status'], unique: false }, + { fields: ['priority'], unique: false }, + ], + enable: { trackHistory: true, searchable: true, @@ -245,7 +261,7 @@ export const Case = ObjectSchema.create({ { label: 'Resolution', columns: 1, - fields: ['resolution', 'customer_rating', 'customer_feedback'], + fields: ['resolution', 'customer_rating', 'customer_feedback', 'customer_signature'], }, { label: 'SLA & Metrics', diff --git a/examples/crm/src/domains/crm/lead.object.ts b/examples/crm/src/domains/crm/lead.object.ts index 854891330..ce040de0d 100644 --- a/examples/crm/src/domains/crm/lead.object.ts +++ b/examples/crm/src/domains/crm/lead.object.ts @@ -80,15 +80,11 @@ export const Lead = ObjectSchema.create({ ] }, - rating: { - type: 'select', - label: 'Rating', - options: [ - { label: 'Hot', value: 'hot', color: '#FF0000' }, - { label: 'Warm', value: 'warm', color: '#FFA500' }, - { label: 'Cold', value: 'cold', color: '#4169E1' }, - ] - }, + rating: Field.rating(5, { + label: 'Lead Score', + description: 'Lead quality score (1-5 stars)', + allowHalf: true, + }), lead_source: Field.select(['Web', 'Referral', 'Event', 'Partner', 'Advertisement', 'Cold Call'], { label: 'Lead Source', @@ -127,12 +123,11 @@ export const Lead = ObjectSchema.create({ readonly: true, }), - // Address - street: Field.textarea({ label: 'Street' }), - city: Field.text({ label: 'City' }), - state: Field.text({ label: 'State/Province' }), - postal_code: Field.text({ label: 'Postal Code' }), - country: Field.text({ label: 'Country' }), + // Address (using new address field type) + address: Field.address({ + label: 'Address', + addressFormat: 'international', + }), // Additional Info annual_revenue: Field.currency({ @@ -148,6 +143,12 @@ export const Lead = ObjectSchema.create({ label: 'Description', }), + // Custom notes with rich text formatting + notes: Field.richtext({ + label: 'Notes', + description: 'Rich text notes with formatting', + }), + // Flags do_not_call: Field.boolean({ label: 'Do Not Call', @@ -160,6 +161,14 @@ export const Lead = ObjectSchema.create({ }), }, + // Database indexes for performance + indexes: [ + { fields: ['email'], unique: true }, + { fields: ['owner'], unique: false }, + { fields: ['status'], unique: false }, + { fields: ['company'], unique: false }, + ], + enable: { trackHistory: true, searchable: true, @@ -235,13 +244,13 @@ export const Lead = ObjectSchema.create({ { label: 'Address', columns: 2, - fields: ['street', 'city', 'state', 'postal_code', 'country'], + fields: ['address'], }, { label: 'Additional Information', columns: 2, collapsible: true, - fields: ['do_not_call', 'email_opt_out', 'description'], + fields: ['do_not_call', 'email_opt_out', 'description', 'notes'], }, { label: 'Conversion Information', @@ -273,10 +282,10 @@ export const Lead = ObjectSchema.create({ workflows: [ { - name: 'auto_qualify_hot_leads', + name: 'auto_qualify_high_score_leads', objectName: 'lead', triggerType: 'on_create_or_update', - criteria: 'rating = "hot" AND status = "new"', + criteria: 'rating >= 4 AND status = "new"', active: true, actions: [ { @@ -288,16 +297,16 @@ export const Lead = ObjectSchema.create({ ], }, { - name: 'notify_owner_on_hot_lead', + name: 'notify_owner_on_high_score_lead', objectName: 'lead', triggerType: 'on_create_or_update', - criteria: 'ISCHANGED(rating) AND rating = "hot"', + criteria: 'ISCHANGED(rating) AND rating >= 4.5', active: true, actions: [ { name: 'email_owner', type: 'email_alert', - template: 'hot_lead_notification', + template: 'high_score_lead_notification', recipients: ['{owner.email}'], } ], diff --git a/examples/crm/src/domains/crm/opportunity.object.ts b/examples/crm/src/domains/crm/opportunity.object.ts index ab600a2e0..db68621d1 100644 --- a/examples/crm/src/domains/crm/opportunity.object.ts +++ b/examples/crm/src/domains/crm/opportunity.object.ts @@ -6,6 +6,7 @@ export const Opportunity = ObjectSchema.create({ pluralLabel: 'Opportunities', icon: 'dollar-sign', description: 'Sales opportunities and deals in the pipeline', + nameField: 'name', fields: { // Basic Information @@ -126,6 +127,15 @@ export const Opportunity = ObjectSchema.create({ }), }, + // Database indexes for performance + indexes: [ + { fields: ['name'], unique: false }, + { fields: ['account'], unique: false }, + { fields: ['owner'], unique: false }, + { fields: ['stage'], unique: false }, + { fields: ['close_date'], unique: false }, + ], + // Enable advanced features enable: { trackHistory: true, // Critical for tracking stage changes diff --git a/examples/todo/objectstack.config.ts b/examples/todo/objectstack.config.ts index 0cbf9ddba..02a1c4984 100644 --- a/examples/todo/objectstack.config.ts +++ b/examples/todo/objectstack.config.ts @@ -6,6 +6,11 @@ export default App.create({ label: 'Todo App', description: 'A simple Todo example demonstrating ObjectStack Protocol', version: '1.0.0', + icon: 'check-square', + branding: { + primaryColor: '#10B981', + logo: '/assets/todo-logo.png', + }, objects: [ TodoTask ], diff --git a/examples/todo/src/domains/todo/task.object.ts b/examples/todo/src/domains/todo/task.object.ts index 9c11d8d1b..4463796c6 100644 --- a/examples/todo/src/domains/todo/task.object.ts +++ b/examples/todo/src/domains/todo/task.object.ts @@ -4,15 +4,32 @@ export const TodoTask = ObjectSchema.create({ name: 'todo_task', label: 'Todo Task', icon: 'check-square', + nameField: 'subject', enable: { apiEnabled: true, + trackHistory: true, }, fields: { subject: Field.text({ required: true }), due_date: Field.date(), is_completed: Field.boolean({ defaultValue: false }), - priority: Field.select(['High', 'Normal', 'Low'], { - defaultValue: 'Normal' + priority: Field.rating(3, { + label: 'Priority', + description: 'Task priority (1-3 stars)', + }), + category_color: Field.color({ + label: 'Category Color', + colorFormat: 'hex', + presetColors: ['#FF0000', '#00FF00', '#0000FF', '#FFFF00'], + }), + code_snippet: Field.code('javascript', { + label: 'Code Snippet', + description: 'Optional code to implement', + lineNumbers: true, + }), + notes: Field.richtext({ + label: 'Notes', + description: 'Rich text notes with formatting', }), }, // actions: { From 3e2baa0551de1b96ca5a488585bf197af71b84c9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 20 Jan 2026 10:47:06 +0000 Subject: [PATCH 3/4] Convert inline field definitions to Field helpers and add API security Co-authored-by: huangyiirene <7665279+huangyiirene@users.noreply.github.com> --- examples/crm/src/domains/crm/account.object.ts | 11 +++++------ examples/crm/src/domains/crm/case.object.ts | 10 ++++------ examples/crm/src/domains/crm/lead.object.ts | 5 ++--- examples/crm/src/domains/crm/opportunity.object.ts | 6 +++--- 4 files changed, 14 insertions(+), 18 deletions(-) diff --git a/examples/crm/src/domains/crm/account.object.ts b/examples/crm/src/domains/crm/account.object.ts index e0a300b10..5e0f7a2da 100644 --- a/examples/crm/src/domains/crm/account.object.ts +++ b/examples/crm/src/domains/crm/account.object.ts @@ -24,8 +24,7 @@ export const Account = ObjectSchema.create({ }), // Select fields with custom options - type: { - type: 'select', + type: Field.select({ label: 'Account Type', options: [ { label: 'Prospect', value: 'prospect', color: '#FFA500', default: true }, @@ -33,10 +32,9 @@ export const Account = ObjectSchema.create({ { label: 'Partner', value: 'partner', color: '#0000FF' }, { label: 'Former Customer', value: 'former', color: '#999999' }, ] - }, + }), - industry: { - type: 'select', + industry: Field.select({ label: 'Industry', options: [ { label: 'Technology', value: 'technology' }, @@ -46,7 +44,7 @@ export const Account = ObjectSchema.create({ { label: 'Manufacturing', value: 'manufacturing' }, { label: 'Education', value: 'education' }, ] - }, + }), // Number fields annual_revenue: Field.currency({ @@ -131,6 +129,7 @@ export const Account = ObjectSchema.create({ trackHistory: true, // Track field changes searchable: true, // Include in global search apiEnabled: true, // Expose via REST/GraphQL + apiMethods: ['get', 'list', 'create', 'update', 'delete', 'search', 'export'], // Whitelist allowed API operations files: true, // Allow file attachments feedEnabled: true, // Enable activity feed/chatter trash: true, // Recycle bin support diff --git a/examples/crm/src/domains/crm/case.object.ts b/examples/crm/src/domains/crm/case.object.ts index b9c31f082..8ca50b7b6 100644 --- a/examples/crm/src/domains/crm/case.object.ts +++ b/examples/crm/src/domains/crm/case.object.ts @@ -38,8 +38,7 @@ export const Case = ObjectSchema.create({ }), // Case Management - status: { - type: 'select', + status: Field.select({ label: 'Status', required: true, options: [ @@ -51,10 +50,9 @@ export const Case = ObjectSchema.create({ { label: 'Resolved', value: 'resolved', color: '#00AA00' }, { label: 'Closed', value: 'closed', color: '#006400' }, ] - }, + }), - priority: { - type: 'select', + priority: Field.select({ label: 'Priority', required: true, options: [ @@ -63,7 +61,7 @@ export const Case = ObjectSchema.create({ { label: 'High', value: 'high', color: '#FF4500' }, { label: 'Critical', value: 'critical', color: '#FF0000' }, ] - }, + }), type: Field.select(['Question', 'Problem', 'Feature Request', 'Bug'], { label: 'Case Type', diff --git a/examples/crm/src/domains/crm/lead.object.ts b/examples/crm/src/domains/crm/lead.object.ts index ce040de0d..8500efa8a 100644 --- a/examples/crm/src/domains/crm/lead.object.ts +++ b/examples/crm/src/domains/crm/lead.object.ts @@ -67,8 +67,7 @@ export const Lead = ObjectSchema.create({ }), // Lead Qualification - status: { - type: 'select', + status: Field.select({ label: 'Lead Status', required: true, options: [ @@ -78,7 +77,7 @@ export const Lead = ObjectSchema.create({ { label: 'Unqualified', value: 'unqualified', color: '#FF0000' }, { label: 'Converted', value: 'converted', color: '#00AA00' }, ] - }, + }), rating: Field.rating(5, { label: 'Lead Score', diff --git a/examples/crm/src/domains/crm/opportunity.object.ts b/examples/crm/src/domains/crm/opportunity.object.ts index db68621d1..b766feb18 100644 --- a/examples/crm/src/domains/crm/opportunity.object.ts +++ b/examples/crm/src/domains/crm/opportunity.object.ts @@ -47,8 +47,7 @@ export const Opportunity = ObjectSchema.create({ }), // Sales Process - stage: { - type: 'select', + stage: Field.select({ label: 'Stage', required: true, options: [ @@ -60,7 +59,7 @@ export const Opportunity = ObjectSchema.create({ { label: 'Closed Won', value: 'closed_won', color: '#00AA00' }, { label: 'Closed Lost', value: 'closed_lost', color: '#FF0000' }, ] - }, + }), probability: Field.percent({ label: 'Probability (%)', @@ -141,6 +140,7 @@ export const Opportunity = ObjectSchema.create({ trackHistory: true, // Critical for tracking stage changes searchable: true, apiEnabled: true, + apiMethods: ['get', 'list', 'create', 'update', 'delete', 'aggregate', 'search'], // Whitelist allowed API operations files: true, // Attach proposals, contracts feedEnabled: true, // Team collaboration trash: true, From 3b46de5785223dcd9f45deaca93351e0de329a65 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 20 Jan 2026 10:50:14 +0000 Subject: [PATCH 4/4] Fix rating filter in Lead list view after converting to numeric rating field Co-authored-by: huangyiirene <7665279+huangyiirene@users.noreply.github.com> --- examples/crm/src/domains/crm/lead.object.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/crm/src/domains/crm/lead.object.ts b/examples/crm/src/domains/crm/lead.object.ts index 8500efa8a..3a6fde955 100644 --- a/examples/crm/src/domains/crm/lead.object.ts +++ b/examples/crm/src/domains/crm/lead.object.ts @@ -201,11 +201,11 @@ export const Lead = ObjectSchema.create({ sort: [{ field: 'created_date', order: 'desc' }], }, hot_leads: { - label: 'Hot Leads', + label: 'High Score Leads', type: 'grid', - columns: ['full_name', 'company', 'email', 'phone', 'status', 'owner'], + columns: ['full_name', 'company', 'email', 'phone', 'status', 'rating', 'owner'], filter: [ - ['rating', '=', 'hot'], + ['rating', '>=', 4], ['is_converted', '=', false], ], },