Skip to content

Commit ec5d799

Browse files
authored
Merge pull request #393 from objectstack-ai/copilot/complete-development-according-to-document
2 parents 0159168 + dc38530 commit ec5d799

File tree

14 files changed

+1416
-30
lines changed

14 files changed

+1416
-30
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,9 @@ apps/site/.source
5959
# Test artifacts
6060
test-screenshots
6161

62+
# Vite timestamp files
63+
*.timestamp-*.mjs
64+
6265
# Backup files
6366
*.bak
6467
*.backup

ROADMAP.md

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ FormView.type: 'simple' | 'tabbed' | 'wizard' | 'split' | 'drawer' | 'modal'
6464
- [x] Modal Form - Dialog-based form (Dialog)
6565
- [x] Section/Group support (collapsible, columns per section)
6666
- [x] FormField.colSpan support
67-
- [ ] FormField.dependsOn (field dependencies) — type defined, runtime evaluation pending
67+
- [x] FormField.dependsOn (field dependencies) — runtime evaluation in FormSectionRenderer
6868
- [x] FormField.widget (custom widget override)
6969

7070
---
@@ -97,8 +97,9 @@ Action.params: ActionParam[]
9797
- [x] Conditional visibility & enabled state (via expression evaluation)
9898

9999
**Remaining:**
100-
- [ ] ActionParam UI collection (before execution) — param form dialog
101-
- [ ] FormField.dependsOn (field dependencies) — type defined, runtime evaluation pending
100+
- [x] ActionParam UI collection (before execution) — ActionParamDialog + ParamCollectionHandler
101+
- [x] FormField.dependsOn (field dependencies) — runtime evaluation in FormSectionRenderer
102+
- [x] FormField.visibleOn (expression-based visibility) — evaluated via ExpressionEvaluator
102103

103104
---
104105

@@ -135,8 +136,6 @@ Reusable `useNavigationOverlay` hook in @object-ui/react + `NavigationOverlay` c
135136
**Remaining:**
136137
- [ ] `navigation.view` property — target view/form schema lookup (currently renders field list)
137138
- [ ] ObjectForm integration in overlay content (render forms when editing)
138-
- [ ] ActionParam UI collection (before execution) — param form dialog
139-
- [ ] FormField.dependsOn (field dependencies) — type defined, runtime evaluation pending
140139

141140
---
142141

@@ -198,7 +197,7 @@ PageRenderer supports four page types with region-based layouts and page-level v
198197
- [x] 36 PageRenderer tests + 23 usePageVariables tests, all passing
199198

200199
**Remaining:**
201-
- [ ] Page.template support (predefined layout templates)
200+
- [x] Page.template support (predefined layout templates: default, header-sidebar-main, three-column, dashboard)
202201
- [ ] Page.object data binding (auto-fetch record data)
203202

