diff --git a/CHANGELOG.md b/CHANGELOG.md index 405f32b142..ac0f2f2a13 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Fixed +- **Studio: Code tab now shows CodeExporter** — The Code tab in Studio metadata detail pages + now correctly renders the `CodeExporter` component (TypeScript/JSON export with copy-to-clipboard) + instead of always showing the JSON Inspector preview. The default plugin now registers two separate + viewers: `json-inspector` for preview mode and `code-exporter` for code mode. - **CI Test Failures** — Resolved test failures across multiple packages: - `@objectstack/service-ai`: Fixed SDK fallback test by mocking `@ai-sdk/openai` dynamic import (SDK now available as transitive workspace dependency) diff --git a/apps/studio/src/plugins/built-in/default-plugin.tsx b/apps/studio/src/plugins/built-in/default-plugin.tsx index 5eac0d76bd..4870ce6ecc 100644 --- a/apps/studio/src/plugins/built-in/default-plugin.tsx +++ b/apps/studio/src/plugins/built-in/default-plugin.tsx @@ -3,22 +3,100 @@ /** * Built-in Plugin: Default Metadata Inspector * - * Provides a JSON tree viewer as the fallback for any metadata type - * that doesn't have a specialized plugin. This is the "catch-all" viewer. + * Provides two fallback viewers for any metadata type: + * - **json-inspector** (preview mode): JSON tree viewer via MetadataInspector + * - **code-exporter** (code mode): Exportable TypeScript/JSON via CodeExporter * * Priority is set to -1 so any type-specific plugin will take precedence. */ +import { useState, useEffect } from 'react'; import { defineStudioPlugin } from '@objectstack/spec/studio'; +import { useClient } from '@objectstack/client-react'; import { MetadataInspector } from '@/components/MetadataInspector'; +import { CodeExporter } from '@/components/CodeExporter'; +import type { CodeExporterProps } from '@/components/CodeExporter'; import type { StudioPlugin, MetadataViewerProps } from '../types'; -// ─── Viewer Component (adapts MetadataInspector to plugin interface) ─ +// ─── Helpers ───────────────────────────────────────────────────────── -function DefaultViewerComponent({ metadataType, metadataName, packageId }: MetadataViewerProps) { +/** Map Studio metadataType (often plural) to CodeExporter's ExportType (singular). */ +const METADATA_TO_EXPORT_TYPE: Record = { + object: 'object', + objects: 'object', + view: 'view', + views: 'view', + flow: 'flow', + flows: 'flow', + agent: 'agent', + agents: 'agent', + app: 'app', + apps: 'app', +}; + +// ─── Preview Viewer (JSON Inspector) ───────────────────────────────── + +function PreviewViewerComponent({ metadataType, metadataName, packageId }: MetadataViewerProps) { return ; } +// ─── Code Viewer (Code Exporter) ───────────────────────────────────── + +function CodeViewerComponent({ metadataType, metadataName, data, packageId }: MetadataViewerProps) { + const client = useClient(); + const [definition, setDefinition] = useState | null>(data ?? null); + const [loading, setLoading] = useState(!data); + + useEffect(() => { + // If data was passed directly, use it + if (data) { + setDefinition(data as Record); + setLoading(false); + return; + } + + let mounted = true; + setLoading(true); + + async function load() { + try { + const result: any = await client.meta.getItem(metadataType, metadataName, packageId ? { packageId } : undefined); + if (mounted) { + setDefinition(result?.item || result); + } + } catch (err) { + console.error(`[CodeViewer] Failed to load ${metadataType}/${metadataName}:`, err); + } finally { + if (mounted) setLoading(false); + } + } + load(); + return () => { mounted = false; }; + }, [client, metadataType, metadataName, data, packageId]); + + if (loading) { + return ( +
Loading definition…
+ ); + } + + if (!definition) { + return ( +
+ Definition not found: {metadataName} +
+ ); + } + + const exportType = METADATA_TO_EXPORT_TYPE[metadataType] ?? 'object'; + + return ( +
+ +
+ ); +} + // ─── Plugin Definition ─────────────────────────────────────────────── export const defaultInspectorPlugin: StudioPlugin = { @@ -26,21 +104,29 @@ export const defaultInspectorPlugin: StudioPlugin = { id: 'objectstack.default-inspector', name: 'Default Metadata Inspector', version: '1.0.0', - description: 'JSON tree viewer for any metadata type. Fallback when no specialized viewer is available.', + description: 'JSON tree viewer and code exporter for any metadata type. Fallback when no specialized viewer is available.', contributes: { metadataViewers: [ { id: 'json-inspector', - metadataTypes: ['*'], // Wildcard: matches all types + metadataTypes: ['*'], label: 'JSON Inspector', - priority: -1, // Lowest priority — any plugin overrides this - modes: ['preview', 'code'], + priority: -1, + modes: ['preview'], + }, + { + id: 'code-exporter', + metadataTypes: ['*'], + label: 'Code Export', + priority: -1, + modes: ['code'], }, ], }, }), activate(api) { - api.registerViewer('json-inspector', DefaultViewerComponent); + api.registerViewer('json-inspector', PreviewViewerComponent); + api.registerViewer('code-exporter', CodeViewerComponent); }, };