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
1 change: 1 addition & 0 deletions ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -846,6 +846,7 @@ The `FlowDesigner` is a canvas-based flow editor that bridges the gap between th
- [x] **P2: App Branding** — `logo`, `favicon`, `backgroundColor` fields on CRM app
- [x] **P3: Pages** — Settings page (utility) and Getting Started page (onboarding)
- [x] **P2: Spec Compliance Audit** — Fixed `variant: 'danger'` → `'destructive'` (4 actions), `columns: string` → `number` (33 form sections), added `type: 'dashboard'` to dashboard
- [x] **P2: Dashboard Widget Spec Alignment** — Added `id`, `title`, `object`, `categoryField`, `valueField`, `aggregate` to all dashboard widgets across CRM, Todo, and Kitchen Sink examples (5 new spec-compliance tests)
- [x] **P2: i18n (10 locales)** — Full CRM metadata translations for en, zh, ja, ko, de, fr, es, pt, ru, ar — objects, fields, fieldOptions, navigation, actions, views, formSections, dashboard, reports, pages (24 tests)

### Ecosystem & Marketplace
Expand Down
41 changes: 41 additions & 0 deletions examples/crm/src/__tests__/crm-metadata.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,47 @@ describe('CRM Metadata Spec Compliance', () => {
expect(widget.layout).toHaveProperty('h');
}
});

it('all widgets have unique id', () => {
const ids = CrmDashboard.widgets.map((w) => w.id);
for (const id of ids) {
expect(typeof id).toBe('string');
expect(id!.length).toBeGreaterThan(0);
}
expect(new Set(ids).size).toBe(ids.length);
});

it('all widgets have title', () => {
for (const widget of CrmDashboard.widgets) {
expect(typeof widget.title).toBe('string');
expect(widget.title!.length).toBeGreaterThan(0);
}
});

it('metric widgets have title matching options.label', () => {
const metrics = CrmDashboard.widgets.filter((w) => w.type === 'metric');
for (const widget of metrics) {
const opts = widget.options as { label?: string };
expect(widget.title).toBe(opts.label);
}
});

it('all widgets have object data binding', () => {
for (const widget of CrmDashboard.widgets) {
expect(typeof widget.object).toBe('string');
expect(widget.object!.length).toBeGreaterThan(0);
}
});

it('chart widgets have categoryField, valueField, and aggregate', () => {
const chartTypes = ['bar', 'area', 'donut', 'line', 'pie'];
const charts = CrmDashboard.widgets.filter((w) => chartTypes.includes(w.type));
for (const widget of charts) {
expect(typeof widget.categoryField).toBe('string');
expect(typeof widget.valueField).toBe('string');
expect(typeof widget.aggregate).toBe('string');
}
});
});