204203
---
@@ -283,7 +282,7 @@ WidgetManifest: {
283282

284283
- [ ] Responsive grid layout
285284
- [ ] Multi-column layout components
286-
- [ ] Layout templates
285+
- [x] Layout templates (4 predefined: default, header-sidebar-main, three-column, dashboard)
287286

288287
---
289288

examples/kitchen-sink/objectstack.config.ts

Lines changed: 215 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { defineStack } from '@objectstack/spec';
22
import { App } from '@objectstack/spec/ui';
33
import { KitchenSinkObject } from './src/objects/kitchen_sink.object';
44
import { AccountObject } from './src/objects/account.object';
5+
import { ShowcaseObject } from './src/objects/showcase.object';
56

67
// Helper to create dates relative to today
78
const daysFromNow = (days: number) => {
@@ -13,7 +14,8 @@ const daysFromNow = (days: number) => {
1314
export default defineStack({
1415
objects: [
1516
KitchenSinkObject,
16-
AccountObject
17+
AccountObject,
18+
ShowcaseObject
1719
],
1820
apps: [
1921
App.create({
@@ -39,6 +41,20 @@ export default defineStack({
3941
label: 'All Field Types',
4042
icon: 'test-tubes',
4143
},
44+
{
45+
id: 'nav_showcase',
46+
type: 'object',
47+
objectName: 'showcase',
48+
label: 'Feature Showcase',
49+
icon: 'sparkles',
50+
},
51+
{
52+
id: 'nav_templates',
53+
type: 'page',
54+
pageName: 'template_showcase',
55+
label: 'Page Templates',
56+
icon: 'layout-template',
57+
},
4258
{
4359
id: 'nav_help',
4460
type: 'page',
@@ -162,6 +178,109 @@ export default defineStack({
162178
},
163179
],
164180
pages: [
181+
// Template Showcase Page — demonstrates page templates
182+
{
183+
name: 'template_showcase',
184+
label: 'Page Templates',
185+
type: 'app',
186+
template: 'header-sidebar-main',
187+
regions: [
188+
{
189+
name: 'header',
190+
components: [
191+
{
192+
type: 'container',
193+
properties: {
194+
className: 'bg-muted/30 rounded-lg p-6 border',
195+
children: [
196+
{ type: 'text', properties: { value: '📐 Page Template Showcase', className: 'text-2xl font-bold mb-2 block' } },
197+
{ type: 'text', properties: { value: 'This page uses the "header-sidebar-main" template — a full-width header with sidebar + main content below.', className: 'text-muted-foreground block' } },
198+
],
199+
},
200+
},
201+
],
202+
},
203+
{
204+
name: 'sidebar',
205+
width: 'medium',
206+
components: [
207+
{
208+
type: 'container',
209+
properties: {
210+
className: 'bg-card rounded-lg p-4 border space-y-3',
211+
children: [
212+
{ type: 'text', properties: { value: '🧭 Available Templates', className: 'font-semibold mb-3 block' } },
213+
{ type: 'text', properties: { value: '• **default** — Full-width single column', className: 'text-sm block' } },
214+
{ type: 'text', properties: { value: '• **header-sidebar-main** — Header + sidebar/main (this page)', className: 'text-sm block' } },
215+
{ type: 'text', properties: { value: '• **three-column** — Sidebar + main + aside', className: 'text-sm block' } },
216+
{ type: 'text', properties: { value: '• **dashboard** — 2-column grid layout', className: 'text-sm block' } },
217+
],
218+
},
219+
},
220+
{
221+
type: 'container',
222+
properties: {
223+
className: 'bg-card rounded-lg p-4 border space-y-3 mt-4',
224+
children: [
225+
{ type: 'text', properties: { value: '🔧 New Features', className: 'font-semibold mb-3 block' } },
226+
{ type: 'text', properties: { value: '✅ **dependsOn** — Fields shown when parent has a value', className: 'text-sm block' } },
227+
{ type: 'text', properties: { value: '✅ **visibleOn** — Fields shown based on expression', className: 'text-sm block' } },
228+
{ type: 'text', properties: { value: '✅ **ActionParams** — Parameter dialog before actions', className: 'text-sm block' } },
229+
{ type: 'text', properties: { value: '✅ **Page Templates** — Predefined page layouts', className: 'text-sm block' } },
230+
],
231+
},
232+
},
233+
],
234+
},
235+
{
236+
name: 'main',
237+
components: [
238+
{
239+
type: 'container',
240+
properties: {
241+
className: 'space-y-6',
242+
children: [
243+
{
244+
type: 'container',
245+
properties: {
246+
className: 'bg-card rounded-lg p-6 border',
247+
children: [
248+
{ type: 'text', properties: { value: '## FormField.dependsOn', className: 'text-xl font-semibold mb-3 block' } },
249+
{ type: 'text', properties: { value: 'Fields with `dependsOn` are only shown when the parent field has a value. For example, "Sub-Category" depends on "Category" — it only appears after a category is selected.', className: 'text-muted-foreground mb-3 block' } },
250+
{ type: 'text', properties: { value: '```typescript\n{ field: "sub_category", dependsOn: "category" }\n```', className: 'font-mono text-sm bg-muted/50 p-3 rounded block' } },
251+
],
252+
},
253+
},
254+
{
255+
type: 'container',
256+
properties: {
257+
className: 'bg-card rounded-lg p-6 border',
258+
children: [
259+
{ type: 'text', properties: { value: '## FormField.visibleOn', className: 'text-xl font-semibold mb-3 block' } },
260+
{ type: 'text', properties: { value: 'Fields with `visibleOn` are shown/hidden based on a dynamic expression. For example, pricing fields only appear when status is "active" or "review".', className: 'text-muted-foreground mb-3 block' } },
261+
{ type: 'text', properties: { value: '```typescript\n{ field: "price", visibleOn: "${data.status === \'active\'}" }\n```', className: 'font-mono text-sm bg-muted/50 p-3 rounded block' } },
262+
],
263+
},
264+
},
265+
{
266+
type: 'container',
267+
properties: {
268+
className: 'bg-card rounded-lg p-6 border',
269+
children: [
270+
{ type: 'text', properties: { value: '## ActionParam Collection', className: 'text-xl font-semibold mb-3 block' } },
271+
{ type: 'text', properties: { value: 'Actions can define `params` to collect from the user before execution. A dialog with form fields is shown automatically. Supports text, number, boolean, select, date, and textarea inputs.', className: 'text-muted-foreground mb-3 block' } },
272+
{ type: 'text', properties: { value: '```typescript\nactions: [{\n name: "assign_owner",\n type: "api",\n params: [\n { name: "owner_email", label: "Email", type: "text", required: true },\n { name: "notify", label: "Notify", type: "boolean", defaultValue: true }\n ]\n}]\n```', className: 'font-mono text-sm bg-muted/50 p-3 rounded block' } },
273+
],
274+
},
275+
},
276+
],
277+
},
278+
},
279+
],
280+
},
281+
],
282+
},
283+
// Help page
165284
{
166285
name: 'showcase_help',
167286
label: 'Help & Resources',
@@ -176,11 +295,13 @@ export default defineStack({
176295
className: 'prose max-w-3xl mx-auto p-8 text-foreground',
177296
children: [
178297
{ type: 'text', properties: { value: '# Platform Showcase', className: 'text-3xl font-bold mb-6 block' } },
179-
{ type: 'text', properties: { value: 'This app demonstrates the full range of ObjectStack capabilities — all field types, dashboard widgets, and page layouts.', className: 'text-muted-foreground mb-6 block' } },
298+
{ type: 'text', properties: { value: 'This app demonstrates the full range of ObjectStack capabilities — all field types, dashboard widgets, page layouts, conditional forms, and action parameters.', className: 'text-muted-foreground mb-6 block' } },
180299
{ type: 'text', properties: { value: '## Supported Field Types', className: 'text-xl font-semibold mb-3 block' } },
181300
{ type: 'text', properties: { value: '- **Text** — Text, Textarea, Code, Password, Rich Text\n- **Number** — Integer, Currency, Percentage, Rating\n- **Date** — Date, DateTime\n- **Selection** — Select (single), Multi-Select\n- **Contact** — Email, URL, Phone\n- **Media** — Image, File, Avatar, Signature\n- **Special** — Boolean, Color, Location, Formula, Auto Number\n- **Relations** — Lookup (references other objects)', className: 'whitespace-pre-line mb-6 block' } },
182301
{ type: 'text', properties: { value: '## Dashboard Widgets', className: 'text-xl font-semibold mb-3 block' } },
183302
{ type: 'text', properties: { value: '- **Metric** — KPI cards with trends\n- **Bar Chart** — Categorical comparisons\n- **Donut Chart** — Proportional breakdowns\n- **Area Chart** — Time-series trends\n- **Line Chart** — Multi-series trends\n- **Table** — Tabular data summaries', className: 'whitespace-pre-line mb-6 block' } },
303+
{ type: 'text', properties: { value: '## New Features', className: 'text-xl font-semibold mb-3 block' } },
304+
{ type: 'text', properties: { value: '- **FormField.dependsOn** — Conditional field visibility based on parent field value\n- **FormField.visibleOn** — Expression-based field visibility\n- **ActionParam UI** — Parameter collection dialog before action execution\n- **Page Templates** — Predefined page layout templates (default, header-sidebar-main, three-column, dashboard)', className: 'whitespace-pre-line mb-6 block' } },
184305
{ type: 'text', properties: { value: '## View Types', className: 'text-xl font-semibold mb-3 block' } },
185306
{ type: 'text', properties: { value: '- **Grid** — Default tabular view with sort, filter, search\n- **Kanban** — Card board grouped by any select field\n- **Calendar** — Date-based event visualization\n- **Gantt** — Project timeline with dependencies\n- **Timeline** — Chronological activity stream\n- **Map** — Geographic data on interactive map\n- **Gallery** — Visual card grid layout', className: 'whitespace-pre-line block' } },
186307
],
@@ -196,7 +317,7 @@ export default defineStack({
196317
version: '1.0.0',
197318
type: 'app',
198319
name: 'Platform Showcase',
199-
description: 'Demonstrates all field types, views, and dashboard widgets',
320+
description: 'Demonstrates all field types, views, dashboard widgets, conditional forms, actions, and page templates',
200321
data: [
201322
{
202323
object: 'kitchen_sink',
@@ -286,6 +407,97 @@ export default defineStack({
286407
},
287408
],
288409
},
410+
{
411+
object: 'showcase',
412+
mode: 'upsert',
413+
records: [
414+
{
415+
title: 'Cloud Infrastructure Setup',
416+
description: 'Setting up cloud infrastructure for production deployment',
417+
category: 'software',
418+
sub_category: 'web_app',
419+
priority: 'high',
420+
status: 'active',
421+
price: 4500,
422+
discount_percent: 10,
423+
start_date: daysFromNow(-10),
424+
end_date: daysFromNow(30),
425+
owner_email: 'alice@example.com',
426+
is_featured: true,
427+
tags: ['new', 'premium'],
428+
},
429+
{
430+
title: 'Server Rack Procurement',
431+
description: 'Purchasing new server racks for data center expansion',
432+
category: 'hardware',
433+
sub_category: 'server',
434+
priority: 'medium',
435+
status: 'review',
436+
price: 12000,
437+
discount_percent: 5,
438+
start_date: daysFromNow(-5),
439+
end_date: daysFromNow(60),
440+
owner_email: 'bob@example.com',
441+
is_featured: false,
442+
tags: ['popular'],
443+
},
444+
{
445+
title: 'Security Consulting',
446+
description: 'Annual security audit and consulting engagement',
447+
category: 'service',
448+
sub_category: 'consulting',
449+
priority: 'critical',
450+
status: 'active',
451+
price: 8000,
452+
start_date: daysFromNow(0),
453+
end_date: daysFromNow(90),
454+
owner_email: 'carol@example.com',
455+
is_featured: true,
456+
tags: ['premium'],
457+
},
458+
{
459+
title: 'Mobile App Redesign',
460+
description: 'Redesigning the mobile application with new UX patterns',
461+
category: 'software',
462+
sub_category: 'mobile_app',
463+
priority: 'high',
464+
status: 'draft',
465+
start_date: daysFromNow(15),
466+
owner_email: 'dave@example.com',
467+
is_featured: false,
468+
tags: ['new'],
469+
},
470+
{
471+
title: 'Laptop Refresh Program',
472+
description: 'Annual laptop replacement for engineering team',
473+
category: 'hardware',
474+
sub_category: 'laptop',
475+
priority: 'low',
476+
status: 'completed',
477+
price: 25000,
478+
discount_percent: 15,
479+
start_date: daysFromNow(-60),
480+
end_date: daysFromNow(-5),
481+
owner_email: 'eve@example.com',
482+
is_featured: false,
483+
tags: ['sale'],
484+
},
485+
{
486+
title: 'Team Training Workshop',
487+
description: 'Advanced TypeScript and React training for the team',
488+
category: 'service',
489+
sub_category: 'training',
490+
priority: 'medium',
491+
status: 'active',
492+
price: 3000,
493+
start_date: daysFromNow(20),
494+
end_date: daysFromNow(22),
495+
owner_email: 'frank@example.com',
496+
is_featured: true,
497+
tags: ['popular', 'new'],
498+
},
499+
],
500+
},
289501
],
290502
},
291503
});

0 commit comments

Comments
 (0)