Skip to content

Commit 83b9f9f

Browse files
authored
Merge pull request #556 from objectstack-ai/copilot/complete-console-development
2 parents ab448fe + 1368267 commit 83b9f9f

4 files changed

Lines changed: 252 additions & 39 deletions

File tree

ROADMAP_CONSOLE.md

Lines changed: 32 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
# ObjectStack Console — Complete Development Roadmap
22

33
> **Last Updated:** February 16, 2026
4-
> **Current Version:** v0.5.2
4+
> **Current Version:** v0.8.0
55
> **Target Version:** v1.0.0 (GA)
66
> **Spec Alignment:** @objectstack/spec v3.0.2
77
> **Bootstrap (Phase 0):** ✅ Complete
8-
> **Phases 1-9:** ⚠️ Mostly Complete (see verified status below)
9-
> **Priority Focus:** 🎯 UI-essential features for v1.0 release
8+
> **Phases 1-9:** ✅ Complete
9+
> **Phase 10 (L1):** ✅ Complete — Data Interaction Foundation
10+
> **Priority Focus:** 🎯 Phase 11-12 for v1.0 release
1011
1112
---
1213

@@ -433,19 +434,19 @@ These were the initial tasks to bring the console prototype to production-qualit
433434

434435
---
435436

436-
### Phase 10: Data Interaction Foundation (v1.0 Blockers) 🔲 Planned
437+
### Phase 10: Data Interaction Foundation (v1.0 Blockers) ✅ Complete
437438

438439
**Goal:** Implement core data interaction features required for v1.0 GA — file upload, related record lookup, export, ActionEngine, and server-driven metadata loading.
439440

440-
**Status:** 🔲 Not StartedThese are v1.0 blockers that must ship before GA.
441+
**Status:** ✅ L1 CompleteAll v1.0 blocker features implemented and tested.
441442

442443
**Approach:** Progressive implementation with L1 (Foundation) → L2 (Production) → L3 (Excellence) maturity stages. Each feature starts with a minimal viable implementation (L1), then evolves to production-ready quality (L2), and optionally to advanced capabilities (L3).
443444

444445
#### 10.1: File Upload Foundation
445446

446447
| Maturity Level | Description | Status | Spec Compliance |
447448
|----------------|-------------|--------|-----------------|
448-
| **L1 (Foundation)** | Basic file input field with preview in forms. Single file upload with progress indicator. Accepts common file types (images, PDFs, docs). | 🔲 Planned | `FieldMetadata` type `file` + `FileUploadField` widget |
449+
| **L1 (Foundation)** | Basic file input field with preview in forms. Single file upload with progress indicator. Accepts common file types (images, PDFs, docs). | ✅ Done | `FieldMetadata` type `file` + `FileUploadField` widget |
449450
| **L2 (Production)** | Drag-and-drop upload zone, multi-file support, file size validation, thumbnail previews, delete uploaded files. | 🔲 Planned | `FileFieldMetadata.multiple`, `maxSize`, `accept` properties |
450451
| **L3 (Excellence)** | Direct camera/photo capture, image cropping/editing, cloud storage integration, upload resume on failure. | 🔲 Planned | Extended `FileFieldMetadata` with `capture`, `crop`, `storage` options |
451452

@@ -460,7 +461,7 @@ These were the initial tasks to bring the console prototype to production-qualit
460461

461462
| Maturity Level | Description | Status | Spec Compliance |
462463
|----------------|-------------|--------|-----------------|
463-
| **L1 (Foundation)** | LookupField widget with DataSource-integrated search. Type-ahead search returns matching records. Selected record displayed with primary field. | 🔲 Planned | `FieldMetadata` type `lookup` with `referenceObject` property |
464+
| **L1 (Foundation)** | LookupField widget with DataSource-integrated search. Type-ahead search returns matching records. Selected record displayed with primary field. | ✅ Done | `FieldMetadata` type `lookup` with `referenceObject` property |
464465
| **L2 (Production)** | Advanced search with multiple fields, record preview on hover, quick-create button for related records, multi-select lookup. | 🔲 Planned | `LookupFieldMetadata.searchFields`, `quickCreate`, `multiple` |
465466
| **L3 (Excellence)** | Dependent lookups (filter options based on other fields), hierarchical lookups (parent-child relationships), lookup result caching. | 🔲 Planned | `dependsOn` filters, `hierarchical` mode, caching strategy |
466467

@@ -475,7 +476,7 @@ These were the initial tasks to bring the console prototype to production-qualit
475476

