Skip to content

Streamline Studio roadmap to 3-phase plugin infrastructure plan for 2026#1160

Merged
hotlong merged 2 commits intomainfrom
claude/evaluate-ai-code-business-project
Apr 16, 2026
Merged

Streamline Studio roadmap to 3-phase plugin infrastructure plan for 2026#1160
hotlong merged 2 commits intomainfrom
claude/evaluate-ai-code-business-project

Conversation

@Claude
Copy link
Copy Markdown
Contributor

@Claude Claude AI commented Apr 16, 2026

The Studio roadmap contained overly ambitious goals across 8 phases and lacked clarity on what belongs in the core platform versus external plugins. This update establishes Studio as a plugin infrastructure platform rather than a monolithic IDE.

Architectural Clarification

Core Studio (this repo):

  • Plugin system, kernel, routing, testing infrastructure
  • One built-in example: Basic Object Inspector plugin

Official Designers (objectstack-ai/studio - proprietary):

  • Object Designer, View Designer, Form Designer, Flow Designer, Dashboard Designer, Agent Designer

Community Plugins:

  • Third-party developers can build custom designers independently

Roadmap Changes

  • Consolidated: 8 phases (29 weeks) → 3 phases (16 weeks)
  • Phase 1 (Q2): Plugin infrastructure (URL router, testing, hot reload, plugin API docs)
  • Phase 2 (Q3): AI integration (Copilot enhancement, Command Palette, Monaco Editor)
  • Phase 3 (Q4): Advanced IDE features (multi-tab, plugin DevTools, version control)

Key Additions

  • Plugin Ecosystem section: Documents three-tier model (built-in/official/community)
  • Architectural Guidelines: File structure, registration patterns, data access rules
  • Success Metrics: Realistic KPIs for test coverage, plugin API stability, external plugin count
  • "What We're NOT Building": Clear scope boundaries for core vs. external plugins

This establishes an open-core business model: infrastructure is open-source, advanced designers are proprietary.

@vercel
Copy link
Copy Markdown

vercel bot commented Apr 16, 2026

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

Project Deployment Actions Updated (UTC)
objectstack-demo Ready Ready Preview, Comment Apr 16, 2026 9:44am
spec Ready Ready Preview, Comment Apr 16, 2026 9:44am

Request Review

@github-actions github-actions bot added documentation Improvements or additions to documentation size/m labels Apr 16, 2026
@github-actions github-actions bot added size/l and removed size/m labels Apr 16, 2026
@Claude Claude AI requested a review from hotlong April 16, 2026 01:55
@github-actions github-actions bot added the dependencies Pull requests that update a dependency file label Apr 16, 2026
@hotlong
Copy link
Copy Markdown
Contributor

hotlong commented Apr 16, 2026

@claude[agent] fix merge 冲突

Copilot stopped work on behalf of hotlong due to an error April 16, 2026 04:20
Copilot stopped work on behalf of hotlong due to an error April 16, 2026 05:11
@hotlong hotlong marked this pull request as ready for review April 16, 2026 09:36
Copilot AI review requested due to automatic review settings April 16, 2026 09:36
Claude AI and others added 2 commits April 16, 2026 09:40
- Update tests to use setDataEngine instead of setDatabaseDriver
- Fix Studio usePackages hook to import useClient from @objectstack/client-react
- Fix plugin-dev tsconfig to avoid parent exclude pattern conflicts

All tests now passing after merging latest changes from main.

Agent-Logs-Url: https://github.com/objectstack-ai/framework/sessions/95de72a8-ec40-4b5c-a2c6-8f8a9c9f0e1a

Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
@Claude Claude AI force-pushed the claude/evaluate-ai-code-business-project branch from 8935cca to a6a3e25 Compare April 16, 2026 09:41
@hotlong hotlong merged commit ae40065 into main Apr 16, 2026
10 of 13 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

Updates Studio’s roadmap to a 3-phase “plugin infrastructure” plan and begins implementing Phase 1 foundations (routing + testing + sidebar plugin integration) to position Studio as an extensible plugin host rather than a monolithic IDE.

Changes:

  • Refactors Studio navigation to TanStack Router (new route files, router setup, Vite plugin integration).
  • Adds Vitest + Testing Library setup and initial component/plugin-system tests.
  • Updates metadata plugin tests to validate bridging ObjectQL as a data engine (setDataEngine) and removes dead Studio task types.

Reviewed changes

Copilot reviewed 29 out of 30 changed files in this pull request and generated 14 comments.

