This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
npm run dev # Full stack: Vite (3000) + Express (8081)
npm run dev:client # Frontend only (Vite dev server)
npm run dev:server # Backend only (Express with nodemon)npm test # Run all Jest tests (ESM mode)
npm run test:auth # Run auth route tests only
npm run test:ui # Run UI component tests (Vitest + jsdom)
npm run test:ui:watch # UI tests in watch modenpm run build # Production build (tokens + Vite)
npm run vercel-build # Vercel deployment build
npm run preview # Preview production build locally
npm start # Serve production build (Node)npm run tokens:build # Generate CSS tokens from Style Dictionary
npm run tokens:watch # Watch mode for token changes
npm run tokens:validate # Validate token definitions
npm run tokens:audit # Audit CSS for direct pixel usage
npm run ds:check # Check design system for px usagenpm run generate-thumbnails # Generate AI thumbnails for Image Booth modes
npm run storybook # Launch Storybook on port 6006
npm run build-storybook # Build Storybook static siteThis is not a monolithic SPA—it's a platform of 13 independent micro-apps that communicate exclusively through a centralized Zustand store. Each app:
- Mounts independently via React Router
- Registers itself with
setActiveApp('appName')on mount - Declares its UI layout via layout slots (
setLeftPane,setRightPane) - Communicates with other apps only through the store (zero prop passing)
- Cleans up on unmount (clear panes, stop polling)
Apps: Home (dashboard), Idea Lab (modules), Chat, Image Booth, Character Lab, Calendar AI, Multi Mind Map, Planner, Archiva, Workflows, Kanban, Empathy Lab, Gesture Lab.
✅ Always:
- Use
useStore.use.sliceName()for selectors (memoized viaauto-zustand-selectors-hook) - Use
useStore.use.actions().actionName()for mutations (never mutate store directly) - Call
setActiveApp('appName')on app mount - Return cleanup function from
useEffect(clear panes, stop timers) - Check
connectedServicesbefore calling service APIs - Prefix all backend routes with
/api/
❌ Never:
- Pass props between apps (creates coupling)
- Import one app from another (creates circular dependencies)
- Use
useStore(s => s.sliceName)inline (breaks memoization) - Use localStorage for shared data (use Zustand persist middleware)
- Call third-party APIs directly from client (use
/api/proxy) - Hardcode API URLs (use relative paths)
Apps don't control their own layout—they declare content via slots:
// In any app's useEffect:
setLeftPane(<MySidebar />); // Rendered in left column
setRightPane(<MyPanel />); // Rendered in right column (optional)
// On cleanup:
return () => {
clearLeftPane();
clearRightPane();
};App.jsx orchestrates the layout based on these declarations. This enables consistent UI structure without apps managing positioning.
Store Location: src/shared/lib/store.js (543 lines)
State Organization:
- Core slices:
user,connectedServices,activeApp,leftPane,rightPane - Chat slices:
activeModuleId,assistantHistories,activeChatId,assistantModel - App-specific:
riggingTasks,plannerGraph,archivaEntries,tasks,imageProvider - UI state:
theme,toasts,firstVisit,dockPosition
Action Proxy Pattern:
All mutations go through actions object at store root:
const setTheme = useStore.use.actions().setTheme;
const showToast = useStore.use.actions().showToast;
const sendMessage = useStore.use.actions().sendMessage;Selector Pattern (via auto-zustand-selectors-hook):
// ✅ Correct: Pre-memoized, only re-renders when slice changes
const user = useStore.use.user();
const activeModuleId = useStore.use.activeModuleId();
// ❌ Wrong: Inline selector, re-computes on every store change
const user = useStore(s => s.user);Standard pattern for all features:
User Action
↓
Store Action (via useStore.use.actions())
↓
[Optional: Backend API call via fetch('/api/...')]
↓
Store Update (via Immer mutation in action)
↓
Component Re-render (via selector reactivity)
Apps communicate via 6 mechanisms:
- Shared Store Slices: Multiple apps read/write same data (e.g.,
chat,ideaLab,multiMindMapshareassistantHistories) - Route Navigation with State:
navigate('/app', { state: { data } }) - Global Actions:
showToast,setThemeaccessible everywhere - Layout Slots: Declare sidebars via
setLeftPane/setRightPane - Service Connection Events: React to OAuth status changes
- First-Visit Flags: Track onboarding per app (
firstVisit.appName)
Route Organization (server/routes/):
- All routes prefixed with
/api/ - Auth routes:
auth.js(login, logout, OAuth) - Service routes:
services.js(connection management) - Feature routes:
chat.js,models.js,rigging.js,driveImport.js, etc.
Route Registration (server/apiRouter.js):
import myRoute from './routes/myRoute.js';
router.use('/myroute', myRoute);Protected Routes:
import { requireAuth } from '../lib/authMiddleware.js';
router.get('/endpoint', requireAuth, async (req, res) => { ... });Error Handling:
try {
// Implementation
res.json({ success: true, data });
} catch (error) {
console.error('[Context] Error:', error);
res.status(500).json({ error: error.message });
}Development setup uses Vite proxy to route API calls to Express:
// vite.config.js
proxy: {
'/api': 'http://localhost:8081',
'/auth': 'http://localhost:8081',
'/metrics': 'http://localhost:8081',
'/healthz': 'http://localhost:8081'
}This makes frontend (3000) and backend (8081) appear as single server during dev.
Required (.env):
# Google OAuth (primary auth)
GOOGLE_CLIENT_ID=...
GOOGLE_CLIENT_SECRET=...
# Encryption for OAuth tokens (MUST persist across restarts!)
ENCRYPTION_KEY=... # Generate: openssl rand -hex 32
# Session
SESSION_SECRET=...
# Optional AI Providers
OPENAI_API_KEY=...
ANTHROPIC_API_KEY=...
HUME_API_KEY=...
MESHY_API_KEY=...Critical: ENCRYPTION_KEY must be persistent. If changed, all stored OAuth tokens become invalid.
Development-Only (.env.development.local):
# Disable authentication for local development/testing
# WARNING: Only use in development! Uses demo user instead of OAuth
# Option 1 (legacy format):
REQUIRE_AUTH=true # Set to false to allow dev bypass
# AUTH_BYPASS=1 # Backend: bypasses requireAuth middleware
# VITE_AUTH_BYPASS=1 # Frontend: auto-authenticates as demo user
# Option 2 (new format):
DISABLE_AUTH=true # Backend: bypasses requireAuth middleware
VITE_DISABLE_AUTH=true # Frontend: auto-authenticates as demo user
# Both formats work identically - use either oneWhen auth bypass is enabled, the app will:
- Skip Google OAuth login screen
- Use a default demo user (ID:
demo-user, email:demo@example.com) - Allow all API routes without authentication
- Store user data in S3 under
users/demo-user/path
src/components/App.jsx- Root layout orchestration, renders layout slotssrc/shared/lib/store.js- Zustand store (single source of truth)src/shared/lib/routes.js- React Router configurationsrc/shared/lib/actions/assistantActions.js- AI chat actionssrc/shared/data/appManifests.js- App metadata for dashboard
server/index.js- Express entry pointserver/apiRouter.js- Route aggregatorserver/lib/authMiddleware.js-requireAuthmiddlewareserver/lib/encryptionUtils.js- OAuth token encryption
Each app follows pattern: index.jsx (main), components/ (app-specific), lib/ (actions), styles/ (CSS)
.gemini/project-overview.md- Complete architecture (700+ lines)docs/DATA_FLOW_ARCHITECTURE.md- Data contracts, patterns, templatedocs/DATA_FLOW_DIAGRAM.md- Visual flow diagramsdocs/OAUTH_SETUP.md- Service integration guide
-
Create
src/apps/myApp/index.jsxfollowing pattern:export default function MyApp() { const setActiveApp = useStore.use.actions().setActiveApp; const { setLeftPane, clearLeftPane } = useLeftPane(); useEffect(() => { setActiveApp('myApp'); setLeftPane(<MySidebar />); return () => { clearLeftPane(); }; }, [setActiveApp, setLeftPane, clearLeftPane]); return <MyMainContent />; }
-
Add store slice to
src/shared/lib/store.js:myApp: { data: null, isLoading: false, error: null }, setMyAppData: (data) => set(state => { state.myApp.data = data; }),
-
Add route to
src/shared/lib/routes.js:{ path: '/myapp', element: <MyApp /> }
-
Add manifest to
src/shared/data/appManifests.js:myapp: { id: 'myapp', name: 'My App', icon: 'icon_name', route: '/myapp', category: 'Productivity', // ... }
-
Create
server/routes/myRoute.js:import express from 'express'; import { requireAuth } from '../lib/authMiddleware.js'; const router = express.Router(); router.get('/endpoint', requireAuth, async (req, res) => { try { res.json({ success: true, data }); } catch (error) { res.status(500).json({ error: error.message }); } }); export default router;
-
Register in
server/apiRouter.js:import myRoute from './routes/myRoute.js'; router.use('/myroute', myRoute);
-
Call from frontend:
const response = await fetch('/api/myroute/endpoint', { credentials: 'include' }); const data = await response.json();
- RAG endpoints not implemented - Client code exists (
src/shared/lib/rag.js) but/api/rag/*routes missing - Calendar AI uses localStorage - Should migrate to Zustand persist for consistency
- Mixed error handling - No standard error UI pattern across apps
- No TypeScript - Would catch many runtime errors
- Two
useAvailableModelscopies - In bothsrc/hooks/andsrc/shared/hooks/ - OAuth token refresh - Not implemented, tokens expire after 1 hour
Jest (ESM mode):
- Uses
NODE_OPTIONS=--experimental-vm-modules - API tests use Supertest
- Mock Zustand store in tests
Vitest (UI components):
- Uses jsdom environment
- React Testing Library patterns
- Stories in
src/**/*.stories.jsx
Design Tokens (tokens/):
- Source: Style Dictionary JSON
- Output:
src/styles/tokens/tokens.css - Command:
npm run tokens:build
Migration:
- Old:
--spacing-*(hard-coded values) - New:
--space-*(8px scale) or semantic tokens (--panel-padding-*)
CSS Conventions:
- Component classes: kebab-case (
.chat-sidebar) - App-specific prefix:
.app-name-*(.character-lab-viewer) - Shared components: Generic names (
.panel,.booth-header)
Before committing, append entry to CHANGELOG.md:
echo "$(date +%F): Short description of the change." >> CHANGELOG.md
git add CHANGELOG.mdFor substantial changes, also update docs/IMPLEMENTATION_NOTES.md.
App not rendering?
- Check console for errors
- Verify route exists in
src/shared/lib/routes.js - Ensure
setActiveAppcalled on mount - Check component is default export
Store not updating?
- Verify using selector:
useStore.use.sliceName() - Check action exists in
store.js - Ensure Immer mutation in action (not returning new object)
- Check if slice is initialized in store
Backend API failing?
- Check Vite proxy config (
vite.config.js) - Verify route registered in
apiRouter.js - Check
requireAuthmiddleware if getting 401 - Check backend logs for error details
OAuth redirect failing?
- Verify redirect URI in OAuth app config matches exactly
- Check
FRONTEND_URLenvironment variable - Verify callback route exists in
server/routes/auth.js - Ensure
ENCRYPTION_KEYis set and hasn't changed
- Lazy loading: Markmap and ReactFlow loaded on demand
- Selector memoization:
auto-zustand-selectors-hookprevents unnecessary re-renders - Code splitting: Vite automatically splits vendor chunks
- Model discovery caching: 5-second cache for
/api/modelsendpoint - Static file serving: Efficient with
express.static
Authentication:
- JWT tokens in httpOnly cookies (prevents XSS)
- OAuth2 for third-party services
- Token encryption with AES-256-CBC
API Security:
- All AI calls proxied through backend
- API keys never exposed to client
- Input validation on all endpoints
- CORS restricted to
http://localhost:3000(dev)
For complete architectural details, see .gemini/project-overview.md and docs/DATA_FLOW_ARCHITECTURE.md
LATEST UPDATE: 2025-11-14
What Was Built:
- StandardAppActions Component (src/components/ui /organisms/StandardAppActions.jsx)
- Universal action bar with 4 core buttons for ALL apps
- Info Button - Opens modal with app information
- Save Button - Saves current app state to S3 bucket
- Tasks Button - Creates tasks in Kanban with app context
- Toggle Bucket Button - Shows/hides S3 storage panel
- Supports additional app-specific actions
- AppInfoModal Component (src/components/modals/AppInfoModal.jsx)
- Beautiful modal displaying app information
- Sections: About, Capabilities, Suggested Tasks, Pro Tips
- Fully styled with animations
- Responsive design
- App Info Configuration (src/shared/data/appInfoConfig.js)
- Centralized configuration for all apps
- Contains descriptions, capabilities, tasks, and tips
- Currently configured for: CharacterLab, Chat, ImageBooth, IdeaLab, Kanban, Planner, Workflows
- Easy to extend for more apps
-
Backend API Routes:
Apps Route (server/routes/apps.js):
-
POST /api/apps/save-state - Save app snapshots to S3
-
GET /api/apps/load-state/:appId - Load latest snapshot
-
GET /api/apps/list-states/:appId - List all snapshots
Kanban Route (server/routes/kanban.js):
-
GET /api/kanban/tasks - Get all tasks
-
POST /api/kanban/tasks - Create new task
-
PUT /api/kanban/tasks/:taskId - Update task
-
DELETE /api/kanban/tasks/:taskId - Delete task
- Updated CharacterLab to use the new system as a reference implementation
How It Works:
For CharacterLab (example): <StandardAppActions appInfo={getAppInfo('characterLab')} showBucketPanel={showBucketPanel} onToggleBucket={onToggleBucket} appState={currentState} appId="characterLab" />
Key Features:
✅ Consistent UX - Same 4 buttons across all apps ✅ S3 Integration - Each app saves state to its own S3 folder structure ✅ Kanban Integration - Tasks automatically include app context ✅ Extensible - Apps can add additional specific buttons ✅ Info System - Users can always learn what an app does ✅ State Management - Automatic snapshotting with timestamps
File Structure Created:
src/ ├── components/ │ ├── modals/ │ │ ├── AppInfoModal.jsx (NEW) │ │ └── AppInfoModal.css (NEW) │ └── ui/organisms/ │ └── StandardAppActions.jsx (NEW) └── shared/data/ └── appInfoConfig.js (NEW)
server/routes/ ├── apps.js (NEW) └── kanban.js (NEW)
Next Steps to Complete:
- Apply to all apps - Update remaining app headers to use StandardAppActions
- Test the system - Verify save/load and Kanban integration
- Add more app configs - Fill out remaining apps in appInfoConfig.js
This creates a template system that ensures consistency while allowing flexibility for app-specific needs! 🎯
a header component would make it faster, as well as a UI template as I always thought we would have in template app
∴ Thinking…
The user is right - we should create a StandardAppHeader component that wraps BoothHeader and StandardAppActions, making it even easier to use. And they mention a "template app" that would serve as a boilerplate for creating new apps.
Let me create:
- StandardAppHeader component - combines BoothHeader + StandardAppActions
- A template app structure that can be copied for new apps
This will make it much faster to create new apps with the standardized system.
⏺ You're absolutely right! Let me create a 04:18 PM claude-sonnet-4-5-20250929 StandardAppHeader component and a template app structure to make it even faster:
⏺ Write(/Users/davidcaballero/genbooth-idea-lab/src/components/ui/organisms/StandardAppHeader.jsx ) ⎿ Wrote 78 lines to /Users/davidcaballero/genbooth-idea-lab/src/components/ui/organisms/S ```javascript andardAppHeader.jsx /** * @file StandardAppHeader - Universal header component for all apps * Combines BoothHeader with StandardAppActions for consistent UX * @license SPDX-License-Identifier: Apache-2.0 */
import React from 'react';
import BoothHeader from './BoothHeader.jsx';
import StandardAppActions from './StandardAppActions.jsx';
import { getAppInfo } from '@shared/data/appInfoConfig.js';
/**
* StandardAppHeader - Standardized header for all apps
*
* @param {Object} props
* @param {string} props.appId - App identifier (required)
* @param {string} props.icon - Material icon name (optional, defaults to appInfo)
* @param {string} props.title - App title (optional, defaults to appInfo)
* @param {string} props.typeText - Type/category text (optional)
* @param {string} props.status - Status indicator (optional, default: 'ready')
* @param {string} props.description - App description (optional, defaults to appInfo)
* @param {string} props.align - Alignment (optional, default: 'top')
* @param {boolean} props.showBucketPanel - Bucket panel visibility state
* @param {Function} props.onToggleBucket - Toggle bucket panel callback
* @param {Object} props.appState - Current app state for saving
* @param {Array} props.additionalActions - Additional app-specific actions (optional)
*
* @example
* <StandardAppHeader
* appId="characterLab"
* showBucketPanel={showBucketPanel}
* onToggleBucket={toggleBucketPanel}
* appState={currentState}
* />
*/
export default function StandardAppHeader({
appId,
icon,
title,
typeText,
status = 'ready',
description,
align = 'top',
showBucketPanel = false,
onToggleBucket,
appState = {},
additionalActions = []
}) {
// Get app info from config
const appInfo = getAppInfo(appId);
// Use provided values or fallback to appInfo
const headerIcon = icon || appInfo.icon;
const headerTitle = title || appInfo.name;
const headerDescription = description || appInfo.description;
return (
<BoothHeader
icon={headerIcon}
title={headerTitle}
typeText={typeText}
status={status}
description={headerDescription}
align={align}
actions={
<StandardAppActions
appInfo={appInfo}
showBucketPanel={showBucketPanel}
onToggleBucket={onToggleBucket}
appState={appState}
appId={appId}
additionalActions={additionalActions}
/>
}
/>
);
}
```
## UI / Dashboard rules
- Always keep a dashboard route at `/`
- Dashboard lives at `src/apps/home/`
- Dashboard lists services from `@shared/data/appManifests.js`
- Each service can be connected via `/auth/{slug}` (proxied by Vercel → Render)
- When removing/renaming dashboard, update `CHANGELOG.md` and `src/shared/lib/routes.js`
A robust, cross-app system for standardized header actions and task generation. See docs/TASKS_ARCHITECTURE.md for complete documentation.
- Store Layout Slice (
src/shared/lib/store.js):layout.rightPaneVisible[appId]- Per-app panel visibility- Actions:
setRightPaneVisible(),toggleRightPane() - Persisted across sessions
- Single source of truth for all apps
- Location:
src/components/ui/organisms/StandardAppActions.jsx - 4 Standard Buttons:
- Info - App capabilities modal
- Save - App state to S3
- Tasks - Generate tasks → Kanban
- Toggle Data Panel - Right column visibility
- Store Integration:
- Uses
layout.rightPaneVisiblefrom store - Backward compatible with local state props
- Automatic panel state management
- Uses
- Task Helper (
src/shared/lib/helpers/taskGeneration.js):generateTasksFromContext(appId, appState, options)saveTasksToKanban(tasks, storeSyncFn)generateAndSaveTasks()- Combined operation- Template-based generation (Phase 3)
- MCP/MLX stub for Phase 4
- Enhanced MCP Route (
server/routes/mcpTools.js):- Added
generate_taskstool (Phase 4 stub) - Mock task generation for development
- Ready for MLX-RAG-Lab integration
- Added
- Task Actions in Store:
- Fixed: Task actions now in
store.actionsproxy - Consistent with architecture pattern
- Fixed: Task actions now in
- HugginPapers: Store-based layout state
- Kanban: Hybrid right panel (task detail OR data panel)
- Template App: Comprehensive documentation
✅ Centralized Layout State - Right panel visibility in store
✅ Persistent UI State - Panel state survives refresh
✅ Task Generation Pipeline - App → Helper → Backend → Store → Kanban
✅ Store Actions Proxy - Task actions properly exposed
✅ Backward Compatible - Old apps still work
✅ Phase 4 Ready - MCP/MLX stubs in place
✅ Template Updated - Complete reference implementation
✅ Comprehensive Docs - docs/TASKS_ARCHITECTURE.md