Skip to content

fix(studio): Route Code tab to CodeExporter instead of always rendering preview#1080

Merged
hotlong merged 2 commits intomainfrom
copilot/fix-code-tab-export-issue
Apr 7, 2026
Merged

fix(studio): Route Code tab to CodeExporter instead of always rendering preview#1080
hotlong merged 2 commits intomainfrom
copilot/fix-code-tab-export-issue

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Apr 7, 2026

The default-plugin registered a single json-inspector viewer claiming modes: ['preview', 'code'], but the component unconditionally rendered <MetadataInspector> — the mode prop was ignored.

Changes

  • Split single viewer into two contributions in default-plugin.tsx:
    • json-inspectormodes: ['preview']MetadataInspector (unchanged)
    • code-exportermodes: ['code']CodeExporter (new)
  • New CodeViewerComponent loads metadata via useClient().meta.getItem() (same pattern as MetadataInspector) and renders the existing <CodeExporter> with TypeScript/JSON toggle + copy-to-clipboard
  • Metadata type mapping from plural Studio types (objects, flows, agents, etc.) to CodeExporter's singular ExportType
// Before: one viewer, mode ignored
metadataViewers: [{ id: 'json-inspector', modes: ['preview', 'code'] }]
activate(api) { api.registerViewer('json-inspector', DefaultViewerComponent); }

// After: two viewers, correct routing
metadataViewers: [
  { id: 'json-inspector', modes: ['preview'] },
  { id: 'code-exporter', modes: ['code'] },
]
activate(api) {
  api.registerViewer('json-inspector', PreviewViewerComponent);
  api.registerViewer('code-exporter', CodeViewerComponent);
}

No changes to plugin-host.tsx or CodeExporter.tsx — the infrastructure already resolved viewers by mode correctly; the default-plugin just wasn't wired up properly.

@vercel
Copy link
Copy Markdown

vercel bot commented Apr 7, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
objectstack-play Ready Ready Preview, Comment Apr 7, 2026 8:16am
spec Ready Ready Preview, Comment Apr 7, 2026 8:16am

Request Review

…ing preview

Split default-plugin's single viewer (modes: ['preview', 'code']) into two
separate viewer contributions:
- json-inspector (preview mode) → MetadataInspector
- code-exporter (code mode) → CodeExporter

The CodeViewerComponent loads metadata via useClient().meta.getItem() and maps
plural metadataType names to CodeExporter's singular ExportType.

Agent-Logs-Url: https://github.com/objectstack-ai/framework/sessions/862dcad5-06e8-4973-a996-340dccc3789e

Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix code tab not showing code export in Studio fix(studio): Route Code tab to CodeExporter instead of always rendering preview Apr 7, 2026
Copilot AI requested a review from hotlong April 7, 2026 08:17
@hotlong hotlong marked this pull request as ready for review April 7, 2026 08:57
Copilot AI review requested due to automatic review settings April 7, 2026 08:57
@hotlong hotlong merged commit 7d690ae into main Apr 7, 2026
5 checks passed
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes Studio’s metadata “Code” tab behavior by correctly routing code-mode viewing to CodeExporter (instead of always rendering the JSON preview inspector), improving the default fallback viewer wiring in the built-in Studio plugin.

Changes:

  • Split the default wildcard viewer into two separate viewer contributions: preview (json-inspector) vs code (code-exporter).
  • Added a CodeViewerComponent that loads metadata via useClient().meta.getItem() and renders CodeExporter.
  • Updated changelog to document the Studio Code tab fix.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
CHANGELOG.md Documents the Studio Code tab fix and viewer split.
apps/studio/src/plugins/built-in/default-plugin.tsx Splits viewer contributions by mode and introduces a code-mode viewer that renders CodeExporter.

Comment on lines 109 to 123
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'],
},
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

code-exporter is registered for metadataTypes: ['*'], but CodeExporter only supports a limited set of export types (object|view|flow|agent|app). For other Studio metadata types (e.g. actions, pages, dashboards, workflows, ragPipelines, roles, etc.), exportType falls back to 'object', which will generate misleading/incorrect TypeScript (wrong top-level stack key).

Consider either (a) restricting the code-exporter contribution to only the metadata types you can correctly map, or (b) detecting unsupported types in CodeViewerComponent and showing a clear “export not supported for this type” message / JSON-only export instead of defaulting to 'object'.

Copilot uses AI. Check for mistakes.
Comment on lines +47 to +77
const [definition, setDefinition] = useState<Record<string, unknown> | null>(data ?? null);
const [loading, setLoading] = useState(!data);

useEffect(() => {
// If data was passed directly, use it
if (data) {
setDefinition(data as Record<string, unknown>);
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) {
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CodeViewerComponent keeps definition state across metadata navigation. When metadataType/metadataName changes, the component can render once with the previous definition but the new metadataName (until the effect flips loading), which can briefly show incorrect exported code.

To avoid this, track which {metadataType, metadataName, packageId} the current definition belongs to (e.g., a loadedKey state) and render the loading/empty state whenever the props key doesn’t match, or clear definition synchronously when props change (so you never render with stale data).

Suggested change
const [definition, setDefinition] = useState<Record<string, unknown> | null>(data ?? null);
const [loading, setLoading] = useState(!data);
useEffect(() => {
// If data was passed directly, use it
if (data) {
setDefinition(data as Record<string, unknown>);
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) {
const currentKey = `${metadataType}::${metadataName}::${packageId ?? ''}`;
const [definition, setDefinition] = useState<Record<string, unknown> | null>(data ?? null);
const [loadedKey, setLoadedKey] = useState<string | null>(data ? currentKey : null);
const [loading, setLoading] = useState(!data);
useEffect(() => {
// If data was passed directly, bind it to the current metadata identity.
if (data) {
setDefinition(data as Record<string, unknown>);
setLoadedKey(currentKey);
setLoading(false);
return;
}
let mounted = true;
setLoading(true);
setDefinition(null);
setLoadedKey(null);
async function load() {
try {
const result: any = await client.meta.getItem(metadataType, metadataName, packageId ? { packageId } : undefined);
if (mounted) {
setDefinition((result?.item || result) ?? null);
setLoadedKey(currentKey);
}
} catch (err) {
console.error(`[CodeViewer] Failed to load ${metadataType}/${metadataName}:`, err);
if (mounted) {
setDefinition(null);
setLoadedKey(currentKey);
}
} finally {
if (mounted) setLoading(false);
}
}
load();
return () => { mounted = false; };
}, [client, metadataType, metadataName, data, packageId, currentKey]);
if (loading || loadedKey !== currentKey) {

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

【Studio】Code tab does not show code export (default-plugin viewer always renders preview, not code)

3 participants