feat: Delete remaining duplicate organism TSX files (Task 11)

Delete 7 organisms that have JSON equivalents and are no longer needed:
- AppHeader (routes to @/lib/json-ui/json-components)
- EmptyCanvasState (routes to JSON)
- NavigationMenu (routes to JSON with useNavigationMenu hook)
- PageHeader (routes to JSON)
- SchemaCodeViewer (routes to JSON)
- ToolbarActions (routes to JSON)
- TreeListPanel (routes to JSON)

Updated imports in 2 files to use JSON versions:
- AppMainPanel: AppHeader from json-components
- AppLayout: NavigationMenu from json-components

Remaining organisms (6):
- SchemaEditorLayout, SchemaEditorCanvas, SchemaEditorPropertiesPanel
- SchemaEditorSidebar, SchemaEditorStatusBar, SchemaEditorToolbar
(These don't have JSON equivalents yet)

Build: passing ✓
Component types: 343 ✓

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-21 02:56:35 +00:00
parent d67301883b
commit 16bc7353dc
10 changed files with 7 additions and 696 deletions

View File

@@ -3,7 +3,7 @@ import { toast } from 'sonner'
import AppDialogs from '@/components/app/AppDialogs'
import AppMainPanel from '@/components/app/AppMainPanel'
import { NavigationMenu } from '@/components/organisms/NavigationMenu'
import { NavigationMenu } from '@/lib/json-ui/json-components'
import { SidebarInset, SidebarProvider } from '@/components/ui/sidebar'
import appStrings from '@/data/app-shortcuts.json'
import useAppNavigation from '@/hooks/use-app-navigation'

View File

@@ -1,6 +1,6 @@
import { Suspense } from 'react'
import { AppHeader } from '@/components/organisms'
import { AppHeader } from '@/lib/json-ui/json-components'
import { PWARegistry } from '@/lib/component-registry'
import { RouterProvider } from '@/router'
import type { FeatureToggles, Project } from '@/types/project'

View File

@@ -1,74 +0,0 @@
import { AppBranding, Breadcrumb, SaveIndicator } from '@/components/molecules'
import { SidebarTrigger } from '@/components/ui/sidebar'
import { ToolbarActions } from '@/components/organisms/ToolbarActions'
import { ProjectManager } from '@/components/ProjectManager'
import { FeatureToggles, Project } from '@/types/project'
import { Flex, Stack, Separator, Container } from '@/components/atoms'
interface AppHeaderProps {
activeTab: string
onTabChange: (tab: string) => void
featureToggles: FeatureToggles
errorCount: number
lastSaved: number | null
currentProject: Project
onProjectLoad: (project: Project) => void
onSearch: () => void
onShowShortcuts: () => void
onGenerateAI: () => void
onExport: () => void
onPreview?: () => void
onShowErrors: () => void
}
export function AppHeader({
activeTab,
onTabChange,
featureToggles,
errorCount,
lastSaved,
currentProject,
onProjectLoad,
onSearch,
onShowShortcuts,
onGenerateAI,
onExport,
onPreview,
onShowErrors,
}: AppHeaderProps) {
return (
<header className="border-b border-border bg-card">
<Stack direction="vertical" spacing="none">
<div className="px-4 sm:px-6 py-3 sm:py-4">
<Flex justify="between" align="center" gap="sm">
<Flex align="center" gap="sm" className="flex-1 min-w-0">
<SidebarTrigger />
<AppBranding />
<SaveIndicator lastSaved={lastSaved} />
</Flex>
<Flex gap="xs" shrink className="shrink-0">
<ProjectManager
currentProject={currentProject}
onProjectLoad={onProjectLoad}
/>
<ToolbarActions
onSearch={onSearch}
onShowShortcuts={onShowShortcuts}
onGenerateAI={onGenerateAI}
onExport={onExport}
onPreview={onPreview}
onShowErrors={onShowErrors}
errorCount={errorCount}
showErrorButton={featureToggles.errorRepair && errorCount > 0}
/>
</Flex>
</Flex>
</div>
<Separator className="opacity-50" />
<div className="px-4 sm:px-6 py-2">
<Breadcrumb />
</div>
</Stack>
</header>
)
}

View File

@@ -1,38 +0,0 @@
import { Plus, Folder } from '@phosphor-icons/react'
import { EmptyState, ActionButton, Stack } from '@/components/atoms'
interface EmptyCanvasStateProps {
onAddFirstComponent?: () => void
onImportSchema?: () => void
}
export function EmptyCanvasState({ onAddFirstComponent, onImportSchema }: EmptyCanvasStateProps) {
return (
<div className="h-full flex flex-col items-center justify-center p-8 bg-muted/20">
<EmptyState
icon={<Folder size={64} weight="duotone" />}
title="Empty Canvas"
description="Start building your UI by dragging components from the left panel, or import an existing schema."
>
<Stack direction="horizontal" spacing="md" className="mt-4">
{onImportSchema && (
<ActionButton
icon={<Folder size={16} />}
label="Import Schema"
onClick={onImportSchema}
variant="outline"
/>
)}
{onAddFirstComponent && (
<ActionButton
icon={<Plus size={16} />}
label="Add Component"
onClick={onAddFirstComponent}
variant="default"
/>
)}
</Stack>
</EmptyState>
</div>
)
}

View File

@@ -1,262 +0,0 @@
import { useState } from 'react'
import { Button } from '@/components/ui/button'
import { Sidebar, SidebarContent, SidebarHeader } from '@/components/ui/sidebar'
import { ScrollArea } from '@/components/ui/scroll-area'
import { Collapsible, CollapsibleContent } from '@/components/ui/collapsible'
import { CaretDoubleDown, CaretDoubleUp, CaretDown } from '@phosphor-icons/react'
import { CollapsibleTrigger } from '@/components/ui/collapsible'
import { Badge, Flex, Text, IconWrapper } from '@/components/atoms'
import { navigationGroups, NavigationItemData } from '@/lib/navigation-config'
import { FeatureToggles } from '@/types/project'
import { useRoutePreload } from '@/hooks/use-route-preload'
import navigationMenuCopy from '@/data/navigation-menu.json'
interface NavigationMenuProps {
activeTab: string
onTabChange: (tab: string) => void
featureToggles: FeatureToggles
errorCount?: number
}
interface NavigationMenuControlsProps {
onExpandAll: () => void
onCollapseAll: () => void
}
interface NavigationMenuGroupListProps {
activeTab: string
expandedGroups: Set<string>
featureToggles: FeatureToggles
errorCount: number
onToggleGroup: (groupId: string) => void
onItemClick: (value: string) => void
onItemHover: (value: string) => void
onItemLeave: (value: string) => void
}
function NavigationMenuControls({
onExpandAll,
onCollapseAll,
}: NavigationMenuControlsProps) {
return (
<div className="flex gap-2 mt-4">
<Button
variant="outline"
size="sm"
onClick={onExpandAll}
className="flex-1"
>
<CaretDoubleDown size={16} className="mr-2" />
{navigationMenuCopy.labels.expandAll}
</Button>
<Button
variant="outline"
size="sm"
onClick={onCollapseAll}
className="flex-1"
>
<CaretDoubleUp size={16} className="mr-2" />
{navigationMenuCopy.labels.collapseAll}
</Button>
</div>
)
}
function NavigationMenuHeader({
onExpandAll,
onCollapseAll,
}: NavigationMenuControlsProps) {
return (
<SidebarHeader className="px-4 py-4 border-b">
<h2 className="text-lg font-semibold">{navigationMenuCopy.labels.title}</h2>
<NavigationMenuControls
onExpandAll={onExpandAll}
onCollapseAll={onCollapseAll}
/>
</SidebarHeader>
)
}
function NavigationMenuGroupList({
activeTab,
expandedGroups,
featureToggles,
errorCount,
onToggleGroup,
onItemClick,
onItemHover,
onItemLeave,
}: NavigationMenuGroupListProps) {
const isItemVisible = (item: NavigationItemData) => {
if (!item.featureKey) return true
return featureToggles[item.featureKey]
}
const getVisibleItemsCount = (groupId: string) => {
const group = navigationGroups.find((g) => g.id === groupId)
if (!group) return 0
return group.items.filter(isItemVisible).length
}
const getItemBadge = (item: NavigationItemData) => {
if (item.id === 'errors') return errorCount
return item.badge
}
return (
<div className="space-y-2 py-4">
{navigationGroups.map((group) => {
const visibleItemsCount = getVisibleItemsCount(group.id)
if (visibleItemsCount === 0) return null
const isExpanded = expandedGroups.has(group.id)
return (
<Collapsible
key={group.id}
open={isExpanded}
onOpenChange={() => onToggleGroup(group.id)}
>
{/* NavigationGroupHeader - inlined */}
<CollapsibleTrigger className="w-full flex items-center gap-2 px-2 py-2 rounded-lg hover:bg-muted transition-colors group">
<CaretDown
size={16}
weight="bold"
className={`text-muted-foreground transition-transform ${
isExpanded ? 'rotate-0' : '-rotate-90'
}`}
/>
<h3 className="flex-1 text-left text-xs font-semibold text-muted-foreground uppercase tracking-wider">
{group.label}
</h3>
<span className="text-xs text-muted-foreground">{visibleItemsCount}</span>
</CollapsibleTrigger>
<CollapsibleContent className="mt-1">
<div className="space-y-1 pl-2">
{group.items.map((item) => {
if (!isItemVisible(item)) return null
const isActive = activeTab === item.value
const badge = getItemBadge(item)
return (
<div
key={item.id}
onMouseEnter={() => onItemHover(item.value)}
onMouseLeave={() => onItemLeave(item.value)}
>
{/* NavigationItem - inlined */}
<button
onClick={() => onItemClick(item.value)}
className={`w-full flex items-center gap-3 px-3 py-2 rounded-lg transition-colors ${
isActive
? 'bg-primary text-primary-foreground'
: 'hover:bg-muted text-foreground'
}`}
>
<IconWrapper
icon={item.icon}
size="md"
variant={isActive ? 'default' : 'muted'}
/>
<Text className="flex-1 text-left font-medium" variant="small">
{item.label}
</Text>
{badge !== undefined && badge > 0 && (
<Badge
variant={isActive ? 'secondary' : 'destructive'}
className="ml-auto"
>
{badge}
</Badge>
)}
</button>
</div>
)
})}
</div>
</CollapsibleContent>
</Collapsible>
)
})}
</div>
)
}
export function NavigationMenu({
activeTab,
onTabChange,
featureToggles,
errorCount = 0,
}: NavigationMenuProps) {
const [expandedGroups, setExpandedGroups] = useState<Set<string>>(
new Set(['overview', 'development', 'automation', 'design', 'backend', 'testing', 'tools'])
)
const { preloadRoute, cancelPreload } = useRoutePreload({ delay: 100 })
const handleItemClick = (value: string) => {
onTabChange(value)
}
const handleItemHover = (value: string) => {
console.log(`[NAV] 🖱️ Hover detected on: ${value}`)
preloadRoute(value)
}
const handleItemLeave = (value: string) => {
console.log(`[NAV] 👋 Hover left: ${value}`)
cancelPreload(value)
}
const toggleGroup = (groupId: string) => {
setExpandedGroups((prev) => {
const newSet = new Set(prev)
if (newSet.has(groupId)) {
newSet.delete(groupId)
} else {
newSet.add(groupId)
}
return newSet
})
}
const handleExpandAll = () => {
const allGroupIds = navigationGroups
.filter((group) =>
group.items.some((item) => {
if (!item.featureKey) return true
return featureToggles[item.featureKey]
})
)
.map((group) => group.id)
setExpandedGroups(new Set(allGroupIds))
}
const handleCollapseAll = () => {
setExpandedGroups(new Set())
}
return (
<Sidebar side="left" collapsible="offcanvas">
<NavigationMenuHeader
onExpandAll={handleExpandAll}
onCollapseAll={handleCollapseAll}
/>
<SidebarContent>
<ScrollArea className="h-full px-4">
<NavigationMenuGroupList
activeTab={activeTab}
expandedGroups={expandedGroups}
featureToggles={featureToggles}
errorCount={errorCount}
onToggleGroup={toggleGroup}
onItemClick={handleItemClick}
onItemHover={handleItemHover}
onItemLeave={handleItemLeave}
/>
</ScrollArea>
</SidebarContent>
</Sidebar>
)
}

View File

@@ -1,34 +0,0 @@
import { Stack, Container } from '@/components/atoms'
import { TabIcon } from '@/lib/json-ui/json-components'
import { tabInfo } from '@/lib/navigation-config'
interface PageHeaderProps {
activeTab: string
}
export function PageHeader({ activeTab }: PageHeaderProps) {
const info = tabInfo[activeTab]
if (!info) return null
return (
<Stack
direction="vertical"
spacing="none"
className="border-b border-border bg-card px-4 sm:px-6 py-3 sm:py-4"
>
{/* PageHeaderContent - inlined */}
<div className="flex items-center gap-3">
<TabIcon icon={info.icon} variant="gradient" />
<div className="min-w-0">
<h2 className="text-lg sm:text-xl font-bold truncate">{info.title}</h2>
{info.description && (
<p className="text-xs sm:text-sm text-muted-foreground hidden sm:block">
{info.description}
</p>
)}
</div>
</div>
</Stack>
)
}

View File

@@ -1,43 +0,0 @@
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
import { ScrollArea } from '@/components/ui/scroll-area'
import { Code, Eye } from '@phosphor-icons/react'
import { UIComponent } from '@/types/json-ui'
import { PanelHeader, Text, Code as CodeAtom, Stack, IconText } from '@/components/atoms'
interface SchemaCodeViewerProps {
components: UIComponent[]
schema: any
}
export function SchemaCodeViewer({ components, schema }: SchemaCodeViewerProps) {
const jsonString = JSON.stringify(schema, null, 2)
return (
<div className="h-full flex flex-col bg-card">
<PanelHeader title="Schema Output" icon={<Code size={20} weight="duotone" />} />
<Tabs defaultValue="json" className="flex-1 flex flex-col">
<TabsList className="w-full justify-start px-4 pt-2">
<TabsTrigger value="json">JSON</TabsTrigger>
<TabsTrigger value="preview">Preview</TabsTrigger>
</TabsList>
<TabsContent value="json" className="flex-1 m-0 mt-2">
<ScrollArea className="h-full">
<pre className="p-4 text-xs font-mono text-foreground">
{jsonString}
</pre>
</ScrollArea>
</TabsContent>
<TabsContent value="preview" className="flex-1 m-0 mt-2">
<div className="p-4">
<Text variant="muted">
Live preview coming soon
</Text>
</div>
</TabsContent>
</Tabs>
</div>
)
}

View File

@@ -1,86 +0,0 @@
import { ToolbarButton } from '@/components/molecules'
import { ErrorBadge, Flex, Tooltip, Badge } from '@/components/atoms'
import {
MagnifyingGlass,
Keyboard,
Sparkle,
Download,
Wrench,
Eye,
} from '@phosphor-icons/react'
interface ToolbarActionsProps {
onSearch: () => void
onShowShortcuts: () => void
onGenerateAI: () => void
onExport: () => void
onPreview?: () => void
onShowErrors?: () => void
errorCount?: number
showErrorButton?: boolean
}
export function ToolbarActions({
onSearch,
onShowShortcuts,
onGenerateAI,
onExport,
onPreview,
onShowErrors,
errorCount = 0,
showErrorButton = false,
}: ToolbarActionsProps) {
return (
<Flex gap="xs" shrink className="shrink-0">
<ToolbarButton
icon={<MagnifyingGlass size={18} />}
label="Search (Ctrl+K)"
onClick={onSearch}
data-search-trigger
/>
{showErrorButton && errorCount > 0 && onShowErrors && (
<div className="relative">
<ToolbarButton
icon={<Wrench size={18} />}
label={`${errorCount} ${errorCount === 1 ? 'Error' : 'Errors'}`}
onClick={onShowErrors}
variant="outline"
className="border-destructive text-destructive hover:bg-destructive hover:text-destructive-foreground"
/>
<ErrorBadge count={errorCount} size="sm" />
</div>
)}
{onPreview && (
<ToolbarButton
icon={<Eye size={18} />}
label="Preview (Ctrl+P)"
onClick={onPreview}
variant="outline"
/>
)}
<ToolbarButton
icon={<Keyboard size={18} />}
label="Keyboard Shortcuts (Ctrl+/)"
onClick={onShowShortcuts}
variant="ghost"
className="hidden sm:flex"
/>
<ToolbarButton
icon={<Sparkle size={18} weight="duotone" />}
label="AI Generate (Ctrl+Shift+G)"
onClick={onGenerateAI}
/>
<ToolbarButton
icon={<Download size={18} />}
label="Export Project (Ctrl+E)"
onClick={onExport}
variant="default"
/>
</Flex>
)
}

View File

@@ -1,153 +0,0 @@
import { ScrollArea } from '@/components/ui/scroll-area'
import { EmptyState, Stack, Container, Card, Badge, ActionIcon, IconButton, Flex, Text, Heading, Button, TreeIcon } from '@/components/atoms'
import { ComponentTree } from '@/types/project'
import { FolderOpen } from '@phosphor-icons/react'
interface TreeListPanelProps {
trees: ComponentTree[]
selectedTreeId: string | null
onTreeSelect: (treeId: string) => void
onTreeEdit: (tree: ComponentTree) => void
onTreeDuplicate: (tree: ComponentTree) => void
onTreeDelete: (treeId: string) => void
onCreateNew: () => void
onImportJson: () => void
onExportJson: () => void
}
export function TreeListPanel({
trees,
selectedTreeId,
onTreeSelect,
onTreeEdit,
onTreeDuplicate,
onTreeDelete,
onCreateNew,
onImportJson,
onExportJson,
}: TreeListPanelProps) {
return (
<div className="w-80 border-r border-border bg-card p-4 flex flex-col gap-4">
{/* TreeListHeader - inlined */}
<Stack spacing="sm">
<Flex justify="between" align="center">
<Flex align="center" gap="sm">
<TreeIcon size={20} />
<Heading level={2} className="text-lg font-semibold">Component Trees</Heading>
</Flex>
<IconButton
icon={<ActionIcon action="add" size={16} />}
size="sm"
onClick={onCreateNew}
/>
</Flex>
<Flex gap="sm">
<Button
size="sm"
variant="outline"
onClick={onImportJson}
className="flex-1 text-xs"
leftIcon={<ActionIcon action="upload" size={14} />}
>
Import JSON
</Button>
<Button
size="sm"
variant="outline"
onClick={onExportJson}
disabled={!selectedTreeId}
className="flex-1 text-xs"
leftIcon={<ActionIcon action="download" size={14} />}
>
Export JSON
</Button>
</Flex>
</Stack>
{trees.length === 0 ? (
<Stack
direction="vertical"
align="center"
justify="center"
className="flex-1"
>
<EmptyState
icon={<FolderOpen size={48} weight="duotone" />}
title="No component trees yet"
description="Create your first tree to get started"
action={{
label: 'Create First Tree',
onClick: onCreateNew
}}
/>
</Stack>
) : (
<ScrollArea className="flex-1">
<Stack direction="vertical" spacing="sm">
{trees.map((tree) => {
const isSelected = selectedTreeId === tree.id
const disableDelete = trees.length === 1
return (
// TreeCard - inlined
<Card
key={tree.id}
className={`cursor-pointer transition-all p-4 ${
isSelected ? 'ring-2 ring-primary bg-accent' : 'hover:bg-accent/50'
}`}
onClick={() => onTreeSelect(tree.id)}
>
<Stack spacing="sm">
<Flex justify="between" align="start" gap="sm">
<Stack spacing="xs" className="flex-1 min-w-0">
<Heading level={4} className="text-sm truncate">{tree.name}</Heading>
{tree.description && (
<Text variant="caption" className="line-clamp-2">
{tree.description}
</Text>
)}
<div>
<Badge variant="outline" className="text-xs">
{tree.rootNodes.length} components
</Badge>
</div>
</Stack>
</Flex>
<div onClick={(e) => e.stopPropagation()}>
<Flex gap="xs" className="mt-1">
<IconButton
icon={<ActionIcon action="edit" size={14} />}
variant="ghost"
size="sm"
onClick={() => onTreeEdit(tree)}
title="Edit tree"
/>
<IconButton
icon={<ActionIcon action="duplicate" size={14} />}
variant="ghost"
size="sm"
onClick={() => onTreeDuplicate(tree)}
title="Duplicate tree"
/>
<IconButton
icon={<ActionIcon action="delete" size={14} />}
variant="ghost"
size="sm"
onClick={() => onTreeDelete(tree.id)}
disabled={disableDelete}
title={disableDelete ? "Can't delete last tree" : "Delete tree"}
/>
</Flex>
</div>
</Stack>
</Card>
)
})}
</Stack>
</ScrollArea>
)}
</div>
)
}

View File

@@ -1,6 +1,7 @@
export { NavigationMenu } from './NavigationMenu'
export { AppHeader } from './AppHeader'
export { TreeListPanel } from './TreeListPanel'
export { SchemaEditorLayout } from './SchemaEditorLayout'
export { SchemaCodeViewer } from './SchemaCodeViewer'
export { SchemaEditorCanvas } from './SchemaEditorCanvas'
export { SchemaEditorPropertiesPanel } from './SchemaEditorPropertiesPanel'
export { SchemaEditorSidebar } from './SchemaEditorSidebar'
export { SchemaEditorStatusBar } from './SchemaEditorStatusBar'
export { SchemaEditorToolbar } from './SchemaEditorToolbar'
export { JSONUIShowcase } from '../JSONUIShowcase'