From 5eef3004a36c1d5693c6d6434f624e2c4289fb8c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 15 Feb 2026 01:46:38 +0000 Subject: [PATCH 1/3] Initial plan From 7b68864489c0cf60d679bb471bfad5789282945b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 15 Feb 2026 01:51:49 +0000 Subject: [PATCH 2/3] feat(studio): add Object Designer protocol schemas with field editor, relationship mapper, ER diagram, and object manager specs Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com> --- packages/spec/src/studio/index.ts | 43 ++ .../spec/src/studio/object-designer.test.ts | 538 ++++++++++++++++ .../spec/src/studio/object-designer.zod.ts | 605 ++++++++++++++++++ 3 files changed, 1186 insertions(+) create mode 100644 packages/spec/src/studio/object-designer.test.ts create mode 100644 packages/spec/src/studio/object-designer.zod.ts diff --git a/packages/spec/src/studio/index.ts b/packages/spec/src/studio/index.ts index da63b0b5c5..74b9dcbc2c 100644 --- a/packages/spec/src/studio/index.ts +++ b/packages/spec/src/studio/index.ts @@ -7,6 +7,8 @@ * * Defines the extension model that allows metadata types to contribute * custom viewers, designers, sidebar groups, actions, and commands. + * Also includes the Object Designer protocol for visual field editing, + * relationship mapping, and ER diagram configuration. */ export { @@ -38,3 +40,44 @@ export { // Helpers defineStudioPlugin, } from './plugin.zod'; + +export { + // Object Designer Schemas + FieldPropertySectionSchema, + FieldGroupSchema, + FieldEditorConfigSchema, + RelationshipDisplaySchema, + RelationshipMapperConfigSchema, + ERLayoutAlgorithmSchema, + ERNodeDisplaySchema, + ERDiagramConfigSchema, + ObjectListDisplayModeSchema, + ObjectSortFieldSchema, + ObjectFilterSchema, + ObjectManagerConfigSchema, + ObjectPreviewTabSchema, + ObjectPreviewConfigSchema, + ObjectDesignerDefaultViewSchema, + ObjectDesignerConfigSchema, + + // Object Designer Types + type FieldPropertySection, + type FieldGroup, + type FieldEditorConfig, + type RelationshipDisplay, + type RelationshipMapperConfig, + type ERLayoutAlgorithm, + type ERNodeDisplay, + type ERDiagramConfig, + type ObjectListDisplayMode, + type ObjectSortField, + type ObjectFilter, + type ObjectManagerConfig, + type ObjectPreviewTab, + type ObjectPreviewConfig, + type ObjectDesignerDefaultView, + type ObjectDesignerConfig, + + // Object Designer Helpers + defineObjectDesignerConfig, +} from './object-designer.zod'; diff --git a/packages/spec/src/studio/object-designer.test.ts b/packages/spec/src/studio/object-designer.test.ts new file mode 100644 index 0000000000..7bc7300774 --- /dev/null +++ b/packages/spec/src/studio/object-designer.test.ts @@ -0,0 +1,538 @@ +import { describe, it, expect } from 'vitest'; +import { + FieldPropertySectionSchema, + FieldGroupSchema, + FieldEditorConfigSchema, + RelationshipDisplaySchema, + RelationshipMapperConfigSchema, + ERLayoutAlgorithmSchema, + ERNodeDisplaySchema, + ERDiagramConfigSchema, + ObjectListDisplayModeSchema, + ObjectSortFieldSchema, + ObjectFilterSchema, + ObjectManagerConfigSchema, + ObjectPreviewTabSchema, + ObjectPreviewConfigSchema, + ObjectDesignerDefaultViewSchema, + ObjectDesignerConfigSchema, + defineObjectDesignerConfig, +} from './object-designer.zod'; + +// ─── Field Property Section ────────────────────────────────────────── + +describe('FieldPropertySectionSchema', () => { + it('should accept minimal section with defaults', () => { + const section = { key: 'basics', label: 'Basic Properties' }; + const result = FieldPropertySectionSchema.parse(section); + expect(result.defaultExpanded).toBe(true); + expect(result.order).toBe(0); + expect(result.icon).toBeUndefined(); + }); + + it('should accept full section', () => { + const section = { + key: 'security', + label: 'Security & Compliance', + icon: 'shield', + defaultExpanded: false, + order: 40, + }; + const result = FieldPropertySectionSchema.parse(section); + expect(result.key).toBe('security'); + expect(result.defaultExpanded).toBe(false); + expect(result.order).toBe(40); + }); + + it('should reject missing key', () => { + expect(() => FieldPropertySectionSchema.parse({ label: 'Test' })).toThrow(); + }); + + it('should reject missing label', () => { + expect(() => FieldPropertySectionSchema.parse({ key: 'test' })).toThrow(); + }); +}); + +// ─── Field Group ───────────────────────────────────────────────────── + +describe('FieldGroupSchema', () => { + it('should accept minimal group with defaults', () => { + const group = { key: 'contact_info', label: 'Contact Info' }; + const result = FieldGroupSchema.parse(group); + expect(result.defaultExpanded).toBe(true); + expect(result.order).toBe(0); + }); + + it('should accept full group', () => { + const group = { + key: 'billing', + label: 'Billing', + icon: 'credit-card', + defaultExpanded: false, + order: 20, + }; + expect(() => FieldGroupSchema.parse(group)).not.toThrow(); + }); + + it('should reject missing required fields', () => { + expect(() => FieldGroupSchema.parse({})).toThrow(); + expect(() => FieldGroupSchema.parse({ key: 'x' })).toThrow(); + }); +}); + +// ─── Field Editor Config ───────────────────────────────────────────── + +describe('FieldEditorConfigSchema', () => { + it('should accept empty object with all defaults', () => { + const result = FieldEditorConfigSchema.parse({}); + expect(result.inlineEditing).toBe(true); + expect(result.dragReorder).toBe(true); + expect(result.showFieldGroups).toBe(true); + expect(result.showPropertyPanel).toBe(true); + expect(result.paginationThreshold).toBe(50); + expect(result.batchOperations).toBe(true); + expect(result.showUsageStats).toBe(false); + expect(result.propertySections.length).toBe(6); + expect(result.fieldGroups).toEqual([]); + }); + + it('should accept custom config', () => { + const config = { + inlineEditing: false, + dragReorder: false, + paginationThreshold: 100, + propertySections: [ + { key: 'basics', label: 'Basics' }, + ], + }; + const result = FieldEditorConfigSchema.parse(config); + expect(result.inlineEditing).toBe(false); + expect(result.propertySections.length).toBe(1); + expect(result.propertySections[0].key).toBe('basics'); + }); + + it('should accept with custom field groups', () => { + const config = { + fieldGroups: [ + { key: 'contact_info', label: 'Contact Information', order: 0 }, + { key: 'billing', label: 'Billing Details', order: 10 }, + ], + }; + const result = FieldEditorConfigSchema.parse(config); + expect(result.fieldGroups.length).toBe(2); + }); +}); + +// ─── Relationship Display ──────────────────────────────────────────── + +describe('RelationshipDisplaySchema', () => { + it('should accept minimal config with defaults', () => { + const config = { type: 'lookup' }; + const result = RelationshipDisplaySchema.parse(config); + expect(result.lineStyle).toBe('solid'); + expect(result.color).toBe('#94a3b8'); + expect(result.cardinalityLabel).toBe('1:N'); + }); + + it('should accept full config', () => { + const config = { + type: 'master_detail', + lineStyle: 'dashed', + color: '#ea580c', + highlightColor: '#f97316', + cardinalityLabel: '1:N', + }; + expect(() => RelationshipDisplaySchema.parse(config)).not.toThrow(); + }); + + it('should reject invalid type', () => { + expect(() => RelationshipDisplaySchema.parse({ type: 'invalid' })).toThrow(); + }); + + it('should accept tree type', () => { + const result = RelationshipDisplaySchema.parse({ type: 'tree' }); + expect(result.type).toBe('tree'); + }); +}); + +// ─── Relationship Mapper Config ────────────────────────────────────── + +describe('RelationshipMapperConfigSchema', () => { + it('should accept empty object with all defaults', () => { + const result = RelationshipMapperConfigSchema.parse({}); + expect(result.visualCreation).toBe(true); + expect(result.showReverseRelationships).toBe(true); + expect(result.showCascadeWarnings).toBe(true); + expect(result.displayConfig.length).toBe(3); + }); + + it('should accept custom display config', () => { + const config = { + displayConfig: [ + { type: 'lookup', lineStyle: 'dotted', color: '#ff0000' }, + ], + }; + const result = RelationshipMapperConfigSchema.parse(config); + expect(result.displayConfig.length).toBe(1); + }); +}); + +// ─── ER Layout Algorithm ───────────────────────────────────────────── + +describe('ERLayoutAlgorithmSchema', () => { + it('should accept all valid algorithms', () => { + const algorithms = ['force', 'hierarchy', 'grid', 'circular']; + algorithms.forEach(algo => { + expect(() => ERLayoutAlgorithmSchema.parse(algo)).not.toThrow(); + }); + }); + + it('should reject invalid algorithm', () => { + expect(() => ERLayoutAlgorithmSchema.parse('random')).toThrow(); + }); +}); + +// ─── ER Node Display ───────────────────────────────────────────────── + +describe('ERNodeDisplaySchema', () => { + it('should accept empty object with all defaults', () => { + const result = ERNodeDisplaySchema.parse({}); + expect(result.showFields).toBe(true); + expect(result.maxFieldsVisible).toBe(8); + expect(result.showFieldTypes).toBe(true); + expect(result.showRequiredIndicator).toBe(true); + expect(result.showRecordCount).toBe(false); + expect(result.showIcon).toBe(true); + expect(result.showDescription).toBe(true); + }); + + it('should accept custom config', () => { + const config = { + showFields: false, + maxFieldsVisible: 5, + showRecordCount: true, + }; + const result = ERNodeDisplaySchema.parse(config); + expect(result.showFields).toBe(false); + expect(result.maxFieldsVisible).toBe(5); + expect(result.showRecordCount).toBe(true); + }); +}); + +// ─── ER Diagram Config ─────────────────────────────────────────────── + +describe('ERDiagramConfigSchema', () => { + it('should accept empty object with all defaults', () => { + const result = ERDiagramConfigSchema.parse({}); + expect(result.enabled).toBe(true); + expect(result.layout).toBe('force'); + expect(result.showMinimap).toBe(true); + expect(result.zoomControls).toBe(true); + expect(result.minZoom).toBe(0.1); + expect(result.maxZoom).toBe(3); + expect(result.showEdgeLabels).toBe(true); + expect(result.highlightOnHover).toBe(true); + expect(result.clickToNavigate).toBe(true); + expect(result.dragToConnect).toBe(true); + expect(result.hideOrphans).toBe(false); + expect(result.autoFit).toBe(true); + expect(result.exportFormats).toEqual(['png', 'svg']); + }); + + it('should accept custom config', () => { + const config = { + layout: 'hierarchy', + showMinimap: false, + hideOrphans: true, + exportFormats: ['png', 'svg', 'json'], + }; + const result = ERDiagramConfigSchema.parse(config); + expect(result.layout).toBe('hierarchy'); + expect(result.hideOrphans).toBe(true); + expect(result.exportFormats).toEqual(['png', 'svg', 'json']); + }); + + it('should accept disabled config', () => { + const result = ERDiagramConfigSchema.parse({ enabled: false }); + expect(result.enabled).toBe(false); + }); + + it('should accept nested nodeDisplay overrides', () => { + const config = { + nodeDisplay: { + showFields: false, + maxFieldsVisible: 3, + }, + }; + const result = ERDiagramConfigSchema.parse(config); + expect(result.nodeDisplay.showFields).toBe(false); + expect(result.nodeDisplay.maxFieldsVisible).toBe(3); + // Defaults preserved + expect(result.nodeDisplay.showFieldTypes).toBe(true); + }); +}); + +// ─── Object List Display Mode ──────────────────────────────────────── + +describe('ObjectListDisplayModeSchema', () => { + it('should accept all valid modes', () => { + const modes = ['table', 'cards', 'tree']; + modes.forEach(mode => { + expect(() => ObjectListDisplayModeSchema.parse(mode)).not.toThrow(); + }); + }); + + it('should reject invalid mode', () => { + expect(() => ObjectListDisplayModeSchema.parse('gallery')).toThrow(); + }); +}); + +// ─── Object Sort Field ─────────────────────────────────────────────── + +describe('ObjectSortFieldSchema', () => { + it('should accept all valid sort fields', () => { + const fields = ['name', 'label', 'fieldCount', 'updatedAt']; + fields.forEach(field => { + expect(() => ObjectSortFieldSchema.parse(field)).not.toThrow(); + }); + }); + + it('should reject invalid sort field', () => { + expect(() => ObjectSortFieldSchema.parse('created')).toThrow(); + }); +}); + +// ─── Object Filter ─────────────────────────────────────────────────── + +describe('ObjectFilterSchema', () => { + it('should accept empty object with defaults', () => { + const result = ObjectFilterSchema.parse({}); + expect(result.includeSystem).toBe(false); + expect(result.includeAbstract).toBe(false); + expect(result.package).toBeUndefined(); + expect(result.tags).toBeUndefined(); + }); + + it('should accept full filter', () => { + const filter = { + package: 'app-crm', + tags: ['sales', 'core'], + includeSystem: true, + includeAbstract: true, + hasFieldType: 'lookup', + hasRelationships: true, + searchQuery: 'account', + }; + const result = ObjectFilterSchema.parse(filter); + expect(result.package).toBe('app-crm'); + expect(result.tags).toEqual(['sales', 'core']); + expect(result.hasRelationships).toBe(true); + }); +}); + +// ─── Object Manager Config ─────────────────────────────────────────── + +describe('ObjectManagerConfigSchema', () => { + it('should accept empty object with all defaults', () => { + const result = ObjectManagerConfigSchema.parse({}); + expect(result.defaultDisplayMode).toBe('table'); + expect(result.defaultSortField).toBe('label'); + expect(result.defaultSortDirection).toBe('asc'); + expect(result.showFieldCount).toBe(true); + expect(result.showRelationshipCount).toBe(true); + expect(result.showQuickPreview).toBe(true); + expect(result.enableComparison).toBe(false); + expect(result.showERDiagramToggle).toBe(true); + expect(result.showCreateAction).toBe(true); + expect(result.showStatsSummary).toBe(true); + }); + + it('should accept custom config', () => { + const config = { + defaultDisplayMode: 'cards', + defaultSortField: 'fieldCount', + defaultSortDirection: 'desc', + enableComparison: true, + }; + const result = ObjectManagerConfigSchema.parse(config); + expect(result.defaultDisplayMode).toBe('cards'); + expect(result.enableComparison).toBe(true); + }); +}); + +// ─── Object Preview Tab ────────────────────────────────────────────── + +describe('ObjectPreviewTabSchema', () => { + it('should accept minimal tab with defaults', () => { + const tab = { key: 'fields', label: 'Fields' }; + const result = ObjectPreviewTabSchema.parse(tab); + expect(result.enabled).toBe(true); + expect(result.order).toBe(0); + expect(result.icon).toBeUndefined(); + }); + + it('should accept full tab', () => { + const tab = { + key: 'relationships', + label: 'Relationships', + icon: 'link', + enabled: true, + order: 10, + }; + expect(() => ObjectPreviewTabSchema.parse(tab)).not.toThrow(); + }); + + it('should accept disabled tab', () => { + const tab = { key: 'code', label: 'Code', enabled: false }; + const result = ObjectPreviewTabSchema.parse(tab); + expect(result.enabled).toBe(false); + }); + + it('should reject missing required fields', () => { + expect(() => ObjectPreviewTabSchema.parse({})).toThrow(); + expect(() => ObjectPreviewTabSchema.parse({ key: 'x' })).toThrow(); + }); +}); + +// ─── Object Preview Config ─────────────────────────────────────────── + +describe('ObjectPreviewConfigSchema', () => { + it('should accept empty object with all defaults', () => { + const result = ObjectPreviewConfigSchema.parse({}); + expect(result.tabs.length).toBe(8); + expect(result.defaultTab).toBe('fields'); + expect(result.showHeader).toBe(true); + expect(result.showBreadcrumbs).toBe(true); + }); + + it('should accept custom tabs', () => { + const config = { + tabs: [ + { key: 'fields', label: 'Fields', order: 0 }, + { key: 'data', label: 'Data', order: 10 }, + ], + defaultTab: 'data', + }; + const result = ObjectPreviewConfigSchema.parse(config); + expect(result.tabs.length).toBe(2); + expect(result.defaultTab).toBe('data'); + }); +}); + +// ─── Object Designer Default View ──────────────────────────────────── + +describe('ObjectDesignerDefaultViewSchema', () => { + it('should accept all valid default views', () => { + const views = ['field-editor', 'relationship-mapper', 'er-diagram', 'object-manager']; + views.forEach(view => { + expect(() => ObjectDesignerDefaultViewSchema.parse(view)).not.toThrow(); + }); + }); + + it('should reject invalid view', () => { + expect(() => ObjectDesignerDefaultViewSchema.parse('schema')).toThrow(); + }); +}); + +// ─── Object Designer Config (Top-Level) ────────────────────────────── + +describe('ObjectDesignerConfigSchema', () => { + it('should accept empty object with all defaults', () => { + const result = ObjectDesignerConfigSchema.parse({}); + expect(result.defaultView).toBe('field-editor'); + expect(result.fieldEditor).toBeDefined(); + expect(result.fieldEditor.inlineEditing).toBe(true); + expect(result.relationshipMapper).toBeDefined(); + expect(result.relationshipMapper.visualCreation).toBe(true); + expect(result.erDiagram).toBeDefined(); + expect(result.erDiagram.enabled).toBe(true); + expect(result.objectManager).toBeDefined(); + expect(result.objectManager.defaultDisplayMode).toBe('table'); + expect(result.objectPreview).toBeDefined(); + expect(result.objectPreview.tabs.length).toBe(8); + }); + + it('should accept partial overrides', () => { + const config = { + defaultView: 'er-diagram', + erDiagram: { + layout: 'hierarchy', + hideOrphans: true, + }, + }; + const result = ObjectDesignerConfigSchema.parse(config); + expect(result.defaultView).toBe('er-diagram'); + expect(result.erDiagram.layout).toBe('hierarchy'); + expect(result.erDiagram.hideOrphans).toBe(true); + // Other defaults still applied + expect(result.erDiagram.showMinimap).toBe(true); + expect(result.fieldEditor.inlineEditing).toBe(true); + }); + + it('should accept full config with all sub-configs', () => { + const config = { + defaultView: 'object-manager', + fieldEditor: { + inlineEditing: false, + dragReorder: false, + showFieldGroups: false, + showPropertyPanel: false, + paginationThreshold: 100, + batchOperations: false, + showUsageStats: true, + }, + relationshipMapper: { + visualCreation: false, + showReverseRelationships: false, + showCascadeWarnings: false, + }, + erDiagram: { + enabled: false, + }, + objectManager: { + defaultDisplayMode: 'cards', + defaultSortField: 'name', + defaultSortDirection: 'desc', + enableComparison: true, + }, + objectPreview: { + defaultTab: 'data', + showBreadcrumbs: false, + }, + }; + const result = ObjectDesignerConfigSchema.parse(config); + expect(result.defaultView).toBe('object-manager'); + expect(result.fieldEditor.inlineEditing).toBe(false); + expect(result.fieldEditor.showUsageStats).toBe(true); + expect(result.erDiagram.enabled).toBe(false); + expect(result.objectManager.enableComparison).toBe(true); + expect(result.objectPreview.defaultTab).toBe('data'); + }); +}); + +// ─── defineObjectDesignerConfig ────────────────────────────────────── + +describe('defineObjectDesignerConfig', () => { + it('should return a fully parsed config with defaults', () => { + const result = defineObjectDesignerConfig({}); + expect(result.defaultView).toBe('field-editor'); + expect(result.fieldEditor).toBeDefined(); + expect(result.erDiagram.enabled).toBe(true); + expect(result.objectManager.defaultDisplayMode).toBe('table'); + }); + + it('should accept partial overrides', () => { + const result = defineObjectDesignerConfig({ + defaultView: 'er-diagram', + erDiagram: { layout: 'grid' }, + }); + expect(result.defaultView).toBe('er-diagram'); + expect(result.erDiagram.layout).toBe('grid'); + }); + + it('should throw on invalid input', () => { + expect(() => defineObjectDesignerConfig({ + defaultView: 'invalid' as any, + })).toThrow(); + }); +}); diff --git a/packages/spec/src/studio/object-designer.zod.ts b/packages/spec/src/studio/object-designer.zod.ts new file mode 100644 index 0000000000..a34f220196 --- /dev/null +++ b/packages/spec/src/studio/object-designer.zod.ts @@ -0,0 +1,605 @@ +// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license. + +/** + * @module studio/object-designer + * + * Object Designer Protocol — Visual Field Editor, Relationship Mapper & ER Diagram + * + * Defines the specification for the Object Designer experience within ObjectStack Studio, + * including: + * - **Field Editor**: Visual field creation/editing with type-aware property panels + * - **Relationship Mapper**: Visual lookup/master-detail relationship configuration + * - **ER Diagram**: Entity-Relationship diagram rendering and interaction + * - **Object Manager**: Unified object list with search, filtering, and bulk operations + * + * ## Architecture + * + * The Object Designer is composed of four interconnected panels: + * + * ``` + * ┌─────────────────────────────────────────────────────────────────┐ + * │ Object Manager (list / search) │ + * ├──────────────┬──────────────────────────┬──────────────────────┤ + * │ Object List │ Field Editor │ Property Panel │ + * │ (sidebar) │ (table + inline edit) │ (type-specific) │ + * │ │ │ │ + * │ ─ search │ ─ drag-to-reorder │ ─ constraints │ + * │ ─ filter │ ─ inline type picker │ ─ validation │ + * │ ─ group │ ─ batch add/remove │ ─ security │ + * │ ─ create │ ─ field groups │ ─ relationships │ + * ├──────────────┴──────────────────────────┴──────────────────────┤ + * │ ER Diagram (toggle panel) │ + * │ ─ auto-layout (force / hierarchy / grid) │ + * │ ─ interactive: click node → navigate to object │ + * │ ─ hover: highlight connected relationships │ + * │ ─ zoom/pan/minimap │ + * └─────────────────────────────────────────────────────────────────┘ + * ``` + * + * @example + * ```typescript + * import { + * ObjectDesignerConfigSchema, + * ERDiagramConfigSchema, + * } from '@objectstack/spec/studio'; + * + * const config = ObjectDesignerConfigSchema.parse({ + * defaultView: 'field-editor', + * fieldEditor: { + * inlineEditing: true, + * dragReorder: true, + * showFieldGroups: true, + * }, + * erDiagram: { + * enabled: true, + * layout: 'force', + * showFieldDetails: true, + * }, + * }); + * ``` + */ + +import { z } from 'zod'; + +// ─── Field Editor ──────────────────────────────────────────────────── + +/** + * Field property panel section — groups related field properties + * in the right-side property inspector. + */ +export const FieldPropertySectionSchema = z.object({ + /** Unique section key */ + key: z.string().describe('Section key (e.g., "basics", "constraints", "security")'), + + /** Display label */ + label: z.string().describe('Section display label'), + + /** Lucide icon name */ + icon: z.string().optional().describe('Lucide icon name'), + + /** Whether section is expanded by default */ + defaultExpanded: z.boolean().default(true).describe('Whether section is expanded by default'), + + /** Sort order — lower values appear first */ + order: z.number().default(0).describe('Sort order (lower = higher)'), +}); + +export type FieldPropertySection = z.infer; + +/** + * Field grouping configuration — organizes fields into collapsible groups + * within the field editor table (e.g., "Contact Info", "Billing", "System"). + */ +export const FieldGroupSchema = z.object({ + /** Group key (matches field.group value) */ + key: z.string().describe('Group key matching field.group values'), + + /** Display label */ + label: z.string().describe('Group display label'), + + /** Lucide icon name */ + icon: z.string().optional().describe('Lucide icon name'), + + /** Whether group is expanded by default */ + defaultExpanded: z.boolean().default(true).describe('Whether group is expanded by default'), + + /** Sort order — lower values appear first */ + order: z.number().default(0).describe('Sort order (lower = higher)'), +}); + +export type FieldGroup = z.infer; + +/** + * Field Editor configuration — controls the visual field editing experience. + */ +export const FieldEditorConfigSchema = z.object({ + /** Enable inline editing of field properties in the table */ + inlineEditing: z.boolean().default(true).describe('Enable inline editing of field properties'), + + /** Enable drag-and-drop field reordering */ + dragReorder: z.boolean().default(true).describe('Enable drag-and-drop field reordering'), + + /** Show field group headers for organizing fields */ + showFieldGroups: z.boolean().default(true).describe('Show field group headers'), + + /** Show the type-specific property panel on the right */ + showPropertyPanel: z.boolean().default(true).describe('Show the right-side property panel'), + + /** Default property panel sections to display */ + propertySections: z.array(FieldPropertySectionSchema).default([ + { key: 'basics', label: 'Basic Properties', defaultExpanded: true, order: 0 }, + { key: 'constraints', label: 'Constraints & Validation', defaultExpanded: true, order: 10 }, + { key: 'relationship', label: 'Relationship Config', defaultExpanded: true, order: 20 }, + { key: 'display', label: 'Display & UI', defaultExpanded: false, order: 30 }, + { key: 'security', label: 'Security & Compliance', defaultExpanded: false, order: 40 }, + { key: 'advanced', label: 'Advanced', defaultExpanded: false, order: 50 }, + ]).describe('Property panel section definitions'), + + /** Field groups for organizing fields in the editor */ + fieldGroups: z.array(FieldGroupSchema).default([]).describe('Field group definitions'), + + /** Maximum fields before pagination kicks in */ + paginationThreshold: z.number().default(50).describe('Number of fields before pagination is enabled'), + + /** Enable batch field operations (add multiple fields at once) */ + batchOperations: z.boolean().default(true).describe('Enable batch add/remove field operations'), + + /** Show field usage statistics (views, formulas, relationships referencing this field) */ + showUsageStats: z.boolean().default(false).describe('Show field usage statistics'), +}); + +export type FieldEditorConfig = z.infer; + +// ─── Relationship Mapper ───────────────────────────────────────────── + +/** + * Relationship display configuration — controls how relationships + * are visualized in the mapper and ER diagram. + */ +export const RelationshipDisplaySchema = z.object({ + /** Relationship type to configure */ + type: z.enum(['lookup', 'master_detail', 'tree']).describe('Relationship type'), + + /** Line style for this relationship type */ + lineStyle: z.enum(['solid', 'dashed', 'dotted']).default('solid').describe('Line style in diagrams'), + + /** Line color (CSS color value) */ + color: z.string().default('#94a3b8').describe('Line color (CSS value)'), + + /** Highlighted color on hover/select */ + highlightColor: z.string().default('#0891b2').describe('Highlighted color on hover/select'), + + /** Cardinality label to display */ + cardinalityLabel: z.string().default('1:N').describe('Cardinality label (e.g., "1:N", "1:1", "N:M")'), +}); + +export type RelationshipDisplay = z.infer; + +/** + * Relationship Mapper configuration — controls the relationship + * editing and visualization experience. + */ +export const RelationshipMapperConfigSchema = z.object({ + /** Enable visual relationship creation (drag from source to target) */ + visualCreation: z.boolean().default(true).describe('Enable drag-to-create relationships'), + + /** Show reverse relationships (child → parent) */ + showReverseRelationships: z.boolean().default(true).describe('Show reverse/child-to-parent relationships'), + + /** Show cascade delete warnings */ + showCascadeWarnings: z.boolean().default(true).describe('Show cascade delete behavior warnings'), + + /** Relationship display configuration by type */ + displayConfig: z.array(RelationshipDisplaySchema).default([ + { type: 'lookup', lineStyle: 'dashed', color: '#0891b2', highlightColor: '#06b6d4', cardinalityLabel: '1:N' }, + { type: 'master_detail', lineStyle: 'solid', color: '#ea580c', highlightColor: '#f97316', cardinalityLabel: '1:N' }, + { type: 'tree', lineStyle: 'dotted', color: '#8b5cf6', highlightColor: '#a78bfa', cardinalityLabel: '1:N' }, + ]).describe('Visual config per relationship type'), +}); + +export type RelationshipMapperConfig = z.infer; + +// ─── ER Diagram ────────────────────────────────────────────────────── + +/** Layout algorithm for ER diagram */ +export const ERLayoutAlgorithmSchema = z.enum([ + 'force', // Force-directed graph (natural clustering) + 'hierarchy', // Top-down hierarchy (master → detail) + 'grid', // Uniform grid layout + 'circular', // Circular arrangement +]).describe('ER diagram layout algorithm'); + +export type ERLayoutAlgorithm = z.infer; + +/** + * Node display options — controls what information is shown + * on each entity node in the ER diagram. + */ +export const ERNodeDisplaySchema = z.object({ + /** Show field list within the node */ + showFields: z.boolean().default(true).describe('Show field list inside entity nodes'), + + /** Maximum fields to show before collapsing (0 = no limit) */ + maxFieldsVisible: z.number().default(8).describe('Max fields visible before "N more..." collapse'), + + /** Show field types alongside field names */ + showFieldTypes: z.boolean().default(true).describe('Show field type badges'), + + /** Show required field indicators */ + showRequiredIndicator: z.boolean().default(true).describe('Show required field indicators'), + + /** Show record count on each node (requires data access) */ + showRecordCount: z.boolean().default(false).describe('Show live record count on nodes'), + + /** Show object icon */ + showIcon: z.boolean().default(true).describe('Show object icon on node header'), + + /** Show object description on hover tooltip */ + showDescription: z.boolean().default(true).describe('Show description tooltip on hover'), +}); + +export type ERNodeDisplay = z.infer; + +/** + * ER Diagram configuration — controls the entity-relationship + * diagram rendering, interaction, and layout. + */ +export const ERDiagramConfigSchema = z.object({ + /** Enable the ER diagram panel */ + enabled: z.boolean().default(true).describe('Enable ER diagram panel'), + + /** Default layout algorithm */ + layout: ERLayoutAlgorithmSchema.default('force').describe('Default layout algorithm'), + + /** Node display options */ + nodeDisplay: ERNodeDisplaySchema.default({ + showFields: true, + maxFieldsVisible: 8, + showFieldTypes: true, + showRequiredIndicator: true, + showRecordCount: false, + showIcon: true, + showDescription: true, + }).describe('Node display configuration'), + + /** Show minimap for navigation */ + showMinimap: z.boolean().default(true).describe('Show minimap for large diagrams'), + + /** Enable zoom controls */ + zoomControls: z.boolean().default(true).describe('Show zoom in/out/fit controls'), + + /** Minimum zoom level */ + minZoom: z.number().default(0.1).describe('Minimum zoom level'), + + /** Maximum zoom level */ + maxZoom: z.number().default(3).describe('Maximum zoom level'), + + /** Show relationship labels (cardinality) on edges */ + showEdgeLabels: z.boolean().default(true).describe('Show cardinality labels on relationship edges'), + + /** Highlight connected entities on hover */ + highlightOnHover: z.boolean().default(true).describe('Highlight connected entities on node hover'), + + /** Click behavior: navigate to object designer */ + clickToNavigate: z.boolean().default(true).describe('Click node to navigate to object detail'), + + /** Enable drag-and-drop to create relationships */ + dragToConnect: z.boolean().default(true).describe('Drag between nodes to create relationships'), + + /** Filter to show only objects with relationships (hide orphans) */ + hideOrphans: z.boolean().default(false).describe('Hide objects with no relationships'), + + /** Auto-fit diagram to viewport on initial load */ + autoFit: z.boolean().default(true).describe('Auto-fit diagram to viewport on load'), + + /** Export diagram options */ + exportFormats: z.array(z.enum(['png', 'svg', 'json'])).default(['png', 'svg']).describe('Available export formats for diagram'), +}); + +export type ERDiagramConfig = z.infer; + +// ─── Object Manager ────────────────────────────────────────────────── + +/** Object list display mode */ +export const ObjectListDisplayModeSchema = z.enum([ + 'table', // Traditional table with columns + 'cards', // Card grid (visual overview) + 'tree', // Hierarchical tree (grouped by package/namespace) +]).describe('Object list display mode'); + +export type ObjectListDisplayMode = z.infer; + +/** Object list sort field */ +export const ObjectSortFieldSchema = z.enum([ + 'name', // Sort by API name + 'label', // Sort by display label + 'fieldCount', // Sort by number of fields + 'updatedAt', // Sort by last modified +]).describe('Object list sort field'); + +export type ObjectSortField = z.infer; + +/** Object filter criteria */ +export const ObjectFilterSchema = z.object({ + /** Filter by package/namespace */ + package: z.string().optional().describe('Filter by owning package'), + + /** Filter by tags */ + tags: z.array(z.string()).optional().describe('Filter by object tags'), + + /** Show system objects */ + includeSystem: z.boolean().default(false).describe('Include system-level objects'), + + /** Show abstract objects */ + includeAbstract: z.boolean().default(false).describe('Include abstract base objects'), + + /** Show only objects with specific field types */ + hasFieldType: z.string().optional().describe('Filter to objects containing a specific field type'), + + /** Show only objects with relationships */ + hasRelationships: z.boolean().optional().describe('Filter to objects with lookup/master_detail fields'), + + /** Text search across name, label, description */ + searchQuery: z.string().optional().describe('Free-text search across name, label, and description'), +}); + +export type ObjectFilter = z.infer; + +/** + * Object Manager configuration — controls the unified object list, + * search, and management experience. + */ +export const ObjectManagerConfigSchema = z.object({ + /** Default display mode */ + defaultDisplayMode: ObjectListDisplayModeSchema.default('table').describe('Default list display mode'), + + /** Default sort field */ + defaultSortField: ObjectSortFieldSchema.default('label').describe('Default sort field'), + + /** Default sort direction */ + defaultSortDirection: z.enum(['asc', 'desc']).default('asc').describe('Default sort direction'), + + /** Default filters */ + defaultFilter: ObjectFilterSchema.default({ + includeSystem: false, + includeAbstract: false, + }).describe('Default filter configuration'), + + /** Show field count badge on each object row */ + showFieldCount: z.boolean().default(true).describe('Show field count badge'), + + /** Show relationship count badge */ + showRelationshipCount: z.boolean().default(true).describe('Show relationship count badge'), + + /** Show quick-preview tooltip with field list on hover */ + showQuickPreview: z.boolean().default(true).describe('Show quick field preview tooltip on hover'), + + /** Enable object comparison (diff two objects side-by-side) */ + enableComparison: z.boolean().default(false).describe('Enable side-by-side object comparison'), + + /** Show ER diagram toggle button in the toolbar */ + showERDiagramToggle: z.boolean().default(true).describe('Show ER diagram toggle in toolbar'), + + /** Show "Create Object" quick action */ + showCreateAction: z.boolean().default(true).describe('Show create object action'), + + /** Show object statistics summary bar (total objects, fields, relationships) */ + showStatsSummary: z.boolean().default(true).describe('Show statistics summary bar'), +}); + +export type ObjectManagerConfig = z.infer; + +// ─── Object Preview ────────────────────────────────────────────────── + +/** + * Preview tab configuration — defines the tabs available + * when viewing a single object. + */ +export const ObjectPreviewTabSchema = z.object({ + /** Tab key */ + key: z.string().describe('Tab key'), + + /** Tab display label */ + label: z.string().describe('Tab display label'), + + /** Lucide icon name */ + icon: z.string().optional().describe('Lucide icon name'), + + /** Whether this tab is enabled */ + enabled: z.boolean().default(true).describe('Whether this tab is available'), + + /** Sort order */ + order: z.number().default(0).describe('Sort order (lower = higher)'), +}); + +export type ObjectPreviewTab = z.infer; + +/** + * Object Preview configuration — defines the tabs and layout + * when viewing/editing a single object's metadata. + */ +export const ObjectPreviewConfigSchema = z.object({ + /** Tabs to show in the object detail view */ + tabs: z.array(ObjectPreviewTabSchema).default([ + { key: 'fields', label: 'Fields', icon: 'list', enabled: true, order: 0 }, + { key: 'relationships', label: 'Relationships', icon: 'link', enabled: true, order: 10 }, + { key: 'indexes', label: 'Indexes', icon: 'zap', enabled: true, order: 20 }, + { key: 'validations', label: 'Validations', icon: 'shield-check', enabled: true, order: 30 }, + { key: 'capabilities', label: 'Capabilities', icon: 'settings', enabled: true, order: 40 }, + { key: 'data', label: 'Data', icon: 'table-2', enabled: true, order: 50 }, + { key: 'api', label: 'API', icon: 'globe', enabled: true, order: 60 }, + { key: 'code', label: 'Code', icon: 'code-2', enabled: true, order: 70 }, + ]).describe('Object detail preview tabs'), + + /** Default active tab */ + defaultTab: z.string().default('fields').describe('Default active tab key'), + + /** Show object header with summary info */ + showHeader: z.boolean().default(true).describe('Show object summary header'), + + /** Show breadcrumbs */ + showBreadcrumbs: z.boolean().default(true).describe('Show navigation breadcrumbs'), +}); + +export type ObjectPreviewConfig = z.infer; + +// ─── Top-Level Object Designer Config ──────────────────────────────── + +/** Default view when entering the Object Designer */ +export const ObjectDesignerDefaultViewSchema = z.enum([ + 'field-editor', // Field table editor (default) + 'relationship-mapper', // Visual relationship view + 'er-diagram', // Full ER diagram + 'object-manager', // Object list/manager +]).describe('Default view when entering the Object Designer'); + +export type ObjectDesignerDefaultView = z.infer; + +/** + * Object Designer configuration — top-level config that composes + * all sub-configurations for the visual object design experience. + * + * @example + * ```typescript + * const config = ObjectDesignerConfigSchema.parse({ + * defaultView: 'field-editor', + * fieldEditor: { + * inlineEditing: true, + * dragReorder: true, + * showFieldGroups: true, + * }, + * erDiagram: { + * enabled: true, + * layout: 'force', + * }, + * objectManager: { + * defaultDisplayMode: 'table', + * showERDiagramToggle: true, + * }, + * }); + * ``` + */ +export const ObjectDesignerConfigSchema = z.object({ + /** Default view when opening the designer */ + defaultView: ObjectDesignerDefaultViewSchema.default('field-editor').describe('Default view'), + + /** Field editor configuration */ + fieldEditor: FieldEditorConfigSchema.default({ + inlineEditing: true, + dragReorder: true, + showFieldGroups: true, + showPropertyPanel: true, + propertySections: [ + { key: 'basics', label: 'Basic Properties', defaultExpanded: true, order: 0 }, + { key: 'constraints', label: 'Constraints & Validation', defaultExpanded: true, order: 10 }, + { key: 'relationship', label: 'Relationship Config', defaultExpanded: true, order: 20 }, + { key: 'display', label: 'Display & UI', defaultExpanded: false, order: 30 }, + { key: 'security', label: 'Security & Compliance', defaultExpanded: false, order: 40 }, + { key: 'advanced', label: 'Advanced', defaultExpanded: false, order: 50 }, + ], + fieldGroups: [], + paginationThreshold: 50, + batchOperations: true, + showUsageStats: false, + }).describe('Field editor configuration'), + + /** Relationship mapper configuration */ + relationshipMapper: RelationshipMapperConfigSchema.default({ + visualCreation: true, + showReverseRelationships: true, + showCascadeWarnings: true, + displayConfig: [ + { type: 'lookup', lineStyle: 'dashed', color: '#0891b2', highlightColor: '#06b6d4', cardinalityLabel: '1:N' }, + { type: 'master_detail', lineStyle: 'solid', color: '#ea580c', highlightColor: '#f97316', cardinalityLabel: '1:N' }, + { type: 'tree', lineStyle: 'dotted', color: '#8b5cf6', highlightColor: '#a78bfa', cardinalityLabel: '1:N' }, + ], + }).describe('Relationship mapper configuration'), + + /** ER diagram configuration */ + erDiagram: ERDiagramConfigSchema.default({ + enabled: true, + layout: 'force', + nodeDisplay: { + showFields: true, + maxFieldsVisible: 8, + showFieldTypes: true, + showRequiredIndicator: true, + showRecordCount: false, + showIcon: true, + showDescription: true, + }, + showMinimap: true, + zoomControls: true, + minZoom: 0.1, + maxZoom: 3, + showEdgeLabels: true, + highlightOnHover: true, + clickToNavigate: true, + dragToConnect: true, + hideOrphans: false, + autoFit: true, + exportFormats: ['png', 'svg'], + }).describe('ER diagram configuration'), + + /** Object manager configuration */ + objectManager: ObjectManagerConfigSchema.default({ + defaultDisplayMode: 'table', + defaultSortField: 'label', + defaultSortDirection: 'asc', + defaultFilter: { + includeSystem: false, + includeAbstract: false, + }, + showFieldCount: true, + showRelationshipCount: true, + showQuickPreview: true, + enableComparison: false, + showERDiagramToggle: true, + showCreateAction: true, + showStatsSummary: true, + }).describe('Object manager configuration'), + + /** Object preview configuration */ + objectPreview: ObjectPreviewConfigSchema.default({ + tabs: [ + { key: 'fields', label: 'Fields', icon: 'list', enabled: true, order: 0 }, + { key: 'relationships', label: 'Relationships', icon: 'link', enabled: true, order: 10 }, + { key: 'indexes', label: 'Indexes', icon: 'zap', enabled: true, order: 20 }, + { key: 'validations', label: 'Validations', icon: 'shield-check', enabled: true, order: 30 }, + { key: 'capabilities', label: 'Capabilities', icon: 'settings', enabled: true, order: 40 }, + { key: 'data', label: 'Data', icon: 'table-2', enabled: true, order: 50 }, + { key: 'api', label: 'API', icon: 'globe', enabled: true, order: 60 }, + { key: 'code', label: 'Code', icon: 'code-2', enabled: true, order: 70 }, + ], + defaultTab: 'fields', + showHeader: true, + showBreadcrumbs: true, + }).describe('Object preview configuration'), +}); + +export type ObjectDesignerConfig = z.infer; + +// ─── Helper: defineObjectDesignerConfig ────────────────────────────── + +/** + * Type-safe helper for defining Object Designer configuration. + * + * @example + * ```typescript + * const config = defineObjectDesignerConfig({ + * defaultView: 'er-diagram', + * erDiagram: { + * layout: 'hierarchy', + * showMinimap: true, + * }, + * objectManager: { + * defaultDisplayMode: 'cards', + * }, + * }); + * ``` + */ +export function defineObjectDesignerConfig( + input: z.input, +): ObjectDesignerConfig { + return ObjectDesignerConfigSchema.parse(input); +} From a1bd206362d23713280f0f1e1120724a536dfbba Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 15 Feb 2026 01:53:54 +0000 Subject: [PATCH 3/3] docs: update ROADMAP.md and Studio ROADMAP.md with Object Designer optimization plan and protocol schema details Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com> --- ROADMAP.md | 16 ++++++++++------ apps/studio/ROADMAP.md | 20 +++++++++++++++++--- 2 files changed, 27 insertions(+), 9 deletions(-) diff --git a/ROADMAP.md b/ROADMAP.md index 84906c2b8a..52628eb678 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -1,6 +1,6 @@ # ObjectStack Protocol — Road Map -> **Last Updated:** 2026-02-13 +> **Last Updated:** 2026-02-15 > **Current Version:** v3.0.2 > **Status:** Protocol Specification Complete · Runtime Implementation In Progress @@ -70,13 +70,13 @@ These are the backbone of ObjectStack's enterprise capabilities. | Packages (total) | 23 | | Apps | 2 (Studio, Docs) | | Examples | 4 (Todo, CRM, Host, BI Plugin) | -| Zod Schema Files | 175 | +| Zod Schema Files | 176 | | Exported Schemas | 1,100+ | -| `.describe()` Annotations | 7,111 | +| `.describe()` Annotations | 7,111+ | | Service Contracts | 25 | | Contracts Implemented | 7 (28%) | -| Test Files | 195 | -| Tests Passing | 5,269 / 5,269 | +| Test Files | 197 | +| Tests Passing | 5,363 / 5,363 | | `@deprecated` Items | 3 | | Protocol Domains | 15 (Data, UI, AI, API, Automation, Cloud, Contracts, Identity, Integration, Kernel, QA, Security, Shared, Studio, System) | @@ -309,7 +309,11 @@ These are the backbone of ObjectStack's enterprise capabilities. ### 8.1 Studio IDE -- [ ] Object Designer — visual field editor, relationship mapper +- [x] Object Designer Protocol — field editor, relationship mapper, ER diagram, object manager schemas defined (`studio/object-designer.zod.ts`) +- [ ] Object Designer Runtime — visual field editor with inline editing, drag-reorder, type-aware property panels +- [ ] Relationship Mapper — visual lookup/master-detail/tree creation with drag-to-connect +- [ ] ER Diagram — interactive entity-relationship diagram with force/hierarchy/grid layouts, minimap, zoom, export (PNG/SVG) +- [ ] Object Manager — unified object list with search, filter, card/table/tree views, quick preview, statistics - [ ] View Builder — drag-and-drop list/form/dashboard designers - [ ] Flow Builder — visual automation flow editor - [ ] Security Console — permission matrix, RLS policy editor diff --git a/apps/studio/ROADMAP.md b/apps/studio/ROADMAP.md index 9766431689..3cbdac5fb3 100644 --- a/apps/studio/ROADMAP.md +++ b/apps/studio/ROADMAP.md @@ -1,6 +1,6 @@ # ObjectStack Studio — Development Roadmap -> **Last Updated:** 2026-02-09 +> **Last Updated:** 2026-02-15 > **Version:** 2.0.0 → 3.0.0 > **Goal:** Transform Studio from a metadata inspector into a full-featured visual IDE for the ObjectStack platform. @@ -44,6 +44,8 @@ **Spec defines 100+ metadata types. Studio has specialized viewers for only 1 (Object).** All other types fall back to the generic JSON inspector. The plugin system is ready — it just needs content. +**Object Designer Protocol:** The `ObjectDesignerConfigSchema` (in `@objectstack/spec/studio`) now defines the full specification for the visual object design experience, including field editor, relationship mapper, ER diagram, object manager, and object preview configurations. The runtime implementation should consume these schemas. + --- ## 🗺️ Roadmap @@ -71,8 +73,18 @@ | # | Task | Plugin ID | Priority | |---|------|-----------|----------| -| 1.1 | **Object Designer — Edit Mode** | `objectstack.object-designer` | 🔴 P0 | -| | Add field creation/editing inline. Support drag-and-drop field reordering. Validate field schemas via Zod. | | | +| 1.0 | **Object Designer Protocol** ✅ | `@objectstack/spec` | ✅ Done | +| | Zod schemas for field editor, relationship mapper, ER diagram, object manager, and object preview configs. `ObjectDesignerConfigSchema`, `ERDiagramConfigSchema`, `FieldEditorConfigSchema`, etc. 46 tests passing. | | | +| 1.1 | **Object Designer — Visual Field Editor** | `objectstack.object-designer` | 🔴 P0 | +| | Inline field creation/editing with type-aware property panel (6 sections: basics, constraints, relationship, display, security, advanced). Drag-and-drop field reordering. Field grouping by `field.group`. Batch add/remove operations. Validate field schemas via Zod. Usage statistics (views/formulas referencing each field). Pagination for 50+ field objects. | | | +| 1.1a | **Object Designer — Relationship Mapper** | `objectstack.object-designer` | 🔴 P0 | +| | Visual relationship creation via drag-from-source-to-target. Support lookup, master_detail, and tree relationship types. Show reverse relationships (child → parent). Cascade delete behavior warnings. Configurable line styles and colors per relationship type. | | | +| 1.1b | **Object Designer — ER Diagram** | `objectstack.object-designer` | 🟡 P1 | +| | Interactive entity-relationship diagram with 4 layout algorithms (force-directed, hierarchy, grid, circular). Entity nodes show field list with type badges and required indicators. Minimap for large schemas. Zoom controls (0.1x–3x). Click-to-navigate to object detail. Drag-to-connect for relationship creation. Hover highlighting of connected entities. Export to PNG/SVG/JSON. Auto-fit on initial load. Optional orphan hiding. | | | +| 1.1c | **Object Manager — Unified List** | `objectstack.object-designer` | 🟡 P1 | +| | Object list with table/card/tree display modes. Search across name, label, description. Filter by package, tags, field types, relationships. Sort by name, label, field count, last updated. Quick-preview tooltip with field list on hover. Statistics summary bar (total objects, fields, relationships). Side-by-side object comparison mode. ER diagram toggle from toolbar. | | | +| 1.1d | **Object Preview — Enhanced Tabs** | `objectstack.object-designer` | 🟡 P1 | +| | 8-tab object detail view: Fields, Relationships, Indexes, Validations, Capabilities, Data, API, Code. Configurable tab ordering and enable/disable. Object summary header with namespace, owner package, field count. Breadcrumb navigation. | | | | 1.2 | **Dataset Editor** | `objectstack.dataset-editor` | 🔴 P0 | | | Visual seed data editor. Import CSV/JSON. Preview before apply. Environment scoping (dev/test/prod). | | | | 1.3 | **Datasource Manager** | `objectstack.datasource-manager` | 🟡 P1 | @@ -336,6 +348,8 @@ export const myPlugin: StudioPlugin = { | Metric | Current | Phase 2 Target | v3.0 Target | |--------|---------|----------------|-------------| | Metadata types with dedicated viewer | 1 / 30+ | 15 / 30+ | 30+ / 30+ | +| Object Designer protocol schemas | 16 schemas | — | — | +| Object Designer protocol tests | 46 tests | — | — | | Component test coverage | 0% | 50% | 80% | | Deep-linkable views | 0 | All | All | | Plugin count (built-in) | 7 | 20 | 35+ |