Last Updated: April 14, 2026 Current Version: v0.5.x Spec Version: @objectstack/spec v3.3.0 Client Version: @objectstack/client v3.3.0 Target UX Benchmark: 🎯 Airtable parity Current Priority: AppShell Navigation · Designer Interaction · View Config Live Preview Sync ✅ · Dashboard Config Panel · Airtable UX Polish · Flow Designer ✅ · App Creation & Editing Flow ✅ · System Settings & App Management ✅ · Right-Side Visual Editor Drawer ✅ · Object Manager & Field Designer ✅ · AI SDUI Chatbot (service-ai + vercel/ai) ✅ · Unified Home Dashboard ✅ · Unified Copilot Skills Architecture ✅
ObjectUI is a universal Server-Driven UI (SDUI) engine built on React + Tailwind + Shadcn. It renders JSON metadata from the @objectstack/spec protocol into pixel-perfect, accessible, and interactive enterprise interfaces.
Where We Are: Foundation is solid and shipping — 35 packages, 99+ components, 6,700+ tests, 80 Storybook stories, 43/43 builds passing, ~85% protocol alignment. SpecBridge, Expression Engine, Action Engine, data binding, all view plugins (Grid/Kanban/Calendar/Gantt/Timeline/Map/Gallery), Record components, Report engine, Dashboard BI features, mobile UX, i18n (11 locales), WCAG AA accessibility, Console through Phase 20 (L3), AppShell Navigation Renderer (P0.1), Flow Designer (P2.4), Feed/Chatter UI (P1.5), App Creation & Editing Flow (P1.11), System Settings & App Management (P1.12), Page/Dashboard Editor Console Integration (P1.11), Right-Side Visual Editor Drawer (P1.11), Console Engine Schema Integration (P1.14), and Unified Home Dashboard (P1.7.1) — all ✅ complete. ViewDesigner has been removed — its capabilities (drag-to-reorder, undo/redo) are now provided by the ViewConfigPanel (right-side config panel).
What Remains: The gap to Airtable-level UX is primarily in:
AppShell — No dynamic navigation renderer from spec JSON (last P0 blocker)✅ Complete- Designer Interaction — DataModelDesigner has undo/redo, field type selectors, inline editing, Ctrl+S save. ViewDesigner has been removed; its capabilities (drag-to-reorder columns via @dnd-kit, undo/redo via useConfigDraft history) are now integrated into ViewConfigPanel (right-side config panel) ✅
View Config Live Preview Sync — Config panel changes sync in real-time for Grid, but✅ Complete — all 7 phases of P1.8.1 done, 100% coverage across all view typesshowSort/showSearch/showFilters/striped/borderednot yet propagated to Kanban/Calendar/Timeline/Gallery/Map/Gantt (see P1.8.1)- Dashboard Config Panel — Airtable-style right-side configuration panel for dashboards (data source, layout, widget properties, sub-editors, type definitions). Widget config live preview sync and scatter chart type switch ✅ fixed (P1.10 Phase 10). Dashboard save/refresh metadata sync ✅ fixed (P1.10 Phase 11). Data provider field override for live preview ✅ fixed (P1.10 Phase 12). Table/Pivot widget enhancements and context-aware config panel ✅ (P1.10 Phase 13).
- Console Advanced Polish — Remaining upgrades for forms, import/export, automation, comments
- PWA Sync — Background sync is simulated only
Upgraded from
@objectstack/spec v3.0.8→v3.0.9on February 22, 2026. UI sub-export is unchanged; changes are in Automation, Kernel, Data, and API layers.
New Protocol Capabilities (v3.0.9):
| Area | What's New | Impact on ObjectUI |
|---|---|---|
| Workflow Nodes | parallel_gateway, join_gateway, boundary_event node types |
ProcessDesigner (P2.1) & Automation builder (P1.6) |
| BPMN Interop | BpmnImportOptions, BpmnExportOptions, BpmnInteropResult, BpmnElementMapping |
plugin-workflow BPMN import/export (P2.4) |
| Wait/Timer Executors | WaitEventType (condition/manual/webhook/timer/signal), WaitExecutorConfig, WaitTimeoutBehavior |
Automation builder wait-step UI (P1.6) |
| Execution Tracking | ExecutionLog, ExecutionStepLog, Checkpoint, ExecutionError, ExecutionStatus |
Automation execution history (P1.6) |
| Flow Edges | conditional edge type, isDefault flag |
ProcessDesigner conditional routing (P2.1) |
| Retry Config | backoffMultiplier, maxRetryDelayMs, jitter on retry policy |
Automation retry settings UI (P1.6) |
| Flow Versioning | FlowVersionHistory, ConcurrencyPolicy, ScheduleState |
Flow version management & scheduling (P1.6) |
| Data Export | ExportFormat, ExportJobStatus, CreateExportJobRequest (export.zod) |
Import/Export feature (P1.3) |
| App Engine | Optional engine: { objectstack: string } on App config |
Version pinning support (P2.4) |
| Package Upgrade | PackageArtifact, ArtifactChecksum, UpgradeContext, DependencyStatus |
Package management (P2.4) |
| Kernel Enhancements | PluginBuildOptions, PluginPublishOptions, PluginValidateOptions, MetadataCategory, NamespaceConflictError |
Plugin development tooling (P2.4) |
UI Sub-Export: No breaking changes — @objectstack/spec/ui types are identical between v3.0.8 and v3.0.9.
Upgraded from
@objectstack/spec v3.0.10→v3.2.0on March 2, 2026. All@objectstack/*packages upgraded to v3.2.0.
Breaking Changes Applied:
- Actions with
type: 'api'now require atargetfield (the API endpoint/handler name). Addedtargetto all API actions across examples (CRM, todo, msw-todo, kitchen-sink).
Upgraded from
@objectstack/spec v3.0.9→v3.0.10on February 25, 2026. UI sub-export adds new Zod schemas andViewFilterRuletype. Dashboard widgets now requireidfield. View filters must use object format.
New Protocol Capabilities (v3.0.10):
| Area | What's New | Impact on ObjectUI |
|---|---|---|
| UI Schemas | DensityModeSchema, ThemeModeSchema, WcagContrastLevelSchema Zod schemas |
Re-exported from @object-ui/types for runtime validation |
| View Filter Rules | ViewFilterRule, ViewFilterRuleSchema — structured filter format { field, operator, value } |
All view filters migrated from tuple ['field', '=', 'value'] to object format |
| Dashboard Widgets | id field now required on DashboardWidgetSchema |
All example dashboard widgets updated with explicit id |
| Filter AST | isFilterAST, parseFilterAST, VALID_AST_OPERATORS in data sub-export |
Filter engine utilities (P2.4) |
| Multi-Tenant | TursoMultiTenantConfig, TenantResolverStrategy, TenantDatabaseLifecycle |
Cloud multi-tenancy (P2.4) |
| Contracts | IAppLifecycleService, IDeployPipelineService, IProvisioningService, ITenantRouter, ISchemaDiffService |
Cloud deployment & lifecycle (P2.4) |
Breaking Changes Applied:
- All CRM view filters converted from
['field', '=', 'value']to[{ field, operator: '=', value }] - All dashboard widgets (kitchen-sink, todo, CRM) given explicit
idfields - Todo active filter converted from
[['status', '!=', 'Done']]to[{ field: 'status', operator: '!=', value: 'Done' }]
Last remaining P0 blocker. Without this, Console cannot render a sidebar from
AppSchemaJSON.
- Implement
AppSchemarenderer consuming spec JSON (name, label, icon, branding) - Build navigation tree renderer (7 nav item types: object, dashboard, page, url, report, action, group)
- Implement
NavigationAreaSchemasupport (business domain partitioning) - Implement mobile navigation modes (drawer/bottom_nav/hamburger)
- Add permission guards (
requiredPermissions,visible) on navigation items
Priority #1. All items below directly affect end-user experience. Target: indistinguishable from Airtable for core CRUD workflows.
Source: ROADMAP_DESIGNER Phase 2. ViewDesigner has been removed — its capabilities (column reorder, undo/redo) are now provided by ViewConfigPanel.
ViewDesigner: (Removed — replaced by ViewConfigPanel)
- Column drag-to-reorder via
@dnd-kit/core(replace up/down buttons with drag handles) - Add
Ctrl+S/Cmd+Skeyboard shortcut to save - Add field type selector dropdown with icons from
DESIGNER_FIELD_TYPES - Column width validation (min/max/pattern check)
- Removed: ViewDesigner replaced by ViewConfigPanel (right-side config panel)
- ViewConfigPanel upgraded: undo/redo integrated into
useConfigDrafthook - ViewConfigPanel upgraded: drag-and-drop column sorting via
@dnd-kit/sortable
DataModelDesigner:
- Entity drag-to-move on canvas
- Inline editing for entity labels (click to edit)
- Field type selector dropdown (replaces hardcoded
'text'type) - Confirmation dialogs for destructive actions (delete entity cascades to relationships)
Shared Infrastructure:
- Implement
useDesignerHistoryhook (command pattern with undo/redo stacks) - Wire undo/redo to DataModelDesigner
- ModalForm responsive optimization: sections layout auto-upgrades modal size, slider for percent/progress fields, tablet 2-column layout
- Camera capture for mobile file upload
- Image cropping/rotation in file fields
- Cloud storage integration (S3, Azure Blob) for file upload
- Upload resume on network failure
- Advanced lookup: dependent lookups (filter based on other fields)
- Hierarchical lookups (parent-child relationships)
- Lookup result caching
- Lookup field dynamic DataSource loading — popup fetches records via
DataSource.find()with$searchdebounce, loading/error/empty states - Lookup field context DataSource — reads DataSource from SchemaRendererContext so forms work without explicit prop
- Lookup field UX polish — arrow key navigation, description field display, quick-create entry, ARIA listbox roles
- Enterprise Record Picker —
RecordPickerDialogcomponent with multi-column table, pagination, search; LookupField two-level interaction (quick-select + "Show All Results" → full picker);lookup_columns/lookup_page_sizeschema config - CRM Enterprise Lookup Metadata — all 14 lookup fields across 8 CRM objects configured with
lookup_columns(type-aware cell rendering),lookup_filters(business-level base filters),description_field; uses post-create injection to bypassObjectSchema.create()Zod stripping; 12 dedicated test cases - Form conditional logic with branching
- Multi-page forms with progress indicator
Spec v3.0.9 introduces a formal Data Export/Import Protocol (
export.zod) with streaming export, import validation, field mapping templates, and scheduled export jobs.
- Excel (XLSX) export with formatting
- PDF export with custom formatting
- Export all data (not just visible rows)
- Custom column selection for export
- Scheduled exports via automation
- Export templates with custom formatting
- Import field mapping UI (map CSV columns to object fields)
- Import validation preview with error correction
- Duplicate detection during import
- Integrate spec v3.0.9
ExportFormatenum (json, csv, xlsx, jsonl, parquet) - Integrate spec v3.0.9
CreateExportJobRequest/ExportJobStatusfor async streaming exports - Import template-based field mapping using spec protocol schemas
- Cross-session undo stack persistence (survive page refresh)
- Undo grouping (batch multiple field changes as one undo step)
- Visual undo history panel (timeline of changes)
- Undo/redo for bulk operations
- @mention notification delivery (email/push)
- Comment search across all records
- Comment pinning/starring
- Activity feed filtering (comments only / field changes only)
- Airtable-style Feed/Chatter UI components (P0/P1/P2):
-
FeedItem/FieldChangeEntry/Mention/Reaction/RecordSubscriptiontypes -
RecordActivityTimeline— unified timeline renderer (filter, pagination, actor display) -
RecordChatterPanel— sidebar/inline/drawer panel (collapsible) -
CommentInput— comment input with Ctrl+Enter submit -
FieldChangeItem— field change history (old→new display values) -
MentionAutocomplete— @mention autocomplete dropdown -
SubscriptionToggle— bell notification toggle -
ReactionPicker— emoji reaction selector -
ThreadedReplies— collapsible comment reply threading - Comprehensive unit tests for all 6 core Feed/Chatter components (96 tests)
- Console
RecordDetailViewintegration:CommentThread→RecordChatterPanelwithFeedItem[]data model - Documentation for Feed/Chatter plugin in
content/docs/plugins/plugin-detail.mdx(purpose/use cases, JSON schema, props, and Console integration forRecordChatterPanel,RecordActivityTimeline, and related components)
-
Spec v3.0.9 significantly expanded the automation/workflow protocol. New node types, BPMN interop, execution tracking, and wait/timer executors are now available in the spec.
- Multi-step automation builder (if-then chains)
- Scheduled automations (cron-based triggers)
- Webhook triggers and actions
- Email notification actions
- Automation execution history and logs
- Support new v3.0.9 workflow node types:
parallel_gateway,join_gateway,boundary_event - BPMN import/export interop (spec provides
BpmnImportOptions,BpmnExportOptions,BpmnInteropResult) - Wait/timer executor UI (
waitEventConfig: condition, manual, webhook, timer, signal events) - Execution tracking dashboard (
ExecutionLog,ExecutionStepLog,Checkpoint,ExecutionError) - Conditional edge support (
conditionaledge type,isDefaultflag on edges) - Enhanced retry configuration UI (
backoffMultiplier,maxRetryDelayMs,jitter) - Flow version history viewer (
FlowVersionHistory) - Concurrency policy configuration (
ConcurrencyPolicy)
- AppShell
AppSchemarenderer (spec-driven sidebar from JSON) - Area switcher with grouped navigation
- User-customizable sidebar (drag reorder, pin favorites)
- Search within sidebar navigation
- Console integration: Navigation search filtering (
filterNavigationItems+SidebarInput) - Console integration: Badge indicators on navigation items (
badge+badgeVariant) - Console integration: Drag reorder upgrade — replace HTML5 DnD with
@dnd-kitviaNavigationRenderer - Console integration: Navigation pin —
useNavPinshook +NavigationRendererenablePinning/onPinToggle - Console integration:
AppSchemaRendererslot system —sidebarHeader,sidebarExtra,sidebarFooterslots for Console customization - Navigation Sync Service —
useNavigationSynchook auto-syncs App navigation tree on Page/Dashboard CRUD (create, delete, rename) with toast + undo - Navigation Sync auto-detection —
NavigationSyncEffectcomponent monitors metadata changes and auto-syncs navigation across ALL apps when pages/dashboards are added or removed - Navigation Sync all-apps convenience API —
sync*AllAppsmethods iterate all apps without requiring explicitappName - ListView Navigation Mode Fix — All 6 navigation modes (page/drawer/modal/split/popover/new_window) now work correctly on row click:
- ✅
pagemode navigates to/record/:recordIddetail page via React Router - ✅
new_windowmode opens correct Console URL in a new browser tab (delegates toonNavigate) - ✅
splitmode renders resizable split panels with main content + detail panel - ✅
popovermode falls back to compact dialog when nopopoverTriggeris provided - ✅
useNavigationOverlayhook delegatesnew_windowtoonNavigatewhen available for app-specific URL control - ✅ plugin-view
handleRowClicksupportssplitandpopoverbranches
- ✅
- HomePage component — Unified landing page displaying all available applications
- Route integration —
/homeroute added with proper authentication guards - App cards grid — Responsive grid layout showing all active apps with icons, descriptions, and branding colors
- QuickActions section — Quick access cards for creating apps, managing objects, and system settings
- Recent items — Display recently accessed objects, dashboards, and pages using
useRecentItemshook - Starred items — Display user-favorited items using
useFavoriteshook with star/unstar toggle - Empty state — Helpful guidance for new users with "Create First App" and "System Settings" CTAs
- i18n support — All labels support internationalization via
useObjectTranslation - RootRedirect update — Root path (
/) now redirects to/homeinstead of first app - Responsive design — Mobile-friendly grid layouts that adapt to screen size
- Airtable/Notion UX pattern — Inspired by industry-leading workspace home pages
- HomeLayout shell — Lightweight layout wrapper with sticky top nav bar, Home branding, and user menu dropdown (Profile, Settings, Sign Out)
- Home page user menu — Complete user menu dropdown in HomeLayout header with avatar, name, email, Profile/Settings/Sign Out actions
- Return-to-Home navigation — "Home" entry in AppSidebar app switcher dropdown for navigating back to
/homefrom any application
Impact: Users now have a unified workspace dashboard that provides overview of all applications, quick actions, and recent activity. This eliminates the previous behavior of auto-redirecting to the first app, giving users better control and visibility.
- Inline ViewConfigPanel for all view types (Airtable-style right sidebar)
- Column visibility toggle from config panel
- Column reorder (move up/down) from config panel with real-time preview
- Sort/filter/group config from right sidebar
- Type-specific options in config panel (kanban/calendar/map/gallery/timeline/gantt)
- Unified create/edit mode (
mode="create"|"edit") — single panel entry point - Unified data model (
UnifiedViewConfig) for view configuration - ViewDesigner removed — its capabilities replaced by ViewConfigPanel (right-side config panel)
- Panel header breadcrumb navigation (Page > List/Kanban/Gallery)
- Collapsible/expandable sections with chevron toggle
- Data section: Sort by (summary), Group by, Prefix field, Fields (count visible)
- Appearance section: Color, Field text color, Row height (icon toggle), Wrap headers, Show field descriptions, Collapse all by default
- User actions section: Edit records inline (→ inlineEdit), Add/delete records inline, Navigation mode (page/drawer/modal/split/popover/new_window/none)
- Calendar endDateField support
- i18n for all 11 locales (en, zh, ja, de, fr, es, ar, ru, pt, ko)
- Live preview: ViewConfigPanel changes sync in real-time to all list types (Grid/Kanban/Calendar/Timeline/Gallery/Map) (all 7 phases complete — see P1.8.1 gap analysis below)
- ✅
showSortadded toObjectViewSchemaand propagated through plugin-view (Grid only) - ✅ Appearance properties (
rowHeight,densityMode) flow throughrenderListViewschema for all view types - ✅
gridSchemain plugin-view includesstriped/borderedfrom active view config (Grid only) - ✅ Plugin
renderContentpassesrowHeight,densityMode,groupBytorenderListViewschema - ✅
useMemodependency arrays expanded to cover full view config - ✅
generateViewSchemapropagatesshowSearch/showSort/showFilters/striped/bordered/colorfromactiveViewfor all view types (hardcodedshowSearch: falseremoved) - ✅ Console
renderListViewpassesshowSort/showSearch/showFilters/striped/bordered/color/filter/sorttofullSchema - ✅
NamedListViewtype declaresshowSearch/showSort/showFilters/striped/bordered/coloras first-class properties - ✅
ListViewSchemaTypeScript interface and Zod schema includeshowSearch/showSort/showFilters/color - ✅ ViewConfigPanel refactored into Page Config (toolbar/shell) and ListView Config (data/appearance) sections
- ✅
NamedListViewtype extended with 24 new properties: navigation, selection, pagination, searchableFields, filterableFields, resizable, densityMode, rowHeight, hiddenFields, exportOptions, rowActions, bulkActions, sharing, addRecord, conditionalFormatting, quickFilters, showRecordCount, allowPrinting, virtualScroll, emptyState, aria - ✅
ListViewSchemaZod schema extended with all new properties - ✅ ViewConfigPanel aligned to full
ListViewSchemaspec: navigation mode, selection, pagination, export sub-config, searchable/filterable/hidden fields, resizable, density mode, row/bulk actions, sharing, addRecord sub-editor, conditional formatting, quick filters, showRecordCount, allowPrinting, virtualScroll, empty state, ARIA accessibility - ✅ Semantic fix:
editRecordsInline→inlineEditfield name alignment (i18n keys, data-testid, component label all unified toinlineEdit) - ✅ Semantic fix:
rowHeightvalues aligned to full spec — all 5 RowHeight enum values (compact/short/medium/tall/extra_tall) now supported in NamedListView, ObjectGridSchema, ListViewSchema, Zod schema, UI, and ObjectGrid rendering (cell classes, cycle toggle, icon mapping) - ✅
clickIntoRecordDetailstoggle added to UserActions section (NamedListView spec field — previously only implicit via navigation mode) - ✅ Strict spec-order alignment: All fields within each section reordered to match NamedListView property declaration order:
- PageConfig: showSort before showFilters; allowExport before navigation (per spec)
- Data: columns → filter → sort (per spec); prefixField after sort
- Appearance: striped/bordered first, then color, wrapHeaders, etc. (per spec)
- UserActions: inlineEdit before clickIntoRecordDetails (per spec)
- ✅ Spec source annotations: Every field annotated with
// spec: NamedListView.*or// UI extensioncomment - ✅ Protocol suggestions documented: description, _source, _groupBy, _typeOptions identified as UI extensions pending spec addition
- ✅ Comprehensive spec field coverage test: All 44 NamedListView properties verified mapped to UI fields; field ordering validated per spec
- ✅ i18n keys verified complete for en/zh and all 10 locale files
- ✅ Console ObjectView fullSchema propagates all 18 new spec properties
- ✅ PluginObjectView renderListView schema propagates all 18 new spec properties
- ✅ Per-view-type integration tests added for Grid/Kanban/Calendar/Timeline/Gantt/Gallery/Map config sync (Phase 7 complete)
- ✅
- Conditional formatting rules (editor in Appearance section)
Ref: Issue #711 — Right-side view config panel changes not syncing in real-time to all list types.
Current Config Property Propagation Matrix:
| Property | Grid | Kanban | Calendar | Timeline | Gallery | Map | Gantt |
|---|---|---|---|---|---|---|---|
showSearch |
✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
showSort |
✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
showFilters |
✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
showHideFields |
✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
showGroup |
✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
showColor |
✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
showDensity |
✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
allowExport |
✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
rowHeight |
✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
densityMode |
✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
striped |
✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
bordered |
✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
groupBy |
N/A | ✅ | N/A | N/A | N/A | N/A | N/A |
color |
✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
filter/sort |
✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| Type-specific options | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
Root Causes (resolved):
→ Now propagates fromgenerateViewSchema(plugin-view): HardcodesshowSearch: falsefor non-grid viewsactiveViewConsole→ Now passes all config propertiesrenderListView: Omits toolbar/display flags fromfullSchema→ Added as first-class propertiesNamedListViewtype: Missing toolbar/display propertiesPlugin→ Now propagated (PR #771)renderListViewschema missing toolbar flags:renderContent→renderListViewschema did not includeshowSearch/showFilters/showSortListView toolbar unconditionally rendered: Search/Filter/Sort buttons always visible regardless of schema flags→ Now conditionally rendered based onschema.showSearch/showFilters/showSort(PR #771);userActions.sort/search/filter/rowHeight/addRecordFormnow override toolbar flags (Issue #737)Hide Fields/Group/Color/Density buttons always visible: No schema property to control visibility→ AddedshowHideFields/showGroup/showColor/showDensitywith conditional rendering (Issue #719)Export toggle broken: ViewConfigPanel writes→ Export now checks bothallowExport: booleanbut ListView checksexportOptionsobjectexportOptions && allowExport !== false; Console clearsexportOptionswhenallowExport === false(Issue #719)→ Fixed tohasExportlogic bug:draft.allowExport !== falsewas always true when undefineddraft.allowExport === true || draft.exportOptions != null(Issue #719)- No per-view-type integration tests: Pending — tests verify config reaches
fullSchema, but per-renderer integration tests still needed → Removedkey={refreshKey}on PluginObjectView: Console wrapped PluginObjectView withkey={refreshKey}, which only changed on save/create, preventing live preview of config changeskey={refreshKey}; props changes now flow naturally without remounting (Issue #784)Navigation overlay not consuming→ Navigation now uses priority:activeView.navigation: Detail overlay only readobjectDef.navigation, ignoring view-level navigation configactiveView.navigation > objectDef.navigation > default drawer(Issue #784)
Phase 1 — Grid/Table View (baseline, already complete):
-
gridSchemaincludesstriped/borderedfromactiveView -
showSort/showSearch/showFilterspassed viaObjectViewSchema -
useMemodependency arrays cover all grid config - ListView toolbar buttons conditionally rendered based on
schema.showSearch/showFilters/showSort(PR #771) -
renderListViewschema includes toolbar toggle flags (showSearch/showFilters/showSort) and display props (striped/bordered/color) (PR #771) - Hide Fields/Group/Color/Density toolbar buttons conditionally rendered via
showHideFields/showGroup/showColor/showDensity(Issue #719) - Export button checks both
exportOptionsandallowExport(Issue #719) -
hasExportlogic fixed — no longer always true whenallowExportis undefined (Issue #719) - ViewConfigPanel includes toggles for
showHideFields/showGroup/showColor/showDensity(Issue #719) -
showHideFields/showGroup/showColor/showDensity/allowExportpropagated through ConsolefullSchemaand PluginObjectViewrenderListView(Issue #719) - Full end-to-end data flow: all ViewConfigPanel props (
inlineEdit/wrapHeaders/clickIntoRecordDetails/addRecordViaForm/addDeleteRecordsInline/collapseAllByDefault/fieldTextColor/prefixField/showDescription) propagated through ConsolefullSchema→ PluginObjectViewrenderListView→ ListView (Issue #719) - ListView forwards
striped/bordered/wrapHeadersto childviewComponentSchema(grid getswrapHeaders, all views getstriped/bordered) (Issue #719) - ViewConfigPanel includes
striped/borderedtoggles in Appearance section (Issue #719) - Type definitions complete:
NamedListView+ListViewSchema+ Zod schema include all 22 view-config properties (Issue #719)
Phase 2 — Kanban Live Preview:
- Propagate
showSort/showSearch/showFiltersthroughgenerateViewSchemakanban branch - Pass
color/striped/borderedinrenderContent→renderListViewfor kanban - Ensure
groupByconfig changes reflect immediately (currently ✅ viarenderListView) - Add integration test: ViewConfigPanel kanban config change → Kanban renderer receives updated props
Phase 3 — Calendar Live Preview:
- Propagate
showSort/showSearch/showFiltersthroughgenerateViewSchemacalendar branch - Pass
filter/sort/appearance properties to calendar renderer in real-time - Verify
startDateField/endDateFieldconfig changes trigger re-render viauseMemodeps - Add integration test: ViewConfigPanel calendar config change → Calendar renderer receives updated props
Phase 4 — Timeline/Gantt Live Preview:
- Propagate
showSort/showSearch/showFiltersthroughgenerateViewSchematimeline/gantt branches - Pass appearance properties (
color,striped,bordered) throughrenderListViewschema - Ensure
dateField/startDateField/endDateFieldconfig changes trigger re-render - Add integration tests for timeline and gantt config sync
Phase 5 — Gallery & Map Live Preview:
- Propagate
showSort/showSearch/showFiltersthroughgenerateViewSchemagallery/map branches - Pass appearance properties through
renderListViewschema for gallery/map - Ensure gallery
imageField/titleFieldand maplocationField/zoom/centerconfig changes trigger re-render - Add integration tests for gallery and map config sync
Phase 6 — Data Flow & Dependency Refactor:
- Add
showSearch/showSort/showFilters/striped/bordered/colortoNamedListViewtype in@object-ui/types - Add
showHideFields/showGroup/showColor/showDensity/allowExporttoNamedListViewandListViewSchematypes and Zod schema (Issue #719) - Add
inlineEdit/wrapHeaders/clickIntoRecordDetails/addRecordViaForm/addDeleteRecordsInline/collapseAllByDefault/fieldTextColor/prefixField/showDescriptiontoNamedListViewandListViewSchematypes and Zod schema (Issue #719) - Update Console
renderListViewto pass all config properties infullSchema - Audit all
useMemo/useEffectdependency arrays inplugin-view/ObjectView.tsxfor missingactiveViewsub-properties — all hooks correctly use wholeactiveViewobject reference; React shallow equality handles sub-property changes - Remove hardcoded
showSearch: falsefromgenerateViewSchema— useactiveView.showSearch ?? schema.showSearchinstead
Phase 7 — End-to-End Integration Tests:
- Per-view-type test: Grid config sync (showSort, showSearch, showFilters, striped, bordered)
- Per-view-type test: Kanban config sync (groupBy, color, showSearch)
- Per-view-type test: Calendar config sync (startDateField, endDateField, showFilters)
- Per-view-type test: Timeline/Gantt config sync (dateField, appearance)
- Per-view-type test: Gallery config sync (imageField, titleField, appearance)
- Per-view-type test: Map config sync (locationField, zoom, center, appearance)
- Cross-view-type test: Switch view type in ViewConfigPanel → verify config properties transfer correctly
Airtable-style right-side configuration panel for dashboards. Phased rollout from shared infrastructure to full type-safe editing.
Phase 0 — Component Infrastructure:
- Extract
ConfigRow/SectionHeaderfromViewConfigPanelinto@object-ui/componentsas reusable primitives - Implement
useConfigDraftgeneric hook for draft state management (dirty tracking, save/discard) - Define
ConfigPanelSchema/ConfigSection/ConfigFieldtypes for schema-driven panel generation - Implement
ConfigFieldRenderersupporting input/switch/select/checkbox/slider/color/icon-group/field-picker/filter/sort/custom - Implement
ConfigPanelRenderer— schema-driven panel with header, breadcrumb, collapsible sections, sticky footer - Add
configPaneli18n keys to all 10 locale files
Phase 1 — Dashboard-Level Config Panel:
- Develop
DashboardConfigPanelsupporting layout (columns/gap/rowHeight), data (refreshInterval), appearance (title/description/theme) - Add Storybook stories for
ConfigPanelRendererandDashboardConfigPanel - Add Vitest tests (65 tests: useConfigDraft 10, ConfigFieldRenderer 22, ConfigPanelRenderer 21, DashboardConfigPanel 12)
Phase 2 — Widget-Level Configuration:
- Support click-to-select widget → sidebar switches to widget property editor (title, type, data binding, layout)
- Implement
WidgetConfigPanelwith schema-driven fields: general (title, description, type), data binding (object, categoryField, valueField, aggregate), layout (width, height), appearance (colorVariant, actionUrl) - Add Vitest tests (14 tests for WidgetConfigPanel)
Phase 3 — Sub-Editor Integration:
- Integrate
FilterBuilderfor dashboard global filters (ConfigFieldRendererfiltertype now renders inline FilterBuilder) - Integrate
SortBuilderfor sort configuration (ConfigFieldRenderersorttype now renders inline SortBuilder) - Add
fieldsprop toConfigFieldtype for filter/sort field definitions - Dropdown filter selector and action button sub-panel visual editing
Phase 4 — Composition & Storybook:
- Build
DashboardWithConfigcomposite component (dashboard + config sidebar) - Support widget selection → WidgetConfigPanel switch with back navigation
- Add Storybook stories for
WidgetConfigPanel,DashboardWithConfig, andDashboardWithConfigClosed - Add Vitest tests (9 tests for DashboardWithConfig)
Phase 5 — Type Definitions & Validation:
- Add
DashboardConfigtypes to@object-ui/types - Add Zod schema validation for
DashboardConfig
Phase 6 — Design Mode Preview Click-to-Select:
- Add
designMode,selectedWidgetId,onWidgetClickprops toDashboardRendererfor preview-area widget selection - Implement click-to-select with primary ring highlight (light/dark theme compatible, a11y focus-visible ring)
- Click empty space to deselect; Escape key to deselect
- Keyboard navigation: ArrowRight/ArrowDown to next widget, ArrowLeft/ArrowUp to previous, Enter/Space to select, Tab/Shift+Tab for focus
- Add
selectedWidgetIdandonWidgetSelectprops toDashboardEditorfor external controlled selection - Sync selection between
DashboardRenderer(preview) andDashboardEditor(drawer) via shared state inDashboardView - Property changes in editor panel instantly reflected in preview (live preview path verified end-to-end)
- Auto-save property changes to backend via DesignDrawer
- Add Vitest tests (15 DashboardRenderer design mode + 9 DashboardEditor external selection + 8 DashboardView integration = 32 new tests)
Phase 7 — Non-Modal Drawer & Property Panel UX Fix:
-
SheetContent— addedhideOverlayprop to conditionally skip the full-screen backdrop overlay -
DesignDrawer—modal={false}+hideOverlayso preview widgets are clickable while drawer is open -
DashboardEditor— property panel renders above widget grid (stackedflex-collayout) for immediate visibility in narrow drawer -
DashboardEditor— property panel uses full width (removed fixedw-72) for better readability in drawer context - Preview click → editor property panel linkage now works end-to-end (select, switch, deselect)
- Add 11 new tests (7 DashboardDesignInteraction integration + 4 DashboardEditor.propertyPanelLayout)
Phase 8 — Inline Config Panel Refactor (ListView Parity):
- Replace
DesignDrawer+DashboardEditorinDashboardViewwith inlineDashboardConfigPanel/WidgetConfigPanel - Right-side panel shows
DashboardConfigPanelwhen no widget selected (dashboard-level properties: columns, gap, refresh, theme) - Right-side panel switches to
WidgetConfigPanelwhen a widget is selected (title, type, data binding, layout, appearance) - Config panels use standard
ConfigPanelRendererwith save/discard/footer (matches ListView/PageDesigner pattern) - Add-widget toolbar moved to main area header (visible only in edit mode)
- Main area remains WYSIWYG preview via
DashboardRendererwithdesignModeclick-to-select - Widget config flattening/unflattening (layout.w ↔ layoutW, layout.h ↔ layoutH)
- Auto-save on config save via
useAdapter().update() - Live preview updates via
onFieldChangecallback - Config draft stabilization via
configVersioncounter (matching ViewConfigPanel'sstableActiveViewpattern) — preventsuseConfigDraftdraft reset on live field changes - Widget delete via
headerExtradelete button in WidgetConfigPanel header -
WidgetConfigPanel— addedheaderExtraprop for custom header actions - Update 21 integration tests (10 DashboardDesignInteraction + 11 DashboardViewSelection) to verify inline config panel pattern, widget deletion, live preview sync
Phase 9 — Design Mode Widget Selection Click-Through Fix:
- Fix: Widget content (charts, tables via
SchemaRenderer) intercepted click events, preventing selection in edit mode - Defense layer 1:
pointer-events-noneonSchemaRenderercontent wrappers disables chart/table hover and tooltip interactivity in design mode - Defense layer 2: Transparent click-capture overlay (
absolute inset-0 z-10) renders on top of widget content in design mode — guarantees click reaches widget handler even if SVG children overridepointer-events - Self-contained (metric) widgets: both
pointer-events-noneon SchemaRenderer + overlay insiderelativewrapper - Card-based (chart/table) widgets:
pointer-events-noneonCardContentinner wrapper + overlay insiderelativeCard - No impact on non-design mode — widgets remain fully interactive when not editing
- Updated SchemaRenderer mock to forward
classNameand include interactive child button for more realistic testing - Add 9 new Vitest tests: pointer-events-none presence/absence, overlay presence/absence, relative positioning, click-to-select on Card-based widgets
Phase 10 — Widget Config Live Preview Sync & Type Switch Fix:
- Fix:
DashboardWithConfigdid not passonFieldChangetoWidgetConfigPanel, preventing live preview of widget config changes - Add internal
liveSchemastate toDashboardWithConfigfor real-time widget preview during editing - Add
configVersioncounter to stabilizeselectedWidgetConfigand preventuseConfigDraftdraft reset loops - Fix:
scatterchart type was not handled inDashboardRendererandDashboardGridLayout— switching to scatter caused errors - Add
scatterto chart type conditions in bothDashboardRenderer.getComponentSchema()andDashboardGridLayout.getComponentSchema() - Fix: Data binding fields (
categoryField,valueField,object,aggregate) from config panel did not affect rendering —getComponentSchema()only read fromoptions.xField/options.yField/options.data - Add widget-level field fallbacks:
widget.categoryField || options.xField,widget.valueField || options.yFieldin bothDashboardRendererandDashboardGridLayout - Support object-chart construction from widget-level fields when no explicit data provider exists (e.g. newly created widgets via config panel)
- Support data-table construction from
widget.objectwhen no data provider exists (table widgets created via config panel) - Add 7 new Vitest tests: scatter chart (2), widget-level field fallbacks (2), object-chart from widget fields, data-table from widget.object, DashboardWithConfig live preview
Phase 11 — Dashboard Save/Refresh Metadata Sync:
- Fix:
saveSchemainDashboardViewdid not callmetadata.refresh()after PATCH — closing config panel showed stale data from cached metadata - Fix:
previewSchemaonly usededitSchemawhenconfigPanelOpen=true— changed toeditSchema || dashboardso edits remain visible after panel close until metadata refreshes - Add
useEffectto clear staleeditSchemawhen metadata refreshes while config panel is closed (seamless transition) - Clear
editSchemaand config panel state on dashboard navigation (dashboardNamechange) - Fix:
DashboardDesignPage.saveSchemadid not callmetadata.refresh()— other pages saw stale dashboard data after save - Add 5 new Vitest tests: metadata refresh after widget save (2), metadata refresh after widget delete (2), metadata refresh after DashboardDesignPage save (1)
Phase 12 — Data Provider Field Override for Live Preview:
- Fix: Widget-level fields (
categoryField,valueField,aggregate,object) did not override data provider config (widget.data.aggregate) — editing these fields in the config panel had no effect on the rendered chart when a data provider was present -
getComponentSchema()inDashboardRendererandDashboardGridLayoutnow merges widget-level fields with data provider aggregate config, with widget-level fields taking precedence - Fix:
objectNamefor table/pivot widgets usedwidgetData.object || widget.object— reversed towidget.object || widgetData.objectso config panel edits to data source are reflected immediately - Fix:
DashboardWithConfigdid not passdesignMode,selectedWidgetId, oronWidgetClicktoDashboardRenderer— widgets could not be selected or live-previewed in the plugin-level component - Add 10 new Vitest tests: widget-level field overrides for aggregate groupBy/field/function (3), objectName precedence for chart/table (2), simultaneous field overrides (1), DashboardWithConfig design mode and widget selection (2), existing live preview tests (2)
Phase 13 — Table/Pivot Widget Enhancements & Context-Aware Config Panel:
- Add
pivottoDASHBOARD_WIDGET_TYPESconstant andWidgetConfigPaneltype options dropdown - Context-aware
WidgetConfigPanel: sections shown/hidden viavisibleWhenbased on widget type — Pivot shows Rows/Columns/Values/Totals, Chart shows Axis & Series, Table shows Columns config - Pivot-specific config: Row field, Column field, Value field, Sort by (Group/Value icon-group), Sort order (↑/↓ icon-group), Show label toggle, Show totals toggle for both rows and columns, Aggregation, Number format
- Chart-specific config: X-axis label, Y-axis label, Show legend toggle
- Table-specific config: Searchable toggle, Pagination toggle
- Breadcrumb adapts to widget type ("Pivot table", "Table", "Chart", "Widget")
- I18nLabel resolution:
WidgetConfigPanelpre-processestitleanddescriptionconfig values viaresolveLabel()to prevent[object Object]display -
DashboardRenderer: widget description rendered in card headers withline-clamp-2; I18nLabel resolved viaresolveLabel() -
ObjectPivotTable: new async-aware pivot wrapper (following ObjectChart pattern) — skeleton loading, error state, no-data-source message, empty state delegation to PivotTable -
ObjectDataTable: new async-aware table wrapper — skeleton loading, error state, empty state, auto-column derivation from fetched data keys -
DashboardRenderer: pivot widgets withobjectNameorprovider: 'object'routed toobject-pivottype (ObjectPivotTable) for async data loading -
DashboardRenderer: table widgets withobjectNameorprovider: 'object'routed toobject-data-tabletype (ObjectDataTable) for async data loading -
DashboardRenderer: grid column clamping — widgetlayout.wclamped toMath.min(w, columns)preventing layout overflow -
MetricWidget: overflow protection —overflow-hiddenon Card,truncateon label/value/description,shrink-0on icon/trend -
PivotTable: friendly empty state with grid icon + "No data available" message instead of empty table body -
PivotTable: improved total/subtotal row styling —bg-muted/40on tfoot,bg-muted/20on row-total column,font-boldon grand total - Add 29 new Vitest tests: ObjectPivotTable (8), ObjectDataTable (6), context-aware sections (6), I18nLabel resolution (2), pivot type option (1), pivot object binding (1), widget description rendering (2), grid column clamping (1), pivot empty state (2)
Migrated the Console ViewConfigPanel from imperative implementation (~1655 lines) to Schema-Driven architecture using
ConfigPanelRenderer+useConfigDraft+ConfigPanelSchema, reducing to ~170 lines declarative wrapper + schema factory.
Phase 1 — Infrastructure & Utils Extraction:
- Extract operator mapping (
SPEC_TO_BUILDER_OP,BUILDER_TO_SPEC_OP),normalizeFieldType,parseSpecFilter,toSpecFilterto sharedview-config-utils.ts - Extract
parseCommaSeparated,parseNumberList,VIEW_TYPE_LABELS,ROW_HEIGHT_OPTIONSto shared utils - Add
deriveFieldOptions,toFilterGroup,toSortItemsbridge helpers - Enhance
ConfigPanelRendererwith accessibility props (panelRef,role,ariaLabel,tabIndex) - Enhance
ConfigPanelRendererwith test ID override props (testId,closeTitle,footerTestId,saveTestId,discardTestId)
Phase 2 — Schema Factory (All Sections):
- Page Config section: label, description, viewType, toolbar toggles (7 switches), navigation mode/width/openNewTab, selection, addRecord sub-editor, export + sub-config, showRecordCount, allowPrinting
- Data section: source, sortBy (expandable), groupBy (grid/gallery), columns selector (expandable w/ reorder), filterBy (expandable), pagination, searchable/filterable/hidden fields (expandable), quickFilters (expandable), virtualScroll (grid only), type-specific options (kanban/calendar/map/gallery/timeline/gantt)
- Appearance section: color (grid/calendar/timeline/gantt), rowHeight (icon group, grid only), wrapHeaders (grid only), showDescription, striped/bordered (grid only), resizable (grid only), conditionalFormatting (expandable, grid/kanban), emptyState (title/message/icon)
- User Actions section: inlineEdit (grid only), addDeleteRecordsInline (grid only), rowActions/bulkActions (expandable, grid/kanban)
- Sharing section: sharingEnabled, sharingVisibility (visibleWhen: sharing.enabled)
- Accessibility section: ariaLabel, ariaDescribedBy, ariaLive
-
ExpandableWidgetcomponent for hook-safe expandable sub-sections within custom render functions
Phase 3 — ViewConfigPanel Wrapper:
- Rewrite ViewConfigPanel as thin wrapper (~170 lines) using
useConfigDraft+buildViewConfigSchema+ConfigPanelRenderer - Stabilize source reference with
useMemokeyed toactiveView.id(prevents draft reset on parent re-renders) - Create/edit mode support preserved (onCreate/onSave, discard behavior)
- All spec format bridging preserved (filter/sort conversion)
Phase 4 — Testing & Validation:
- All 122 existing ViewConfigPanel tests pass (test mock updated for ConfigPanelRenderer + useConfigDraft)
- All 23 ObjectView integration tests pass (test ID and title props forwarded)
- 53 new schema-driven tests (utils + schema factory coverage)
- 14 new ObjectGrid rowHeight tests (all 5 enum values: initialization, cycle, label, toggle visibility)
- Full affected test suite: 2457+ tests across 81+ files, all pass
Phase 5 — Spec Alignment Completion (Issue #745):
- ObjectGrid rowHeight: full 5-enum rendering (cellClassName, cycleRowHeight, icon map) — was hardcoded to 3
- 18 new ViewConfigPanel interaction tests: collapseAllByDefault, showDescription, clickIntoRecordDetails, addDeleteRecordsInline toggles; sharing visibility conditional hide; navigation width/openNewTab conditional rendering; all 5 rowHeight button clicks; boundary tests (empty actions, long labels, special chars); pageSizeOptions input; densityMode/ARIA live enums; addRecord conditional sub-editor; sharing visibility select
- 8 new schema-driven spec tests: accessibility field ordering, emptyState compound field, switch field defaults, comprehensive visibleWhen predicates (sharing, navigation width, navigation openNewTab)
- All spec fields verified: Appearance/UserActions/Sharing/Accessibility sections 100% covered with UI controls, defaults, ordering, and conditional visibility
- Add
descriptionfield toNamedListViewprotocol interface (spec alignment) - Add
disabledWhenpredicate toConfigFieldtype — grid-only fields (striped/bordered/wrapHeaders/resizable) disabled for non-grid views - Add
expandedSectionsprop toConfigPanelRendererfor external section collapse control (auto-focus/highlight) - Add
helpTextto navigation-dependent fields (width/openNewTab) with i18n hints (all 11 locales) - 24 new tests: expandedSections override (3), disabledWhen evaluation (2), grid-only disabledWhen predicates (16), helpText validation (2), description spec alignment (1)
Phase 6 — Config Panel Cleanup (Invalid Items Fix):
- Remove
densityModefield from appearance section (redundant withrowHeightwhich provides finer 5-value granularity) - Remove
prefixFieldfrom data section (not consumed by any runtime renderer) - Remove
collapseAllByDefaultfrom appearance section (not consumed by any runtime renderer) - Remove
fieldTextColorfrom appearance section (not consumed by any runtime renderer) - Remove
clickIntoRecordDetailsfrom userActions section (controlled implicitly via navigation mode, not directly consumed) - Add view-type-aware
visibleWhento toolbar toggles:showGroup(grid/kanban/gallery),showColor(grid/calendar/timeline/gantt),showDensity(grid only),showRecordCount(grid only),allowPrinting(grid only) - Add view-type-aware
visibleWhento data fields:_groupBy(grid/gallery — kanban uses dedicated type-specific option),virtualScroll(grid only) - Add view-type-aware
visibleWhento appearance fields:striped/bordered/wrapHeaders/resizable/rowHeight(grid only, changed from disabledWhen to visibleWhen),color(grid/calendar/timeline/gantt),conditionalFormatting(grid/kanban) - Add view-type-aware
visibleWhento userActions fields:inlineEdit/addDeleteRecordsInline(grid only),rowActions/bulkActions(grid/kanban) - Correct
searchableFields/filterableFields/quickFilters/showDescriptionto universal (all view types) — data fetch/toolbar features not view-specific - Extend
buildSwitchFieldandbuildFieldMultiSelecthelpers to acceptvisibleWhenparameter - Define semantic predicates:
supportsGrouping,supportsColorField,supportsConditionalFormatting,supportsRowActions,supportsGenericGroupBy - 103 schema tests pass (updated field key lists, visibleWhen predicates for all view types, removed field verification)
- 136 ViewConfigPanel interaction tests pass (removed tests for deleted fields)
- 10 config-sync integration tests pass
Phase 7 — Section Restructure & Field Selector Upgrade (Airtable UX Parity):
- Split monolithic
pageConfigsection into 5 clear sub-sections: General, Toolbar, Navigation, Records, Export & Print - General section: label, description, viewType (always expanded, non-collapsible)
- Toolbar section: showSearch, showSort, showFilters, showHideFields, showGroup, showColor, showDensity (collapsible)
- Navigation section: navigation mode, width, openNewTab (collapsible)
- Records section: selection mode, addRecord sub-editor (collapsible)
- Export & Print section: allowExport + sub-config, showRecordCount, allowPrinting (collapsible, defaultCollapsed)
- Field selector upgrade: eye/eye-off toggle for visibility (replacing checkboxes), search input for field filtering, Show All/Hide All batch operations, grip handles for visual reorder hints
- i18n keys added for all 11 locales (en, zh, ja, de, fr, es, ar, ru, pt, ko, + ar)
- 110 schema tests pass (+7 new section tests)
- 136 ViewConfigPanel interaction tests pass (updated for eye toggles, section expansion)
- 31 ObjectView integration tests pass (updated for section expansion)
- 10 config-sync integration tests pass
Phase 8 — Tab Gear Icon, Panel Animation & UX Polish:
- Add
onConfigViewprop to ViewTabBar with Settings2 gear icon on active tab - Wire gear icon in ObjectView: click opens ViewConfigPanel for that view's settings
- Panel slide-in/slide-out animation: CSS transition on max-width + opacity (300ms ease-in-out)
- Auto-sync panel content on view tab switch (ViewConfigPanel resets draft when activeView.id changes)
- 5 new ViewTabBar gear icon tests (show on active, hide on inactive, callback, event isolation)
- 3 new ViewConfigPanel tests (search input, Show All, Hide All)
- 49 ViewTabBar tests pass, 139 ViewConfigPanel tests pass, 31 ObjectView tests pass
Code Reduction: ~1655 lines imperative → ~170 lines declarative wrapper + ~1100 lines schema factory + ~180 lines shared utils = >50% net reduction in component code with significantly improved maintainability
- Add
min-w-0/overflow-hiddento flex layout chain (SidebarInset → AppShell → ObjectView → PluginObjectView) to prevent content overflow - Fix Gantt task list width — responsive sizing (120px mobile, 200px tablet, 300px desktop) instead of hardcoded 300px
- Fix Kanban board overflow containment (
min-w-0on swimlane and flat containers) - Fix Calendar header responsive wrapping and date label sizing
- Fix Map container overflow containment via
cn()merge - Fix Timeline container
min-w-0to prevent overflow - Fix ListView container
min-w-0 overflow-hiddento prevent overflow - Mobile/tablet end-to-end testing for all view types
- Dynamic width calculation for Gantt task list and Kanban columns based on container width
Full app creation & editing experience aligned with Airtable Interface Designer UX. Components live in
@object-ui/plugin-designer, types in@object-ui/types.
Types & Interfaces:
-
AppWizardDraft/AppWizardStep/BrandingConfig/ObjectSelection/EditorModetypes -
isValidAppName()snake_case validator function -
wizardDraftToAppSchema()draft-to-schema conversion function
App Creation Wizard (4-step):
- Step 1: Basic Info — name (snake_case validated), title, description, icon, template, layout selector
- Step 2: Object Selection — card grid with search, select all/none, toggle selection
- Step 3: Navigation Builder — auto-generates NavigationItem[] from selected objects, add group/URL/separator, reorder up/down, remove
- Step 4: Branding — logo URL, primary color, favicon, live preview card
- Step indicator with connected progress dots
- Step validation (step 1 requires valid snake_case name + title)
- onComplete callback returns full AppWizardDraft
Navigation Designer:
- Recursive NavigationItem tree renderer (supports infinite nesting)
- Quick add buttons for all 7 nav item types (object, dashboard, page, report, group, URL, separator)
- Inline label editing (double-click to edit, Enter/Escape to commit/discard)
- Add child to groups (nested navigation)
- Reorder items (up/down buttons)
- Deep tree operations (remove/reorder works at any depth)
- Live preview sidebar showing navigation as rendered
- Type badges with color coding
- Icon editing (inline icon name input with pencil button, Enter/Escape commit/discard)
- Visibility toggle (eye/eye-off icon per node, hidden badge display)
- Export/Import navigation JSON Schema (toolbar buttons with file I/O, custom callbacks)
- Mobile responsive layout (flex-col on mobile, sm:flex-row on desktop)
Page Canvas Editor:
- Component palette (Grid, Kanban, Calendar, Gallery, Dashboard, Form, Layout Grid)
- Component list with drag handles, selection, reorder
- Property panel for selected component (label, type, ID)
- Add/remove/reorder components
- Syncs PageSchema children on every change
- Undo/Redo integration via
useUndoRedohook (Ctrl+Z / Ctrl+Y keyboard shortcuts) - JSON Schema export/import (Download/Upload toolbar buttons with
onExport/onImportcallbacks) - Preview mode toggle (Eye icon, renders PagePreview panel)
- Page/Dashboard mode tab switching (role="tablist" with aria-selected)
- i18n integration via
useDesignerTranslation(all labels use translation keys) - Keyboard shortcuts (Delete/Backspace to remove selected component)
- Mobile responsive layout (flex-col on mobile, sm:flex-row on desktop)
Dashboard Editor:
- 6 widget types (KPI Metric, Bar Chart, Line Chart, Pie Chart, Table, Grid)
- Widget card grid with selection, drag handles, reorder
- Widget property panel (title, type, data source, value field, aggregate, color variant)
- Add/remove/reorder widgets
- Grid layout based on DashboardSchema columns
- Undo/Redo integration via
useUndoRedohook (Ctrl+Z / Ctrl+Y keyboard shortcuts) - JSON Schema export/import (Download/Upload toolbar buttons with
onExport/onImportcallbacks) - Preview mode toggle (Eye icon, renders DashboardPreview panel)
- Widget layout size editing (width/height inputs in property panel)
- i18n integration via
useDesignerTranslation(all labels use translation keys) - Keyboard shortcuts (Delete/Backspace to remove selected widget)
- Mobile responsive layout (flex-col on mobile, sm:flex-row on desktop)
Object View Configurator:
- 7 view type switcher (Grid, Kanban, Calendar, Gallery, Timeline, Map, Gantt)
- Column visibility toggle with visible count
- Column reorder (up/down)
- Toolbar toggles (showSearch, showFilters, showSort)
- Appearance section (row height, striped, bordered)
- Collapsible sections
Editor Mode Toggle:
- Three-way toggle (Edit / Preview / Code)
- Radio group accessibility (role="radiogroup", aria-checked)
- Disabled state support
i18n:
-
appDesignersection with 133 keys added to all 10 locales (en, zh, ja, de, fr, es, ar, ru, pt, ko) -
useDesignerTranslationsafe wrapper hook with English fallback (no I18nProvider required) - AppCreationWizard fully i18n-integrated (all labels, buttons, step names, validation messages)
- NavigationDesigner fully i18n-integrated (type badges, quick-add labels, aria-labels, preview, icon editing, visibility, export/import)
- DashboardEditor fully i18n-integrated (toolbar labels, preview text)
- PageCanvasEditor fully i18n-integrated (toolbar labels, mode tabs, preview text)
- BrandingEditor fully i18n-integrated (14 new keys: editor title, export/import, preview, palette, font, light/dark, mobile preview, sample text)
UX Enhancements:
- Cancel confirmation dialog with unsaved-changes detection
-
onSaveDraftcallback for partial progress save -
useConfirmDialoghook integration for cancel workflow
Testing:
- 11 type tests (isValidAppName, wizardDraftToAppSchema, type shapes)
- 41 AppCreationWizard tests (rendering, steps 1-4, navigation, callbacks, cancel confirm, save draft, i18n, read-only)
- 33 NavigationDesigner tests (rendering, add, remove, groups, badges, i18n, read-only, icon editing, visibility toggle, export/import, responsive)
- 7 EditorModeToggle tests (render, active mode, onChange, accessibility, disabled)
- 22 DashboardEditor tests (rendering, add/remove widgets, property panel, read-only, undo/redo, export/import, preview mode, widget layout)
- 23 PageCanvasEditor tests (rendering, add/remove components, property panel, read-only, mode tabs, undo/redo, export/import, preview mode)
- 12 ObjectViewConfigurator tests (rendering, view type switch, column visibility, toggles, read-only)
- 29 BrandingEditor tests (rendering, editing, light/dark preview, read-only, undo/redo, export/import, keyboard shortcuts, preview content)
- Total: 238 tests across 10 files, all passing
ComponentRegistry:
- Registered:
app-creation-wizard,navigation-designer,dashboard-editor,page-canvas-editor,object-view-configurator,branding-editor
Branding Editor:
- Logo URL input with live preview (light/dark logo placeholders)
- Visual color picker with native
<input type="color">and text hex input - 16-color preset palette swatches (Blue, Indigo, Violet, Purple, Pink, Red, Orange, Amber, Yellow, Green, Teal, Cyan, Sky, Slate, Dark, Navy)
- Favicon URL input with preview
- Font family selector (9 common web fonts + system default)
- Light/Dark mode preview toggle
- Real-time preview panel (desktop + mobile)
- Undo/Redo via
useUndoRedohook (Ctrl+Z / Ctrl+Shift+Z / Ctrl+Y keyboard shortcuts) - JSON Schema export/import (Download/Upload toolbar buttons with
onExport/onImportcallbacks) - Read-only mode support (disables all inputs, palette clicks, undo/redo, import)
- Mobile responsive layout (flex-col on mobile, sm:flex-row on desktop)
- i18n integration via
useDesignerTranslation(14 new translation keys in all 10 locales) - Outputs to
BrandingConfigtype (AppSchema.branding protocol) - 29 unit tests (rendering, editing, light/dark preview, read-only, undo/redo, export/import, keyboard shortcuts, preview content)
Console Integration:
-
CreateAppPage— rendersAppCreationWizardwithuseMetadata()objects,onComplete/onCancel/onSaveDraftcallbacks -
EditAppPage— reuses wizard withinitialDraftfrom existing app config - Routes:
/apps/:appName/create-app,/apps/:appName/edit-app/:editAppName - AppSidebar "Add App" button → navigates to
/create-app - AppSidebar "Edit App" button → navigates to
/edit-app/:appName - CommandPalette "Create New App" command (⌘+K → Actions group)
- Empty state CTA "Create Your First App" when no apps configured
-
wizardDraftToAppSchema()conversion on completion — includesicon,label,brandingfields -
EditAppPagemerges wizard output with original app config to preserve fields not in wizard (e.g.active) -
client.meta.saveItem('app', name, schema)— persists app metadata to backend on create/edit - MSW PUT handler for
/meta/:type/:name— dev/mock mode metadata persistence - MSW handler refactored to use
MSWPlugin+ protocol broker shim — filter/sort/top/pagination now work correctly in dev/mock mode (Issue #858) - Draft persistence to localStorage with auto-clear on success
-
createAppi18n key added to all 10 locales - 13 console integration tests (routes, wizard callbacks, draft persistence, saveItem, CommandPalette)
-
PageDesignPage— integratesPageCanvasEditorat/design/page/:pageNameroute with auto-save, JSON export/import -
DashboardDesignPage— integratesDashboardEditorat/design/dashboard/:dashboardNameroute with auto-save, JSON export/import - "Edit" button on
PageViewandDashboardView— opens right-sideDesignDrawerwith real-time preview (no page navigation) -
DesignDrawercomponent — reusable right-side Sheet panel hosting editors with auto-save, Ctrl+S shortcut, and live schema sync - Ctrl+S/Cmd+S keyboard shortcut to explicitly save in both design pages (with toast confirmation)
- Storybook stories for
PageCanvasEditorandDashboardEditor(Designers/PageCanvasEditor, Designers/DashboardEditor) - 12 console design page tests (PageDesignPage + DashboardDesignPage: routes, 404 handling, editor rendering, onChange, Ctrl+S save)
- 7 DesignDrawer tests (drawer open/close, editor rendering, real-time preview sync, auto-save, Ctrl+S, no navigation)
Unified system settings hub, app management page, and permission management page.
System Hub Page (/system/):
- Card-based overview linking to all system administration sections
- Live statistics for each section (users, orgs, roles, permissions, audit logs, apps)
- Navigation to Apps, Users, Organizations, Roles, Permissions, Audit Log, Profile
App Management Page (/system/apps):
- Full app list with search/filter
- Enable/disable toggle per app
- Set default app
- Delete app with confirmation
- Bulk select with enable/disable operations
- Navigate to Create App / Edit App pages
- Navigate to app home
Permission Management Page (/system/permissions):
- CRUD grid for
sys_permissionobject - Search/filter permissions
- Admin-only create/delete controls
ObjectView-Driven System Pages (P1.12.2):
- Shared
SystemObjectViewPagecomponent usingObjectViewfrom@object-ui/plugin-view - User Management (
/system/users) driven bysys_usermetadata via ObjectView - Organization Management (
/system/organizations) driven bysys_orgmetadata via ObjectView - Role Management (
/system/roles) driven bysys_rolemetadata via ObjectView - Permission Management (
/system/permissions) driven bysys_permissionmetadata via ObjectView - Audit Log (
/system/audit-log) driven bysys_audit_logmetadata via ObjectView (read-only) - Admin-only CRUD operations controlled via ObjectView
operationsconfig - Automatic search, sort, filter, pagination from ObjectView capabilities
- 22 system page tests passing
Sidebar & Navigation Updates:
- Settings button →
/system/hub (was/system/profile) - App switcher "Manage All Apps" link →
/system/apps
Empty State & System Route Accessibility (P1.12.1):
- "Create App" button always shown in empty state (even when config loading fails)
- "System Settings" link always shown alongside "Create App" in empty state
- Top-level
/system/*routes accessible without any app context (promoted to main routes) - Top-level
/create-approute accessible without any app context - Sidebar fallback navigation with system menu items when no apps are configured
- System pages (
SystemHubPage,AppManagementPage) handle missingappNamegracefully - Login/Register/Forgot password pages remain always accessible regardless of app state
Routes:
-
/system/→ SystemHubPage -
/system/apps→ AppManagementPage -
/system/permissions→ PermissionManagementPage -
/system/metadata/:metadataType→ MetadataManagerPage (generic, registry-driven)
Unified Metadata Management (P1.12.3):
- Metadata type registry (
config/metadataTypeRegistry.ts) — centralized config for all metadata types - Generic
MetadataManagerPagefor listing/managing items of any registered type - SystemHubPage auto-generates metadata type cards from registry (dashboard, page, report)
- Dynamic
/system/metadata/:metadataTyperoutes in all route contexts - Generic
MetadataServicemethods:getItems(),saveMetadataItem(),deleteMetadataItem() - Types with custom pages (
app,object) link to existing dedicated routes - Legacy routes preserved — no breaking changes
- 40+ new tests (registry, MetadataManagerPage, MetadataService generic, SystemHubPage registry)
Tests:
- 11 new tests (SystemHubPage, AppManagementPage, PermissionManagementPage)
- Total: 20 system page tests passing
Status: Complete — Grid/List components now match Airtable UX patterns for date formatting, row interactions, editing, density, headers, filters, and empty states.
Date Field Humanized Format:
-
formatDate,formatDateTime,DateTimeCellRendereruse browser locale (undefinedinstead of'en-US') - All date columns auto-format to localized human-readable format (e.g., "2024/2/28 12:57am")
Row Hover "Open >" Button:
- Expand button changed from icon-only
<Expand>to text "Open >" with<ChevronRight>icon - Consistent across Grid and ListView (shown on row hover)
Single-Click Edit Mode:
- Added
singleClickEditprop toDataTableSchemaandObjectGridSchema - When true, clicking a cell enters edit mode (instead of double-click)
Default Compact Row Height:
- ObjectGrid default changed from
'medium'to'compact'(32-36px rows) - ListView default density changed from
'comfortable'to'compact' - Row height toggle preserved in toolbar
Single-Click Edit Mode:
- Added
singleClickEditprop toDataTableSchemaandObjectGridSchema - ObjectGrid defaults
singleClickEdittotrue(click-to-edit by default) - InlineEditing component already compatible (click-to-edit native)
Column Header Minimal Style:
- Headers use
text-xs font-normal text-muted-foreground(wastext-[11px] font-semibold uppercase tracking-wider) - Sort arrows inline with header text
Filter Pill/Chip Styling:
- Filter badges use
rounded-fullfor Airtable-style pill appearance - "More" overflow button matches pill styling
Column Width Auto-Sizing:
- Auto column width estimation based on header and data content (80-400px range)
- Samples up to 50 rows for width calculation
Row Selection Checkbox Style:
- Added
selectionStyleprop ('always'|'hover') toDataTableSchema - 'hover' mode shows checkboxes only on row hover
Empty Table Ghost Row:
- Empty tables show 3 ghost placeholder rows with skeleton-like appearance
- Ghost rows use varying widths for visual variety
Status: Complete — Console now consumes HeaderBarSchema, ViewSwitcher allowCreateView/viewActions, SidebarNav enhanced props, and Airtable UX defaults.
ViewSwitcher allowCreateView & viewActions:
- Added
allowCreateViewandviewActionstoObjectViewSchematype - Engine
ObjectViewpassesallowCreateView/viewActionstoViewSwitcherSchemabuilder - Engine
ObjectViewaccepts and passesonCreateView/onViewActioncallbacks toViewSwitcher - Console
ObjectViewsetsallowCreateView: isAdminandviewActions(settings/share/duplicate/delete) - Console wires
onCreateView→ open ViewConfigPanel in create mode - Console wires
onViewAction('settings')→ open ViewConfigPanel in edit mode
AppHeader HeaderBarSchema Alignment:
- Console
AppHeaderbreadcrumb items typed as engineBreadcrumbItemtype - Breadcrumbs with
siblingsdropdown navigation working - Search triggers ⌘K command palette (desktop + mobile)
SidebarNav Enhanced Props:
- Storybook stories for badges, nested items, NavGroups, search filtering
- Documentation updated for
SidebarNavenhanced API (badges, badgeVariant, children, searchEnabled, NavGroup)
Airtable UX Defaults Propagation:
- Verified: Console
ObjectViewdoes NOT overriderowHeight,density, orsingleClickEdit - Engine defaults (
compactrows,singleClickEdit: true, browser locale dates) flow through correctly
-
useObjectLabelhook in@object-ui/i18n— convention-based resolver (objectLabel,objectDescription,fieldLabel) - Dynamic app namespace discovery (no hardcoded
crm.prefix — scans i18next resources for app namespaces) -
useSafeFieldLabelshared wrapper for plugins without I18nProvider - Wired into Console
ObjectView(breadcrumb, page title, description, drawer title) - Wired into
ObjectGridcolumn headers (ListColumn, string[], auto-generated paths) - Wired into
ListViewtoolbar field labels (hide fields, group by, sort/filter builder) - Wired into
NavigationRenderervia optionalresolveObjectLabel+t()props for full i18n - Wired into Console
AppSidebarto pass resolver andtto NavigationRenderer - Wired into all form variants (ObjectForm, ModalForm, WizardForm, DrawerForm, TabbedForm, SplitForm)
-
I18nProviderloads app-specific translations on mount (fixes initial language loading) - 10 unit tests for
useObjectLabelhook - Zero changes to object metadata files or translation files
Status: Complete —
ObjectManagerandFieldDesignercomponents shipped in@object-ui/plugin-designer. Object Detail View enhanced with Power Apps-style sections.
Enterprise-grade visual designers for managing object definitions and configuring fields. Supports the full metadata platform workflow: define objects, configure fields with advanced properties, and maintain relationships.
Object Manager (ObjectManager):
- CRUD operations on object definitions (custom and system objects)
- Visual configuration of object properties (name, label, plural label, description, icon, group, sort order, enabled)
- Object relationship display and maintenance
- Inline property editor with collapsible sections
- Search/filter functionality
- Grouped object display with badges
- System object protection (non-deletable, name-locked)
- Read-only mode support
- Confirm dialog for destructive actions
- 18 unit tests
Object Detail View (Power Apps alignment):
- Dedicated Relationships section with type badges, foreign key info, and empty state
- Keys section auto-extracting primary keys, unique keys, and external IDs from field metadata
- Data Experience placeholder section (Forms, Views, Dashboards) — UI structure ready for future implementation
- Inline data preview placeholder section (Power Apps sample data grid parity)
- System field non-editable visual hints
- Enhanced object properties card with separated concern sections
Metadata Manager Grid Mode:
-
listMode: 'grid' | 'table' | 'card'configuration on MetadataTypeConfig - Professional table rendering with column headers and action buttons in grid mode
- Report type configured with grid mode by default
- Reusable
MetadataGridcomponent extracted for cross-page reuse
MetadataFormDialog Enhancements:
-
numberfield type — renders HTML number input -
booleanfield type — renders Shadcn Switch toggle with Yes/No label
MetadataDetailPage & Provider Enhancements:
- Auto-redirect for custom page types (object →
/system/objects/:name) — removed; object now uses metadata pipeline -
getItemsByType(type)method on MetadataProvider for dynamic registry access - Object type merged into MetadataManagerPage pipeline —
ObjectManagerPageremoved, replaced byObjectManagerListAdaptervialistComponentextension point -
listComponentextension point on MetadataTypeConfig for injecting custom list UIs - All entry points (sidebar, QuickActions, hub cards) unified to
/system/metadata/object
Technical Debt Cleanup:
- Unified icon resolver — consolidated 3 duplicated ICON_MAP/resolveIcon into shared
getIconutility - Extracted
toObjectDefinition/toFieldDefinitionto sharedutils/metadataConverters.ts
Field Designer (FieldDesigner):
- CRUD operations on field definitions with 27 supported field types
- Advanced field properties: uniqueness, default values, options/picklists, read-only, hidden, validation rules, external ID, history tracking, indexed
- Field grouping, sorting, and layout management
- System reserved field protection
- Type-specific property editors (lookup reference, formula expression, select options)
- Validation rule builder (min, max, minLength, maxLength, pattern, custom)
- Search and type-based filtering
- Read-only mode support
- 22 unit tests
Metadata Persistence (Console Integration):
-
MetadataServiceclass wrappingclient.meta.saveItemfor object/field CRUD -
useMetadataServicehook for adapter-aware service access - Optimistic update pattern with rollback on API failure
- Object create/update persisted via
saveItem('object', name, data) - Object delete via soft-delete (
enabled: false, _deleted: true) - Field changes persisted as part of parent object metadata
- MetadataProvider
refresh()called after successful mutations - Saving state indicators with loading spinners
- Accurate toast messages (create/update/delete action labels, error details)
- 16 new MetadataService unit tests + 2 ObjectManagerPage API integration tests
Type Definitions (@object-ui/types):
-
ObjectDefinition,ObjectDefinitionRelationship,ObjectManagerSchema -
DesignerFieldType(27 types),DesignerFieldOption,DesignerValidationRule -
DesignerFieldDefinition,FieldDesignerSchema
i18n Support:
- Full translations for all 10 locales (en, zh, ja, ko, de, fr, es, pt, ru, ar)
- 50 new translation keys per locale (objectManager + fieldDesigner sections)
- Fallback translations in
useDesignerTranslationfor standalone usage
Status: Complete —
FlowDesignercomponent shipped in@object-ui/plugin-workflow.
The FlowDesigner is a canvas-based flow editor that bridges the gap between the approval-focused WorkflowDesigner and the BPMN-heavyweight ProcessDesigner. It targets automation/integration flows with spec v3.0.9 types.
Core Canvas:
- Drag-to-reposition nodes on SVG canvas
- Undo/redo with keyboard shortcuts (
Ctrl+Z/Ctrl+Y) - Ctrl+S save shortcut with
onSavecallback - Zoom in/out/reset controls
- Edge creation by clicking source → target connection ports
- Node deletion via delete button or
Deletekey
Node Types (spec v3.0.9):
- Standard nodes:
start,end,task,user_task,service_task,script_task,approval,condition - Gateway nodes:
parallel_gateway,join_gateway - Event nodes:
boundary_event,delay,notification,webhook
Edges (spec v3.0.9):
- Edge types:
default,conditional,timeout - Conditional edges with
conditionexpression field -
isDefaultflag for default/fallthrough edge marking - Visual differentiation: dashed lines for conditional, dotted for default
Property Panel:
- Node property editor: label, type, description
- Node executor configuration:
FlowNodeExecutorDescriptor(type,inputSchema,outputSchema,timeoutMs, retry policy) - Wait event config for delay nodes:
FlowWaitEventType(condition/manual/webhook/timer/signal) - Boundary event config:
FlowBoundaryConfig(attachedToNodeId,eventType,cancelActivity,timer,errorCode) - Edge property editor: label, type, condition expression,
isDefaulttoggle
Spec v3.0.9 Protocol Features:
-
FlowVersionEntry[]version history panel with current/previous entries -
FlowConcurrencyPolicybadge display (allow/forbid/replace/queue) -
FlowExecutionStep[]execution monitoring overlay with per-node status icons -
FlowBpmnInteropResultBPMN XML export with warning/error reporting
ComponentRegistry:
- Registered as
'flow-designer'with all inputs documented
- PageDesigner: Component drag-to-reorder and drag-to-position
- ProcessDesigner: Node drag-to-move
- ProcessDesigner/FlowDesigner: Support v3.0.9 node types (
parallel_gateway,join_gateway,boundary_event) — implemented in FlowDesigner - ProcessDesigner/FlowDesigner: Support v3.0.9 conditional edges and default edge marking — implemented in FlowDesigner
- ReportView: Refactored to left-right split layout (preview + DesignDrawer) — consistent with DashboardView/ListView
- ReportConfigPanel: Schema-driven config via ConfigPanelRenderer + useConfigDraft (replaces full-page ReportBuilder in edit mode)
- ReportConfigPanel: Report type selector (Tabular/Summary/Matrix), visual field picker (multi-select columns), undo/redo, dashboard-style consistency
- ReportView: Live preview linkage fix — config panel changes instantly refresh report preview (title, description, fields, filters, groupBy)
- ReportViewer: Client-side grouping — groupBy config renders grouped table with group headers and row counts
- ReportType added to ReportSchema types (tabular/summary/matrix)
- ReportConfigPanel: Per-column aggregation (sum/avg/count/min/max) in field picker
- ReportConfigPanel: Visual chart editor — chart type selection + X/Y axis field mapping (no JSON editing)
- ReportConfigPanel: Conditional formatting UI — rule builder with field/operator/value + background color
- ReportConfigPanel: Section/block manager — add/remove/reorder header, summary, table, chart, text sections
- ReportViewer: Conditional formatting applied to table cells (background/text color)
- ReportView: Chart config from panel auto-generates chart section in preview
- ReportDesigner: Element drag-to-reposition within sections
- FlowDesigner: Edge creation UI (click source port → click target port)
- FlowDesigner: Property editing for node labels/types + executor config + boundary config
- Confirmation dialogs for ProcessDesigner destructive actions
- Full property editors for all designers (PageDesigner, ProcessDesigner, ReportDesigner)
- i18n integration: all hardcoded strings through
resolveI18nLabel - Canvas pan/zoom with minimap
- Auto-layout algorithms (force-directed for DataModel, Dagre for Process)
- Copy/paste support (
Ctrl+C/Ctrl+V) across all designers - Multi-select (
Ctrl+Click,Shift+Click) and bulk operations - Responsive collapsible panels
- Wire
CollaborationProviderinto each designer's state management - Live cursor positions (PresenceCursors component)
- Operation-based undo/redo sync
- Conflict resolution UI (merge dialog)
- Version history browser with restore
-
@object-ui/core: DataScope module — row-level permission enforcement -
@object-ui/core: Custom validator registration API -
@object-ui/core: STDEV, VARIANCE, PERCENTILE, MEDIAN formula functions -
@object-ui/react: useTheme hook for component-level theme access -
@object-ui/react: Re-export usePermissions for unified hook API -
@object-ui/components: ErrorBoundary wrapper per component -
@object-ui/fields: Inline validation message rendering -
@object-ui/plugin-charts: Drill-down click handler for chart segments -
@object-ui/plugin-charts: Fixobject-chartcrash when data is non-array — addedArray.isArray()guards in ObjectChart, ChartRenderer, and AdvancedChartImpl to prevent Rechartsr.slice is not a functionerror -
@object-ui/plugin-workflow: FlowDesigner — canvas-based flow editor (flow-designercomponent) with drag-to-reposition nodes, edge creation UI, undo/redo, Ctrl+S save, property panel, and BPMN export -
@object-ui/plugin-workflow: Support v3.0.9 BPMN interop types —FlowBpmnInteropResultwithbpmnXmlexport,nodeCount,edgeCount,warnings -
@object-ui/plugin-workflow: Support v3.0.9 node executor descriptors —FlowNodeExecutorDescriptorwithinputSchema,outputSchema, wait event config, retry policy -
@object-ui/plugin-workflow: Support v3.0.9inputSchema/outputSchemaon flow nodes viaFlowNodeExecutorDescriptor -
@object-ui/plugin-workflow: Support v3.0.9boundaryConfigfor boundary event nodes —FlowBoundaryConfigwithattachedToNodeId,eventType,cancelActivity,timer,errorCode -
@object-ui/plugin-workflow: Support v3.0.9 node types —parallel_gateway,join_gateway,boundary_eventinFlowNodeType -
@object-ui/plugin-workflow: Support v3.0.9 conditional edges —FlowEdge.type = 'conditional'+isDefaultflag -
@object-ui/plugin-workflow: Support v3.0.9FlowVersionHistory—FlowVersionEntry[]with version number, author, changeNote, isCurrent -
@object-ui/plugin-workflow: Support v3.0.9ConcurrencyPolicy—FlowConcurrencyPolicy(allow/forbid/replace/queue) -
@object-ui/plugin-workflow: Support v3.0.9WaitEventType—FlowWaitEventType(condition/manual/webhook/timer/signal) -
@object-ui/plugin-workflow: Support v3.0.9 execution tracking —FlowExecutionStepwith status overlay on nodes -
@object-ui/plugin-ai: Configurable AI endpoint adapter (OpenAI, Anthropic) - Navigation
widthproperty: apply to drawer/modal overlays across all plugins - Navigation
viewproperty: specify target form/view on record click across all plugins - Support App
enginefield ({ objectstack: string }) for version pinning (v3.0.9) - Integrate v3.0.9 package upgrade protocol (
PackageArtifact,ArtifactChecksum,UpgradeContext) -
@object-ui/types: AlignViewTypewith spec ListView type enum — add'gallery'and'gantt'(was missing fromviews.tsandviews.zod.ts) -
@object-ui/types: SyncDetailViewFieldSchemaZod validator with TS interface — add data-oriented field types (number,currency,percent,boolean,select,lookup,master_detail,email,url,phone,user) and missing properties (options,reference_to,reference_field,currency) -
@object-ui/types: AddshowBorderandheaderColortoDetailViewSectionSchemaZod validator (already in TS interface) -
@object-ui/plugin-view: Addgalleryandganttto ViewSwitcher default labels and icons -
@object-ui/plugin-list: Fixlist-viewandlistregistrationviewTypeenum — remove invalid'list'/'chart', add'gallery'/'timeline'/'gantt'/'map'to match spec -
@object-ui/plugin-detail: Add missingdetail-viewregistration inputs (layout,columns,loading,backUrl,editUrl,deleteConfirmation,header,footer) -
@object-ui/plugin-detail: AddshowBorderandheaderColorinputs todetail-sectionregistration
All items from the ListView Spec Protocol analysis have been implemented.
P0 — Core Protocol:
-
data(ViewDataSchema): ListView consumesschema.data— supportsprovider: value(inline items),provider: object(fetch from objectName), and plain array shorthand. Falls back todataSource.find()when not set. -
groupingrendering: Group button enabled with GroupBy field picker popover. Grouping config wired to ObjectGrid child view, which renders collapsible grouped sections viauseGroupedDatahook. -
rowColorrendering: Color button enabled with color-field picker popover. Row color config wired to ObjectGrid child view, which applies row background colors viauseRowColorhook.
P1 — Structural Alignment:
-
quickFiltersstructure reconciliation: Auto-normalizes spec{ field, operator, value }format into ObjectUI{ id, label, filters[] }format. Both formats supported simultaneously. Dual-format type union (QuickFilterItem = ObjectUIQuickFilterItem | SpecQuickFilterItem) exported from@object-ui/types. StandalonenormalizeQuickFilter()/normalizeQuickFilters()adapter functions in@object-ui/core. Bridge (list-view.ts) normalizes at spec→SchemaNode transform time. Spec shorthand operators (eq,ne,gt,gte,lt,lte) mapped to ObjectStack AST operators. Mixed-format arrays handled transparently. -
conditionalFormattingexpression reconciliation: Supports spec{ condition, style }format alongside ObjectUI field/operator/value rules. Dual-format type union (ConditionalFormattingRule = ObjectUIConditionalFormattingRule | SpecConditionalFormattingRule) exported from@object-ui/types. Zod validator updated withz.union()for both formats.evaluatePlainCondition()convenience function in@object-ui/corefor safe plain/template expression evaluation with record context. Plain expressions (e.g.,status == 'overdue') evaluated directly without${}wrapper; record fields spread into evaluator context for direct field references alongsidedata.namespace. Mixed-format arrays handled transparently. -
exportOptionsschema reconciliation: Accepts both specstring[]format (e.g.,['csv', 'xlsx']) and ObjectUI object format{ formats, maxRecords, includeHeaders, fileNamePrefix }. - Column
pinned:pinnedproperty added to ListViewSchema column type. Bridge passes through to ObjectGrid which supportsfrozenColumns. ObjectGrid reorders columns (left-pinned first, right-pinned last with sticky CSS). Zod schema updated withpinnedfield.useColumnSummaryhook created. - Column
summary:summaryproperty added to ListViewSchema column type. Bridge passes through for aggregation rendering. ObjectGrid renders summary footer with count/sum/avg/min/max aggregations viauseColumnSummaryhook. Zod schema updated withsummaryfield. - Column
link: ObjectGrid renders click-to-navigate buttons on link-type columns withnavigation.handleClick. Primary field auto-linked. - Column
action: ObjectGrid renders action dispatch buttons viaexecuteActionon action-type columns. -
tabs(ViewTabSchema): TabBar component renders view tabs above the ListView toolbar. Supports icon (Lucide), pinned (always visible), isDefault (auto-selected), visible filtering, order sorting, and active tab state. Tab switch applies filter config. Extracted as reusableTabBarcomponent inpackages/plugin-list/src/components/TabBar.tsx. i18n keys added for all 10 locales.
P2 — Advanced Features:
-
rowActions: Row-level dropdown action menu per row in ObjectGrid.schema.rowActionsstring array items rendered as dropdown menu items, dispatched viaexecuteAction. -
bulkActions: Bulk action bar rendered in ListView when rows are selected andschema.bulkActionsis configured. FiresonBulkActioncallback with action name and selected rows. -
sharingschema reconciliation: Supports both ObjectUI{ visibility, enabled }and spec{ type: personal/collaborative, lockedBy }models. Share button renders when eitherenabled: trueortypeis set. Zod validator updated withtypeandlockedByfields. Bridge normalizes spec format:type: personal→visibility: private,type: collaborative→visibility: team, auto-setsenabled: true. -
exportOptionsschema reconciliation: Zod validator updated to accept both specstring[]format and ObjectUI object format viaz.union(). ListView normalizes string[] to{ formats }at render time. -
pagination.pageSizeOptionsbackend integration: Page size selector is now a controlled component that dynamically updateseffectivePageSize, triggering data re-fetch.onPageSizeChangecallback fires on selection. Full test coverage for selector rendering, option enumeration, and data reload. -
$expandauto-injection:buildExpandFields()utility in@object-ui/corescans schema fields forlookup/master_detailtypes and returns field names for$expand. Integrated into all data-fetching plugins (ListView, ObjectGrid, ObjectKanban, ObjectCalendar, ObjectGantt, ObjectMap, ObjectTimeline, ObjectGallery, ObjectView, ObjectAgGrid) so the backend (objectql) returns expanded objects instead of raw foreign-key IDs. Supports column-scoped expansion (ListColumn[]compatible) and graceful fallback when$expandis not supported. Cross-repo: objectql engine expand support required for multi-level nesting.
All items from the UI consistency optimization (Issue #749) have been implemented.
Global Theme & Design Tokens:
- Hardcoded gray colors in
GridField.tsx,ReportRenderer.tsx, andObjectGrid.tsxreplaced with theme tokens (text-muted-foreground,bg-muted,border-border,border-foreground) - Global font-family (
Inter, ui-sans-serif, system-ui) injected inindex.css:root -
--config-panel-widthCSS custom property added for unified config panel sizing (updated to320pxin P2.8) - Border radius standardized to
rounded-lgacross report/grid components -
transition-colors duration-150added to all interactive elements (toolbar buttons, tab bar, sidebar menu buttons) -
LayoutRenderer.tsxouter shellbg-slate-50/50 dark:bg-zinc-950replaced withbg-backgroundtheme token
Sidebar Navigation:
-
SidebarMenuButtonactive state enhanced with 3px left indicator bar viabefore:pseudo-element -
SidebarMenuButtontransition expanded to includecolor, background-colorwithduration-150 -
SidebarGroupLabelvisual separator added (border-t border-border/30 pt-3 mt-2) - Collapsed-mode tooltip support in
SidebarNavviatooltip={item.title}prop -
LayoutRenderer.tsxhand-written sidebar →SidebarNavunification (deferred: requires extending SidebarNav to support nested menus, logo, version footer)
ListView Toolbar:
- Search changed from expandable button to always-visible inline
<Input>(w-48) - Activated state (
bg-primary/10 border border-primary/20) added to Filter/Sort/Group/Color buttons when active - Toolbar overflow improved with
overflow-x-autofor responsive behavior -
transition-colors duration-150added to all toolbar buttons
ObjectGrid Cell Renderers:
-
formatRelativeDate()function added for relative time display ("Today", "2 days ago", "Yesterday") - DataTable/VirtualGrid header styling unified:
text-[11px] font-semibold uppercase tracking-wider text-muted-foreground/70 bg-muted/30 - Remaining hardcoded gray colors in ObjectGrid loading spinner and status badge fallback replaced with theme tokens
- Select/status type Badge rendering —
getCellRenderer()returns<Badge>with color mapping fromfield.options; auto-generated options from unique data values when type is inferred; priority semantic colors (Critical→red, High→orange, Medium→yellow, Low→gray); muted default style for unconfigured colors - Date type human-readable formatting —
DateCellRendererdefaults to relative format ("Today", "Yesterday", "3 days ago"); overdue dates styled with red text; ISO timestamp shown as hover tooltip;formatRelativeDate()threshold tightened to 7 days - Boolean type visual rendering —
BooleanCellRendererrenders<Checkbox disabled>for true/false; null/undefined values display as—
ConfigPanelRenderer:
-
<Separator>added between sections for visual clarity - Panel width uses
--config-panel-widthCSS custom property
View Tab Bar:
- Tab spacing tightened (
gap-0.5,px-3 py-1.5) - Active tab indicator changed to bottom border (
border-b-2 border-primary font-medium text-foreground) -
transition-colors duration-150added to tab buttons
Platform-level grid, toolbar, sidebar, and config panel optimizations for Airtable-level experience (Issue #768).
Platform: DataTable & ObjectGrid Enhancements:
-
rowStylecallback prop added toDataTableSchematype — enables inline CSSProperties per row -
<TableRow>in data-table.tsx appliesrowStylecallback for runtime row styling - ObjectGrid:
conditionalFormattingrules wired torowStyle— evaluates both spec-format (condition/style) and ObjectUI-format (field/operator/value) rules per row usingevaluatePlainConditionfrom@object-ui/core - Row number (#) column: hover shows
<Checkbox>for multi-select (whenselectablemode is enabled), replacing expand icon
Platform: ListView Toolbar:
- Visual
<div>separators (h-4 w-px bg-border/60) between toolbar button groups: Search | Hide Fields | Filter/Sort/Group | Color/Density | Export - Separators conditionally rendered only when adjacent groups are visible
- Inline search moved to toolbar left end (
w-48, Airtable-style) - Density button: activated state highlight (
bg-primary/10 border border-primary/20) when density is non-default - Merged UserFilters row and tool buttons row into single toolbar line — left: field filter badges, right: tool buttons with separator
- Search changed from inline input to icon button + Popover — saves toolbar space, matches Airtable pattern
- UserFilters
maxVisibleprop added — overflow badges collapse into "More" dropdown with Popover - UserFilters config panel:
_userFiltersintegrated intobuildDataSectionin ViewConfigPanel — element type selector (dropdown/tabs/toggle), field picker, live preview sync, i18n (en/zh),NamedListView.userFilterstype parity withListViewSchema.userFilters - FilterBuilder: multi-select support for
in/notInoperators — checkbox list UI replaces text input for select/lookup/master_detail fields - FilterBuilder: lookup/master_detail field types now show dropdown selector instead of manual ID input
- FilterBuilder: all field types mapped —
currency/percent/rating→ number operators,datetime/time→ date operators with proper input types,status→ select operators,user/owner→ lookup operators - FilterUI:
multi-selectfilter type added — checkbox-based multi-value selection for filter forms - FilterCondition value type expanded to support arrays
(string | number | boolean)[]for multi-value filters - ObjectView filterSchema:
lookup/master_detail/user/ownerfields auto-map tomulti-selecttype;statusfields auto-map toselect - Console
normalizeFieldType:lookup/master_detail/user/owner/status/timetypes now properly classified for filter/sort config - Toolbar layout uses flex with
min-w-0overflow handling for responsive behavior
Platform: ViewTabBar:
- Tab "•" dot indicator replaced with descriptive badge (
F/S/FS) + tooltip showing "Active filters", "Active sort"
Platform: Console Sidebar:
- Recent items section: default collapsed with chevron toggle (saves sidebar space)
Platform: ViewConfigPanel Advanced Sections:
-
userActions,sharing, andaccessibilitysections set todefaultCollapsed: true— common settings remain expanded, advanced settings folded by default
Platform: Config Panel Width:
-
--config-panel-widthCSS variable increased from280pxto320pxfor wider config panel
CRM Example: Product Grid Column Configs:
- All columns upgraded from
string[]toListColumn[]with explicitfield,label,width,type,align -
IS ACTIVE:type: 'boolean'for<Checkbox disabled>rendering - Price:
type: 'currency',align: 'right'forformatCurrencyformatting - Default
rowHeight: 'short'for compact density - Conditional formatting: stock=0 red, stock<5 yellow warning
- Column widths: NAME 250, SKU 120, CATEGORY 110, PRICE 120, STOCK 80
Tests:
- 7 new CRM metadata tests validating column types, widths, rowHeight, conditionalFormatting
- 136 ViewConfigPanel tests updated for defaultCollapsed sections (expand before access)
- 411 ListView + ViewTabBar tests passing (255 plugin-list tests including 9 new toolbar/collapse tests)
- 11 AppSidebar tests passing
Platform-level sidebar, navigation, and grid/table UX improvements (Issue #XX).
Sidebar Navigation:
- Pin icons show-on-hover:
SidebarMenuActioninNavigationRenderernow usesshowOnHover(always true) — all pin/unpin icons only appear on hover for a cleaner sidebar. Applied to bothactionand leaf navigation item types. - Search placeholder contrast: Search icon in AppSidebar improved from
opacity-50→opacity-70for better readability. - Recent section position: Recent items section moved above Record Favorites in AppSidebar for quicker access to recently visited items.
- Favorites section: Already hides automatically when no pinned items exist (verified).
- Resizable sidebar width:
SidebarRailenhanced with pointer-event drag-to-resize (min 200px, max 480px). Width persisted tolocalStorage. Click toggles sidebar, double-click resets to default.useSidebar()hook now exposessidebarWidthandsetSidebarWidth.
Grid/Table Field Inference:
- Percent field auto-inference:
inferColumnType()in ObjectGrid now detects fields with names containingprobability,percent,percentage,completion,progress,rateand assignsPercentCellRendererwith progress bar display. - ISO datetime fallback: ObjectGrid
inferColumnType()now detects ISO 8601 datetime strings (YYYY-MM-DDTHH:MM) in data values as a catch-all for fields whose names don't match date/datetime patterns. - Date/datetime human-friendly display:
DateCellRenderer(relative format) andDateTimeCellRenderer(split date/time) already registered in field registry for all grid/table views. - Currency/status/boolean renderers: Already implemented with proper formatting (currency symbol, Badge colors, checkbox display).
- accessorKey-format type inference:
generateColumns()in ObjectGrid now appliesinferColumnType()+getCellRenderer()toaccessorKey-format columns that don't already have acellrenderer. Previously,accessorKeycolumns bypassed the entire inference pipeline, showing raw dates, plain text status/priority, and raw numbers for progress. - humanizeLabel() utility: New
humanizeLabel()export in@object-ui/fieldsconverts snake_case/kebab-case values to Title Case (e.g.,in_progress→In Progress). Used as fallback inSelectCellRendererwhen no explicitoption.labelexists. - PercentCellRenderer progress-field normalization:
PercentCellRenderernow uses field name to disambiguate 0-1 fraction vs 0-100 whole number — fields matching/progress|completion/treat values as already in 0-100 range (e.g.,75→75%), while other fields likeprobabilitystill treat0.75→75%.
Header & Breadcrumb i18n:
- AppHeader breadcrumb labels (
Dashboards,Pages,Reports,System) now uset()translation viauseObjectTranslation. -
console.breadcrumbi18n keys added to all 11 locales (en, zh, ja, ko, de, fr, es, pt, ru, ar). -
header-barrenderer:resolveCrumbLabel()handles both string andI18nLabelobjects for breadcrumb labels. -
breadcrumbrenderer:resolveItemLabel()handles both string andI18nLabelobjects for item labels.
Tests:
- 46 NavigationRenderer tests passing (pin/favorites/search/reorder)
- 86 field cell renderer tests passing (date/datetime/select/boolean/percent/humanizeLabel/progress)
- 293 ObjectGrid tests passing (inference, rendering, accessibility, accessorKey-format inference)
- 28 DataTable tests passing
- 78 Layout tests passing (NavigationRenderer + AppSchemaRenderer)
- 11 AppSidebar tests passing
- 32 i18n tests passing
Location-aware action toolbar component and React hook for ActionEngine. Bridges ActionSchema metadata → visible, clickable action buttons at all defined locations.
-
action:barcomponent — Composite toolbar renderer that acceptsActionSchema[]+locationfilter- Filters actions by
ActionLocation(list_toolbar, list_item, record_header, record_more, record_related, global_nav) - Resolves each action's
componenttype (action:button, action:icon, action:menu, action:group) via ComponentRegistry - Overflow support: actions beyond
maxVisiblethreshold grouped into "More" dropdown (action:menu) - Supports horizontal/vertical direction, gap, variant/size defaults, custom className
- WCAG:
role="toolbar"+aria-label="Actions" - Registered in ComponentRegistry with inputs/defaultProps for Designer/Studio integration
- Filters actions by
-
useActionEnginehook — React wrapper aroundActionEnginegetActionsForLocation(location)— returns filtered, priority-sorted actionsgetBulkActions()— returns bulk-enabled actionsexecuteAction(name)— executes by name with optional context overridehandleShortcut(keys)— keyboard shortcut dispatch- Memoized engine instance, stable callbacks
- Tests: 23 tests (12 ActionBar, 11 useActionEngine) covering registration, rendering, location filtering, overflow, styling, execution, shortcuts
- Background sync queue → real server sync (replace simulation)
- Conflict resolution on reconnection wired into Console flow
- Optimistic updates with TransactionManager state application
Status: Phase 1 complete — Plugin class standard, install/uninstall API, example plugin classes. Phase 1.5 complete — composeStacks unified to
@objectstack/spec, plugin isolation, duplicate merge removal.
Plugin architecture refactoring to support true modular development, plugin isolation, and dynamic plugin install/uninstall at runtime.
Phase 1 — Plugin Class Standard & Example Plugins ✅
- Define
AppMetadataPlugininterface in@object-ui/types(name, version, type, description, init/start/stop/getConfig) - Define
PluginContextinterface for lifecycle hook context (logger, kernel) - Add
install(plugin)/uninstall(pluginName)convenience methods toPluginSystemin@object-ui/core - Create
CRMPluginwith full lifecycle (init/start/stop/getConfig) —examples/crm/plugin.ts - Create
TodoPlugin—examples/todo/plugin.ts - Create
KitchenSinkPlugin—examples/kitchen-sink/plugin.ts - Update
package.jsonexports for all example apps (./pluginentry point) - Refactor root
objectstack.config.tsto use plugin-based config collection viagetConfig() - Unit tests for
install()/uninstall()(5 new tests, 18 total in PluginSystem)
Phase 1.5 — Plugin Isolation & Config Composition ✅
- Add explicit
objectNameto all example plugin actions (CRM: 28 actions, Todo: 6 actions, Kitchen Sink: 3 actions) - Rename Kitchen Sink
account→ks_accountto eliminate same-name object conflicts across plugins - Create
composeStacks()utility in@object-ui/core— declarative stack merging withobjectConflictoption, automatic views→objects and actions→objects mapping - Remove duplicate
mergeActionsIntoObjects()from root config and console shared config - Remove duplicate
mergeViewsIntoObjects()from root config and console shared config (moved intocomposeStacks) - Refactor root
objectstack.config.tsandapps/console/objectstack.shared.tsto usecomposeStacks() - Eliminate
defineStack()double-pass hack — singlecomposeStacks()call produces final config with runtime properties (listViews, actions) preserved.defineStack()Zod validation stripped these fields, requiring a secondcomposeStackspass to restore them. - Use
composed.appsunified flow in console shared config — replaced manual[...crmApps, ...(todoConfig.apps || []), ...]spreading with CRM navigation patch applied to composed output - Use
composed.reportsin console shared config — replaced...(crmConfig.reports || [])with...(composed.reports || [])to include reports from all stacks - composeStacks unified to
@objectstack/spec: Removed@object-ui/corecomposeStacks implementation and its 15-test suite (coverage now lives upstream in@objectstack/spec). All config composition now usescomposeStacksfrom@objectstack/spec(protocol-level: object dedup, array concatenation, actions→objects mapping, manifest selection, i18n). Runtime-specificmergeViewsIntoObjectsadapter extracted to@object-ui/coreand applied post-composition at call sites. A deprecated re-export ofcomposeStacksis kept in@object-ui/corefor backward compatibility.
Phase 2 — Dynamic Plugin Loading (Planned)
- Hot-reload / lazy loading of plugins for development
- Runtime plugin discovery and loading from registry
- Plugin dependency graph visualization in Console
Phase 3 — Plugin Identity & Isolation (Planned)
- Eliminate same-name object conflicts across plugins (Kitchen Sink
account→ks_account) - Preserve origin plugin metadata on objects, actions, dashboards for runtime inspection
- Per-plugin i18n namespace support
- Per-plugin permissions and data isolation
- Move
mergeViewsIntoObjectsfromcomposeStacksto runtime/provider layer
Phase 4 — Cross-Repo Plugin Ecosystem (Planned)
- Plugin marketplace / registry for third-party plugins
- Plugin publish/validate tooling (spec v3.0.9
PluginBuildOptions,PluginPublishOptions) - Cross-repo plugin loading from npm packages
Comprehensive metadata expansion for the CRM reference implementation.
- P0: Reports — Sales Report and Pipeline Report with full ReportSchema (fields, groupBy, sections, schedule, export)
- P0: Report Navigation — Native
type: 'report'navigation items (no shared config hack) - P1: Seed Data — Users — 7 users covering admin, manager, user, viewer roles + inactive user
- P1: Seed Data — Orders — 7 orders covering all statuses (draft, pending, paid, shipped, delivered, cancelled)
- P1: Seed Data — Products — 2 inactive products (
is_active: false) for filter testing - P1: Order ↔ Product Junction —
order_itemsobject with line items (quantity, price, discount, item_type) + 12 seed records - P1: Opportunity ↔ Contact Junction —
opportunity_contactsobject with role-based relationships + 7 seed records - P1: Contact ↔ Event Attendees —
participantsfield populated on all event seed data - P2: Dashboard Dynamic Data — "Revenue by Account" widget using
provider: 'object'aggregation. DashboardRenderer now delegatesprovider: 'object'widgets to ObjectChart (type: 'object-chart') for async data loading + client-side aggregation (sum/count/avg/min/max) - P2: Fix Revenue by Account Chart — Fixed 3 bugs preventing "Revenue by Account" chart from displaying data: (1) ObjectChart
extractRecords()now handlesresults.dataandresults.valueresponse formats in addition toresults.records, (2) DashboardRenderer auto-adapts seriesdataKeyfromaggregate.fieldwhen aggregate config is present, (3) CRM dashboardyFieldaligned to aggregate fieldamount(wastotal). CentralizedextractRecords()utility in@object-ui/coreand unified data extraction across all 6 data components (ObjectChart, ObjectMap, ObjectCalendar, ObjectGantt, ObjectTimeline, ObjectKanban). Added 16 new tests. - P2: App Branding —
logo,favicon,backgroundColorfields on CRM app - P3: Pages — Settings page (utility) and Getting Started page (onboarding)
- P2: Spec Compliance Audit — Fixed
variant: 'danger'→'destructive'(4 actions),columns: string→number(33 form sections), addedtype: 'dashboard'to dashboard - P2: Dashboard Widget Spec Alignment — Added
id,title,object,categoryField,valueField,aggregateto all dashboard widgets across CRM, Todo, and Kitchen Sink examples (5 new spec-compliance tests) - 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)
- P2: Full Examples Metadata Audit — Systematic spec compliance audit across all 4 examples: added
type: 'dashboard'+descriptionto todo/kitchen-sink dashboards, refactored msw-todo to useObjectSchema.create+Field.*with snake_case field names, added explicit views to kitchen-sink and msw-todo, added missingsuccessMessageon CRM opportunity action, 21 automated compliance tests - P2: CRM Dashboard Full provider:'object' Adaptation — Converted all chart and table widgets in CRM dashboard from static
provider: 'value'to dynamicprovider: 'object'with aggregation configs. 12 widgets total: 4 KPI metrics (static), 7 charts (sum/count/avg/max aggregation across opportunity, product, order objects), 1 table (dynamic fetch). Cross-object coverage (order), diverse aggregate functions (sum, count, avg, max). Fixed tableclose_datefield alignment. Added i18n for 2 new widgets (10 locales). 9 new CRM metadata tests, 6 new DashboardRenderer rendering tests (area/donut/line/cross-object + edge cases). All provider:'object' paths covered. - P1: Dashboard provider:'object' Crash & Blank Rendering Fixes — Fixed 3 critical bugs causing all charts to be blank and tables to crash on provider:'object' dashboards: (1) DashboardRenderer
...optionsspread was leaking provider config objects asdatain data-table and pivot schemas — fixed by destructuringdataout before spread, (2) DataTableRenderer and PivotTable now guard withArray.isArray()for graceful degradation when non-array data arrives, (3) ObjectChart now shows visible loading/warning messages instead of silently rendering blank whendataSourceis missing. Also added provider:'object' support to DashboardGridLayout (charts, tables, pivots). 2 new regression tests. - P1: Dashboard Widget Data Blank — useDataScope/dataSource Injection Fix — Fixed root cause of dashboard widgets showing blank data with no server requests:
useDataScope(undefined)was returning the full contextdataSource(service adapter) instead ofundefinedwhen no bind path was given, causing ObjectChart and all data components (ObjectKanban, ObjectGallery, ObjectTimeline, ObjectGrid) to treat the adapter as pre-bound data and skip async fetching. FixeduseDataScopeto returnundefinedwhen no path is provided. Also improved ObjectChart fault tolerance: usesuseContextdirectly instead ofuseSchemaContext(no throw without provider), validatesdataSource.findis callable before invoking. 14 new tests (7 useDataScope + 7 ObjectChart data fetch/fault tolerance). - P1: URL-Driven Debug/Developer Panel — Universal debug mode activated via
?__debugURL parameter (amis devtools-style).@object-ui/core: exportedDebugFlags,DebugCollector(perf/expr/event data collection, tree-shakeable),parseDebugFlags(), enhancedisDebugEnabled()(URL → globalThis → env resolution, SSR-safe).@object-ui/react:useDebugModehook with URL detection, Ctrl+Shift+D shortcut, manual toggle;SchemaRendererContextextended withdebugFlags;SchemaRendererinjectsdata-debug-type/data-debug-idattrs + reports render perf toDebugCollectorwhen debug enabled.@object-ui/components: floatingDebugPanelwith 7 built-in tabs (Schema, Data, Perf, Expr, Events, Registry, Flags), plugin-extensible viaextraTabs. ConsoleMetadataInspectorauto-opens when?__debugdetected. Fine-grained sub-flags:?__debug_schema,?__debug_perf,?__debug_data,?__debug_expr,?__debug_events,?__debug_registry. 48 new tests. - P1: Chart Widget Server-Side Aggregation — Fixed chart widgets (bar/line/area/pie/donut/scatter) downloading all raw data and aggregating client-side. Added optional
aggregate()method toDataSourceinterface (AggregateParams,AggregateResulttypes) enabling server-side grouping/aggregation via analytics API (e.g.GET /api/v1/analytics/{resource}?category=…&metric=…&agg=…).ObjectChartnow prefersdataSource.aggregate()when available, falling back todataSource.find()+ client-side aggregation for backward compatibility. Implementedaggregate()inValueDataSource(in-memory),ApiDataSource(HTTP), andObjectStackAdapter(analytics API with client-side fallback). Only detail widgets (grid/table/list) continue to fetch full data. 9 new tests. - P1: Spec-Aligned CRM I18n — Fixed CRM internationalization not taking effect on the console. Root cause: CRM metadata used plain string labels instead of spec-aligned
I18nLabelobjects. Fix: (1) Updated CRM app/dashboard/navigation metadata to useI18nLabelobjects ({ key, defaultValue }) per spec. (2) UpdatedNavigationItemandNavigationAreatypes to support I18nLabel. (3) AddedresolveLabel()helper in NavigationRenderer. (4) UpdatedresolveI18nLabel()to acceptt()function for translation. (5) AddedloadLanguagecallback in I18nProvider for API-based translation loading. (6) Added/api/v1/i18n/translations/:langendpoint to mock server. Console contains zero CRM-specific code. - P0: Opportunity List View & ObjectDef Column Enrichment — Fixed ObjectGrid not using objectDef field metadata for type-aware rendering when columns are
string[]orListColumn[]without full options. (1) Schema resolution always fetches full schema from DataSource for field type metadata. (2) String[] column path enriched with objectDef types, options (with colors), currency, precision for proper CurrencyCellRenderer, SelectCellRenderer (colored badges), PercentCellRenderer, DateCellRenderer. (3) ListColumn[] fieldMeta deep-merged with objectDef field properties (select options with colors, currency code, precision). (4) Opportunity view columns upgraded from barestring[]toListColumn[]with explicit types, alignment, and summary aggregation. 9 new tests. - P1: Actions Merge into Object Definitions — Fixed action buttons never showing in Console/Studio because example object definitions lacked
actionsfield. Initially addedmergeActionsIntoObjects()helper with longest-prefix name matching. Later refactored: all actions now declare explicitobjectName, and merging is handled bycomposeStacks()from@objectstack/spec. Created todo task actions (6: complete, start, clone, defer, set_reminder, assign) and kitchen-sink showcase actions (3: change_status, assign_owner, archive). All CRM/Todo/Kitchen Sink objects now serveactionsin metadata. Fixes #840. - P1: Unified Debug/Metadata Entry — Remove Redundant Metadata Button — Removed the visible
<MetadataToggle>button from RecordDetailView, DashboardView, PageView, and ReportView headers. End users no longer see a "</> Metadata" button that had no practical purpose. The MetadataInspector panel is now only accessible via?__debugURL parameter (auto-opens when debug mode is active). ObjectView retains its admin-only Design Tools menu entry for metadata inspection. This unifies the debug entry point and improves end-user UX by removing redundant UI elements.
- Plugin marketplace website with search, ratings, and install count
- Plugin publishing CLI (
os ui publish) with automated validation - 25+ official plugins
- Official website (www.objectui.org) with interactive playground
- Discord community, monthly webinars, technical blog, YouTube tutorials
- Project hosting, online editor, Database as a Service
- One-click deployment, performance monitoring
- Billing system (Free / Pro / Enterprise)
- CRM, ERP, HRM, E-commerce, Project Management accelerators
- AI-powered schema generation from natural language
| Metric | Current | v1.0 Target | How Measured |
|---|---|---|---|
| Protocol Alignment | ~85% | 90%+ (UI-facing) | Protocol Consistency Assessment |
| AppShell Renderer | ✅ Complete | Sidebar + nav tree from AppSchema JSON |
Console renders from spec JSON |
| Designer Interaction | Phase 2 (most complete) | DataModelDesigner drag/undo; ViewDesigner removed (replaced by ViewConfigPanel) | Manual UX testing |
| Build Status | 43/43 pass | 43/43 pass | pnpm build |
| Test Count | 6,700+ | 6,700+ | pnpm test summary |
| Test Coverage | 90%+ | 90%+ | pnpm test:coverage |
| Storybook Stories | 80 | 91+ (1 per component) | Story file count |
| Console i18n | 100% | 100% | No hardcoded strings |
Root Cause: Three compounding issues caused "Record not found" when navigating from a list to a record detail page in external metadata environments:
DetailView.tsx's alt-ID fallback only triggered whenfindOnereturned null. If it threw an error (server 500, network failure, etc.), the error propagated to the outer catch handler and the fallback was never tried.ObjectStackAdapter.findOnewith$expandusedrawFindWithPopulatewith$filter: { _id: id }, which some external servers don't support. On failure it threw, instead of falling back to the simplerdata.get()call.- Record IDs in navigation URLs were not URL-encoded, which could cause routing issues with IDs containing special characters.
Fix: Made DetailView catch all errors from the first findOne (converting to null) so the alt-ID fallback always runs. Made ObjectStackAdapter.findOne fall through to direct data.get() when the $expand raw request fails with a non-404 error. Added encodeURIComponent for record IDs in all navigation URL construction points.
Tests: 32 DetailView tests, 12 expand tests, 33 useNavigationOverlay tests, 6 RecordDetailEdit tests — all pass.
Root Cause: createKernel.ts (MSW mode) and objectstack.config.ts (Server mode) did not load AuthPlugin, so the kernel had no 'auth' service. All /api/v1/auth/* endpoints (sign-up, sign-in, get-session, sign-out) returned 404.
Fix:
- Added
AuthPluginfrom@objectstack/plugin-authtoobjectstack.config.tsfor server mode (pnpm dev:server). - Created
authHandlers.tswith in-memory mock implementations of better-auth endpoints for MSW mode (pnpm dev). Mock handlers are added tocustomHandlersin bothbrowser.tsandserver.ts. - Mock handlers support: sign-up/email, sign-in/email, get-session, sign-out, forget-password (better-auth convention), reset-password, update-user.
Tests: 11 new auth handler tests, all existing MSW (7) and auth (24) tests pass.
Root Cause: Three compounding issues created a stale closure chain in ObjectView.tsx:
{ mode: 'page' }fallback created a new object literal every render, causinguseNavigationOverlay'shandleClickuseCallbackto be recreated with potentially stale deps.- The
onNavigatecallback was an inline arrow function with unstable identity, so React could batch-skip the render wherehandleClickpicks up the fresh closure. - Cascade instability:
navOverlay(fromuseMemo) got a new reference every render because its deps (handleClick, etc.) changed, propagating stale closures throughrenderListView.
Fix: Memoized detailNavigation with useMemo (stable reference for the { mode: 'page' } fallback) and extracted onNavigate into a useCallback (handleNavOverlayNavigate) with [navigate, viewId] deps. This ensures stable identities for both inputs to useNavigationOverlay, preventing stale closures.
Tests: Added 2 stale closure prevention tests in useNavigationOverlay.test.ts: (1) verify handleClick uses latest onNavigate after re-render with new callback, (2) verify navigation works after config changes from undefined to explicit page mode. All 31 useNavigationOverlay tests and 739 console tests pass.
Root Cause: viewComponentSchema useMemo in ListView.tsx was missing groupingConfig, rowColorConfig, and navigation.handleClick in its dependency array. When users toggled grouping fields via the toolbar popover, the state changed but the memoized schema was not recomputed, so the child grid/kanban/gallery never received the updated grouping config.
Fix: Added groupingConfig, rowColorConfig, and navigation.handleClick to the useMemo dependency array at line 856 of ListView.tsx.
Tests: Added integration test in ListViewGroupingPropagation.test.tsx that verifies toggling a group field via the toolbar immediately updates the rendered schema. All 117 ListView tests pass.
Root Cause: onRowClick in both PluginObjectView (line 772) and renderListView (line 599) of ObjectView.tsx fell back to onEdit / editHandler, which only opens an edit form. The navOverlay.handleClick from useNavigationOverlay — which handles drawer/modal/page navigation modes — was never connected to these click handlers. Additionally, the useNavigationOverlay hook was missing the onNavigate callback needed for mode: 'page' to update the URL.
Fix: Replaced onEdit/editHandler fallbacks with navOverlay.handleClick in both row click handlers, added onNavigate callback to useNavigationOverlay that sets the recordId URL search parameter, and added navOverlay to the renderListView useCallback dependency array.
Tests: All 32 ObjectView tests and 29 useNavigationOverlay tests pass.
Root Cause: App.tsx defined a handleRowClick that hardcoded setSearchParams({recordId}) (drawer-only behavior) and passed it unconditionally to <ObjectView> via onRowClick={handleRowClick}. This overrode the internal navOverlay.handleClick in Console's ObjectView, preventing page/modal/split/popover/new_window navigation modes from working.
Fix: Removed handleRowClick from App.tsx and the onRowClick prop from both <ObjectView> route elements. Updated Console ObjectView to always use navOverlay.handleClick (3 locations) instead of falling back to the external onRowClick prop. Removed the unused onRowClick prop from the ObjectView function signature.
Tests: All 42 ObjectView tests and 33 useNavigationOverlay tests pass (3 new tests added).
Root Cause: While drawer/modal modes worked in the Console ObjectView, the remaining 4 navigation modes had gaps:
- Console's
onNavigatecallback relied on implicit fallthrough forviewaction (page mode) — not explicit. PluginObjectView'sformLayoutonly mappeddrawer/modalmodes;split/popoverfell through to the default layout (drawer), rendering the wrong overlay type.PluginObjectViewlackedNavigationOverlayintegration forsplit(resizable side-by-side panels) andpopover(compact dialog preview).
Fix:
- Console
onNavigatenow explicitly checks foraction === 'view'(page mode) alongside the existing'new_window'check. PluginObjectViewformLayoutnow includessplitandpopoverbranches.PluginObjectViewimports and rendersNavigationOverlayfrom@object-ui/componentsfor bothsplitmode (withmainContentwrapping the grid) andpopovermode (Dialog fallback when nopopoverTrigger).- Split mode close button properly resets form state via
handleFormCancel.
Tests: Updated split/popover tests to verify NavigationOverlay rendering (close panel button for split, dialog role for popover). Added split close-and-return test. All 29 PluginObjectView tests and 37 Console ObjectView tests pass.
Root Cause: When grouping is enabled in list view, buildGroupTableSchema in ObjectGrid.tsx sets pagination: false but inherits pageSize: 10 from the parent schema. The DataTableRenderer filler row logic (Array.from({ length: Math.max(0, pageSize - paginatedData.length) })) pads each group table with empty rows up to pageSize, creating many blank lines.
Fix: Added pagination && condition to filler row rendering in data-table.tsx. Filler rows only render when pagination is enabled, since their purpose is maintaining consistent page height during paginated views.
Tests: Added 2 tests in data-table-airtable-ux.test.tsx verifying filler rows are skipped when pagination is off and still rendered when pagination is on. All 59 related tests pass.
Root Cause: MetricWidget and MetricCard rendered I18nLabel objects ({key, defaultValue}) directly as React children. When CRM dashboard metadata used I18nLabel objects for trend.label (e.g. { key: 'crm.dashboard.trendLabel', defaultValue: 'vs last month' }), React threw error #31 ("Objects are not valid as a React child").
Fix: Added resolveLabel() helper to MetricWidget.tsx and MetricCard.tsx that converts I18nLabel objects to plain strings before rendering. Updated prop types to accept both string and I18nLabel objects for label, description, and trend.label.
Tests: Added 3 new tests: 1 in DashboardRenderer.widgetData.test.tsx verifying metric widgets with I18nLabel trend labels render correctly, and 2 in MetricCard.test.tsx verifying I18nLabel resolution for title and description. All 159 dashboard tests pass.
Root Cause: Multiple renderer defects caused incorrect field value display across views:
-
LookupCellRenderer— Destructured onlyvalue, ignoring thefieldprop. When the API returned a raw primitive ID (e.g.customer: 2), the renderer fell through toString(value)and showed"2"instead of the related record's name. No attempt was made to resolve IDs viafield.options. -
UserCellRenderer— Did not guard against primitive values (number/string user IDs). Accessing.name/.usernameon a number returnedundefined, silently falling through to"User"as the generic label. -
getCellRendererstandardMap —lookupandmaster_detailwere mapped toSelectCellRendererinstead ofLookupCellRendererin the fallback map. Although the fieldRegistry pre-registration shadowed this bug, it was semantically incorrect. -
status,user,ownertypes — Not pre-registered infieldRegistry. All went through thestandardMappath, making their association with renderers implicit and invisible.
Fix:
LookupCellRenderer: now accepts thefieldprop and resolves primitive IDs againstfield.options(matching byString(opt.value) === String(val)for type-safe comparison). Arrays of primitive IDs are resolved via the same logic. Null/empty-string guard updated from!valuetovalue == null || value === ''to handle0correctly.UserCellRenderer: primitive values (typeof !== 'object') return a plain<span>with the string representation. Array items that are not objects are also handled gracefully.getCellRendererstandardMap:lookupandmaster_detailnow correctly referenceLookupCellRenderer.fieldRegistrynow explicitly registersstatus→SelectCellRenderer,user→UserCellRenderer, andowner→UserCellRendereralongside the existinglookup/master_detail/selectregistrations.
Tests: Added 36 new tests in cell-renderers.test.tsx:
getCellRendererregistry assertions forlookup,master_detail,status,user,ownertypesTextCellRenderer: null, undefined, empty string, numeric zero (0 renders "0" not "-"), boolean falseLookupCellRenderer: null, empty-string, primitive ID (number), primitive ID (string), unresolved primitive, object with name/label/_id, array of objects, array of primitive IDs resolved via optionsUserCellRenderer: null, primitive number ID, primitive string ID, object with name, object with username, array of user objects
TextCellRenderer— Usedvalue || '-'which incorrectly rendered'-'for numeric0(falsy zero). Updated to(value != null && value !== '') ? String(value) : '-'for consistent null-only suppression.
All 313 @object-ui/fields tests pass.
Root Cause (1 — Toolbar defaults): showHideFields, showColor, and showDensity in ListView.tsx used opt-out logic (!== false), making secondary toolbar buttons visible by default. Airtable hides these controls unless explicitly enabled.
Fix: Changed default logic from !== false (opt-out) to === true (opt-in) for showHideFields, showColor, and showDensity in the toolbarFlags computation. Updated @default JSDoc comments in NamedListView and ListViewSchema interfaces from @default true to @default false.
Root Cause (2 — Duplicate record count): Both ListView.tsx (record-count-bar) and ObjectView.tsx (record-count-footer) independently rendered the record count at the bottom, causing duplicate display.
Fix: Removed the record-count-footer from ObjectView.tsx since ListView already renders the authoritative record-count-bar.
Tests: Updated 11 tests across ListView.test.tsx and ObjectView.test.tsx. All 112 ListView tests and 32 ObjectView tests pass.
Type-aware rendering, responsive layout, virtual scrolling, metadata-driven highlights, performance optimization, and activity panel collapse-when-empty.
HeaderHighlight (@object-ui/plugin-detail):
- Use
getCellRendererfor type-aware display (currency →$250,000.00, select → Badge, etc.) instead of rawString(value) - Add
objectSchemaprop for field metadata enrichment (type, options, currency, precision, format)
autoLayout (@object-ui/plugin-detail):
-
inferDetailColumnsaccepts optionalcontainerWidthfor responsive column capping (<640px→1col,<900px→max 2col) -
applyDetailAutoLayoutpasses throughcontainerWidthparameter
RecordChatterPanel (@object-ui/plugin-detail):
-
collapseWhenEmptyprop: auto-collapse panel when no feed items exist - Pass
collapseWhenEmptythrough to embeddedRecordActivityTimeline
DetailSection (@object-ui/plugin-detail):
-
virtualScrollconfig (VirtualScrollOptions): progressive batch rendering for sections with many fields - Export
VirtualScrollOptionstype from package index
RecordDetailView (apps/console):
- Wrap
detailSchemaconstruction withuseMemo(deps:objectDef,pureRecordId,related,childRelatedData,actionRefreshKey) - Remove hardcoded
HIGHLIGHT_FIELD_NAMES; read exclusively fromobjectDef.views.detail.highlightFields(no fallback) - Enable
collapseWhenEmpty+collapsible: trueonRecordChatterPanel
Tests: 125 plugin-detail tests passing (17 new) covering HeaderHighlight type-aware rendering, autoLayout responsive columns, RecordChatterPanel collapseWhenEmpty, DetailSection virtualScroll.
Platform-level DetailView enhancements: auto-grouping from form sections, empty value hiding, smart header with primaryField/summaryFields, responsive breakpoint fix, and activity timeline collapse.
Types (@object-ui/types):
-
DetailViewSection.hideEmpty?: boolean— filter null/undefined/empty string fields; hide empty sections -
DetailViewSchema.primaryField?: string— record-level title from data field -
DetailViewSchema.summaryFields?: string[]— render key attributes as Badge in header
DetailSection (@object-ui/plugin-detail):
-
hideEmptyfiltering: fields with null/undefined/empty string values are removed; section returns null when all fields hidden - Responsive breakpoint fix:
sm:grid-cols-2→md:grid-cols-2,sm:grid-cols-2 md:grid-cols-3→md:grid-cols-2 lg:grid-cols-3(correct behavior on iPad+sidebar)
DetailView Header (@object-ui/plugin-detail):
- Header renders
data[primaryField]as h1 title (falls back toschema.title) -
summaryFieldsrendered as<Badge variant="secondary">next to title
RecordActivityTimeline (@object-ui/plugin-detail):
-
collapseWhenEmptyprop: suppress "No activity recorded" message when true, showing only comment input
RecordDetailView (apps/console):
- Read
objectDef.views?.form?.sectionsfor section grouping; fallback to flat field list - Remove
columns: 2hardcode — letautoLayoutinfer optimal columns - Auto-detect
primaryFieldfrom object fields (name/title)
Field Renderers (@object-ui/fields):
-
EmailCellRenderer: mailto link + hover copy-to-clipboard button -
PhoneCellRenderer: tel link with call icon + hover copy-to-clipboard button -
BooleanCellRenderer: warning Badge for active/enabled/verified fields when false (e.g. "Active — Off")
Tests: 94 plugin-detail tests passing (11 new), 100 field renderer tests passing (12 new) covering hideEmpty filtering, empty section hiding, primaryField/summaryFields rendering, responsive breakpoints, collapseWhenEmpty, autoLayout undefined-columns regression, email copy, phone copy+icon, boolean warning badge.
Storybook: Added PrimaryFieldWithBadges and HideEmptyFields stories.
Issue #939: Lookup/master_detail fields displayed raw IDs (e.g.
o1,p1) instead of expanded names across ListView, DetailView, and DetailSection.
Root Causes (5 independent bugs):
- ListView
$expandrace condition —expandFieldsdepended on asyncobjectDefwhich could benullon first fetch, causing data to be requested without$expandand returning raw foreign-key IDs. - DetailView missing
$expandand objectSchema —findOne()was called without$expandparameters and without loadingobjectSchema, so lookup fields could never be expanded. - DetailSection missing objectSchema enrichment — When
field.typewas not explicitly set,displayValuefell through toString(value), bypassing type-aware CellRenderers even when objectSchema metadata was available. - ObjectStackAdapter
find()dropping$expand— The@objectstack/clientv3.0.10'sdata.find()(GET) does not supportexpandin itsQueryOptionsinterface, so$expandwas silently dropped duringconvertQueryParams(). - ObjectStackAdapter
findOne()ignoring params — ThefindOne()method declared its params argument as_params(unused), meaning$expandwas never sent to the server.
Fix:
- ListView (
packages/plugin-list/src/ListView.tsx): AddedobjectDefLoadedstate flag. Data fetch effect is gated onobjectDefLoadedso the first fetch always includes correct$expandparameters. TheobjectDeffetch effect sets the flag infinallyblock to handle both success and error cases. - DetailView (
packages/plugin-detail/src/DetailView.tsx): AddedobjectSchemastate. Data fetch effect now callsgetObjectSchema()first, computes$expandviabuildExpandFields(), and passes the params tofindOne(). The resolvedobjectSchemais passed toDetailSectioncomponents. - DetailSection (
packages/plugin-detail/src/DetailSection.tsx): Added optionalobjectSchemaprop. Fields are enriched with missing metadata (options, currency, precision, format, reference_to, reference_field) fromobjectSchemaregardless of whetherfield.typeis explicitly set. Whenfield.typeis not set, the type is also resolved from objectSchema. Explicitfield.typealways takes precedence for the resolved type. - ObjectStackAdapter
find()(packages/data-objectstack/src/index.ts): When$expandis present, routes throughrawFindWithPopulate()which issues a raw GET request with apopulate=query parameter derived from the$expandfield names. The server's REST plugin routesGET /data/:objecttofindData()which processespopulate(comma-separated string) for lookup expansion. Falls back toclient.data.find()when no expand is needed. - ObjectStackAdapter
findOne()(packages/data-objectstack/src/index.ts): When$expandis present, uses the samerawFindWithPopulate()mechanism withfilter={"_id":"..."}andpopulate=to fetch the single record with expanded lookup fields. Falls back toclient.data.get()when no expand is needed.
Tests: 15 new tests added (2 ListView, 3 DetailView, 4 DetailSection, 8 data-objectstack). All 505 plugin tests + 94 data-objectstack tests pass.
Bug: On mobile devices, detail page fields displayed in 3-column layout instead of single column, causing content to squeeze together and severely impacting readability.
Root Cause: When applyAutoSpan set span: 3 on wide fields (textarea, markdown, etc.) in a 3-column layout, the resulting col-span-3 CSS class was applied without responsive prefixes. In CSS Grid, when an item has col-span-3 in a grid-cols-1 layout, the browser creates 2 implicit column tracks — and subsequent auto-placed items flow into those implicit columns, producing a 3-column layout even on mobile.
Fix:
- DetailSection (
packages/plugin-detail/src/DetailSection.tsx): AddedgetResponsiveSpanClass()helper that generates responsive col-span classes matching the grid breakpoints. For a 3-column layout: no col-span at base (mobile single-column),md:col-span-2at tablet,lg:col-span-3at desktop. For a 2-column layout: no col-span at base,md:col-span-2at tablet+. This ensures col-span never exceeds the visible column count at each breakpoint, preventing implicit grid columns on mobile.
Tests: 11 new tests (8 getResponsiveSpanClass unit tests + 3 DetailSection integration tests verifying no bare col-span-N classes appear). All 52 plugin-detail tests pass.
Issue #107: All Action buttons on record detail pages (Change Stage, Mark as Won, etc.) clicked with zero response — no dialogs, no API calls, no toast, no data refresh.
Root Causes (6 independent bugs):
- Missing
ActionProvider—RecordDetailViewdidn't wrapDetailViewwithActionProvider, souseAction()fell back to an emptyActionRunnerwith no handlers. - Action type overwritten —
action:barcomponent overrodeaction.type('api') with the component type ('action:button'), soActionRunnernever matched the registered'api'handler. - No API handler —
apiaction targets were logical names (e.g.,'opportunity_change_stage'), not HTTP URLs. The built-inexecuteAPI()triedfetch('opportunity_change_stage')which failed silently. - No param collection —
ActionParam[]was passed asparams(values) instead ofactionParams(definitions to collect), so the param collection dialog was never triggered. - No confirm/toast handlers —
confirmTextfell back towindow.confirm, success/error messages were silently dropped. - No visibility context —
useConditionevaluatedvisibleexpressions like"stage !== 'closed_won'"with empty context, always returningtrue.
Fix:
- RecordDetailView (
apps/console/src/components/RecordDetailView.tsx): WrappedDetailViewwith<ActionProvider>providingonConfirm,onToast,onNavigate,onParamCollectionhandlers and a customapihandler that maps logical action targets todataSource.update()operations. - action-bar (
packages/components/src/renderers/action/action-bar.tsx): Preserves originalaction.typeasactionTypewhen overriding with component type. Forwardsdataprop to child action renderers for visibility context. - action-button (
packages/components/src/renderers/action/action-button.tsx): UsesactionTypefor execution. DetectsActionParam[]arrays and passes asactionParams. Passes recorddatatouseConditionfor visibility expressions. - ActionConfirmDialog (
apps/console/src/components/ActionConfirmDialog.tsx): Promise-based confirmation dialog using ShadcnAlertDialog. - ActionParamDialog (
apps/console/src/components/ActionParamDialog.tsx): Dynamic form dialog for collecting action parameters (select, text, textarea) using ShadcnDialog.
Tests: 6 new integration tests covering: action button rendering, confirm dialog show/accept/cancel, param collection dialog, toast notification, dataSource.update invocation. All 764 console tests pass.
| Risk | Mitigation |
|---|---|
| AppShell complexity (7 nav types, areas, mobile) | Start with static nav tree, add mobile modes incrementally |
| Designer DnD integration time | Use @dnd-kit/core (already proven in Kanban/Dashboard) |
| Airtable UX bar is high | Focus on Grid + Kanban + Form triad first; defer Gallery/Timeline polish |
| PWA real sync complexity | Keep simulated sync as fallback; real sync behind feature flag |
| Performance regression | Performance budgets in CI, 10K-record benchmarks |
| View config live preview dependency chain breakage | generateViewSchema hardcodes non-grid defaults; per-view-type integration tests required (see P1.8.1) |
Config property type gaps (NamedListView missing fields) |
Add first-class properties to @object-ui/types; use Zod schema to validate at runtime |
Status: Complete — Comprehensive UX polish for Airtable-level table experience.
i18n Compliance:
- All hardcoded UI strings in
data-table.tsxreplaced witht()calls (12+ strings) - All hardcoded UI strings in
ListView.tsxreplaced witht()calls (6+ strings) - New i18n keys added to
en.tsandzh.tslocale files (table/list sections) - Default fallback translations ensure standalone usage works without I18nProvider
Row Selection Auto-Enable:
-
ObjectGridauto-enablesselectionMode: 'multiple'whenbatchActions/bulkActionsare defined - No explicit
selectableconfig needed for bulk action workflows
Column Width Optimization:
- Selection checkbox and row number columns reduced from
w-12(48px) tow-10(40px) - Frozen column offset calculations updated to match new widths
Border/Cell Contrast:
- Table row borders increased from
border-border/50toborder-borderfor better scanability
Pagination Density:
- Filler row
colSpannow correctly includesshowRowNumberscount
Goal: Salesforce-style detail page enhancements: i18n for all detail page UI elements, improved empty value display, related list actions, and auto-discovery of related lists.
i18n Integration:
- Add
detail.*translation keys to all 11 locale files (en, zh, ja, de, fr, es, ar, ru, pt, ko) -
useDetailTranslationsafe wrapper hook with English fallback (follows existing useGridTranslation/useListViewTranslation pattern) - DetailView fully i18n-integrated (Back, Edit, Share, Delete, Duplicate, Export, View history, Record not found, Related heading, favorites, navigation)
- DetailSection copy tooltip i18n via
useSectionTranslation - RelatedList i18n-integrated (record counts, loading, empty state)
- Add
'detail'toBUILTIN_KEYSinuseObjectLabel.tsto prevent namespace collision
Empty Value Display:
- Replace hardcoded
-with styled em-dash (—) usingtext-muted-foreground/50 text-xs italicfor elegant empty state
Related List Enhancements:
- Add
onNewprop and "New" button to RelatedList header - Add
onViewAllprop and "View All" button to RelatedList header - Record count uses singular/plural i18n keys
Tests:
- 10 new RelatedList tests (title, record counts, empty state, New/View All buttons)
- 2 new DetailView i18n fallback tests (Record not found text, Related heading)
- Updated DetailSection tests for new empty value styling
Completed:
- Auto-discover related lists from objectSchema reference fields
- Tab layout (Details/Related/Activity) for detail page
- Related list row-level Edit/Delete quick actions
- Related list pagination, sorting, filtering
- Collapsible section groups
- Header highlight area with key fields
- Console
RecordDetailViewintegration:autoTabs,autoDiscoverRelated,highlightFields,sectionGroupswired intodetailSchemafor end-to-end availability - Console reverse-reference discovery: child objects (e.g.,
order_item→order) auto-discovered and rendered with filtered data -
useSafeFieldLabelwired intoDetailSection,RelatedList,HeaderHighlightfor convention-based field label i18n (#968, #883, #942) -
objectNamethreaded throughDetailView→SectionGroup→DetailSection/HeaderHighlight/RelatedList - RelatedList sortable headers fixed to use
effectiveColumns(auto-generated from schema) instead of rawcolumnsprop - Added missing
detail.*i18n keys (activity,editRow,deleteRow,previousPage,nextPage, etc.) to en.ts and zh.ts - RelatedList auto-generated columns use
getCellRendererfor type-aware cell rendering (date, currency, select/badge, lookup, boolean, etc.) - Console
RecordDetailViewfallback section title usest('detail.details')instead of hardcoded English
- CONTRIBUTING.md — Contribution guidelines
- QUICK_REFERENCE.md — Developer quick reference
- Plugin Development Guide
Roadmap Status: 🎯 Active — AppShell ✅ · Designer Interaction · View Config Live Preview Sync ✅ · Dashboard Config Panel ✅ · Schema-Driven View Config Panel ✅ · Right-Side Visual Editor Drawer ✅ · Feed/Chatter Documentation ✅ · Airtable UX Parity Next Review: March 15, 2026 Contact: hello@objectui.org | https://github.com/objectstack-ai/objectui