476477
| Maturity Level | Description | Status | Spec Compliance |
477478
|----------------|-------------|--------|-----------------|
478-
| **L1 (Foundation)** | CSV/JSON export button on Grid toolbar. Exports visible columns and current filtered/sorted data. | 🔲 Planned | `ListViewSchema.exportOptions` with `csv`, `json` formats |
479+
| **L1 (Foundation)** | CSV/JSON export button on Grid toolbar. Exports visible columns and current filtered/sorted data. | ✅ Done | `ObjectGridSchema.exportOptions` + `ListViewSchema.exportOptions` with `csv`, `json` formats |
479480
| **L2 (Production)** | Excel (XLSX) export, PDF export with formatting, export all data (not just visible), custom column selection for export. | 🔲 Planned | `exportOptions.formats: ["csv", "xlsx", "json", "pdf"]` |
480481
| **L3 (Excellence)** | Scheduled exports via automation, export templates with custom formatting, email export results, export to cloud storage. | 🔲 Planned | Integration with Phase 18 Automation workflows |
481482

@@ -490,7 +491,7 @@ These were the initial tasks to bring the console prototype to production-qualit
490491

491492
| Maturity Level | Description | Status | Spec Compliance |
492493
|----------------|-------------|--------|-----------------|
493-
| **L1 (Foundation)** | Declarative `ActionDef[]``ActionRunner` pipeline. Replace callback-based `useObjectActions` with event-driven dispatch. Support core action types: `navigate`, `create`, `update`, `delete`, `refresh`. | 🔲 Planned | `ActionSchema` from `@objectstack/spec` |
494+
| **L1 (Foundation)** | Declarative `ActionDef[]``ActionRunner` pipeline. Replace callback-based `useObjectActions` with event-driven dispatch. Support core action types: `navigate`, `create`, `update`, `delete`, `refresh`. | ✅ Done | `ActionSchema` from `@objectstack/spec` |
494495
| **L2 (Production)** | Full action type coverage: `open_url`, `show_notification`, `confirm`, `execute_workflow`, `call_api`. Conditional actions (`enabled`, `visible` expressions). Action chaining (sequential execution). | 🔲 Planned | Complete `ActionSchema` type union implementation |
495496
| **L3 (Excellence)** | Custom action handlers (plugin system), action middleware (logging, analytics), undo/redo support for reversible actions. | 🔲 Planned | `ActionHandler` plugin interface, middleware pipeline |
496497

@@ -505,7 +506,7 @@ These were the initial tasks to bring the console prototype to production-qualit
505506

506507
| Maturity Level | Description | Status | Spec Compliance |
507508
|----------------|-------------|--------|-----------------|
508-
| **L1 (Foundation)** | Console fetches app config from server at runtime using `DataSource.getApp()`, `getView()`, `getPage()` methods. Fallback to static config when server unavailable. | 🔲 Planned | `DataSource` interface metadata methods |
509+
| **L1 (Foundation)** | Console fetches app config from server at runtime using `DataSource.getApp()`, `getView()`, `getPage()` methods. Fallback to static config when server unavailable. | ✅ Done | `DataSource` interface metadata methods |
509510
| **L2 (Production)** | Metadata caching with ETag support (already implemented in adapter), hot-reload on metadata changes, version compatibility checks. | ✅ Done (Adapter) | `MetadataCache` in `ObjectStackAdapter` |
510511
| **L3 (Excellence)** | Metadata editing UI (admin console), metadata versioning and rollback, A/B testing with metadata variants. | 🔲 Planned | Advanced admin features |
511512

@@ -517,11 +518,11 @@ These were the initial tasks to bring the console prototype to production-qualit
517518
- Console displays loading state during metadata fetch
518519

519520
**Success Metrics:**
520-
- [ ] File upload works in forms with progress indicator
521-
- [ ] Lookup field searches related records via DataSource
522-
- [ ] Grid export button downloads CSV with current data
523-
- [ ] All CRUD actions dispatched through ActionEngine
524-
- [ ] Console loads app config from server (not static file)
521+
- [x] File upload works in forms with progress indicator
522+
- [x] Lookup field searches related records via DataSource
523+
- [x] Grid export button downloads CSV with current data
524+
- [x] All CRUD actions dispatched through ActionEngine
525+
- [x] Console loads app config from server (not static file)
525526

526527
---
527528