describe('Reports', () => {
Expand Down
41 changes: 40 additions & 1 deletion examples/crm/src/dashboards/crm.dashboard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@ export const CrmDashboard = {
widgets: [
// --- KPI Row ---
{
id: 'crm_total_revenue',
title: 'Total Revenue',
type: 'metric' as const,
object: 'opportunity',
layout: { x: 0, y: 0, w: 1, h: 1 },
options: {
label: 'Total Revenue',
Expand All @@ -15,7 +18,10 @@ export const CrmDashboard = {
}
},
{
id: 'crm_active_deals',
title: 'Active Deals',
type: 'metric' as const,
object: 'opportunity',
layout: { x: 1, y: 0, w: 1, h: 1 },
options: {
label: 'Active Deals',
Expand All @@ -25,7 +31,10 @@ export const CrmDashboard = {
}
},
{
id: 'crm_win_rate',
title: 'Win Rate',
type: 'metric' as const,
object: 'opportunity',
layout: { x: 2, y: 0, w: 1, h: 1 },
options: {
label: 'Win Rate',
Expand All @@ -35,7 +44,10 @@ export const CrmDashboard = {
}
},
{
id: 'crm_avg_deal_size',
title: 'Avg Deal Size',
type: 'metric' as const,
object: 'opportunity',
layout: { x: 3, y: 0, w: 1, h: 1 },
options: {
label: 'Avg Deal Size',
Expand All @@ -47,8 +59,13 @@ export const CrmDashboard = {

// --- Row 2: Charts ---
{
id: 'crm_revenue_trends',
title: 'Revenue Trends',
type: 'area' as const,
type: 'area' as const,
object: 'opportunity',
categoryField: 'month',
valueField: 'revenue',
aggregate: 'sum',
layout: { x: 0, y: 1, w: 3, h: 2 },
options: {
xField: 'month',
Expand All @@ -68,8 +85,13 @@ export const CrmDashboard = {
},
},
{
id: 'crm_lead_source',
title: 'Lead Source',
type: 'donut' as const,
object: 'opportunity',
categoryField: 'source',
valueField: 'value',
aggregate: 'count',
layout: { x: 3, y: 1, w: 1, h: 2 },
options: {
xField: 'source',
Expand All @@ -88,8 +110,13 @@ export const CrmDashboard = {

// --- Row 3: More Charts ---
{
id: 'crm_pipeline_by_stage',
title: 'Pipeline by Stage',
type: 'bar' as const,
object: 'opportunity',
categoryField: 'stage',
valueField: 'amount',
aggregate: 'sum',
layout: { x: 0, y: 3, w: 2, h: 2 },
options: {
xField: 'stage',
Expand All @@ -107,8 +134,13 @@ export const CrmDashboard = {
},
},
{
id: 'crm_top_products',
title: 'Top Products',
type: 'bar' as const,
object: 'product',
categoryField: 'name',
valueField: 'sales',
aggregate: 'sum',
layout: { x: 2, y: 3, w: 2, h: 2 },
options: {
xField: 'name',
Expand All @@ -127,8 +159,10 @@ export const CrmDashboard = {

// --- Row 4: Table ---
{
id: 'crm_recent_opportunities',
title: 'Recent Opportunities',
type: 'table' as const,
object: 'opportunity',
layout: { x: 0, y: 5, w: 4, h: 2 },
options: {
columns: [
Expand All @@ -152,8 +186,13 @@ export const CrmDashboard = {

// --- Row 5: Dynamic KPI from Object Data ---
{
id: 'crm_revenue_by_account',
title: 'Revenue by Account',
type: 'bar' as const,
object: 'opportunity',
categoryField: 'account',
valueField: 'total',
aggregate: 'sum',
layout: { x: 0, y: 7, w: 4, h: 2 },
options: {
xField: 'account',
Expand Down
27 changes: 27 additions & 0 deletions examples/kitchen-sink/objectstack.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,10 @@ export default defineStack({
widgets: [
// --- KPI Row ---
{
id: 'ks_total_records',
title: 'Total Records',
type: 'metric',
object: 'kitchen_sink',
layout: { x: 0, y: 0, w: 1, h: 1 },
options: {
label: 'Total Records',
Expand All @@ -82,7 +85,10 @@ export default defineStack({
},
},
{
id: 'ks_active_items',
title: 'Active Items',
type: 'metric',
object: 'kitchen_sink',
layout: { x: 1, y: 0, w: 1, h: 1 },
options: {
label: 'Active Items',
Expand All @@ -92,7 +98,10 @@ export default defineStack({
},
},
{
id: 'ks_total_value',
title: 'Total Value',
type: 'metric',
object: 'kitchen_sink',
layout: { x: 2, y: 0, w: 1, h: 1 },
options: {
label: 'Total Value',
Expand All @@ -102,7 +111,10 @@ export default defineStack({
},
},
{
id: 'ks_avg_rating',
title: 'Avg Rating',
type: 'metric',
object: 'kitchen_sink',
layout: { x: 3, y: 0, w: 1, h: 1 },
options: {
label: 'Avg Rating',
Expand All @@ -114,8 +126,13 @@ export default defineStack({

// --- Charts Row ---
{
id: 'ks_records_by_category',
title: 'Records by Category',
type: 'donut',
object: 'kitchen_sink',
categoryField: 'category',
valueField: 'count',
aggregate: 'count',
layout: { x: 0, y: 1, w: 2, h: 2 },
options: {
xField: 'category',
Expand All @@ -131,8 +148,13 @@ export default defineStack({
},
},
{
id: 'ks_value_distribution',
title: 'Value Distribution',
type: 'bar',
object: 'kitchen_sink',
categoryField: 'name',
valueField: 'amount',
aggregate: 'sum',
layout: { x: 2, y: 1, w: 2, h: 2 },
options: {
xField: 'name',
Expand All @@ -152,8 +174,13 @@ export default defineStack({

// --- Trend Row ---
{
id: 'ks_monthly_trend',
title: 'Monthly Trend',
type: 'area',
object: 'kitchen_sink',
categoryField: 'month',
valueField: 'value',
aggregate: 'sum',
layout: { x: 0, y: 3, w: 4, h: 2 },
options: {
xField: 'month',
Expand Down
22 changes: 22 additions & 0 deletions examples/todo/objectstack.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,10 @@ export default defineStack({
label: 'Task Overview',
widgets: [
{
id: 'todo_total_tasks',
title: 'Total Tasks',
type: 'metric',
object: 'todo_task',
layout: { x: 0, y: 0, w: 1, h: 1 },
options: {
label: 'Total Tasks',
Expand All @@ -120,7 +123,10 @@ export default defineStack({
},
},
{
id: 'todo_in_progress',
title: 'In Progress',
type: 'metric',
object: 'todo_task',
layout: { x: 1, y: 0, w: 1, h: 1 },
options: {
label: 'In Progress',
Expand All @@ -130,7 +136,10 @@ export default defineStack({
},
},
{
id: 'todo_completed',
title: 'Completed',
type: 'metric',
object: 'todo_task',
layout: { x: 2, y: 0, w: 1, h: 1 },
options: {
label: 'Completed',
Expand All @@ -140,7 +149,10 @@ export default defineStack({
},
},
{
id: 'todo_overdue',
title: 'Overdue',
type: 'metric',
object: 'todo_task',
layout: { x: 3, y: 0, w: 1, h: 1 },
options: {
label: 'Overdue',
Expand All @@ -150,8 +162,13 @@ export default defineStack({
},
},
{
id: 'todo_tasks_by_status',
title: 'Tasks by Status',
type: 'donut',
object: 'todo_task',
categoryField: 'status',
valueField: 'count',
aggregate: 'count',
layout: { x: 0, y: 1, w: 2, h: 2 },
options: {
xField: 'status',
Expand All @@ -169,8 +186,13 @@ export default defineStack({
},
},
{
id: 'todo_tasks_by_category',
title: 'Tasks by Category',
type: 'bar',
object: 'todo_task',
categoryField: 'category',
valueField: 'count',
aggregate: 'count',
layout: { x: 2, y: 1, w: 2, h: 2 },
options: {
xField: 'category',
Expand Down
Loading