diff --git a/frontend/src/components/CanvasMenubar.tsx b/frontend/src/app/canvas/CanvasMenubar.tsx
similarity index 100%
rename from frontend/src/components/CanvasMenubar.tsx
rename to frontend/src/app/canvas/CanvasMenubar.tsx
diff --git a/frontend/src/app/canvas/CanvasPage.tsx b/frontend/src/app/canvas/CanvasPage.tsx
index 3937c56..acb230f 100644
--- a/frontend/src/app/canvas/CanvasPage.tsx
+++ b/frontend/src/app/canvas/CanvasPage.tsx
@@ -37,8 +37,8 @@ import {
ContextMenuTrigger,
ContextMenuGroup,
} from '@/components/ui/context-menu'
-import { CanvasMenubar } from '@/components/CanvasMenubar'
-import { FlowKeyboardShortcuts } from '@/components/FlowKeyboardShortcuts'
+import { CanvasMenubar } from '@/app/canvas/CanvasMenubar'
+import { FlowKeyboardShortcuts } from '@/components/base/FlowKeyboardShortcuts'
import {
Empty,
EmptyContent,
@@ -485,11 +485,10 @@ export function CanvasPage({ projectId: _projectId }: CanvasPageProps) {
{canvasMessage && (
{canvasMessage.text}
diff --git a/frontend/src/app/platform/AppSidebar.tsx b/frontend/src/app/platform/AppSidebar.tsx
index 665406b..60c0b8e 100644
--- a/frontend/src/app/platform/AppSidebar.tsx
+++ b/frontend/src/app/platform/AppSidebar.tsx
@@ -53,10 +53,10 @@ export function AppSidebar({
-
+
Zo
- Zui
+ Zui
diff --git a/frontend/src/app/platform/NewProjectDialog.tsx b/frontend/src/app/platform/NewProjectDialog.tsx
index 00a06bb..4d5587b 100644
--- a/frontend/src/app/platform/NewProjectDialog.tsx
+++ b/frontend/src/app/platform/NewProjectDialog.tsx
@@ -27,7 +27,7 @@ type NewProjectDialogProps = {
export function NewProjectDialog({ onCreate, trigger }: NewProjectDialogProps) {
const [open, setOpen] = useState(false)
const [name, setName] = useState('')
- const [iconId, setIconId] = useState('layout')
+ const [iconId, setIconId] = useState('cat')
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault()
@@ -41,7 +41,7 @@ export function NewProjectDialog({ onCreate, trigger }: NewProjectDialogProps) {
}
onCreate(project)
setName('')
- setIconId('layout')
+ setIconId('cat')
setOpen(false)
}
diff --git a/frontend/src/app/platform/icon-map.tsx b/frontend/src/app/platform/icon-map.tsx
index 0348042..3f89596 100644
--- a/frontend/src/app/platform/icon-map.tsx
+++ b/frontend/src/app/platform/icon-map.tsx
@@ -1,27 +1,40 @@
/**
* Map project icon ids to Lucide icons for the sidebar.
+ * Only Lucide "Animals" category icons are allowed for projects.
*/
import {
- LayoutDashboard,
- Folder,
- FileStack,
- Sparkles,
- Box,
- Layers,
+ Bird,
+ Bug,
+ Cat,
+ Dog,
+ Egg,
+ Fish,
+ Rabbit,
+ Rat,
+ Shrimp,
+ Snail,
+ Squirrel,
+ Turtle,
type LucideIcon,
} from 'lucide-react'
import type { ProjectIconId } from './types'
export const PROJECT_ICON_MAP: Record = {
- layout: LayoutDashboard,
- folder: Folder,
- 'file-stack': FileStack,
- sparkles: Sparkles,
- box: Box,
- layers: Layers,
+ bird: Bird,
+ bug: Bug,
+ cat: Cat,
+ dog: Dog,
+ fish: Fish,
+ rat: Rat,
+ rabbit: Rabbit,
+ squirrel: Squirrel,
+ turtle: Turtle,
+ snail: Snail,
+ shrimp: Shrimp,
+ egg: Egg
}
export function getProjectIcon(iconId: string): LucideIcon {
- return PROJECT_ICON_MAP[iconId as ProjectIconId] ?? LayoutDashboard
+ return PROJECT_ICON_MAP[iconId as ProjectIconId] ?? Cat
}
diff --git a/frontend/src/app/platform/types.ts b/frontend/src/app/platform/types.ts
index 255eb85..3c4808f 100644
--- a/frontend/src/app/platform/types.ts
+++ b/frontend/src/app/platform/types.ts
@@ -12,13 +12,20 @@ export type Project = {
createdAt: number
}
+/** Project icon ids: Lucide "Animals" category only */
export const PROJECT_ICON_IDS = [
- 'layout',
- 'folder',
- 'file-stack',
- 'sparkles',
- 'box',
- 'layers',
+ 'bird',
+ 'bug',
+ 'cat',
+ 'dog',
+ 'fish',
+ 'rat',
+ 'rabbit',
+ 'squirrel',
+ 'turtle',
+ 'snail',
+ 'shrimp',
+ 'egg'
] as const
export type ProjectIconId = (typeof PROJECT_ICON_IDS)[number]
diff --git a/frontend/src/components/AppMenubar.tsx b/frontend/src/components/AppMenubar.tsx
deleted file mode 100644
index 741b443..0000000
--- a/frontend/src/components/AppMenubar.tsx
+++ /dev/null
@@ -1,143 +0,0 @@
-import React, { useEffect } from 'react'
-import {
- Menubar,
- MenubarContent,
- MenubarItem,
- MenubarMenu,
- MenubarSeparator,
- MenubarTrigger,
- MenubarSub,
- MenubarSubTrigger,
- MenubarSubContent,
- MenubarCheckboxItem,
-} from '@/components/ui/menubar'
-import { Kbd, KbdGroup } from '@/components/ui/kbd'
-import { useTheme } from '@/lib/themeContext'
-import { Download, FolderOpen, Moon, Redo2, Sun, Undo2 } from 'lucide-react'
-
-type AppMenubarProps = {
- onImport: () => void
- onExport: () => void
- undo: () => void
- redo: () => void
- canUndo: boolean
- canRedo: boolean
- onFitView?: () => void
-}
-
-const UNDO_KEYS = { key: 'z', shiftKey: false }
-const REDO_KEYS = { key: 'z', shiftKey: true }
-
-function matchKey(ev: KeyboardEvent, want: { key: string; shiftKey: boolean }) {
- const mod = ev.ctrlKey || ev.metaKey
- return (
- ev.key.toLowerCase() === want.key &&
- !!mod &&
- !!ev.shiftKey === want.shiftKey
- )
-}
-
-export function AppMenubar({ onImport, onExport, undo, redo, canUndo, canRedo, onFitView }: AppMenubarProps) {
- const { theme, setTheme } = useTheme()
-
- useEffect(() => {
- const onKeyDown = (ev: KeyboardEvent) => {
- if (matchKey(ev, UNDO_KEYS)) {
- if (canUndo) {
- ev.preventDefault()
- ev.stopPropagation()
- undo()
- }
- return
- }
- if (matchKey(ev, REDO_KEYS)) {
- if (canRedo) {
- ev.preventDefault()
- ev.stopPropagation()
- redo()
- }
- }
- }
- // Capture phase so we run before CodeMirror/inputs; then graph undo applies even when focus is in an editor
- window.addEventListener('keydown', onKeyDown, true)
- return () => window.removeEventListener('keydown', onKeyDown, true)
- }, [undo, redo, canUndo, canRedo])
-
- return (
-
-
- Project
-
-
-
- Import…
-
-
-
- Export…
-
-
-
-
- Edit
-
-
-
- Undo
-
-
- ⌘ + Z
-
-
-
-
-
- Redo
-
-
- ⌘ + ⇧ + Z
-
-
-
-
-
-
- View
-
- {onFitView && (
-
- Fit View
-
-
- ⌘0
-
-
-
- )}
- {onFitView && }
-
- Theme
-
- setTheme('light')}
- className="gap-2"
- >
-
- Light
-
- setTheme('dark')}
- className="gap-2"
- >
-
- Dark
-
-
-
-
-
-
- )
-}
diff --git a/frontend/src/components/FlowKeyboardShortcuts.tsx b/frontend/src/components/base/FlowKeyboardShortcuts.tsx
similarity index 100%
rename from frontend/src/components/FlowKeyboardShortcuts.tsx
rename to frontend/src/components/base/FlowKeyboardShortcuts.tsx
diff --git a/frontend/src/components/ui/sidebar.tsx b/frontend/src/components/ui/sidebar.tsx
index 2c3df7c..c7ca99c 100644
--- a/frontend/src/components/ui/sidebar.tsx
+++ b/frontend/src/components/ui/sidebar.tsx
@@ -145,7 +145,7 @@ const SidebarProvider = React.forwardRef<
} as React.CSSProperties
}
className={cn(
- "group/sidebar-wrapper flex h-svh w-full has-[[data-variant=inset]]:bg-sidebar overflow-hidden",
+ "group/sidebar-wrapper flex min-h-svh w-full has-[[data-variant=inset]]:bg-sidebar",
className
)}
ref={ref}
@@ -308,7 +308,7 @@ const SidebarRail = React.forwardRef<
onClick={toggleSidebar}
title="Toggle Sidebar"
className={cn(
- "absolute inset-y-0 z-20 hidden w-4 -translate-x-1/2 transition-all ease-linear after:absolute after:inset-y-0 after:left-1/2 after:w-[2px] after:bg-transparent hover:after:bg-sidebar-border/50 group-data-[side=left]:-right-4 group-data-[side=right]:left-0 sm:flex",
+ "absolute inset-y-0 z-20 hidden w-4 -translate-x-1/2 transition-all ease-linear after:absolute after:inset-y-0 after:left-1/2 after:w-[2px] hover:after:bg-sidebar-border group-data-[side=left]:-right-4 group-data-[side=right]:left-0 sm:flex",
"[[data-side=left]_&]:cursor-w-resize [[data-side=right]_&]:cursor-e-resize",
"[[data-side=left][data-state=collapsed]_&]:cursor-e-resize [[data-side=right][data-state=collapsed]_&]:cursor-w-resize",
"group-data-[collapsible=offcanvas]:translate-x-0 group-data-[collapsible=offcanvas]:after:left-full group-data-[collapsible=offcanvas]:hover:bg-sidebar",
diff --git a/frontend/src/styles.css b/frontend/src/styles.css
index 7c53242..652e10b 100644
--- a/frontend/src/styles.css
+++ b/frontend/src/styles.css
@@ -164,6 +164,12 @@ pre {
scrollbar-color: hsl(var(--muted-foreground) / 0.35) transparent;
}
+*,
+::before,
+::after {
+ border-color: unset;
+}
+
.dark * {
scrollbar-color: hsl(var(--muted-foreground) / 0.4) transparent;
}
diff --git a/frontend/tailwind.config.cjs b/frontend/tailwind.config.cjs
index 635ebf7..fb0f5cd 100644
--- a/frontend/tailwind.config.cjs
+++ b/frontend/tailwind.config.cjs
@@ -3,65 +3,65 @@ module.exports = {
darkMode: ['class'],
content: ['./index.html', './src/**/*.{ts,tsx,js,jsx}'],
theme: {
- extend: {
- borderRadius: {
- lg: 'var(--radius)',
- md: 'calc(var(--radius) - 2px)',
- sm: 'calc(var(--radius) - 4px)'
- },
- colors: {
- background: 'hsl(var(--background))',
- foreground: 'hsl(var(--foreground))',
- card: {
- DEFAULT: 'hsl(var(--card))',
- foreground: 'hsl(var(--card-foreground))'
- },
- popover: {
- DEFAULT: 'hsl(var(--popover))',
- foreground: 'hsl(var(--popover-foreground))'
- },
- primary: {
- DEFAULT: 'hsl(var(--primary))',
- foreground: 'hsl(var(--primary-foreground))'
- },
- secondary: {
- DEFAULT: 'hsl(var(--secondary))',
- foreground: 'hsl(var(--secondary-foreground))'
- },
- muted: {
- DEFAULT: 'hsl(var(--muted))',
- foreground: 'hsl(var(--muted-foreground))'
- },
- accent: {
- DEFAULT: 'hsl(var(--accent))',
- foreground: 'hsl(var(--accent-foreground))'
- },
- destructive: {
- DEFAULT: 'hsl(var(--destructive))',
- foreground: 'hsl(var(--destructive-foreground))'
- },
- border: 'hsl(var(--border))',
- input: 'hsl(var(--input))',
- ring: 'hsl(var(--ring))',
- chart: {
- '1': 'hsl(var(--chart-1))',
- '2': 'hsl(var(--chart-2))',
- '3': 'hsl(var(--chart-3))',
- '4': 'hsl(var(--chart-4))',
- '5': 'hsl(var(--chart-5))'
- },
- sidebar: {
- DEFAULT: 'hsl(var(--sidebar-background))',
- foreground: 'hsl(var(--sidebar-foreground))',
- primary: 'hsl(var(--sidebar-primary))',
- 'primary-foreground': 'hsl(var(--sidebar-primary-foreground))',
- accent: 'hsl(var(--sidebar-accent))',
- 'accent-foreground': 'hsl(var(--sidebar-accent-foreground))',
- border: 'hsl(var(--sidebar-border))',
- ring: 'hsl(var(--sidebar-ring))'
- }
- }
- }
- },
+ extend: {
+ borderRadius: {
+ lg: 'var(--radius)',
+ md: 'calc(var(--radius) - 2px)',
+ sm: 'calc(var(--radius) - 4px)'
+ },
+ colors: {
+ background: 'hsl(var(--background))',
+ foreground: 'hsl(var(--foreground))',
+ card: {
+ DEFAULT: 'hsl(var(--card))',
+ foreground: 'hsl(var(--card-foreground))'
+ },
+ popover: {
+ DEFAULT: 'hsl(var(--popover))',
+ foreground: 'hsl(var(--popover-foreground))'
+ },
+ primary: {
+ DEFAULT: 'hsl(var(--primary))',
+ foreground: 'hsl(var(--primary-foreground))'
+ },
+ secondary: {
+ DEFAULT: 'hsl(var(--secondary))',
+ foreground: 'hsl(var(--secondary-foreground))'
+ },
+ muted: {
+ DEFAULT: 'hsl(var(--muted))',
+ foreground: 'hsl(var(--muted-foreground))'
+ },
+ accent: {
+ DEFAULT: 'hsl(var(--accent))',
+ foreground: 'hsl(var(--accent-foreground))'
+ },
+ destructive: {
+ DEFAULT: 'hsl(var(--destructive))',
+ foreground: 'hsl(var(--destructive-foreground))'
+ },
+ border: 'hsl(var(--border))',
+ input: 'hsl(var(--input))',
+ ring: 'hsl(var(--ring))',
+ chart: {
+ '1': 'hsl(var(--chart-1))',
+ '2': 'hsl(var(--chart-2))',
+ '3': 'hsl(var(--chart-3))',
+ '4': 'hsl(var(--chart-4))',
+ '5': 'hsl(var(--chart-5))'
+ },
+ sidebar: {
+ DEFAULT: 'hsl(var(--sidebar-background))',
+ foreground: 'hsl(var(--sidebar-foreground))',
+ primary: 'hsl(var(--sidebar-primary))',
+ 'primary-foreground': 'hsl(var(--sidebar-primary-foreground))',
+ accent: 'hsl(var(--sidebar-accent))',
+ 'accent-foreground': 'hsl(var(--sidebar-accent-foreground))',
+ border: 'hsl(var(--sidebar-border))',
+ ring: 'hsl(var(--sidebar-ring))'
+ }
+ }
+ }
+ },
plugins: [require('tailwindcss-animate')],
}