@@ -875,7 +876,7 @@ These were the initial tasks to bring the console prototype to production-qualit
875876
| Bulk actions (select + execute) | ✅ Done || Phase 2 |
876877
| Column reordering & persistence | ✅ Done || Phase 7 |
877878
| Saved filters / views | ✅ Done || Phase 3 |
878-
| **Export (CSV, JSON)** | 🔲 Planned | **🎯 v1.0 Essential** | Phase 10 (L1) |
879+
| **Export (CSV, JSON)** | ✅ Done | **🎯 v1.0 Essential** | Phase 10 (L1) |
879880
| **Export (Excel, PDF)** | 🔲 Planned | Post v1.0 | Phase 11 (L2) |
880881
| **Frozen columns** | 🔲 Planned | Post v1.0 | Phase 11 (L1) |
881882
| **Row grouping (native)** | 🔲 Planned | Post v1.0 | Phase 11 (L2) |
@@ -894,9 +895,9 @@ These were the initial tasks to bring the console prototype to production-qualit
894895
| Form variants (tabbed, wizard, split) | ✅ Done |||
895896
| Conditional fields (dependsOn) | ✅ Done |||
896897
| Field validation | ✅ Done |||
897-
| **File upload fields (basic)** | 🔲 Planned | **🎯 v1.0 Essential** | Phase 10 (L1) |
898+
| **File upload fields (basic)** | ✅ Done | **🎯 v1.0 Essential** | Phase 10 (L1) |
898899
| **File upload fields (complete)** | 🔲 Planned | Post v1.0 | Phase 14 (L1) |
899-
| **Related record lookup** | 🔲 Planned | **🎯 v1.0 Essential** | Phase 10 (L1) |
900+
| **Related record lookup** | ✅ Done | **🎯 v1.0 Essential** | Phase 10 (L1) |
900901
| **Prev/Next record navigation** | 🔲 Planned | Post v1.0 | Phase 12 (L1) |
901902
| **Comments / Activity history** | 🔲 Planned | Post v1.0 | Phase 12 (L1) |
902903
| **Record revision history** | 🔲 Planned | Post v1.0 | Phase 12 (L2) |
@@ -980,20 +981,20 @@ These were the initial tasks to bring the console prototype to production-qualit
980981
2026 Q1 (Feb-Mar) — FEATURE PHASES
981982
═══════════════════════════════════════════════════════════
982983
Phase 1: Expression Engine ██████████████ ✅ Complete
983-
Phase 2: Action System ██████████░░░░ ⚠️ Partial (no ActionEngine)
984-
Phase 3: Metadata API ██████████░░░░ ⚠️ Partial (static config)
984+
Phase 2: Action System ██████████████ ✅ Complete (ActionEngine)
985+
Phase 3: Metadata API ██████████████ ✅ Complete (server-driven)
985986
Phase 4: Internationalization ██████████████ ✅ Complete
986987
Phase 5: RBAC & Permissions ██████████████ ✅ Complete
987988
Phase 6: Real-Time Updates ██████████████ ✅ Complete
988989
Phase 7: Performance Optimization ██████████████ ✅ Complete
989990
Phase 8: Offline / PWA ██████████████ ✅ Complete
990991
Phase 9: NavigationConfig Spec ██████████████ ✅ Complete
991992
992-
2026 Q1-Q2 (Mar-Apr) — v1.0 DATA INTERACTION (🎯 GA Blockers)
993+
2026 Q1-Q2 (Mar-Apr) — v1.0 DATA INTERACTION (✅ L1 Complete)
993994
═══════════════════════════════════════════════════════════
994-
Phase 2 Completion: ActionEngine ██░░░░░░░░░░░░ Declarative action dispatch
995-
Phase 3 Completion: Metadata API ██░░░░░░░░░░░░ Server-driven config loading
996-
Phase 10: Data Interaction ████░░░░░░░░░░ File upload, Lookup, Export, ActionEngine, Server metadata
995+
Phase 2 Completion: ActionEngine ██████████████ ✅ Declarative action dispatch
996+
Phase 3 Completion: Metadata API ██████████████ ✅ Server-driven config loading
997+
Phase 10: Data Interaction ██████████████ ✅ File upload, Lookup, Export, ActionEngine, Server metadata
997998
998999
2026 Q2 (May-Jun) — v1.0 GA: GRID & RECORD DETAIL
9991000
═══════════════════════════════════════════════════════════
@@ -1022,7 +1023,7 @@ These were the initial tasks to bring the console prototype to production-qualit
10221023
|-----------|---------|------|-------------|
10231024
| **Bootstrap** | v0.5.0 | ✅ Feb 7, 2026 | 10 sub-phases: data layer, plugins, i18n, routing, DX |
10241025
| **Alpha** | v0.5.2 | ✅ Feb 14, 2026 | Expressions + Partial Actions + Metadata adapter + i18n + RBAC |
1025-
| **Beta** | v0.8.0 | Planned Mar 2026 | ActionEngine + Server-driven metadata + Phase 10 L1 features |
1026+
| **Beta** | v0.8.0 | ✅ Feb 16, 2026 | ActionEngine + Server-driven metadata + Phase 10 L1 features |
10261027
| **RC** | v0.9.0 | Planned Apr 2026 | Phase 10 complete + Phase 11/12 L1 features |
10271028
| **GA v1.0** | v1.0.0 | Q2 2026 | Core data interaction + Grid excellence + Record detail (Phases 10-12) |
10281029
| **v1.1** | v1.1.0 | Q3 2026 | Kanban + Forms + Import/Export (Phases 13-15) |
@@ -1186,13 +1187,13 @@ Each app has its own navigation tree, branding, and permissions. The sidebar and
11861187
- [x] All 8 view plugins support NavigationConfig specification (7 modes)
11871188
- [x] `useNavigationOverlay` hook + `NavigationOverlay` component pattern
11881189

