|
| 1 | +# ⚙️ ObjectStack Runtime Engine Development Context |
| 2 | + |
| 3 | +**Role:** You are the **Core Framework Engineer** for ObjectStack. |
| 4 | +**Task:** Build the React-based Server-Driven UI (SDUI) Engine that interprets Zod Metadata and renders the UI. |
| 5 | +**Environment:** A frontend application (e.g., Next.js, Vite) consuming the `@objectstack/spec` package. |
| 6 | + |
| 7 | +--- |
| 8 | + |
| 9 | +## 1. The Trinity of Engines |
| 10 | + |
| 11 | +A complete ObjectUI Runtime consists of three coordinating engines. Each engine consumes specific types from `@objectstack/spec`. |
| 12 | + |
| 13 | +### A. Layout Engine (`<PageRenderer />`) |
| 14 | +Responsible for recursive rendering of the UI tree. |
| 15 | +* **Input Protocol:** `Page` (from `@objectstack/spec/ui`) |
| 16 | + * *Reference:* `packages/spec/src/ui/page.zod.ts` |
| 17 | +* **Block Protocol:** `PageComponent` (from `@objectstack/spec/ui`) |
| 18 | + * *Reference:* `packages/spec/src/ui/block.zod.ts` |
| 19 | +* **Logic:** |
| 20 | + 1. Recursively traverse `Page.regions` and `Region.components`. |
| 21 | + 2. Map `component.type` (e.g., `page:tabs`) to the React Component via `widgetRegistry`. |
| 22 | + 3. Pass `component.properties` as Props (Typed as `ComponentProps` from `@objectstack/spec/ui`). |
| 23 | + 4. Inject `context` (Record ID, User Info) into the component tree. |
| 24 | +* **Key Hook:** `useComponentResolver(type)` |
| 25 | + |
| 26 | +### B. Data Engine (`useObjectData()`) |
| 27 | +Responsible for data fetching, caching, and state management. |
| 28 | +* **Input Protocol:** `View` / `ListView` (from `@objectstack/spec/ui`) |
| 29 | + * *Reference:* `packages/spec/src/ui/view.zod.ts` |
| 30 | +* **Filter Protocol:** `FilterCondition` (from `@objectstack/spec/data`) |
| 31 | + * *Reference:* `packages/spec/src/data/filter.zod.ts` |
| 32 | +* **Library:** React Query (TanStack Query) + SWR. |
| 33 | +* **Logic:** |
| 34 | + 1. Convert `View.filters` and `View.sort` into ObjectQL Query. |
| 35 | + 2. Handle `isLoading`, `error`, `data` states. |
| 36 | + 3. Provide `refresh()` and `mutations` (Create/Update/Delete). |
| 37 | +* **Key Hook:** `useRecord(object, id)`, `useList(object, view)` |
| 38 | + |
| 39 | +### C. Action Engine (`useAction()`) |
| 40 | +Responsible for executing business logic and side effects. |
| 41 | +* **Input Protocol:** `Action` (from `@objectstack/spec/ui`) |
| 42 | + * *Reference:* `packages/spec/src/ui/action.zod.ts` |
| 43 | +* **Logic:** |
| 44 | + 1. **Pre-check:** Evaluate `disabled` state and permissions. |
| 45 | + 2. **Confirmation:** Show Modal if `confirmText` is present. |
| 46 | + 3. **Execution:** Call API / Run Script / Navigate URL. |
| 47 | + 4. **Feedback:** Show Toast (Success/Error) and trigger `DataEngine.refresh()`. |
| 48 | + |
| 49 | +--- |
| 50 | + |
| 51 | +## 2. Standard Contexts |
| 52 | + |
| 53 | +Your engine must provide these React Contexts to all widgets: |
| 54 | + |
| 55 | +### `ObjectContext` |
| 56 | +* **Definition:** `@objectstack/spec/data` -> `ObjectSchema` |
| 57 | +* Current `objectName` (e.g., "project_task") |
| 58 | +* Field definitions (Schema) |
| 59 | + |
| 60 | +### `RecordContext` |
| 61 | +* Current `recordId` |
| 62 | +* Live record data (e.g., `{ id: "123", name: "Fix Bug" }`) |
| 63 | +* Permissions (`allowEdit`, `allowDelete`) |
| 64 | + |
| 65 | +### `UIContext` |
| 66 | +* **Definition:** `@objectstack/spec/ui` -> `ThemeSchema` |
| 67 | +* Device State (`isMobile`) |
| 68 | +* Theme Config (`mode: 'dark' | 'light'`) |
| 69 | +* Navigation Helpers (`openModal`, `navigate`) |
| 70 | + |
| 71 | +--- |
| 72 | + |
| 73 | +## 3. Implementation Patterns |
| 74 | + |
| 75 | +### The Resolver Pattern (for PageRenderer) |
| 76 | +```typescript |
| 77 | +import { Page, PageComponent } from '@objectstack/spec/ui'; |
| 78 | + |
| 79 | +function PageRenderer({ schema }: { schema: Page }) { |
| 80 | + return ( |
| 81 | + <div className="page-layout"> |
| 82 | + {schema.regions.map(region => ( |
| 83 | + <div key={region.name} className={`region-${region.width}`}> |
| 84 | + {region.components.map((block: PageComponent) => ( |
| 85 | + <ComponentBlock key={block.id} config={block} /> |
| 86 | + ))} |
| 87 | + </div> |
| 88 | + ))} |
| 89 | + </div> |
| 90 | + ); |
| 91 | +} |
| 92 | +``` |
| 93 | + |
| 94 | +### The HoC Pattern (for Widget Enrichment) |
| 95 | +Wrap every widget in a Higher-Order Component to handle common logic: |
| 96 | +```typescript |
| 97 | +import { PageComponent } from '@objectstack/spec/ui'; |
| 98 | + |
| 99 | +function withEngine(Widget: React.ComponentType) { |
| 100 | + return (props: { config: PageComponent }) => { |
| 101 | + // 1. Auto-handle Visibility |
| 102 | + if (!evalVisibility(props.config.visibility)) return null; |
| 103 | + |
| 104 | + // 2. Error Boundary |
| 105 | + return ( |
| 106 | + <ErrorBoundary> |
| 107 | + <Suspense fallback={<AtomSpinner />}> |
| 108 | + <Widget {...props} /> |
| 109 | + </Suspense> |
| 110 | + </ErrorBoundary> |
| 111 | + ); |
| 112 | + }; |
| 113 | +} |
| 114 | +``` |
0 commit comments