Show a summary per file
File Description
pnpm-lock.yaml Locks new router + testing dependencies (TanStack Router, Testing Library, coverage).
packages/plugins/plugin-dev/tsconfig.json Excludes node_modules/dist from plugin-dev TS compilation.
packages/metadata/src/metadata.test.ts Updates tests to validate ObjectQL → MetadataManager.setDataEngine() bridging.
apps/studio/vite.config.ts Enables TanStack Router Vite plugin during dev/build.
apps/studio/vitest.config.ts Adds Studio-specific Vitest config (happy-dom, setup, coverage excludes).
apps/studio/test/setup.ts Adds centralized RTL cleanup setup.
apps/studio/test/** Adds new plugin system + component tests (currently with path/API issues).
apps/studio/src/App.tsx Switches app root to RouterProvider.
apps/studio/src/router.ts Creates TanStack Router instance from generated route tree.
apps/studio/src/routes/** Introduces file-based routes (/__root, /, /$package, etc.).
apps/studio/src/routeTree.gen.ts Adds generated TanStack Router route tree.
apps/studio/src/hooks/usePackages.ts New hook for loading and selecting installed packages.
apps/studio/src/hooks/useObjectStackClient.ts New hook for creating the API client instance.
apps/studio/src/components/app-sidebar.tsx Moves sidebar selection/navigation to URL-driven routing and plugin-contributed groups/icons.
apps/studio/src/components/ObjectExplorer.tsx Replaces setTimeout refresh hack with a refreshTrigger counter.
apps/studio/src/components/ObjectDataTable.tsx Adds refreshTrigger dependency to refetch data.
apps/studio/src/types.ts Removes stale Task types.
apps/studio/package.json Adds router + testing scripts/deps.
apps/studio/ROADMAP.md Consolidates roadmap to 3 phases and clarifies core vs official vs community plugin scope.
Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported
Comments suppressed due to low confidence (1)

apps/studio/src/components/app-sidebar.tsx:382

  • The Overview nav item is marked active only when a package route param exists, but the actual overview page currently lives at / (no params). Also, the click target navigates to /${selectedPackageId}, which currently maps to the /$package layout that renders an empty <Outlet /> (no index route). Align the Overview nav's active logic and navigation target with the actual overview route (e.g., add a /$package index route and/or redirect //$package).
                  <span>Overview</span>
                </SidebarMenuButton>
              </SidebarMenuItem>
            </SidebarMenu>
          </SidebarGroupContent>
        </SidebarGroup>

        {/* ── Search ── */}
        <div className="px-4 pb-2">
          <div className="relative">
            <Search className="absolute left-2.5 top-1/2 h-3.5 w-3.5 -translate-y-1/2 text-muted-foreground" />
            <SidebarInput

import { describe, it, expect, vi, beforeEach } from 'vitest';
import { render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { ObjectDataForm } from '../src/components/ObjectDataForm';
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

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

The relative import path is incorrect for this file's location (apps/studio/test/components/...): ../src/components/ObjectDataForm resolves to apps/studio/test/src/... which doesn't exist. Use ../../src/... or @/components/ObjectDataForm.

Suggested change
import { ObjectDataForm } from '../src/components/ObjectDataForm';
import { ObjectDataForm } from '../../src/components/ObjectDataForm';

Copilot uses AI. Check for mistakes.
Comment on lines +31 to +41
];

function renderWithProviders(component: React.ReactElement) {
return render(
<ObjectStackProvider client={mockClient}>
<PluginRegistryProvider plugins={[]}>
{component}
</PluginRegistryProvider>
</ObjectStackProvider>
);
}
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

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

This test references React.ReactElement, but React is not imported. With jsx: react-jsx, the React namespace isn't global, so this will fail typechecking. Import type React/ReactElement from 'react' or use JSX.Element instead.

Copilot uses AI. Check for mistakes.
Comment on lines +29 to +36
};