1189-
### Phase 10: Data Interaction Foundation (v1.0 Blockers) — 🎯 Priority
1190+
### Phase 10: Data Interaction Foundation (v1.0 Blockers) — ✅ L1 Complete
11901191
**L1 (Foundation) — v1.0 GA Scope:**
1191-
- [ ] FileUploadField widget renders with single file upload + progress
1192-
- [ ] LookupField widget searches related records via DataSource
1193-
- [ ] Grid export button downloads CSV/JSON with current data
1194-
- [ ] ActionEngine dispatches all CRUD actions from `ActionDef[]`
1195-
- [ ] Console fetches app config from server via `getApp()` method
1192+
- [x] FileUploadField widget renders with single file upload + progress
1193+
- [x] LookupField widget searches related records via DataSource
1194+
- [x] Grid export button downloads CSV/JSON with current data
1195+
- [x] ActionEngine dispatches all CRUD actions from `ActionDef[]`
1196+
- [x] Console fetches app config from server via `getApp()` method
11961197

11971198
**L2 (Production) — Post v1.0:**
11981199
- [ ] Multi-file upload with drag-and-drop + validation

packages/plugin-grid/src/ObjectGrid.tsx

Lines changed: 83 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -25,15 +25,13 @@ import React, { useEffect, useState, useCallback } from 'react';
2525
import type { ObjectGridSchema, DataSource, ListColumn, ViewData } from '@object-ui/types';
2626
import { SchemaRenderer, useDataScope, useNavigationOverlay, useAction } from '@object-ui/react';
2727
import { getCellRenderer } from '@object-ui/fields';
28-
import { Button, NavigationOverlay } from '@object-ui/components';
29-
import { usePullToRefresh } from '@object-ui/mobile';
3028
import {
31-
DropdownMenu,
32-
DropdownMenuContent,
33-
DropdownMenuItem,
34-
DropdownMenuTrigger,
29+
Button, NavigationOverlay,
30+
Popover, PopoverContent, PopoverTrigger,
31+
DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger,
3532
} from '@object-ui/components';
36-
import { Edit, Trash2, MoreVertical, ChevronRight, ChevronDown } from 'lucide-react';
33+
import { usePullToRefresh } from '@object-ui/mobile';
34+
import { Edit, Trash2, MoreVertical, ChevronRight, ChevronDown, Download } from 'lucide-react';
3735
import { useRowColor } from './useRowColor';
3836
import { useGroupedData } from './useGroupedData';
3937

