Skip to content

Commit 5a0aeee

Browse files
os-zhuangCopilot
andcommitted
feat(studio): remove MSW runtime, wire @object-ui live previews
Remove MSW mode entirely from Studio — runtime is now server-only, talking to the ObjectStack API via same-origin (when embedded) or the Vite proxy (in dev). Drops the runtime-mode toggle, MSW badge, init script, and corresponding optimizeDeps entries. Add a single MetadataPreview dispatcher that renders live previews of objects, forms, views, kanbans, calendars, and detail pages by binding @object-ui components (ObjectForm / ObjectGrid / ObjectKanban / DetailView / ObjectCalendar) to a memoised ObjectStack DataSource adapter. Supports both flat view specs and multi-view documents (e.g. { list, kanban, calendar, form }) with a sub-view tab bar. Wire MetadataPreview into: • MetadataListPage — Eye-icon Preview button on every previewable card (object, view, dashboard) • Object Hub Forms + Views tabs — Preview button per row Verified end-to-end in browser: • sys_user → ObjectGrid with live record (dev user) • crm/account view → List + Form sub-view tabs render correctly Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 5faadda commit 5a0aeee

16 files changed

Lines changed: 776 additions & 269 deletions

apps/studio/package.json

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,8 @@
88
"dist"
99
],
1010
"scripts": {
11-
"msw:init": "msw init -y public",
12-
"dev": "pnpm msw:init && vite",
13-
"build": "pnpm msw:init && vite build",
11+
"dev": "vite",
12+
"build": "vite build",
1413
"typecheck": "tsc --noEmit",
1514
"test": "vitest run",
1615
"test:watch": "vitest",
@@ -23,6 +22,15 @@
2322
},
2423
"dependencies": {
2524
"@ai-sdk/react": "^3.0.187",
25+
"@object-ui/core": "^5.0.1",
26+
"@object-ui/data-objectstack": "^5.0.1",
27+
"@object-ui/plugin-calendar": "^5.0.1",
28+
"@object-ui/plugin-detail": "^5.0.1",
29+
"@object-ui/plugin-form": "^5.0.1",
30+
"@object-ui/plugin-grid": "^5.0.1",
31+
"@object-ui/plugin-kanban": "^5.0.1",
32+
"@object-ui/react": "^5.0.1",
33+
"@object-ui/types": "^5.0.1",
2634
"@objectstack/client": "workspace:*",
2735
"@objectstack/client-react": "workspace:*",
2836
"@objectstack/driver-memory": "workspace:*",

apps/studio/src/components/DeveloperOverview.tsx

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,8 @@ import { Badge } from "@/components/ui/badge";
88
import { Button } from "@/components/ui/button";
99
import {
1010
Package, Database, Layers, Globe, Terminal, Box,
11-
RefreshCw, ExternalLink, Code2, Cpu
11+
RefreshCw, ExternalLink, Code2
1212
} from 'lucide-react';
13-
import { config } from '@/lib/config';
1413
import type { InstalledPackage } from '@objectstack/spec/kernel';
1514

1615
interface DeveloperOverviewProps {
@@ -98,10 +97,6 @@ export function DeveloperOverview({ packages, selectedPackage, onNavigate = () =
9897
</p>
9998
</div>
10099
<div className="flex items-center gap-2">
101-
<Badge variant="outline" className="font-mono text-xs gap-1">
102-
<Cpu className="h-3 w-3" />
103-
{config.mode.toUpperCase()}
104-
</Badge>
105100
<Button variant="outline" size="sm" onClick={loadStats} disabled={stats.loading}>
106101
<RefreshCw className={`h-3.5 w-3.5 mr-1.5 ${stats.loading ? 'animate-spin' : ''}`} />
107102
Refresh

apps/studio/src/components/MetadataListPage.tsx

Lines changed: 51 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,26 @@
1919

2020
import { useCallback, useEffect, useMemo, useState } from 'react';
2121
import { useNavigate } from '@tanstack/react-router';
22-
import { Search } from 'lucide-react';
22+
import { Eye, Search } from 'lucide-react';
2323
import { useClient, useMetadataSubscriptionCallback } from '@objectstack/client-react';
2424
import { Card, CardContent } from '@/components/ui/card';
2525
import { Badge } from '@/components/ui/badge';
2626
import { Input } from '@/components/ui/input';
2727
import { Button } from '@/components/ui/button';
28+
import {
29+
Dialog,
30+
DialogContent,
31+
DialogDescription,
32+
DialogHeader,
33+
DialogTitle,
34+
} from '@/components/ui/dialog';
35+
import { MetadataPreview } from './MetadataPreview';
2836
import { navItemForType } from './studio-nav';
2937
import type { LucideIcon } from 'lucide-react';
3038

39+
/** Metadata types we can render a live preview for via @object-ui. */
40+
const PREVIEWABLE_TYPES = new Set(['object', 'view', 'dashboard']);
41+
3142
export interface MetadataListPageProps {
3243
/** Display title (e.g. "Objects", "Forms"). */
3344
title: string;
@@ -78,6 +89,7 @@ export function MetadataListPage({
7889
const [loading, setLoading] = useState(true);
7990
const [error, setError] = useState<string | null>(null);
8091
const [query, setQuery] = useState('');
92+
const [previewRow, setPreviewRow] = useState<Row | null>(null);
8193
const [activeTypes, setActiveTypes] = useState<Set<string>>(new Set());
8294

8395
const load = useCallback(async () => {
@@ -229,10 +241,11 @@ export function MetadataListPage({
229241
<div className="grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
230242
{filtered.map((row) => {
231243
const Icon = iconForType?.(row.type) ?? navItemForType(row.type)?.icon;
244+
const canPreview = PREVIEWABLE_TYPES.has(row.type);
232245
return (
233246
<Card
234247
key={`${row.type}:${row.name}`}
235-
className="cursor-pointer transition hover:border-primary hover:shadow-sm"
248+
className="group cursor-pointer transition hover:border-primary hover:shadow-sm"
236249
onClick={() => openRow(row)}
237250
>
238251
<CardContent className="flex flex-col gap-2 p-4">
@@ -245,14 +258,49 @@ export function MetadataListPage({
245258
{row.type}
246259
</Badge>
247260
</div>
248-
<code className="truncate text-xs text-muted-foreground">{row.name}</code>
261+
<div className="flex items-center justify-between gap-2">
262+
<code className="truncate text-xs text-muted-foreground">{row.name}</code>
263+
{canPreview && (
264+
<Button
265+
variant="ghost"
266+
size="sm"
267+
className="h-6 gap-1 px-1.5 text-[11px]"
268+
onClick={(e) => {
269+
e.stopPropagation();
270+
setPreviewRow(row);
271+
}}
272+
>
273+
<Eye className="h-3 w-3" /> Preview
274+
</Button>
275+
)}
276+
</div>
249277
</CardContent>
250278
</Card>
251279
);
252280
})}
253281
</div>
254282
)}
255283
</div>
284+
285+
<Dialog open={!!previewRow} onOpenChange={(o) => !o && setPreviewRow(null)}>
286+
<DialogContent className="max-w-5xl">
287+
<DialogHeader>
288+
<DialogTitle>{previewRow?.label ?? previewRow?.name}</DialogTitle>
289+
<DialogDescription>
290+
Live preview rendered with @object-ui against the configured backend.
291+
</DialogDescription>
292+
</DialogHeader>
293+
{previewRow && (
294+
<div className="h-[70vh] overflow-hidden">
295+
<MetadataPreview
296+
type={previewRow.type}
297+
name={previewRow.name}
298+
spec={previewRow.raw?.spec ?? previewRow.raw}
299+
/>
300+
</div>
301+
)}
302+
</DialogContent>
303+
</Dialog>
256304
</div>
257305
);
258306
}

0 commit comments

Comments
 (0)