function renderWithProvider(component: React.ReactElement) {
return render(
<ObjectStackProvider client={mockClient}>
{component}
</ObjectStackProvider>
);
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

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

This test references React.ReactElement, but React is not imported. With jsx: react-jsx, the React namespace isn't global, so this will fail typechecking. Import type React/ReactElement from 'react' or use JSX.Element instead.

Copilot uses AI. Check for mistakes.
Comment on lines +3 to +26
import { createFileRoute, Outlet } from '@tanstack/react-router';
import { AppSidebar } from '../components/app-sidebar';
import { SiteHeader } from '@/components/site-header';
import { usePackages } from '../hooks/usePackages';
import { useEffect } from 'react';

function PackageLayoutComponent() {
const { package: packageId } = Route.useParams();
const { packages, selectedPackage, setSelectedPackage } = usePackages();

// Update selected package when route param changes
useEffect(() => {
const pkg = packages.find(p => p.manifest?.id === packageId);
if (pkg && pkg !== selectedPackage) {
setSelectedPackage(pkg);
}
}, [packageId, packages, selectedPackage, setSelectedPackage]);

return (
<>
<AppSidebar
packages={packages}
selectedPackage={selectedPackage}
onSelectPackage={setSelectedPackage}
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

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

onSelectPackage is wired to setSelectedPackage, but this route also syncs selectedPackage from the URL param in an effect. If the user selects a different package from the sidebar dropdown while staying on the same /$package/... URL, the effect will immediately revert the selection back to the package in the URL. Consider making package selection drive navigation (update the package param) and deriving selectedPackage from the router params to keep state/URL consistent.

Suggested change
import { createFileRoute, Outlet } from '@tanstack/react-router';
import { AppSidebar } from '../components/app-sidebar';
import { SiteHeader } from '@/components/site-header';
import { usePackages } from '../hooks/usePackages';
import { useEffect } from 'react';
function PackageLayoutComponent() {
const { package: packageId } = Route.useParams();
const { packages, selectedPackage, setSelectedPackage } = usePackages();
// Update selected package when route param changes
useEffect(() => {
const pkg = packages.find(p => p.manifest?.id === packageId);
if (pkg && pkg !== selectedPackage) {
setSelectedPackage(pkg);
}
}, [packageId, packages, selectedPackage, setSelectedPackage]);
return (
<>
<AppSidebar
packages={packages}
selectedPackage={selectedPackage}
onSelectPackage={setSelectedPackage}
import { createFileRoute, Outlet, useNavigate } from '@tanstack/react-router';
import { AppSidebar } from '../components/app-sidebar';
import { SiteHeader } from '@/components/site-header';
import { usePackages } from '../hooks/usePackages';
function PackageLayoutComponent() {
const { package: packageId } = Route.useParams();
const navigate = useNavigate();
const { packages } = usePackages();
const selectedPackage = packages.find(p => p.manifest?.id === packageId);
const handleSelectPackage = (pkg: (typeof packages)[number]) => {
const nextPackageId = pkg.manifest?.id;
if (!nextPackageId || nextPackageId === packageId) {
return;
}
navigate({
params: prev => ({
...prev,
package: nextPackageId,
}),
});
};
return (
<>
<AppSidebar
packages={packages}
selectedPackage={selectedPackage}
onSelectPackage={handleSelectPackage}

Copilot uses AI. Check for mistakes.
Comment on lines +21 to +36
return (
<>
<AppSidebar
packages={packages}
selectedPackage={selectedPackage}
onSelectPackage={setSelectedPackage}
/>
<main className="flex min-w-0 flex-1 flex-col h-svh overflow-hidden bg-background">
<SiteHeader
selectedView="overview"
packageLabel={selectedPackage?.manifest?.name || selectedPackage?.manifest?.id}
/>
<div className="flex flex-1 flex-col overflow-hidden">
<Outlet />
</div>
</main>
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

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

/$package is being used as a layout route (sidebar + header + <Outlet />), but there is no index/child route that renders the package overview. Navigating to /${packageId} will therefore render an empty main content area. Consider adding an index child route under /$package for the overview (or render the overview directly here instead of an empty <Outlet />).

Copilot uses AI. Check for mistakes.
Comment on lines +35 to +40
];

function renderWithProvider(component: React.ReactElement) {
return render(
<ObjectStackProvider client={mockClient}>
{component}
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

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

This test references React.ReactElement, but React is not imported anywhere in the file. With jsx: react-jsx, the React namespace is not global, so this will fail typechecking. Either import type React from 'react' (or import type { ReactElement } from 'react') or change the annotation to JSX.Element / ReactElement without the React. namespace.

Copilot uses AI. Check for mistakes.
Comment on lines +4 to +34
import { AppSidebar } from '../components/app-sidebar';
import { SiteHeader } from '@/components/site-header';
import { PluginHost } from '../plugins';
import { usePackages } from '../hooks/usePackages';

function ObjectViewComponent() {
const { name } = Route.useParams();
const { packages, selectedPackage, setSelectedPackage } = usePackages();

return (
<>
<AppSidebar
packages={packages}
selectedPackage={selectedPackage}
onSelectPackage={setSelectedPackage}
/>
<main className="flex min-w-0 flex-1 flex-col h-svh overflow-hidden bg-background">
<SiteHeader
selectedObject={name}
selectedView="object"
packageLabel={selectedPackage?.manifest?.name || selectedPackage?.manifest?.id}
/>
<div className="flex flex-1 flex-col overflow-hidden">
<PluginHost
metadataType="object"
metadataName={name}
packageId={selectedPackage?.manifest?.id}
/>
</div>
</main>
</>
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

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

This route is declared as a child of /$package (see routeTree.gen.ts), but it also renders its own AppSidebar + <main> wrapper. That will cause nested/duplicated sidebars and headers when routed under the /$package layout. The child route component should usually render only the inner content (e.g., PluginHost) and rely on /$package for the shared layout, or alternatively remove the layout wrapper from /$package and keep it only in leaf routes.

Suggested change
import { AppSidebar } from '../components/app-sidebar';
import { SiteHeader } from '@/components/site-header';
import { PluginHost } from '../plugins';
import { usePackages } from '../hooks/usePackages';
function ObjectViewComponent() {
const { name } = Route.useParams();
const { packages, selectedPackage, setSelectedPackage } = usePackages();
return (
<>
<AppSidebar
packages={packages}
selectedPackage={selectedPackage}
onSelectPackage={setSelectedPackage}
/>
<main className="flex min-w-0 flex-1 flex-col h-svh overflow-hidden bg-background">
<SiteHeader
selectedObject={name}
selectedView="object"
packageLabel={selectedPackage?.manifest?.name || selectedPackage?.manifest?.id}
/>
<div className="flex flex-1 flex-col overflow-hidden">
<PluginHost
metadataType="object"
metadataName={name}
packageId={selectedPackage?.manifest?.id}
/>
</div>
</main>
</>
import { PluginHost } from '../plugins';
function ObjectViewComponent() {
const { package: packageId, name } = Route.useParams();
return (
<PluginHost
metadataType="object"
metadataName={name}
packageId={packageId}
/>

Copilot uses AI. Check for mistakes.
</SidebarProvider>
</ErrorBoundary>
</PluginRegistryProvider>
<TanStackRouterDevtools />
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

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

TanStackRouterDevtools is rendered unconditionally. This typically pulls dev-only code into production bundles. Consider rendering it only in dev (e.g., if (import.meta.env.DEV)) or behind a feature flag.

Suggested change
<TanStackRouterDevtools />
{import.meta.env.DEV ? <TanStackRouterDevtools /> : null}

Copilot uses AI. Check for mistakes.
Comment on lines +6 to +13
import { PluginRegistry, PluginRegistryProvider, usePluginRegistry } from '../src/plugins';
import { defineStudioPlugin } from '@objectstack/spec/studio';
import type { StudioPlugin } from '../src/plugins/types';

// Test component that uses the plugin registry
function TestPluginConsumer() {
const registry = usePluginRegistry();
const viewers = registry.getViewersForType('object');
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

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

The relative imports look incorrect for this file's location (apps/studio/test/plugins/...): ../src/... resolves to apps/studio/test/src/... which doesn't exist. Also, the test uses PluginRegistry APIs that don't exist in the implementation (new PluginRegistry([plugins]), getAllPlugins, getPlugin, activateAll, getViewersForType). Update imports to point at ../../src/... (or use the @/* alias) and align the assertions with the actual registry API (register, activate, getPlugins, getViewers, etc.).

Suggested change
import { PluginRegistry, PluginRegistryProvider, usePluginRegistry } from '../src/plugins';
import { defineStudioPlugin } from '@objectstack/spec/studio';
import type { StudioPlugin } from '../src/plugins/types';
// Test component that uses the plugin registry
function TestPluginConsumer() {
const registry = usePluginRegistry();
const viewers = registry.getViewersForType('object');
import { PluginRegistry, PluginRegistryProvider, usePluginRegistry } from '../../src/plugins';
import { defineStudioPlugin } from '@objectstack/spec/studio';
import type { StudioPlugin } from '../../src/plugins/types';
// Test component that uses the plugin registry
function TestPluginConsumer() {
const registry = usePluginRegistry();
const viewers = registry.getViewers('object');

Copilot uses AI. Check for mistakes.
Comment on lines +6 to +9
import { AppSidebar } from '../src/components/app-sidebar';
import { ObjectStackProvider } from '@objectstack/client-react';
import { ObjectStackClient } from '@objectstack/client';
import { PluginRegistryProvider } from '../src/plugins';
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

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

The relative import path is incorrect for this file's location (apps/studio/test/components/...): ../src/... resolves to apps/studio/test/src/... which doesn't exist. Use ../../src/... or the @/* alias instead.

Suggested change
import { AppSidebar } from '../src/components/app-sidebar';
import { ObjectStackProvider } from '@objectstack/client-react';
import { ObjectStackClient } from '@objectstack/client';
import { PluginRegistryProvider } from '../src/plugins';
import { AppSidebar } from '../../src/components/app-sidebar';
import { ObjectStackProvider } from '@objectstack/client-react';
import { ObjectStackClient } from '@objectstack/client';
import { PluginRegistryProvider } from '../../src/plugins';

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

dependencies Pull requests that update a dependency file documentation Improvements or additions to documentation size/xl tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants