-
Notifications
You must be signed in to change notification settings - Fork 0
Description
1. Understanding the Airtable Pattern
From the screenshots, Airtable's Interface Designer implements a Design → Stage → Publish workflow:
— List View Interface: A customer management list page with a left sidebar navigation, configurable view (Sort, Filter, Group), and a right-hand property panel controlling Page type, Data binding, Appearance, and User Actions.
— Dashboard Interface: A performance dashboard with KPI cards, charts (Line, Bar, Pie), and a right-hand property panel for chart configuration (Source, Axes, Appearance).
Key observations from both screenshots:
| Feature | Detail |
|---|---|
| Publish Status | "No changes · Last published Feb 21, 2026" / "Interface has unpublished changes · Last published Feb 18, 2026" |
| Workflow | Design (edit mode, blue dashed borders) → Stage (preview as "Yourself") → Publish (red button) |
| Navigation | Hierarchical sidebar: App → Sections → Pages |
| Right Panel | Context-aware property inspector (changes based on selected element) |
| View Types | List, Chart, Dashboard (composable elements) |
| Share Model | "Share interface" — published interfaces are shared independently |
2. Evaluation: Do We Need This?
✅ YES — This is a Core Requirement
This is not a "nice-to-have". The Design → Stage → Publish pattern is essential for ObjectUI's positioning as a "JSON-to-Shadcn" SDUI engine. Here's why:
| Reason | Explanation |
|---|---|
| Enterprise Readiness | No enterprise will deploy UI changes directly to production. A staging/publish gate is table stakes. |
| Our Differentiator | Airtable's Interface Designer proves the market wants visual, metadata-driven UI building. We provide the same experience but with Shadcn-quality output instead of Airtable's locked-in components. |
| Schema-Driven by Nature | Our JSON protocol (UIComponent) already IS the "staged" artifact. We just need to formalize the lifecycle around it. |
| Backend Agnostic | Unlike Airtable (tied to its own DB), we can connect to any data source — making our version more powerful. |
Risk if we DON'T implement:
- ObjectUI becomes "just a renderer" — a commodity.
- No competitive moat against Retool, Appsmith, or ToolJet.
3. Architecture Design: Interface Lifecycle Protocol
3.1 The Three-Phase Lifecycle
┌─────────┐ save ┌─────────┐ publish ┌─────────────┐
│ DESIGN │ ────────────► │ DRAFT │ ────────────► │ PUBLISHED │
│ (edit) │ │ (staged)│ │ (immutable) │
└─────────┘ └─────────┘ └─────────────┘
▲ │ │
│ revert │ rollback │
└─────────────────────────┘◄─────────────────────────┘
| Phase | Description | Storage |
|---|---|---|
| Design | Active editing. Blue dashed borders (as in screenshots). Auto-saved to draft. | Mutable draft record |
| Draft (Staged) | Preview-ready snapshot. "Interface has unpublished changes" banner. | Versioned draft snapshot |
| Published | Immutable, live version. "Last published Feb 21, 2026". End-users see this. | Immutable version record |
3.2 Protocol Additions to @objectstack/spec
Let me first check the current state of both repositories:Now let me look at the existing UI protocol schemas in the spec repo and the current packages structure in objectui to understand the current state:Now let me dig deeper into the specific interface and designer-related schemas and the gap analysis document to understand the current state of the publish/staging flow.Now let me check the current state of the app.zod.ts in the spec repo since the gap analysis document mentions it was superseded, and also look at the plugin-designer package for the existing design-time tooling:Now I have a comprehensive picture of both repositories. Let me synthesize this into a thorough assessment.
Assessment: Airtable-Style App Design Pattern for ObjectStack
Overview: What You're Asking
Based on the Airtable screenshots (Image 1: Customer List Interface with Grid/Tabs, Image 2: Performance Dashboard with Charts), you're evaluating the Design → Stage → Publish lifecycle pattern — specifically:
- Design Mode — Drag-and-drop interface builder with right-panel property inspector
- Staging — "Unpublished changes" state where edits are buffered before going live
- Publish — One-click deployment to end-users (Airtable's red "Publish" button)
1. Current State Assessment
✅ What's Already Done (Spec Protocol — objectstack-ai/spec)
The spec repo has extensive schema coverage for the Airtable Interface model. Per the gap analysis doc:
| Area | Status | Schema Location |
|---|---|---|
| Interface as first-class entity | ✅ | InterfaceSchema in src/ui/interface.zod.ts |
| 16 page types (grid, list, dashboard, kanban, etc.) | ✅ | PageTypeSchema in src/ui/page.zod.ts |
| Element palette (text, number, image, divider, button, filter, form, record_picker) | ✅ | PageComponentType extensions |
| Per-element data binding | ✅ | ElementDataSourceSchema |
| Record Review workflow | ✅ | RecordReviewConfigSchema |
| Free-form canvas | ✅ | BlankPageLayoutSchema |
| Builder config (snap, zoom, palette, layers) | ✅ | InterfaceBuilderConfigSchema |
| Sharing & embedding | ✅ | SharingConfigSchema, EmbedConfigSchema |
| Interface page config (hierarchy, filters, appearance) | ✅ | InterfacePageConfigSchema |
| App → Interface navigation bridge | ✅ | App.interfaces[], InterfaceNavItemSchema |
❌ What's Missing — The Draft→Published Lifecycle
This is the critical gap. The Airtable screenshots clearly show:
- Image 1:
"No changes · Last published Feb 21, 2026"— a clean published state - Image 2:
"Interface has unpublished changes · Last published Feb 18, 2026"— a dirty draft state with "All changes saved" auto-save
This versioning/staging/publish lifecycle is explicitly listed as a Phase D (v4.1) TODO in the roadmap:
"Interface versioning (draft → published → archived lifecycle)" — Section 7.4
❌ What's Missing in ObjectUI (Runtime — objectstack-ai/objectui)
The objectui repo has the AppCreationWizard (4-step wizard for creating apps) and WorkflowDesigner with save/publish buttons, but lacks:
| Gap | Description |
|---|---|
| Interface Builder UI | Drag-and-drop canvas is not built (confirmed: "Implement Studio Interface Builder UI — runtime task") |
| Draft/Publish state management | No useDesignerState() hook or store for tracking dirty/clean/published states |
| Auto-save mechanism | Airtable auto-saves all design changes to a draft — no such mechanism exists |
| Publish flow | No "Publish" button that snapshots current draft to a published version |
| Version comparison | No diff between published and draft versions |
| Preview mode | DesignPreviewSchema defined in spec but not built in runtime |
2. Recommended Architecture for Design → Stage → Publish
2.1 Protocol Layer (spec repo)
We need a new Interface Versioning Protocol. Here's the proposed schema:
// Proposed: packages/spec/src/ui/interface-lifecycle.zod.ts
import { z } from 'zod';
/**
* Interface Lifecycle Status
* Models Airtable's "No changes / Unpublished changes / Last published" states.
*/
export const InterfaceLifecycleStatus = z.enum([
'draft', // Never published, work in progress
'published', // Live version matches last edit (clean)
'modified', // Has unpublished changes (dirty)
'archived', // Soft-deleted / historical
]);
/**
* Interface Version Snapshot
* Immutable snapshot of an interface at a point in time.
*/
export const InterfaceVersionSchema = z.object({
/** Version number (auto-incrementing) */
version: z.number().int().min(1),
/** Snapshot of the full interface definition (pages, elements, config) */
snapshot: z.any().describe('Serialized InterfaceSchema at publish time'),
/** Who published this version */
publishedBy: z.string().optional(),
/** When this version was published */
publishedAt: z.string().datetime(),
/** Optional release note */
changeNote: z.string().optional(),
});
/**
* Interface Draft State
* Tracks the working copy and its relationship to the published version.
*/
export const InterfaceDraftStateSchema = z.object({
/** Current lifecycle status */
status: InterfaceLifecycleStatus,
/** Working copy (auto-saved, mutable) */
draft: z.any().describe('Current working InterfaceSchema'),
/** Last published version number */
publishedVersion: z.number().int().optional(),
/** Last published timestamp */
lastPublishedAt: z.string().datetime().optional(),
/** Last auto-save timestamp */
lastSavedAt: z.string().datetime().optional(),
/** Whether the draft differs from the published version */
hasUnpublishedChanges: z.boolean().default(false),
/** Version history (ordered newest first) */
versionHistory: z.array(InterfaceVersionSchema).optional(),
});2.2 Runtime Layer (objectui repo)
A. State Store (Zustand)
// Proposed: packages/plugin-designer/src/stores/useInterfaceDesigner.ts
import { create } from 'zustand';
interface InterfaceDesignerState {
// === Lifecycle ===
status: 'draft' | 'published' | 'modified' | 'archived';
publishedVersion: number | null;
lastPublishedAt: string | null;
lastSavedAt: string | null;
hasUnpublishedChanges: boolean;
// === Draft ===
draft: InterfaceSchema | null;
// === Actions ===
/** Update draft (triggers auto-save debounce) */
updateDraft: (partial: Partial<InterfaceSchema>) => void;
/** Publish current draft → new published version */
publish: () => Promise<void>;
/** Revert draft to last published version */
revertToPublished: () => void;
/** Load interface (hydrate from server) */
loadInterface: (id: string) => Promise<void>;
}B. Auto-Save Hook
// Proposed: packages/plugin-designer/src/hooks/useAutoSave.ts
/**
* Auto-save hook that debounces draft changes and persists them.
* Mirrors Airtable's "All changes saved" behavior.
*/
export function useAutoSave(draft: InterfaceSchema | null, options?: {
debounceMs?: number; // default 2000
onSave?: (draft: InterfaceSchema) => Promise<void>;
onStatusChange?: (status: 'saving' | 'saved' | 'error') => void;
}) {
// Debounced save logic
// Returns: { saveStatus: 'saving' | 'saved' | 'error', lastSavedAt: Date | null }
}C. Publish Flow UI
The top bar should mirror Airtable's pattern:
┌─────────────────────────────────────────────────────────────────┐
│ Previewing as: [Yourself ▼] │
│ │
│ [status indicator] [Publish] [Share ▼] │
│ "Interface has unpublished changes · │
│ Last published Feb 18, 2026" │
└─────────────────────────────────────────────────────────────────┘
States to render:
- Clean:
"No changes · Last published {date}"— Publish button disabled - Dirty:
"Interface has unpublished changes · Last published {date}"— Publish button red/active - Saving:
"All changes saved"indicator (green checkmark) - Never published:
"Draft · Never published"— Publish button active
2.3 Data Flow
┌──────────────┐ Auto-Save (2s debounce) ┌──────────────┐
│ User edits │ ──────────────────────────────── │ Draft (DB) │
│ in Designer │ │ status: │
│ │ │ 'modified' │
└──────────────┘ └──────────────┘
│
[Publish] click
│
▼
┌──────────────┐
│ Published │
│ Version N+1 │
│ (immutable │
│ snapshot) │
└──────────────┘
│
Rendered to
end-users
│
▼
┌──────────────┐
│ Runtime │
│ Renderer │
│ (published │
│ version │
│ only) │
└──────────────┘
3. Gap-by-Gap Implementation Plan
| # | Gap | Where | Effort | Priority |
|---|---|---|---|---|
| L1 | InterfaceLifecycleStatus + InterfaceVersionSchema |
spec → src/ui/interface-lifecycle.zod.ts |
1 week | 🔴 P0 |
| L2 | InterfaceDraftStateSchema with version history |
spec → same file |
1 week | 🔴 P0 |
| L3 | useInterfaceDesigner Zustand store |
objectui → packages/plugin-designer |
2 weeks | 🔴 P0 |
| L4 | Auto-save hook (useAutoSave) |
objectui → packages/plugin-designer |
1 week | 🔴 P0 |
| L5 | Publish flow UI (top bar + confirmation dialog) | objectui → packages/plugin-designer |
1 week | 🔴 P0 |
| L6 | Version history panel (view past snapshots) | objectui → packages/plugin-designer |
1 week | 🟡 P1 |
| L7 | Revert to published version | objectui → packages/plugin-designer |
3 days | 🟡 P1 |
| L8 | Diff view (draft vs published) | objectui → packages/plugin-designer |
2 weeks | 🟡 P1 |
| L9 | "Preview as" user impersonation | objectui → packages/plugin-designer |
2 weeks | 🟡 P1 |
| L10 | Interface Builder drag-and-drop canvas | objectui → packages/plugin-designer |
6–8 weeks | 🔴 P0 |
Total estimated effort: ~12–16 weeks for full Airtable-parity design/stage/publish lifecycle.
4. Verdict: Is It Needed?
Yes, absolutely — this is a P0 strategic requirement.
Reasoning:
-
It's the Add metamodel interfaces for ObjectQL/ObjectUI contract #1 differentiator for low-code platforms. Every competitor (Airtable, Retool, Appsmith, Salesforce Lightning) has a design → preview → publish flow. Without it, ObjectUI is a component library, not an app builder.
-
The spec is already 80% there. Phases A, B, and C of the Airtable gap analysis are largely complete in the spec repo. The missing 20% is the lifecycle protocol (draft/publish) and the runtime builder UI.
-
It directly maps to your screenshots. Both images show the exact UX pattern — the top bar with publish state, right-panel property inspector, left sidebar navigation tree — all of which have spec schemas but no runtime implementation.
-
The existing
plugin-designerpackage is the natural home. It already hasAppCreationWizard, which proves the wizard/design pattern. Extending it with the Interface Builder + Publish flow is architecturally clean.
How to Prioritize
| Phase | What | Timeline |
|---|---|---|
| Phase 1 | Define InterfaceLifecycleStatus protocol in spec. Build useInterfaceDesigner store + auto-save + publish flow in objectui. |
4 weeks |
| Phase 2 | Build Interface Builder canvas (drag-and-drop elements from palette, property panel). This is the biggest chunk. | 6–8 weeks |
| Phase 3 | Version history, diff view, preview-as-user, revert. | 4 weeks |
Would you like me to create issues in one or both repos to track this work?