## Codebase Patterns - Project uses Next.js 16 with App Router, TypeScript, and TailwindCSS 4 - Source files are in `src/` directory (app, components, lib, types) - Supabase is configured with @supabase/supabase-js and @supabase/ssr packages - Environment variables follow NEXT_PUBLIC_* convention for client-side access - Use `npm run typecheck` to run TypeScript type checking (tsc --noEmit) - Flowchart types exported from `src/types/flowchart.ts` - Supabase migrations go in `supabase/migrations/` with timestamp prefix (YYYYMMDDHHMMSS_*.sql) - Database has profiles table (linked to auth.users) and projects table (with flowchart_data JSONB) - RLS policies enforce user_id = auth.uid() for project access - Supabase client utilities in `src/lib/supabase/`: client.ts (browser), server.ts (App Router), middleware.ts (route protection) - Next.js middleware.ts at project root handles route protection using updateSession helper - Public auth routes: /login, /signup, /forgot-password, /reset-password - Protected routes: /dashboard, /editor/* (redirect to /login if unauthenticated) - Auth pages use 'use client' with useState, createClient() from lib/supabase/client.ts, and useRouter for redirects - For lists with client-side updates (delete/add), use wrapper client component that receives initialData from server component - Toast component in `src/components/Toast.tsx` for success/error notifications (auto-dismiss after 3s) - Admin operations use SUPABASE_SERVICE_ROLE_KEY (server-side only via server actions) - Admin users have is_admin=true in profiles table; check via .select('is_admin').eq('id', user.id).single() - React Flow editor is in `src/app/editor/[projectId]/` with page.tsx (server) and FlowchartEditor.tsx (client) - React Flow requires 'use client' and importing 'reactflow/dist/style.css' - Use toReactFlowNodes/toReactFlowEdges helpers to convert app types to React Flow types - Custom node components go in `src/components/editor/nodes/` with NodeProps typing and useReactFlow() for updates - Register custom node types in nodeTypes object (memoized with useMemo) and pass to ReactFlow component - FlowchartEditor uses ReactFlowProvider wrapper + inner component pattern for useReactFlow() hook access - Use nanoid for generating unique node IDs (import from 'nanoid') - Reusable LoadingSpinner component in `src/components/LoadingSpinner.tsx` with size ('sm'|'md'|'lg') and optional message - Toast component supports an optional `action` prop: `{ label: string; onClick: () => void }` for retry/undo buttons - Settings page at `/dashboard/settings` reuses dashboard layout; re-auth via signInWithPassword before updateUser --- ## 2026-01-21 - US-001 - What was implemented: Project scaffolding and configuration - Files changed: - package.json - project dependencies and scripts - tsconfig.json - TypeScript configuration - next.config.ts - Next.js configuration - postcss.config.mjs - PostCSS with TailwindCSS - eslint.config.mjs - ESLint configuration - .env.example - environment variables template - .gitignore - git ignore rules - src/app/ - Next.js App Router pages - src/components/.gitkeep - components directory placeholder - src/lib/.gitkeep - lib directory placeholder - src/types/.gitkeep - types directory placeholder - **Learnings for future iterations:** - Next.js 16 uses `@tailwindcss/postcss` for TailwindCSS 4 integration - Use --src-dir flag for create-next-app to put source in src/ folder - npm package names can't have capital letters (use lowercase) - .gitignore needs explicit exclusion for .env files, but include .env.example --- ## 2026-01-21 - US-002 - What was implemented: TypeScript types for flowchart data structures - Files changed: - src/types/flowchart.ts - new file with all flowchart type definitions - package.json - added typecheck script (tsc --noEmit) - **Learnings for future iterations:** - Position is a helper type for {x, y} coordinates used by nodes - FlowchartNode is a union type of DialogueNode | ChoiceNode | VariableNode - ChoiceOption is a separate type to make options array cleaner - All types use `export type` for TypeScript isolatedModules compatibility --- ## 2026-01-21 - US-003 - What was implemented: Supabase schema for users and projects - Files changed: - supabase/migrations/20260121000000_create_profiles_and_projects.sql - new file with all database schema - **Learnings for future iterations:** - Supabase migrations are plain SQL files in supabase/migrations/ directory - Migration filenames use timestamp prefix (YYYYMMDDHHMMSS_description.sql) - RLS policies need separate policies for SELECT, INSERT, UPDATE, DELETE operations - Admin check policy uses EXISTS subquery to check is_admin flag on profiles table - projects table references profiles.id (not auth.users.id directly) for proper FK relationships - flowchart_data column uses JSONB type with default empty structure - Added auto-update trigger for updated_at timestamp on projects table --- ## 2026-01-21 - US-004 - What was implemented: Supabase client configuration utilities - Files changed: - src/lib/supabase/client.ts - browser client using createBrowserClient from @supabase/ssr - src/lib/supabase/server.ts - server client for App Router with async cookies() API - src/lib/supabase/middleware.ts - middleware helper with updateSession function - src/lib/.gitkeep - removed (no longer needed) - **Learnings for future iterations:** - @supabase/ssr package provides createBrowserClient and createServerClient functions - Server client requires async cookies() from next/headers in Next.js 16 - Middleware client returns both user object and supabaseResponse for route protection - Cookie handling uses getAll/setAll pattern for proper session management - setAll in server.ts wrapped in try/catch to handle Server Component limitations --- ## 2026-01-21 - US-005 - What was implemented: Protected routes middleware for authentication - Files changed: - middleware.ts - new file at project root for route protection - **Learnings for future iterations:** - Next.js middleware.ts must be at project root (not in src/) - updateSession helper from lib/supabase/middleware.ts returns { user, supabaseResponse } - Use startsWith() for route matching to handle nested routes (e.g., /editor/*) - Matcher config excludes static files and images to avoid unnecessary middleware calls - Clone nextUrl before modifying pathname for redirects --- ## 2026-01-21 - US-006 - What was implemented: Login page with email/password authentication - Files changed: - src/app/login/page.tsx - new file with login form and Supabase auth - **Learnings for future iterations:** - Auth pages use 'use client' directive since they need useState and form handling - Use createClient() from lib/supabase/client.ts for browser-side auth operations - supabase.auth.signInWithPassword returns { error } object for handling failures - useRouter from next/navigation for programmatic redirects after auth - Error state displayed in red alert box with dark mode support - Loading state disables submit button and shows "Signing in..." text - TailwindCSS dark mode uses dark: prefix (e.g., dark:bg-zinc-950) --- ## 2026-01-21 - US-007 - What was implemented: Sign up page for invite-only account setup - Files changed: - src/app/signup/page.tsx - new file with signup form and Supabase auth - **Learnings for future iterations:** - Supabase invite tokens come via URL hash fragment (window.location.hash) - Parse hash with URLSearchParams after removing leading '#' - Check for type=invite or type=signup to detect invite flow - Use setSession() with access_token and refresh_token to establish session from invite link - For invited users, update password with updateUser() then create profile with upsert() - Use upsert() instead of insert() for profiles to handle edge cases - Validate password confirmation before submission (passwords match check) - display_name defaults to email prefix (split('@')[0]) --- ## 2026-01-21 - US-008 - What was implemented: Logout functionality component - Files changed: - src/components/LogoutButton.tsx - new client component with signOut and redirect - src/components/.gitkeep - removed (no longer needed) - **Learnings for future iterations:** - LogoutButton is a reusable component that will be used in the navbar (US-011) - Component uses 'use client' directive for client-side auth operations - Loading state prevents double-clicks during signOut - Styled with neutral zinc colors to work as a secondary button in navbars --- ## 2026-01-21 - US-009 - What was implemented: Password reset - forgot password page - Files changed: - src/app/forgot-password/page.tsx - new file with forgot password form and email reset - **Learnings for future iterations:** - resetPasswordForEmail requires redirectTo option to specify where user lands after clicking reset link - Use `window.location.origin` to get the current site URL for redirectTo - Page shows different UI after success (conditional rendering with success state) - Use ' for apostrophe in JSX to avoid HTML entity issues - Follow same styling pattern as login page for consistency across auth pages --- ## 2026-01-21 - US-010 - What was implemented: Password reset - set new password page - Files changed: - src/app/reset-password/page.tsx - new file with password reset form - src/app/login/page.tsx - updated to show success message from password reset - **Learnings for future iterations:** - Supabase recovery tokens come via URL hash fragment with type=recovery - Use setSession() with access_token and refresh_token from hash to establish recovery session - Show loading state while verifying token validity (tokenValid === null) - Show error state with link to request new reset if token is invalid - After password update, sign out the user and redirect to login with success message - Use query param (message=password_reset_success) to pass success state between pages - Login page uses useSearchParams to read and display success messages - Success messages styled with green background (bg-green-50) --- ## 2026-01-21 - US-011 - What was implemented: Dashboard layout with navbar component - Files changed: - src/app/dashboard/layout.tsx - new file with dashboard layout wrapper - src/components/Navbar.tsx - new reusable navbar component - **Learnings for future iterations:** - Dashboard layout is a server component that fetches user data via createClient() from lib/supabase/server.ts - Navbar accepts userEmail prop to display current user - Layout wraps children with consistent max-w-7xl container and padding - Navbar uses Link component to allow clicking app title to go back to dashboard - Navbar has border-b styling with dark mode support for visual separation - Use gap-4 for spacing between navbar items (user email and logout button) --- ## 2026-01-21 - US-012 - What was implemented: Dashboard page listing user projects - Files changed: - src/app/dashboard/page.tsx - new file with project listing, cards, and empty state - **Learnings for future iterations:** - Dashboard page is a server component that fetches projects directly from Supabase - Use .eq('user_id', user.id) for RLS-backed queries (though RLS also enforces this) - Order by updated_at descending to show most recent projects first - formatDate() helper with toLocaleDateString for human-readable dates - Project cards use Link component for navigation to /editor/[projectId] - Empty state uses dashed border (border-dashed) with centered content and icon - Hover effects on cards: border-blue-300, shadow-md, and text color change on title - Error state displayed if Supabase query fails --- ## 2026-01-21 - US-013 - What was implemented: Create new project functionality - Files changed: - src/components/NewProjectButton.tsx - new client component with modal dialog - src/app/dashboard/page.tsx - added NewProjectButton to header area - src/app/signup/page.tsx - fixed lint error (setState in effect) by initializing email from searchParams - **Learnings for future iterations:** - Modal dialogs use fixed positioning with backdrop (bg-black/50) for overlay effect - Form submission uses Supabase insert with .select('id').single() to get the new record ID - Initialize flowchart_data with { nodes: [], edges: [] } for new projects - router.push() for programmatic navigation after successful creation - autoFocus on input for better UX when modal opens - Prevent modal close while loading (check isLoading before calling handleClose) - ESLint rule react-hooks/set-state-in-effect warns against synchronous setState in useEffect - Initialize state from searchParams directly in useState() instead of setting in useEffect --- ## 2026-01-21 - US-014 - What was implemented: Delete project functionality with confirmation dialog and toast - Files changed: - src/components/ProjectCard.tsx - new client component replacing Link, with delete button and confirmation dialog - src/components/ProjectList.tsx - new wrapper component to manage project list state and toast notifications - src/components/Toast.tsx - new reusable toast notification component - src/app/dashboard/page.tsx - updated to use ProjectList instead of inline rendering - **Learnings for future iterations:** - To enable client-side state updates (like removing items), extract list rendering from server components into client components - ProjectList accepts initialProjects from server and manages state locally for immediate UI updates - Use onDelete callback pattern to propagate deletion events from child (ProjectCard) to parent (ProjectList) - Delete button uses e.stopPropagation() to prevent card click navigation when clicking delete - Confirmation dialogs should disable close/cancel while action is in progress (isDeleting check) - Toast component uses useCallback for handlers and auto-dismiss with setTimeout - Toast animations can use TailwindCSS animate-in utilities (fade-in, slide-in-from-bottom-4) - Delete icon appears on hover using group-hover:opacity-100 with parent group class --- ## 2026-01-21 - US-015 - What was implemented: Rename project functionality - Files changed: - src/components/ProjectCard.tsx - added rename button, modal dialog, and Supabase update logic - src/components/ProjectList.tsx - added handleRename callback and toast notification - **Learnings for future iterations:** - Multiple action buttons on a card can be grouped in a flex container with gap-1 - Rename modal follows same pattern as delete dialog: fixed positioning, backdrop, form - Use onKeyDown to handle Enter key for quick form submission - Reset form state (newName, error) when opening modal to handle edge cases - Check if name is unchanged before making API call to avoid unnecessary requests - Trim whitespace from input value before validation and submission - handleRename callback updates project name in state using map() to preserve list order --- ## 2026-01-21 - US-016 - What was implemented: Admin invite user functionality - Files changed: - src/app/admin/invite/page.tsx - new admin-only page with access check (redirects non-admins) - src/app/admin/invite/InviteForm.tsx - client component with invite form and state management - src/app/admin/invite/actions.ts - server action using service role key to call inviteUserByEmail - src/components/Navbar.tsx - added isAdmin prop and "Invite User" link (visible only to admins) - src/app/dashboard/layout.tsx - fetches profile.is_admin and passes it to Navbar - .env.example - added SUPABASE_SERVICE_ROLE_KEY and NEXT_PUBLIC_SITE_URL - **Learnings for future iterations:** - Admin operations require SUPABASE_SERVICE_ROLE_KEY (server-side only, not NEXT_PUBLIC_*) - Use createClient from @supabase/supabase-js directly for admin client (not @supabase/ssr) - Admin client needs auth config: { autoRefreshToken: false, persistSession: false } - inviteUserByEmail requires redirectTo option for the signup link in email - Server actions ('use server') can access private env vars safely - Admin check should happen both in server component (redirect) and server action (double check) - Admin page uses its own layout (not dashboard layout) to have custom styling --- ## 2026-01-21 - US-017 - What was implemented: Editor page with React Flow canvas - Files changed: - package.json - added reactflow dependency - src/app/editor/[projectId]/page.tsx - new server component that fetches project from Supabase, handles auth/not found, renders header with back link - src/app/editor/[projectId]/FlowchartEditor.tsx - new client component with React Flow canvas, Background component, type converters for nodes/edges - src/app/editor/[projectId]/loading.tsx - new loading state component with spinner - **Learnings for future iterations:** - React Flow requires 'use client' directive since it uses browser APIs - Import 'reactflow/dist/style.css' for default React Flow styling - Use useNodesState and useEdgesState hooks for managing nodes/edges state - Convert app types (FlowchartNode, FlowchartEdge) to React Flow types with helper functions - Next.js dynamic route params come as Promise in App Router 16+ (need to await params) - Use notFound() from next/navigation for 404 responses - React Flow canvas needs parent container with explicit height (h-full, h-screen) - Background component accepts variant (Dots, Lines, Cross) and gap/size props - Loading page (loading.tsx) provides automatic loading UI for async server components --- ## 2026-01-21 - US-018 - What was implemented: Canvas pan and zoom controls - Files changed: - src/app/editor/[projectId]/FlowchartEditor.tsx - added Controls import and component - **Learnings for future iterations:** - React Flow Controls component provides zoom +/-, fitView, and lock buttons out of the box - Use position="bottom-right" prop to position controls in bottom-right corner - Pan (click-and-drag) and zoom (mouse wheel) are React Flow defaults, no extra config needed --- ## 2026-01-21 - US-019 - What was implemented: Editor toolbar with add/save/export/import buttons - Files changed: - src/components/editor/Toolbar.tsx - new toolbar component with styled buttons - src/app/editor/[projectId]/FlowchartEditor.tsx - integrated toolbar with placeholder handlers - **Learnings for future iterations:** - Toolbar component accepts callback props for actions (onAddDialogue, onSave, etc.) - Node type buttons use color coding: blue (Dialogue), green (Choice), orange (Variable) - Action buttons (Save, Export, Import) use neutral bordered styling - FlowchartEditor now uses flex-col layout to stack toolbar above canvas - Placeholder handlers with TODO comments help track future implementation work --- ## 2026-01-21 - US-020 - What was implemented: Custom DialogueNode component for displaying/editing character dialogue - Files changed: - src/components/editor/nodes/DialogueNode.tsx - new custom node component with editable speaker and text fields - src/app/editor/[projectId]/FlowchartEditor.tsx - registered DialogueNode as custom node type - **Learnings for future iterations:** - Custom React Flow nodes use NodeProps for typing, where T is the data shape - Use useReactFlow() hook to get setNodes for updating node data from within the node component - Handle components need Position enum (Position.Top, Position.Bottom) for positioning - Custom handles can be styled with className and TailwindCSS, use ! prefix to override defaults (e.g., !h-3, !w-3) - Node types must be registered in a nodeTypes object and passed to ReactFlow component - Memoize nodeTypes with useMemo to prevent unnecessary re-renders - Custom node components go in src/components/editor/nodes/ directory --- ## 2026-01-21 - US-021 - What was implemented: Add dialogue node from toolbar functionality - Files changed: - package.json - added nanoid dependency for unique ID generation - src/app/editor/[projectId]/FlowchartEditor.tsx - implemented handleAddDialogue to create new dialogue nodes at viewport center - **Learnings for future iterations:** - useReactFlow() hook requires ReactFlowProvider wrapper, so split component into inner component and outer wrapper - getViewport() returns { x, y, zoom } representing the current pan/zoom state - Calculate viewport center: centerX = (-viewport.x + halfWidth) / viewport.zoom - nanoid v5+ generates unique IDs synchronously with no dependencies - Node creation pattern: create Node object with { id, type, position, data }, then add to state via setNodes - React Flow nodes are draggable by default, no extra configuration needed --- ## 2026-01-21 - US-022 - What was implemented: Custom ChoiceNode component for displaying branching decisions - Files changed: - src/components/editor/nodes/ChoiceNode.tsx - new custom node component with green styling, editable prompt, and dynamic option handles - src/app/editor/[projectId]/FlowchartEditor.tsx - registered ChoiceNode as custom node type - **Learnings for future iterations:** - ChoiceNode follows same pattern as DialogueNode: NodeProps typing, useReactFlow() for updates - Dynamic handles positioned using style={{ left: `${((index + 1) / (options.length + 1)) * 100}%` }} for even spacing - Handle id format for options: 'option-0', 'option-1', etc. (matching the index) - Each option needs a unique id (string) and label (string) per the ChoiceOption type - updateOptionLabel callback pattern: find option by id, map over options array to update matching one --- ## 2026-01-21 - US-023 - What was implemented: Add choice node from toolbar functionality - Files changed: - src/app/editor/[projectId]/FlowchartEditor.tsx - implemented handleAddChoice to create new choice nodes at viewport center - **Learnings for future iterations:** - handleAddChoice follows same pattern as handleAddDialogue: get viewport center, create node with nanoid, add to state - Choice nodes must be initialized with 2 options (each with unique id via nanoid and empty label) - Node data structure for choice: { prompt: '', options: [{ id, label }, { id, label }] } - React Flow nodes are draggable by default after being added to state --- ## 2026-01-21 - US-024 - What was implemented: Add/remove choice options functionality (2-6 options supported) - Files changed: - src/components/editor/nodes/ChoiceNode.tsx - added addOption and removeOption callbacks, '+' button to add options, 'x' button per option to remove - **Learnings for future iterations:** - Define MIN_OPTIONS and MAX_OPTIONS constants for clear limits - Use disabled prop on buttons to enforce min/max constraints with appropriate styling (opacity-30, cursor-not-allowed) - Remove button uses × character for simple cross icon - Add button styled with border-dashed for visual distinction from action buttons - Handles update dynamically via React Flow re-render when options array changes --- ## 2026-01-21 - US-025 - What was implemented: Custom VariableNode component for setting/modifying story variables - Files changed: - src/components/editor/nodes/VariableNode.tsx - new custom node component with orange styling, editable variable name, operation dropdown, and numeric value input - src/app/editor/[projectId]/FlowchartEditor.tsx - imported and registered VariableNode in nodeTypes - **Learnings for future iterations:** - VariableNode follows same pattern as DialogueNode: NodeProps typing, useReactFlow() for updates - Use parseFloat() with fallback to 0 for number input handling: `parseFloat(e.target.value) || 0` - Operation dropdown uses select element with options for 'set', 'add', 'subtract' - Type assertion needed for select value: `e.target.value as 'set' | 'add' | 'subtract'` - Use `??` (nullish coalescing) for number defaults instead of `||` to allow 0 values: `data.value ?? 0` --- ## 2026-01-21 - US-026 - What was implemented: Add variable node from toolbar functionality - Files changed: - src/app/editor/[projectId]/FlowchartEditor.tsx - implemented handleAddVariable to create new variable nodes at viewport center - **Learnings for future iterations:** - handleAddVariable follows same pattern as handleAddDialogue and handleAddChoice: get viewport center, create node with nanoid, add to state - Variable nodes initialized with { variableName: '', operation: 'set', value: 0 } - All add node handlers share the same pattern and use the getViewportCenter helper --- ## 2026-01-21 - US-027 - What was implemented: Connect nodes with edges including arrow markers and smooth styling - Files changed: - src/app/editor/[projectId]/FlowchartEditor.tsx - added MarkerType import, updated onConnect to create edges with smoothstep type and arrow markers, updated toReactFlowEdges to apply same styling to loaded edges - **Learnings for future iterations:** - Use `type: 'smoothstep'` for cleaner edge curves instead of default bezier - Use `markerEnd: { type: MarkerType.ArrowClosed }` to add directional arrows to edges - Connection type has nullable source/target, but Edge requires non-null strings - guard with early return - Apply consistent edge styling in both onConnect (new edges) and toReactFlowEdges (loaded edges) - Generate unique edge IDs with nanoid in onConnect callback --- ## 2026-01-21 - US-028 - What was implemented: Select and delete nodes functionality - Files changed: - src/app/editor/[projectId]/FlowchartEditor.tsx - added deleteKeyCode prop to enable Delete/Backspace key deletion - **Learnings for future iterations:** - React Flow has built-in node selection via clicking - no extra configuration needed - Use `deleteKeyCode={['Delete', 'Backspace']}` prop to enable keyboard deletion - React Flow automatically removes connected edges when a node is deleted (no manual cleanup needed) - The useNodesState/useEdgesState hooks with onNodesChange/onEdgesChange handle all deletion state updates - No explicit onNodesDelete callback is needed - the onNodesChange handler covers deletion events --- ## 2026-01-21 - US-029 - What was implemented: Select and delete edges functionality - Files changed: - src/app/editor/[projectId]/FlowchartEditor.tsx - added onEdgesDelete callback - **Learnings for future iterations:** - React Flow 11 edges are clickable and selectable by default (interactionWidth renders invisible interaction area) - The `deleteKeyCode` prop works for both nodes and edges - same configuration covers both - onEdgesDelete is optional if you just need state management (onEdgesChange handles removal events) - onEdgesDelete is useful for additional logic like logging, dirty state tracking, or undo/redo - Edge selection shows visual highlight via React Flow's built-in styling --- ## 2026-01-22 - US-030 - What was implemented: Right-click context menu for canvas, nodes, and edges - Files changed: - src/components/editor/ContextMenu.tsx - new component with menu items for different contexts (canvas/node/edge) - src/app/editor/[projectId]/FlowchartEditor.tsx - integrated context menu with handlers for all actions - **Learnings for future iterations:** - Use `onPaneContextMenu`, `onNodeContextMenu`, and `onEdgeContextMenu` React Flow callbacks for context menus - `screenToFlowPosition()` converts screen coordinates to flow coordinates for placing nodes at click position - Context menu state includes type ('canvas'|'node'|'edge') and optional nodeId/edgeId for targeted actions - Use `document.addEventListener('click', handler)` and `e.stopPropagation()` on menu to close on outside click - Escape key listener via `document.addEventListener('keydown', handler)` for menu close - NodeMouseHandler and EdgeMouseHandler types from reactflow provide proper typing for context menu callbacks --- ## 2026-01-22 - US-031 - What was implemented: Condition editor modal for adding/editing/removing conditions on edges - Files changed: - src/components/editor/ConditionEditor.tsx - new modal component with form for variable name, operator, and value - src/app/editor/[projectId]/FlowchartEditor.tsx - integrated condition editor with double-click and context menu triggers - **Learnings for future iterations:** - Use `onEdgeDoubleClick` React Flow callback for double-click on edges - Store condition editor state separately from context menu state (`conditionEditor` vs `contextMenu`) - Use `edge.data.condition` to access condition object on edges - When removing properties from edge data, use `delete` operator instead of destructuring to avoid lint warnings about unused variables - Condition type has operators: '>' | '<' | '==' | '>=' | '<=' | '!=' - Preview condition in modal using template string: `${variableName} ${operator} ${value}` --- ## 2026-01-22 - US-032 - What was implemented: Display conditions on edges with dashed styling and labels - Files changed: - src/components/editor/edges/ConditionalEdge.tsx - new custom edge component with condition display - src/app/editor/[projectId]/FlowchartEditor.tsx - integrated custom edge type, added EdgeTypes import and edgeTypes definition - **Learnings for future iterations:** - Custom React Flow edges use EdgeProps typing where T is the data shape - Use `BaseEdge` component for rendering the edge path, and `EdgeLabelRenderer` for positioning labels - `getSmoothStepPath` returns [edgePath, labelX, labelY] - labelX/labelY are center coordinates for labels - Custom edge types are registered in edgeTypes object (similar to nodeTypes) and passed to ReactFlow - Style edges with conditions using strokeDasharray: '5 5' for dashed lines - Custom edges go in `src/components/editor/edges/` directory - Use amber color scheme for conditional edges to distinguish from regular edges --- ## 2026-01-22 - US-033 - What was implemented: Auto-save to LocalStorage with debounced saves and draft restoration prompt - Files changed: - src/app/editor/[projectId]/FlowchartEditor.tsx - added LocalStorage auto-save functionality, draft check on load, and restoration prompt UI - **Learnings for future iterations:** - Use lazy useState initializer for draft check to avoid ESLint "setState in effect" warning - LocalStorage key format: `vnwrite-draft-{projectId}` for project-specific drafts - Debounce saves with 1 second delay using useRef for timer tracking - Convert React Flow Node/Edge types back to app types using helper functions (fromReactFlowNodes, fromReactFlowEdges) - React Flow Edge has `sourceHandle: string | null | undefined` but app types use `string | undefined` - use nullish coalescing (`?? undefined`) - Check `typeof window === 'undefined'` in lazy initializer for SSR safety - clearDraft is exported for use in save functionality (US-034) to clear draft after successful database save - JSON.stringify comparison works for flowchart data equality check --- ## 2026-01-22 - US-034 - What was implemented: Save project to database functionality - Files changed: - src/app/editor/[projectId]/FlowchartEditor.tsx - implemented handleSave with Supabase update, added isSaving state and Toast notifications - src/components/editor/Toolbar.tsx - added isSaving prop with loading spinner indicator - **Learnings for future iterations:** - Use createClient() from lib/supabase/client.ts for browser-side database operations - Supabase update returns { error } object for error handling - Use async/await with try/catch for async save operations - Set updated_at manually with new Date().toISOString() for Supabase JSONB updates - Clear LocalStorage draft after successful save to avoid stale drafts - Toast state uses object with message and type for flexibility - Loading spinner SVG with animate-spin class for visual feedback during save --- ## 2026-01-22 - US-035 - What was implemented: Export project as .vnflow file functionality - Files changed: - src/app/editor/[projectId]/FlowchartEditor.tsx - implemented handleExport with blob creation and download trigger, added projectName prop - src/app/editor/[projectId]/page.tsx - passed projectName prop to FlowchartEditor - **Learnings for future iterations:** - Use Blob with type 'application/json' for JSON file downloads - JSON.stringify(data, null, 2) creates pretty-printed JSON with 2-space indentation - URL.createObjectURL creates a temporary URL for the blob - Create temporary anchor element with download attribute to trigger file download - Remember to cleanup: remove the anchor from DOM and revoke the object URL - Props needed for export: pass data down from server components (e.g., projectName) to client components that need them --- ## 2026-01-22 - US-036 - What was implemented: Import project from .vnflow file functionality - Files changed: - src/app/editor/[projectId]/FlowchartEditor.tsx - implemented handleImport, handleFileSelect, validation, and confirmation dialog for unsaved changes - **Learnings for future iterations:** - Use hidden `` element with ref to trigger file picker programmatically via `ref.current?.click()` - Accept multiple file extensions using comma-separated values in `accept` attribute: `accept=".vnflow,.json"` - Reset file input value after selection (`event.target.value = ''`) to allow re-selecting the same file - Use FileReader API with `readAsText()` for reading file contents, handle onload and onerror callbacks - Type guard function `isValidFlowchartData()` validates imported JSON structure before loading - Track unsaved changes by comparing current state to initialData using JSON.stringify comparison - Show confirmation dialog before import if there are unsaved changes to prevent accidental data loss --- ## 2026-01-22 - US-037 - What was implemented: Export to Ren'Py JSON format functionality - Files changed: - src/components/editor/Toolbar.tsx - added 'Export to Ren'Py' button with purple styling - src/app/editor/[projectId]/FlowchartEditor.tsx - implemented Ren'Py export types, conversion functions, and handleExportRenpy callback - **Learnings for future iterations:** - Ren'Py export uses typed interfaces for different node types: RenpyDialogueNode, RenpyMenuNode, RenpyVariableNode - Find first node by identifying nodes with no incoming edges (not in any edge's target set) - Use graph traversal (DFS) to organize nodes into labeled sections based on flow - Choice nodes create branching sections - save current section before processing each branch - Track visited nodes to detect cycles and create proper labels for jump references - Labels are generated based on speaker name or incremental counter for uniqueness - Replace node IDs with proper labels in a second pass after traversal completes - Include metadata (projectName, exportedAt) at the top level of the export - Validate JSON output with JSON.parse before download to ensure validity - Use purple color scheme for Ren'Py-specific button to distinguish from generic export --- ## 2026-01-22 - US-038 - What was implemented: Unsaved changes warning with dirty state tracking, beforeunload, and navigation confirmation modal - Files changed: - src/app/editor/[projectId]/FlowchartEditor.tsx - added isDirty tracking via useMemo comparing current state to lastSavedDataRef, beforeunload event handler, navigation warning modal, back button with handleBackClick, moved header from page.tsx into this component - src/app/editor/[projectId]/page.tsx - simplified to only render FlowchartEditor (header moved to client component for dirty state access) - **Learnings for future iterations:** - Dirty state tracking uses useMemo comparing JSON.stringify of current flowchart data to a lastSavedDataRef - lastSavedDataRef is a useRef initialized with initialData and updated after successful save - Browser beforeunload requires both event.preventDefault() and setting event.returnValue = '' for modern browsers - Header with back navigation was moved from server component (page.tsx) to client component (FlowchartEditor.tsx) so it can access isDirty state - Back button uses handleBackClick which checks isDirty before navigating or showing confirmation modal - Navigation warning modal shows "Leave Page" (red) and "Stay" buttons for clear user action - "(unsaved changes)" indicator shown next to project name when isDirty is true --- ## 2026-01-22 - US-039 - What was implemented: Loading and error states with reusable spinner, error page, and toast retry - Files changed: - src/components/LoadingSpinner.tsx - new reusable loading spinner component with size variants and optional message - src/app/editor/[projectId]/loading.tsx - updated to use LoadingSpinner component - src/app/editor/[projectId]/page.tsx - replaced notFound() with custom error UI showing "Project Not Found" with back to dashboard link - src/components/Toast.tsx - added optional action prop for action buttons (e.g., retry) - src/app/editor/[projectId]/FlowchartEditor.tsx - updated toast state type to include action, save error now shows retry button via handleSaveRef pattern - **Learnings for future iterations:** - Use a ref (handleSaveRef) to break circular dependency when a useCallback needs to reference itself for retry logic - Toast action prop uses `{ label: string; onClick: () => void }` for flexible action buttons - Don't auto-dismiss toasts that have action buttons (users need time to click them) - Replace `notFound()` with inline error UI when you need custom styling and navigation links - LoadingSpinner uses size prop ('sm' | 'md' | 'lg') for flexibility across different contexts - Link component from next/link is needed in server components for navigation (no useRouter in server components) --- ## 2026-01-22 - US-040 - What was implemented: Conditionals on choice options - per-option visibility conditions - Files changed: - src/types/flowchart.ts - moved Condition type before ChoiceOption, added optional condition field to ChoiceOption - src/components/editor/nodes/ChoiceNode.tsx - added condition button per option, condition badge display, condition editing state management - src/components/editor/OptionConditionEditor.tsx - new modal component for editing per-option conditions (variable name, operator, value) - src/app/editor/[projectId]/FlowchartEditor.tsx - updated Ren'Py export to include per-option conditions (option condition takes priority over edge condition) - **Learnings for future iterations:** - Per-option conditions use the same Condition type as edge conditions - Condition type needed to be moved above ChoiceOption in types file since ChoiceOption now references it - Use `delete obj.property` pattern instead of destructuring with unused variable to avoid lint warnings - OptionConditionEditor is separate from ConditionEditor because it operates on option IDs vs edge IDs - In Ren'Py export, option-level condition takes priority over edge condition since it represents visibility - Condition button uses amber color scheme (bg-amber-100) when condition is set, neutral when not - Condition badge below option shows "if variableName operator value" text in compact format --- ## 2026-01-22 - US-041 - What was implemented: Change password for logged-in user from settings page - Files changed: - src/app/dashboard/settings/page.tsx - new client component with password change form (current, new, confirm fields) - src/components/Navbar.tsx - added "Settings" link to navbar - **Learnings for future iterations:** - Settings page lives under /dashboard/settings to reuse the dashboard layout (navbar, auth check) - Re-authentication uses signInWithPassword with current password before allowing updateUser - Supabase getUser() returns current user email needed for re-auth signInWithPassword call - Password validation: check match and minimum length (6 chars) before making API calls - Clear form fields after successful password update for security - Settings link in navbar uses neutral zinc colors to distinguish from admin/action links --- ## 2026-01-22 - US-042 - What was implemented: Password reset modal that automatically appears when a recovery token is detected in the URL - Files changed: - src/components/PasswordResetModal.tsx - new client component with modal that detects recovery tokens from URL hash, sets session, and provides password reset form - src/app/login/page.tsx - integrated PasswordResetModal component on the login page - **Learnings for future iterations:** - PasswordResetModal is a standalone component that can be placed on any page to detect recovery tokens - Use window.history.replaceState to clean the URL hash after extracting the token (prevents re-triggering on refresh) - Separate tokenError state from form error state to show different UI (expired link vs. form validation) - Modal uses fixed positioning with z-50 to overlay above page content - After successful password update, sign out the user and redirect to login with success message (same as reset-password page) - The modal coexists with the existing /reset-password page - both handle recovery tokens but in different UX patterns ---