@@ -393,7 +393,7 @@ export function ProjectsPage() {
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault()
- ;(e.currentTarget as HTMLElement).click()
+ ; (e.currentTarget as HTMLElement).click()
}
}}
aria-label="Create new project"
diff --git a/frontend/src/components/base/NodeFooterEdgeIndicators.tsx b/frontend/src/components/base/NodeFooterEdgeIndicators.tsx
index efd4194..8ed2cd6 100644
--- a/frontend/src/components/base/NodeFooterEdgeIndicators.tsx
+++ b/frontend/src/components/base/NodeFooterEdgeIndicators.tsx
@@ -2,7 +2,7 @@ import React, { useContext, useMemo } from 'react'
import FlowContext from '../../lib/flowContext'
import { ArrowDownLeft, ArrowUpRight } from 'lucide-react'
import { NodeHelpPopover } from './NodeHelpPopover'
-import { getNodeType } from '../../lib/nodeRegistry'
+import { getNodeType, getNodeClassificationLabel } from '../../lib/nodeRegistry'
type Props = {
nodeId: string
@@ -27,6 +27,7 @@ export function NodeFooterEdgeIndicators({ nodeId, nodeType, children }: Props)
const descriptor = getNodeType(nodeType)
const showInput = descriptor?.hasInput ?? false
const showOutput = descriptor?.hasOutput ?? false
+ const classificationLabel = getNodeClassificationLabel(nodeType)
return (
@@ -48,6 +49,12 @@ export function NodeFooterEdgeIndicators({ nodeId, nodeType, children }: Props)
{children}
>
)}
+ {classificationLabel != null && (
+ <>
+ {(showInput || showOutput || children != null) &&
|}
+
{classificationLabel}
+ >
+ )}
diff --git a/frontend/src/app/platform/icon-map.tsx b/frontend/src/lib/iconMap.tsx
similarity index 91%
rename from frontend/src/app/platform/icon-map.tsx
rename to frontend/src/lib/iconMap.tsx
index 3f89596..728cec0 100644
--- a/frontend/src/app/platform/icon-map.tsx
+++ b/frontend/src/lib/iconMap.tsx
@@ -18,7 +18,7 @@ import {
Turtle,
type LucideIcon,
} from 'lucide-react'
-import type { ProjectIconId } from './types'
+import type { ProjectIconId } from '../app/platform/types'
export const PROJECT_ICON_MAP: Record
= {
bird: Bird,
diff --git a/frontend/src/lib/nodeRegistry.ts b/frontend/src/lib/nodeRegistry.ts
index cee556c..50b2a76 100644
--- a/frontend/src/lib/nodeRegistry.ts
+++ b/frontend/src/lib/nodeRegistry.ts
@@ -7,6 +7,15 @@
import type React from 'react'
import type { Node } from '@xyflow/react'
+/** Node classification for UI: Psyche (Form/Config, Variable, Behavior/Function), Pneuma (Rendering), Physis (TBD). */
+export type NodeClassification = 'psyche' | 'pneuma' | 'physis'
+
+export const NODE_CLASSIFICATION_LABELS: Record = {
+ psyche: 'Psyche',
+ pneuma: 'Pneuma',
+ physis: 'Physis',
+}
+
export type NodeHelpEntry = {
title: string
content: React.ReactNode
@@ -20,6 +29,8 @@ export type NodeTypeDescriptor = {
idPrefix: string
hasInput: boolean
hasOutput: boolean
+ /** Classification for creation menu and node footer: Psyche, Pneuma, Physis. */
+ classification?: NodeClassification
/** When this type is the connection target, which source types are allowed. Omit = all. */
allowedSourceTypes?: string[]
/** When this type is the connection source, which target types are allowed. Omit = all. */
@@ -48,10 +59,31 @@ export function getNodeType(id: string): NodeTypeDescriptor | undefined {
return registry.get(id)
}
+const CLASSIFICATION_ORDER: NodeClassification[] = ['psyche', 'pneuma', 'physis']
+
export function getRegisteredNodeTypes(): NodeTypeDescriptor[] {
return Array.from(registry.values())
}
+/** Node types grouped by classification for the Create Node menu. Order: Psyche, Pneuma, Physis. */
+export function getRegisteredNodeTypesGroupedByClassification(): {
+ classification: NodeClassification
+ label: string
+ types: NodeTypeDescriptor[]
+}[] {
+ const byClass = new Map()
+ for (const c of CLASSIFICATION_ORDER) byClass.set(c, [])
+ for (const desc of registry.values()) {
+ const c = desc.classification ?? 'physis'
+ byClass.get(c)!.push(desc)
+ }
+ return CLASSIFICATION_ORDER.map((classification) => ({
+ classification,
+ label: NODE_CLASSIFICATION_LABELS[classification],
+ types: byClass.get(classification)!,
+ }))
+}
+
export function getRegisteredNodeTypeIds(): string[] {
return Array.from(registry.keys())
}
@@ -114,3 +146,10 @@ export function getNodeHelp(nodeType: string): NodeHelpEntry {
const desc = getNodeType(nodeType)
return desc?.help ?? { title: nodeType, content: null }
}
+
+/** Classification label for a node type (e.g. "Psyche"). Shown in node footer before help button. */
+export function getNodeClassificationLabel(nodeType: string): string | null {
+ const desc = getNodeType(nodeType)
+ const c = desc?.classification
+ return c ? NODE_CLASSIFICATION_LABELS[c] : null
+}
diff --git a/frontend/src/lib/registerBuiltinNodes.tsx b/frontend/src/lib/registerBuiltinNodes.tsx
index d0e748d..7686ee7 100644
--- a/frontend/src/lib/registerBuiltinNodes.tsx
+++ b/frontend/src/lib/registerBuiltinNodes.tsx
@@ -23,6 +23,7 @@ export function registerBuiltinNodes(): void {
idPrefix: 'cfg_',
hasInput: true,
hasOutput: true,
+ classification: 'psyche',
allowedSourceTypes: ['config', 'variable', 'function'],
allowedTargetTypes: ['config', 'render'],
help: NODE_HELP.config,
@@ -49,6 +50,7 @@ export function registerBuiltinNodes(): void {
idPrefix: 'rnd_',
hasInput: true,
hasOutput: false,
+ classification: 'pneuma',
allowedSourceTypes: ['config'],
help: NODE_HELP.render,
menuLabel: 'Renderer',
@@ -64,6 +66,7 @@ export function registerBuiltinNodes(): void {
idPrefix: 'var_',
hasInput: false,
hasOutput: true,
+ classification: 'psyche',
allowedTargetTypes: ['config', 'function'],
help: NODE_HELP.variable,
menuLabel: 'Variable',
@@ -82,6 +85,7 @@ export function registerBuiltinNodes(): void {
idPrefix: 'fn_',
hasInput: true,
hasOutput: true,
+ classification: 'psyche',
allowedSourceTypes: ['config', 'variable', 'function'],
allowedTargetTypes: ['config', 'function'],
help: NODE_HELP.function,