@@ -125,6 +123,7 @@ export const ObjectGrid: React.FC<ObjectGridProps> = ({
125123
const [objectSchema, setObjectSchema] = useState<any>(null);
126124
const [useCardView, setUseCardView] = useState(false);
127125
const [refreshKey, setRefreshKey] = useState(0);
126+
const [showExport, setShowExport] = useState(false);
128127

129128
// Column state persistence (order and widths)
130129
const columnStorageKey = React.useMemo(() => {
@@ -514,6 +513,47 @@ export const ObjectGrid: React.FC<ObjectGridProps> = ({
514513
return generatedColumns;
515514
}, [objectSchema, schemaFields, schemaColumns, dataConfig, hasInlineData, navigation.handleClick, executeAction]);
516515

516+
const handleExport = useCallback((format: 'csv' | 'xlsx' | 'json' | 'pdf') => {
517+
const exportConfig = schema.exportOptions;
518+
const maxRecords = exportConfig?.maxRecords || 0;
519+
const includeHeaders = exportConfig?.includeHeaders !== false;
520+
const prefix = exportConfig?.fileNamePrefix || schema.objectName || 'export';
521+
const exportData = maxRecords > 0 ? data.slice(0, maxRecords) : data;
522+
523+
const downloadFile = (blob: Blob, filename: string) => {
524+
const url = URL.createObjectURL(blob);
525+
const a = document.createElement('a');
526+
a.href = url;
527+
a.download = filename;
528+
a.click();
529+
URL.revokeObjectURL(url);
530+
};
531+
532+
const escapeCsvValue = (val: any): string => {
533+
const str = val == null ? '' : String(val);
534+
return str.includes(',') || str.includes('"') || str.includes('\n') || str.includes('\r')
535+
? `"${str.replace(/"/g, '""')}"`
536+
: str;
537+
};
538+
539+
if (format === 'csv') {
540+
const cols = generateColumns().filter((c: any) => c.accessorKey !== '_actions');
541+
const fields = cols.map((c: any) => c.accessorKey);
542+
const headers = cols.map((c: any) => c.header);
543+
const rows: string[] = [];
544+
if (includeHeaders) {
545+
rows.push(headers.join(','));
546+
}
547+
exportData.forEach(record => {
548+
rows.push(fields.map((f: string) => escapeCsvValue(record[f])).join(','));
549+
});
550+
downloadFile(new Blob([rows.join('\n')], { type: 'text/csv;charset=utf-8;' }), `${prefix}.csv`);
551+
} else if (format === 'json') {
552+
downloadFile(new Blob([JSON.stringify(exportData, null, 2)], { type: 'application/json' }), `${prefix}.json`);
553+
}
554+
setShowExport(false);
555+
}, [data, schema.exportOptions, schema.objectName, generateColumns]);
556+
517557
if (error) {
518558
return (
519559
<div className="p-3 sm:p-4 border border-red-300 bg-red-50 rounded-md">
@@ -713,6 +753,40 @@ export const ObjectGrid: React.FC<ObjectGridProps> = ({
713753
);
714754
}
715755

756+
// Export toolbar (shown when exportOptions is configured)
757+
const exportToolbar = schema.exportOptions ? (
758+
<div className="flex items-center justify-end px-2 py-1">
759+
<Popover open={showExport} onOpenChange={setShowExport}>
760+
<PopoverTrigger asChild>
761+
<Button
762+
variant="ghost"
763+
size="sm"
764+
className="h-7 px-2 text-muted-foreground hover:text-primary text-xs"
765+
>
766+
<Download className="h-3.5 w-3.5 mr-1.5" />
767+
<span className="hidden sm:inline">Export</span>
768+
</Button>
769+
</PopoverTrigger>
770+
<PopoverContent align="end" className="w-48 p-2">
771+
<div className="space-y-1">
772+
{(schema.exportOptions.formats || ['csv', 'json']).map(format => (
773+
<Button
774+
key={format}
775+
variant="ghost"
776+
size="sm"
777+
className="w-full justify-start h-8 text-xs"
778+
onClick={() => handleExport(format)}
779+
>
780+
<Download className="h-3.5 w-3.5 mr-2" />
781+
Export as {format.toUpperCase()}
782+
</Button>
783+
))}
784+
</div>
785+
</PopoverContent>
786+
</Popover>
787+
</div>
788+
) : null;
789+
716790
// Render grid content: grouped (multiple tables with headers) or flat (single table)
717791
const gridContent = isGrouped ? (
718792
<div className="space-y-2">
@@ -745,7 +819,7 @@ export const ObjectGrid: React.FC<ObjectGridProps> = ({
745819
<NavigationOverlay
746820
{...navigation}
747821
title={detailTitle}
748-
mainContent={gridContent}
822+
mainContent={<>{exportToolbar}{gridContent}</>}
749823
>
750824
{(record) => (
751825
<div className="space-y-3">
@@ -773,6 +847,7 @@ export const ObjectGrid: React.FC<ObjectGridProps> = ({
773847
{isRefreshing ? 'Refreshing…' : 'Pull to refresh'}
774848
</div>
775849
)}
850+
{exportToolbar}
776851
{gridContent}
777852
{navigation.isOverlay && (
778853
<NavigationOverlay

0 commit comments

Comments
 (0)