-
Notifications
You must be signed in to change notification settings - Fork 6
feat: enable system objects visibility in Studio sidebar #918
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 3 commits
c5f7eb1
f9a7744
ba7c4c6
eee9dae
c2fac1c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -19,6 +19,7 @@ import { | |||||||||||||||||||||||||
| AppWindow, | ||||||||||||||||||||||||||
| Layers, | ||||||||||||||||||||||||||
| Eye, | ||||||||||||||||||||||||||
| EyeOff, | ||||||||||||||||||||||||||
| FileCode, | ||||||||||||||||||||||||||
| Palette, | ||||||||||||||||||||||||||
| CheckSquare, | ||||||||||||||||||||||||||
|
|
@@ -30,9 +31,10 @@ import { | |||||||||||||||||||||||||
| Anchor, | ||||||||||||||||||||||||||
| UserCog, | ||||||||||||||||||||||||||
| ChevronRight, | ||||||||||||||||||||||||||
| Settings, | ||||||||||||||||||||||||||
| type LucideIcon, | ||||||||||||||||||||||||||
| } from "lucide-react" | ||||||||||||||||||||||||||
| import { useState, useEffect, useCallback } from "react" | ||||||||||||||||||||||||||
| import { useState, useEffect, useCallback, useMemo } from "react" | ||||||||||||||||||||||||||
| import { useClient } from '@objectstack/client-react'; | ||||||||||||||||||||||||||
| import type { InstalledPackage } from '@objectstack/spec/kernel'; | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
|
|
@@ -128,6 +130,15 @@ const PROTOCOL_GROUPS: ProtocolGroup[] = [ | |||||||||||||||||||||||||
| /** Types that are internal / should be hidden from the sidebar */ | ||||||||||||||||||||||||||
| const HIDDEN_TYPES = new Set(['plugin', 'plugins', 'kind', 'app', 'apps', 'package']); | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| /** System object name prefix — objects with this prefix are grouped under "System" */ | ||||||||||||||||||||||||||
| const SYSTEM_OBJECT_PREFIX = 'sys_'; | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| /** Check if an object item is a system object */ | ||||||||||||||||||||||||||
| function isSystemObject(item: any): boolean { | ||||||||||||||||||||||||||
| const name = item.name || item.id || ''; | ||||||||||||||||||||||||||
| return item.isSystem === true || name.startsWith(SYSTEM_OBJECT_PREFIX); | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| /** Icon mapping for package types */ | ||||||||||||||||||||||||||
| const PKG_TYPE_ICONS: Record<string, LucideIcon> = { | ||||||||||||||||||||||||||
| app: AppWindow, plugin: Layers, driver: Database, server: Globe, | ||||||||||||||||||||||||||
|
|
@@ -161,6 +172,9 @@ export function AppSidebar({ | |||||||||||||||||||||||||
| // Track which metadata *types* are expanded (show individual items) | ||||||||||||||||||||||||||
| const [expandedTypes, setExpandedTypes] = useState<Set<string>>(new Set(['object', 'objects'])); | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| // Toggle to show/hide system objects in the Data protocol group | ||||||||||||||||||||||||||
| const [showSystemInData, setShowSystemInData] = useState(true); | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| const toggleTypeExpanded = (type: string) => { | ||||||||||||||||||||||||||
| setExpandedTypes(prev => { | ||||||||||||||||||||||||||
| const next = new Set(prev); | ||||||||||||||||||||||||||
|
|
@@ -216,12 +230,35 @@ export function AppSidebar({ | |||||||||||||||||||||||||
| label.toLowerCase().includes(searchQuery.toLowerCase()) || | ||||||||||||||||||||||||||
| name.toLowerCase().includes(searchQuery.toLowerCase()); | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| // Extract system objects from loaded metadata (object/objects types) | ||||||||||||||||||||||||||
| const systemObjects = useMemo(() => { | ||||||||||||||||||||||||||
| const objectTypes = ['object', 'objects']; | ||||||||||||||||||||||||||
| const sysItems: any[] = []; | ||||||||||||||||||||||||||
| for (const type of objectTypes) { | ||||||||||||||||||||||||||
| const items = metaItems[type] || []; | ||||||||||||||||||||||||||
| sysItems.push(...items.filter(isSystemObject)); | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| return sysItems; | ||||||||||||||||||||||||||
| }, [metaItems]); | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| // Filter system objects out of the Data protocol group when toggled off | ||||||||||||||||||||||||||
| const filteredMetaItems = useMemo(() => { | ||||||||||||||||||||||||||
| if (showSystemInData) return metaItems; | ||||||||||||||||||||||||||
| const result = { ...metaItems }; | ||||||||||||||||||||||||||
| for (const type of ['object', 'objects']) { | ||||||||||||||||||||||||||
| if (result[type]) { | ||||||||||||||||||||||||||
| result[type] = result[type].filter((item: any) => !isSystemObject(item)); | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| return result; | ||||||||||||||||||||||||||
| }, [metaItems, showSystemInData]); | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| // Compute visible groups: only show groups that have at least one type with items | ||||||||||||||||||||||||||
| const visibleGroups = PROTOCOL_GROUPS.map(group => { | ||||||||||||||||||||||||||
| const visibleTypes = group.types.filter(t => | ||||||||||||||||||||||||||
| metaTypes.includes(t) && !HIDDEN_TYPES.has(t) && (metaItems[t]?.length ?? 0) > 0 | ||||||||||||||||||||||||||
| metaTypes.includes(t) && !HIDDEN_TYPES.has(t) && (filteredMetaItems[t]?.length ?? 0) > 0 | ||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||
| const totalItems = visibleTypes.reduce((sum, t) => sum + (metaItems[t]?.length ?? 0), 0); | ||||||||||||||||||||||||||
| const totalItems = visibleTypes.reduce((sum, t) => sum + (filteredMetaItems[t]?.length ?? 0), 0); | ||||||||||||||||||||||||||
| return { ...group, visibleTypes, totalItems }; | ||||||||||||||||||||||||||
| }).filter(g => g.totalItems > 0); | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
|
|
@@ -328,11 +365,23 @@ export function AppSidebar({ | |||||||||||||||||||||||||
| <group.icon className="mr-1.5 h-3.5 w-3.5" /> | ||||||||||||||||||||||||||
| <span className="flex-1 min-w-0 truncate">{group.label}</span> | ||||||||||||||||||||||||||
| <span className="shrink-0 text-xs tabular-nums text-sidebar-foreground/50">{group.totalItems}</span> | ||||||||||||||||||||||||||
| {/* System objects filter toggle for Data group */} | ||||||||||||||||||||||||||
| {group.key === 'data' && systemObjects.length > 0 && ( | ||||||||||||||||||||||||||
| <button | ||||||||||||||||||||||||||
| title={showSystemInData ? 'Hide system objects' : 'Show system objects'} | ||||||||||||||||||||||||||
| onClick={(e) => { e.stopPropagation(); setShowSystemInData(!showSystemInData); }} | ||||||||||||||||||||||||||
| className="ml-1 shrink-0 rounded p-0.5 text-sidebar-foreground/50 hover:text-sidebar-foreground hover:bg-sidebar-accent transition-colors" | ||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||
| {showSystemInData | ||||||||||||||||||||||||||
| ? <Eye className="h-3 w-3" /> | ||||||||||||||||||||||||||
| : <EyeOff className="h-3 w-3" />} | ||||||||||||||||||||||||||
| </button> | ||||||||||||||||||||||||||
|
Comment on lines
+379
to
+389
|
||||||||||||||||||||||||||
| )} | ||||||||||||||||||||||||||
| </SidebarGroupLabel> | ||||||||||||||||||||||||||
| <SidebarGroupContent> | ||||||||||||||||||||||||||
| <SidebarMenu> | ||||||||||||||||||||||||||
| {group.visibleTypes.map(type => { | ||||||||||||||||||||||||||
| const items = metaItems[type] || []; | ||||||||||||||||||||||||||
| const items = filteredMetaItems[type] || []; | ||||||||||||||||||||||||||
| const TypeIcon = getTypeIcon(type); | ||||||||||||||||||||||||||
| const typeLabel = getTypeLabel(type); | ||||||||||||||||||||||||||
| const isObjectType = type === 'object' || type === 'objects'; | ||||||||||||||||||||||||||
|
|
@@ -408,9 +457,62 @@ export function AppSidebar({ | |||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| {/* ── System ── */} | ||||||||||||||||||||||||||
| <SidebarGroup> | ||||||||||||||||||||||||||
| <SidebarGroupLabel>System</SidebarGroupLabel> | ||||||||||||||||||||||||||
| <SidebarGroupLabel> | ||||||||||||||||||||||||||
| <Settings className="mr-1.5 h-3.5 w-3.5" /> | ||||||||||||||||||||||||||
| <span className="flex-1 min-w-0 truncate">System</span> | ||||||||||||||||||||||||||
| {systemObjects.length > 0 && ( | ||||||||||||||||||||||||||
| <span className="shrink-0 text-xs tabular-nums text-sidebar-foreground/50">{systemObjects.length}</span> | ||||||||||||||||||||||||||
| )} | ||||||||||||||||||||||||||
| </SidebarGroupLabel> | ||||||||||||||||||||||||||
| <SidebarGroupContent> | ||||||||||||||||||||||||||
| <SidebarMenu> | ||||||||||||||||||||||||||
| {/* Dynamic system objects */} | ||||||||||||||||||||||||||
| {systemObjects.length > 0 && ( | ||||||||||||||||||||||||||
| <Collapsible | ||||||||||||||||||||||||||
| open={expandedTypes.has('_system_objects') || !!searchQuery} | ||||||||||||||||||||||||||
| onOpenChange={() => toggleTypeExpanded('_system_objects')} | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
| onOpenChange={() => toggleTypeExpanded('_system_objects')} | |
| onOpenChange={(open) => { | |
| const isExpanded = expandedTypes.has('_system_objects'); | |
| if (open && !isExpanded) { | |
| toggleTypeExpanded('_system_objects'); | |
| } | |
| if (!open && isExpanded) { | |
| toggleTypeExpanded('_system_objects'); | |
| } | |
| }} |
Copilot
AI
Mar 12, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the System Objects list, the prefix hint uses SYSTEM_OBJECT_PREFIX (sys_) and checks itemName.startsWith(SYSTEM_OBJECT_PREFIX), but object names are FQNs like sys__user. As a result, the prefix label never renders for actual system objects. Consider rendering the namespace (e.g., sys__) or deriving a consistent display from parseFQN(itemName) instead of hardcoding sys_.
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -261,7 +261,7 @@ Default view when entering the Object Designer | |||||
| | :--- | :--- | :--- | :--- | | ||||||
| | **package** | `string` | optional | Filter by owning package | | ||||||
| | **tags** | `string[]` | optional | Filter by object tags | | ||||||
| | **includeSystem** | `boolean` | ✅ | Include system-level objects | | ||||||
| | **includeSystem** | `boolean` | ✅ | Include system-level objects (default: `true`) | | ||||||
|
||||||
| | **includeSystem** | `boolean` | ✅ | Include system-level objects (default: `true`) | | |
| | **includeSystem** | `boolean` | ✅ | Include system-level objects (default: `false`) | |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -307,7 +307,7 @@ describe('ObjectSortFieldSchema', () => { | |
| describe('ObjectFilterSchema', () => { | ||
| it('should accept empty object with defaults', () => { | ||
| const result = ObjectFilterSchema.parse({}); | ||
| expect(result.includeSystem).toBe(false); | ||
| expect(result.includeSystem).toBe(true); | ||
| expect(result.includeAbstract).toBe(false); | ||
| expect(result.package).toBeUndefined(); | ||
| expect(result.tags).toBeUndefined(); | ||
|
Comment on lines
308
to
313
|
||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -328,7 +328,7 @@ export const ObjectFilterSchema = z.object({ | |||||||||||
| tags: z.array(z.string()).optional().describe('Filter by object tags'), | ||||||||||||
|
|
||||||||||||
| /** Show system objects */ | ||||||||||||
| includeSystem: z.boolean().default(false).describe('Include system-level objects'), | ||||||||||||
| includeSystem: z.boolean().default(true).describe('Include system-level objects'), | ||||||||||||
|
|
||||||||||||
| /** Show abstract objects */ | ||||||||||||
| includeAbstract: z.boolean().default(false).describe('Include abstract base objects'), | ||||||||||||
|
|
@@ -361,7 +361,7 @@ export const ObjectManagerConfigSchema = z.object({ | |||||||||||
|
|
||||||||||||
| /** Default filters */ | ||||||||||||
| defaultFilter: ObjectFilterSchema.default({ | ||||||||||||
| includeSystem: false, | ||||||||||||
| includeSystem: true, | ||||||||||||
| includeAbstract: false, | ||||||||||||
| }).describe('Default filter configuration'), | ||||||||||||
|
|
||||||||||||
|
Comment on lines
+364
to
367
|
||||||||||||
| includeSystem: true, | |
| includeAbstract: false, | |
| }).describe('Default filter configuration'), | |
| includeAbstract: false, | |
| }).describe('Default filter configuration'), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
SYSTEM_OBJECT_PREFIXis set tosys_, but object names coming from the registry are stored as FQNs likesys__user(see SchemaRegistry.registerObject storingname: fqn). This means thename.startsWith(SYSTEM_OBJECT_PREFIX)branch inisSystemObject()will never match for current system objects; rely on namespace/FQN parsing (e.g.,parseFQN(name).namespace === 'sys') or update the prefix tosys__to keep the fallback accurate.