From b861c51780b08c71b6469b39c8b46c693dcbd096 Mon Sep 17 00:00:00 2001 From: Gustavo Henrique Santos Souza de Miranda Date: Fri, 23 Jan 2026 11:40:01 -0300 Subject: [PATCH] manage conflict --- .../2026-01-23-vn-flowchart-editor/prd.json | 752 ++++++++++++++++++ .../progress.txt | 634 +++++++++++++++ .../editor/ExportValidationModal.tsx | 74 ++ ...d-collaboration-and-character-variables.md | 530 ++++++++++++ 4 files changed, 1990 insertions(+) create mode 100644 archive/2026-01-23-vn-flowchart-editor/prd.json create mode 100644 archive/2026-01-23-vn-flowchart-editor/progress.txt create mode 100644 src/components/editor/ExportValidationModal.tsx create mode 100644 tasks/prd-collaboration-and-character-variables.md diff --git a/archive/2026-01-23-vn-flowchart-editor/prd.json b/archive/2026-01-23-vn-flowchart-editor/prd.json new file mode 100644 index 0000000..196ae27 --- /dev/null +++ b/archive/2026-01-23-vn-flowchart-editor/prd.json @@ -0,0 +1,752 @@ +{ + "project": "WebVNWrite", + "branchName": "ralph/vn-flowchart-editor", + "description": "Visual Novel Flowchart Editor - A web-based tool for authoring visual novels with drag-and-drop nodes, branching connections, user authentication, and Ren'Py JSON export", + "userStories": [ + { + "id": "US-001", + "title": "Project scaffolding and configuration", + "description": "As a developer, I need the project set up with Next.js, TailwindCSS, and Supabase so that I can build the application.", + "acceptanceCriteria": [ + "Initialize Next.js project with TypeScript and App Router", + "Install and configure TailwindCSS", + "Install Supabase client library (@supabase/supabase-js)", + "Create .env.example with NEXT_PUBLIC_SUPABASE_URL and NEXT_PUBLIC_SUPABASE_ANON_KEY", + "Basic folder structure: app/, components/, lib/, types/", + "Typecheck passes" + ], + "priority": 1, + "passes": true, + "notes": "" + }, + { + "id": "US-002", + "title": "Define TypeScript types for flowchart data", + "description": "As a developer, I need TypeScript types for nodes, connections, and conditions.", + "acceptanceCriteria": [ + "Create types/flowchart.ts file", + "DialogueNode type: id, type='dialogue', position: {x,y}, data: { speaker?: string, text: string }", + "ChoiceNode type: id, type='choice', position: {x,y}, data: { prompt: string, options: { id: string, label: string }[] }", + "VariableNode type: id, type='variable', position: {x,y}, data: { variableName: string, operation: 'set'|'add'|'subtract', value: number }", + "Condition type: { variableName: string, operator: '>'|'<'|'=='|'>='|'<='|'!=', value: number }", + "FlowchartEdge type: id, source, sourceHandle?, target, targetHandle?, data?: { condition?: Condition }", + "FlowchartData type: { nodes: (DialogueNode|ChoiceNode|VariableNode)[], edges: FlowchartEdge[] }", + "All types exported from types/flowchart.ts", + "Typecheck passes" + ], + "priority": 2, + "passes": true, + "notes": "" + }, + { + "id": "US-003", + "title": "Supabase schema for users and projects", + "description": "As a developer, I need database tables to store users and their projects.", + "acceptanceCriteria": [ + "Create supabase/migrations/ directory", + "Create SQL migration file with profiles table: id (uuid, references auth.users), email (text), display_name (text), is_admin (boolean default false), created_at (timestamptz)", + "Create projects table: id (uuid), user_id (uuid, foreign key to profiles.id), name (text), flowchart_data (jsonb), created_at (timestamptz), updated_at (timestamptz)", + "Add RLS policy: users can SELECT/INSERT/UPDATE/DELETE their own projects (user_id = auth.uid())", + "Add RLS policy: users can SELECT their own profile", + "Add RLS policy: admin users (is_admin=true) can SELECT all profiles", + "Typecheck passes" + ], + "priority": 3, + "passes": true, + "notes": "" + }, + { + "id": "US-004", + "title": "Supabase client configuration", + "description": "As a developer, I need Supabase client utilities for auth and database access.", + "acceptanceCriteria": [ + "Create lib/supabase/client.ts with browser client (createBrowserClient)", + "Create lib/supabase/server.ts with server client (createServerClient for App Router)", + "Create lib/supabase/middleware.ts with middleware client helper", + "Export typed database client using generated types or manual types", + "Typecheck passes" + ], + "priority": 4, + "passes": true, + "notes": "" + }, + { + "id": "US-005", + "title": "Protected routes middleware", + "description": "As a developer, I need authentication middleware so that only logged-in users can access the app.", + "acceptanceCriteria": [ + "Create middleware.ts at project root", + "Middleware checks Supabase session on each request", + "Unauthenticated users accessing /dashboard or /editor/* are redirected to /login", + "Authenticated users accessing /login or /signup are redirected to /dashboard", + "Public routes allowed without auth: /login, /signup, /forgot-password, /reset-password", + "Typecheck passes" + ], + "priority": 5, + "passes": true, + "notes": "" + }, + { + "id": "US-006", + "title": "Login page", + "description": "As a user, I want to log in with my email and password so that I can access my projects.", + "acceptanceCriteria": [ + "Create app/login/page.tsx", + "Form with email and password input fields", + "Submit button calls Supabase signInWithPassword", + "Show error message for invalid credentials", + "On success, redirect to /dashboard", + "Link to /forgot-password page", + "Styled with TailwindCSS", + "Typecheck passes", + "Verify in browser using dev-browser skill" + ], + "priority": 6, + "passes": true, + "notes": "" + }, + { + "id": "US-007", + "title": "Sign up page (invite-only)", + "description": "As an invited user, I want to complete my account setup so that I can access the tool.", + "acceptanceCriteria": [ + "Create app/signup/page.tsx", + "Form with email (pre-filled if from invite link), password, and confirm password fields", + "Validate passwords match before submission", + "Handle Supabase invite token from URL (type=invite or type=signup)", + "On success, create profile record in profiles table and redirect to /dashboard", + "Show error message if signup fails", + "Styled with TailwindCSS", + "Typecheck passes", + "Verify in browser using dev-browser skill" + ], + "priority": 7, + "passes": true, + "notes": "" + }, + { + "id": "US-008", + "title": "Logout functionality", + "description": "As a user, I want to log out so that I can secure my session.", + "acceptanceCriteria": [ + "Create components/LogoutButton.tsx component", + "Button calls Supabase signOut", + "On success, redirect to /login", + "Typecheck passes", + "Verify in browser using dev-browser skill" + ], + "priority": 8, + "passes": true, + "notes": "" + }, + { + "id": "US-009", + "title": "Password reset - forgot password page", + "description": "As a user, I want to request a password reset if I forget my password.", + "acceptanceCriteria": [ + "Create app/forgot-password/page.tsx", + "Form with email input field", + "Submit button calls Supabase resetPasswordForEmail", + "Show confirmation message after sending (check your email)", + "Link back to /login", + "Styled with TailwindCSS", + "Typecheck passes", + "Verify in browser using dev-browser skill" + ], + "priority": 9, + "passes": true, + "notes": "" + }, + { + "id": "US-010", + "title": "Password reset - set new password page", + "description": "As a user, I want to set a new password after clicking the reset link.", + "acceptanceCriteria": [ + "Create app/reset-password/page.tsx", + "Form with new password and confirm password fields", + "Handle Supabase recovery token from URL", + "Submit calls Supabase updateUser with new password", + "On success, redirect to /login with success message", + "Show error if token invalid or expired", + "Styled with TailwindCSS", + "Typecheck passes", + "Verify in browser using dev-browser skill" + ], + "priority": 10, + "passes": true, + "notes": "" + }, + { + "id": "US-011", + "title": "Dashboard layout with navbar", + "description": "As a user, I want a consistent layout with navigation so that I can move around the app.", + "acceptanceCriteria": [ + "Create app/dashboard/layout.tsx", + "Navbar component with app title/logo", + "Navbar shows current user email", + "Navbar includes LogoutButton", + "Main content area below navbar", + "Styled with TailwindCSS", + "Typecheck passes", + "Verify in browser using dev-browser skill" + ], + "priority": 11, + "passes": true, + "notes": "" + }, + { + "id": "US-012", + "title": "Dashboard - list projects", + "description": "As a user, I want to see all my projects so that I can choose which one to edit.", + "acceptanceCriteria": [ + "Create app/dashboard/page.tsx", + "Fetch projects from Supabase for current user", + "Display projects as cards in a grid", + "Each card shows: project name, last updated date (formatted)", + "Click card navigates to /editor/[projectId]", + "Empty state with message when no projects exist", + "Loading state while fetching", + "Typecheck passes", + "Verify in browser using dev-browser skill" + ], + "priority": 12, + "passes": true, + "notes": "" + }, + { + "id": "US-013", + "title": "Create new project", + "description": "As a user, I want to create a new project so that I can start a new flowchart.", + "acceptanceCriteria": [ + "Add 'New Project' button on dashboard", + "Clicking opens modal with project name input", + "Submit creates project in Supabase with empty flowchart_data: { nodes: [], edges: [] }", + "On success, redirect to /editor/[newProjectId]", + "Show error if creation fails", + "Typecheck passes", + "Verify in browser using dev-browser skill" + ], + "priority": 13, + "passes": true, + "notes": "" + }, + { + "id": "US-014", + "title": "Delete project", + "description": "As a user, I want to delete a project I no longer need.", + "acceptanceCriteria": [ + "Add delete icon/button on each project card", + "Clicking shows confirmation dialog (Are you sure?)", + "Confirm deletes project from Supabase", + "Project removed from dashboard list without page reload", + "Show success toast after deletion", + "Typecheck passes", + "Verify in browser using dev-browser skill" + ], + "priority": 14, + "passes": true, + "notes": "" + }, + { + "id": "US-015", + "title": "Rename project", + "description": "As a user, I want to rename a project to keep my work organized.", + "acceptanceCriteria": [ + "Add edit/rename icon on project card", + "Clicking opens modal or enables inline edit for project name", + "Submit updates project name in Supabase", + "UI updates immediately without page reload", + "Show error if rename fails", + "Typecheck passes", + "Verify in browser using dev-browser skill" + ], + "priority": 15, + "passes": true, + "notes": "" + }, + { + "id": "US-016", + "title": "Admin - invite new user", + "description": "As an admin, I want to invite new users so that collaborators can access the tool.", + "acceptanceCriteria": [ + "Create app/admin/invite/page.tsx", + "Only accessible by users with is_admin=true (redirect others to /dashboard)", + "Form with email address input", + "Submit calls Supabase admin inviteUserByEmail (requires service role key in server action)", + "Show success message with invite sent confirmation", + "Show error if invite fails", + "Link to this page visible in navbar only for admins", + "Typecheck passes", + "Verify in browser using dev-browser skill" + ], + "priority": 16, + "passes": true, + "notes": "" + }, + { + "id": "US-017", + "title": "Editor page with React Flow canvas", + "description": "As a user, I want an editor page with a canvas where I can build my flowchart.", + "acceptanceCriteria": [ + "Install reactflow package", + "Create app/editor/[projectId]/page.tsx", + "Fetch project from Supabase by ID", + "Show error if project not found or user unauthorized", + "Show loading state while fetching", + "Render React Flow canvas filling the editor area", + "Canvas has grid background (React Flow Background component)", + "Header shows project name with back link to /dashboard", + "Initialize React Flow with nodes and edges from flowchart_data", + "Typecheck passes", + "Verify in browser using dev-browser skill" + ], + "priority": 17, + "passes": true, + "notes": "" + }, + { + "id": "US-018", + "title": "Canvas pan and zoom controls", + "description": "As a user, I want to pan and zoom the canvas to navigate large flowcharts.", + "acceptanceCriteria": [ + "Canvas supports click-and-drag panning (React Flow default)", + "Mouse wheel zooms in/out (React Flow default)", + "Add React Flow Controls component with zoom +/- buttons", + "Add fitView button to show all nodes", + "Controls positioned in bottom-right corner", + "Typecheck passes", + "Verify in browser using dev-browser skill" + ], + "priority": 18, + "passes": true, + "notes": "" + }, + { + "id": "US-019", + "title": "Editor toolbar", + "description": "As a user, I want a toolbar with actions for adding nodes and saving/exporting.", + "acceptanceCriteria": [ + "Create components/editor/Toolbar.tsx", + "Toolbar positioned at top of editor below header", + "Buttons: Add Dialogue, Add Choice, Add Variable (no functionality yet)", + "Buttons: Save, Export, Import (no functionality yet)", + "Buttons styled with TailwindCSS, icons optional", + "Typecheck passes", + "Verify in browser using dev-browser skill" + ], + "priority": 19, + "passes": true, + "notes": "" + }, + { + "id": "US-020", + "title": "Create custom dialogue node component", + "description": "As a user, I want dialogue nodes to display and edit character speech.", + "acceptanceCriteria": [ + "Create components/editor/nodes/DialogueNode.tsx", + "Node styled with blue background/border", + "Displays editable input for speaker name (placeholder: 'Speaker')", + "Displays editable textarea for dialogue text (placeholder: 'Dialogue text...')", + "Has one Handle at top (type='target', id='input')", + "Has one Handle at bottom (type='source', id='output')", + "Register as custom node type in React Flow", + "Typecheck passes", + "Verify in browser using dev-browser skill" + ], + "priority": 20, + "passes": true, + "notes": "" + }, + { + "id": "US-021", + "title": "Add dialogue node from toolbar", + "description": "As a user, I want to add dialogue nodes by clicking the toolbar button.", + "acceptanceCriteria": [ + "Clicking 'Add Dialogue' in toolbar creates new DialogueNode", + "Node appears at center of current viewport", + "Node has unique ID (use nanoid or uuid)", + "Node added to React Flow nodes state", + "Node can be dragged to reposition", + "Typecheck passes", + "Verify in browser using dev-browser skill" + ], + "priority": 21, + "passes": true, + "notes": "" + }, + { + "id": "US-022", + "title": "Create custom choice node component", + "description": "As a user, I want choice nodes to display branching decisions.", + "acceptanceCriteria": [ + "Create components/editor/nodes/ChoiceNode.tsx", + "Node styled with green background/border", + "Displays editable input for prompt text (placeholder: 'What do you choose?')", + "Displays 2 default options, each with editable label input", + "Has one Handle at top (type='target', id='input')", + "Each option has its own Handle at bottom (type='source', id='option-0', 'option-1', etc.)", + "Register as custom node type in React Flow", + "Typecheck passes", + "Verify in browser using dev-browser skill" + ], + "priority": 22, + "passes": true, + "notes": "" + }, + { + "id": "US-023", + "title": "Add choice node from toolbar", + "description": "As a user, I want to add choice nodes by clicking the toolbar button.", + "acceptanceCriteria": [ + "Clicking 'Add Choice' in toolbar creates new ChoiceNode", + "Node appears at center of current viewport", + "Node has unique ID", + "Node initialized with 2 options (each with unique id and empty label)", + "Node added to React Flow nodes state", + "Node can be dragged to reposition", + "Typecheck passes", + "Verify in browser using dev-browser skill" + ], + "priority": 23, + "passes": true, + "notes": "" + }, + { + "id": "US-024", + "title": "Add/remove choice options", + "description": "As a user, I want to add or remove choice options (2-6 options supported).", + "acceptanceCriteria": [ + "ChoiceNode has '+' button to add new option", + "Maximum 6 options (button disabled or hidden at max)", + "Each option has 'x' button to remove it", + "Minimum 2 options (remove button disabled or hidden at min)", + "Adding option creates new output Handle dynamically", + "Removing option removes its Handle", + "Node data updates in React Flow state", + "Typecheck passes", + "Verify in browser using dev-browser skill" + ], + "priority": 24, + "passes": true, + "notes": "" + }, + { + "id": "US-025", + "title": "Create custom variable node component", + "description": "As a user, I want variable nodes to set or modify story variables.", + "acceptanceCriteria": [ + "Create components/editor/nodes/VariableNode.tsx", + "Node styled with orange background/border", + "Displays editable input for variable name (placeholder: 'variableName')", + "Displays dropdown/select for operation: set, add, subtract", + "Displays editable number input for value (default: 0)", + "Has one Handle at top (type='target', id='input')", + "Has one Handle at bottom (type='source', id='output')", + "Register as custom node type in React Flow", + "Typecheck passes", + "Verify in browser using dev-browser skill" + ], + "priority": 25, + "passes": true, + "notes": "" + }, + { + "id": "US-026", + "title": "Add variable node from toolbar", + "description": "As a user, I want to add variable nodes by clicking the toolbar button.", + "acceptanceCriteria": [ + "Clicking 'Add Variable' in toolbar creates new VariableNode", + "Node appears at center of current viewport", + "Node has unique ID", + "Node initialized with empty variableName, operation='set', value=0", + "Node added to React Flow nodes state", + "Node can be dragged to reposition", + "Typecheck passes", + "Verify in browser using dev-browser skill" + ], + "priority": 26, + "passes": true, + "notes": "" + }, + { + "id": "US-027", + "title": "Connect nodes with edges", + "description": "As a user, I want to connect nodes with arrows to define story flow.", + "acceptanceCriteria": [ + "Dragging from source Handle to target Handle creates edge (React Flow default)", + "Edges render as smooth bezier curves (default edge type or smoothstep)", + "Edges show arrow marker indicating direction (markerEnd)", + "Edges update position when nodes are moved", + "Cannot connect source-to-source or target-to-target (React Flow handles this)", + "New edges added to React Flow edges state", + "Typecheck passes", + "Verify in browser using dev-browser skill" + ], + "priority": 27, + "passes": true, + "notes": "" + }, + { + "id": "US-028", + "title": "Select and delete nodes", + "description": "As a user, I want to delete nodes to revise my flowchart.", + "acceptanceCriteria": [ + "Clicking a node selects it (visual highlight via React Flow)", + "Pressing Delete or Backspace key removes selected node(s)", + "Deleting node also removes all connected edges", + "Use onNodesDelete callback to handle deletion", + "Typecheck passes", + "Verify in browser using dev-browser skill" + ], + "priority": 28, + "passes": true, + "notes": "" + }, + { + "id": "US-029", + "title": "Select and delete edges", + "description": "As a user, I want to delete connections between nodes.", + "acceptanceCriteria": [ + "Clicking an edge selects it (visual highlight via React Flow)", + "Pressing Delete or Backspace key removes selected edge(s)", + "Use onEdgesDelete callback to handle deletion", + "Typecheck passes", + "Verify in browser using dev-browser skill" + ], + "priority": 29, + "passes": true, + "notes": "" + }, + { + "id": "US-030", + "title": "Right-click context menu", + "description": "As a user, I want a context menu for quick actions.", + "acceptanceCriteria": [ + "Create components/editor/ContextMenu.tsx", + "Right-click on canvas shows menu: Add Dialogue, Add Choice, Add Variable", + "New node created at click position", + "Right-click on node shows menu: Delete", + "Right-click on edge shows menu: Delete, Add Condition", + "Clicking elsewhere or pressing Escape closes menu", + "Menu styled with TailwindCSS", + "Typecheck passes", + "Verify in browser using dev-browser skill" + ], + "priority": 30, + "passes": true, + "notes": "" + }, + { + "id": "US-031", + "title": "Condition editor modal", + "description": "As a user, I want to add conditions to edges so branches depend on variables.", + "acceptanceCriteria": [ + "Create components/editor/ConditionEditor.tsx modal/popover", + "Opens on double-click edge or via context menu 'Add Condition'", + "Form fields: variable name input, operator dropdown (>, <, ==, >=, <=, !=), value number input", + "Pre-fill fields if edge already has condition", + "Save button applies condition to edge data", + "Clear/Remove button removes condition from edge", + "Cancel button closes without saving", + "Typecheck passes", + "Verify in browser using dev-browser skill" + ], + "priority": 31, + "passes": true, + "notes": "" + }, + { + "id": "US-032", + "title": "Display conditions on edges", + "description": "As a user, I want to see conditions displayed on edges.", + "acceptanceCriteria": [ + "Create custom edge component or use edge labels", + "Edges with conditions render as dashed lines (strokeDasharray)", + "Condition label displayed on edge (e.g., 'score > 5')", + "Unconditional edges remain solid lines", + "Typecheck passes", + "Verify in browser using dev-browser skill" + ], + "priority": 32, + "passes": true, + "notes": "" + }, + { + "id": "US-033", + "title": "Auto-save to LocalStorage", + "description": "As a user, I want my work auto-saved locally so I don't lose progress if the browser crashes.", + "acceptanceCriteria": [ + "Save flowchart state (nodes + edges) to LocalStorage on every change", + "Debounce saves (e.g., 1 second delay after last change)", + "LocalStorage key format: 'vnwrite-draft-{projectId}'", + "On editor load, check LocalStorage for saved draft", + "If local draft exists and differs from database, show prompt to restore or discard", + "Typecheck passes", + "Verify in browser using dev-browser skill" + ], + "priority": 33, + "passes": true, + "notes": "" + }, + { + "id": "US-034", + "title": "Save project to database", + "description": "As a user, I want to save my project to the database manually.", + "acceptanceCriteria": [ + "Clicking 'Save' in toolbar saves current nodes/edges to Supabase", + "Update project's flowchart_data and updated_at fields", + "Show saving indicator/spinner while in progress", + "Show success toast on completion", + "Clear LocalStorage draft after successful save", + "Show error toast if save fails", + "Typecheck passes", + "Verify in browser using dev-browser skill" + ], + "priority": 34, + "passes": true, + "notes": "" + }, + { + "id": "US-035", + "title": "Export project as .vnflow file", + "description": "As a user, I want to export my project as a JSON file for backup or sharing.", + "acceptanceCriteria": [ + "Clicking 'Export' in toolbar triggers file download", + "File named '[project-name].vnflow'", + "File contains JSON with nodes and edges arrays", + "JSON is pretty-printed (2-space indent) for readability", + "Uses browser download API (create blob, trigger download)", + "Typecheck passes", + "Verify in browser using dev-browser skill" + ], + "priority": 35, + "passes": true, + "notes": "" + }, + { + "id": "US-036", + "title": "Import project from .vnflow file", + "description": "As a user, I want to import a .vnflow file to restore or share projects.", + "acceptanceCriteria": [ + "Clicking 'Import' in toolbar opens file picker", + "Accept .vnflow and .json file extensions", + "If current project has unsaved changes, show confirmation dialog", + "Validate imported file has nodes and edges arrays", + "Show error toast if file is invalid", + "Load valid data into React Flow state (replaces current flowchart)", + "Typecheck passes", + "Verify in browser using dev-browser skill" + ], + "priority": 36, + "passes": true, + "notes": "" + }, + { + "id": "US-037", + "title": "Export to Ren'Py JSON format", + "description": "As a user, I want to export my flowchart to Ren'Py-compatible JSON for use in my game.", + "acceptanceCriteria": [ + "Add 'Export to Ren'Py' option (button or dropdown item)", + "File named '[project-name]-renpy.json'", + "Dialogue nodes export as: { type: 'dialogue', speaker: '...', text: '...' }", + "Choice nodes export as: { type: 'menu', prompt: '...', choices: [{ label: '...', next: '...' }] }", + "Variable nodes export as: { type: 'variable', name: '...', operation: '...', value: ... }", + "Edges with conditions include condition object on the choice/jump", + "Organize nodes into labeled sections based on flow (traverse from first node)", + "Include metadata: projectName, exportedAt timestamp", + "Output JSON is valid (test with JSON.parse)", + "Typecheck passes" + ], + "priority": 37, + "passes": true, + "notes": "" + }, + { + "id": "US-038", + "title": "Unsaved changes warning", + "description": "As a user, I want a warning before losing unsaved work.", + "acceptanceCriteria": [ + "Track dirty state: true when flowchart modified after last save", + "Set dirty=true on node/edge add, delete, or modify", + "Set dirty=false after successful save", + "Browser beforeunload event shows warning if dirty", + "Navigating to dashboard shows confirmation modal if dirty", + "Typecheck passes", + "Verify in browser using dev-browser skill" + ], + "priority": 38, + "passes": true, + "notes": "" + }, + { + "id": "US-039", + "title": "Loading and error states", + "description": "As a user, I want clear feedback when things are loading or when errors occur.", + "acceptanceCriteria": [ + "Loading spinner component for async operations", + "Editor shows loading spinner while fetching project", + "Error message displayed if project fails to load (with back to dashboard link)", + "Toast notification system for success/error messages", + "Save error shows toast with retry option", + "Typecheck passes", + "Verify in browser using dev-browser skill" + ], + "priority": 39, + "passes": true, + "notes": "" + }, + { + "id": "US-040", + "title": "Conditionals on choice options", + "description": "As a user, I want individual choice options to have variable conditions so that options are only visible when certain conditions are met (e.g., affection > 10).", + "acceptanceCriteria": [ + "Each ChoiceOption can have optional condition (variableName, operator, value)", + "Update ChoiceNode UI to show 'Add condition' button per option", + "Condition editor modal for each option", + "Visual indicator (icon/badge) on options with conditions", + "Update TypeScript types: ChoiceOption gets optional condition field", + "Export includes per-option conditions in Ren'Py JSON", + "Typecheck passes", + "Verify in browser using dev-browser skill" + ], + "priority": 40, + "passes": true, + "notes": "Dependencies: US-018, US-019, US-025. Complexity: M" + }, + { + "id": "US-041", + "title": "Change password for logged-in user", + "description": "As a user, I want to change my own password from a settings/profile page so that I can keep my account secure.", + "acceptanceCriteria": [ + "Settings/profile page accessible from dashboard header", + "Form with: current password, new password, confirm new password fields", + "Calls Supabase updateUser with new password", + "Requires current password verification (re-authenticate)", + "Shows success/error messages", + "Typecheck passes", + "Verify in browser using dev-browser skill" + ], + "priority": 41, + "passes": true, + "notes": "Dependencies: US-004. Complexity: S" + }, + { + "id": "US-042", + "title": "Password reset modal on token arrival", + "description": "As a user, I want a modal to automatically appear when a password reset token is detected so that I can set my new password seamlessly.", + "acceptanceCriteria": [ + "Detect password reset token in URL (from Supabase email link)", + "Show modal/dialog automatically when token present", + "Modal has: new password, confirm password fields", + "Calls Supabase updateUser with token to complete reset", + "On success, close modal and redirect to login", + "On error, show error message", + "Typecheck passes", + "Verify in browser using dev-browser skill" + ], + "priority": 42, + "passes": true, + "notes": "Dependencies: US-006. Complexity: S" + } + ] +} diff --git a/archive/2026-01-23-vn-flowchart-editor/progress.txt b/archive/2026-01-23-vn-flowchart-editor/progress.txt new file mode 100644 index 0000000..c5d0f32 --- /dev/null +++ b/archive/2026-01-23-vn-flowchart-editor/progress.txt @@ -0,0 +1,634 @@ +## 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 +--- diff --git a/src/components/editor/ExportValidationModal.tsx b/src/components/editor/ExportValidationModal.tsx new file mode 100644 index 0000000..3d3d5a5 --- /dev/null +++ b/src/components/editor/ExportValidationModal.tsx @@ -0,0 +1,74 @@ +'use client' + +export type ValidationIssue = { + nodeId: string + nodeType: 'dialogue' | 'choice' | 'variable' | 'edge' + contentSnippet: string + referenceType: 'character' | 'variable' + referenceId: string +} + +type ExportValidationModalProps = { + issues: ValidationIssue[] + onExportAnyway: () => void + onCancel: () => void +} + +export default function ExportValidationModal({ + issues, + onExportAnyway, + onCancel, +}: ExportValidationModalProps) { + return ( +
+
+
+

+ Undefined References Found +

+

+ The following nodes reference characters or variables that are no longer defined. +

+
+ +
+
    + {issues.map((issue, index) => ( +
  • +
    + + {issue.nodeType} + + + undefined {issue.referenceType} + +
    +

    + {issue.contentSnippet} +

    +
  • + ))} +
+
+ +
+ + +
+
+
+ ) +} diff --git a/tasks/prd-collaboration-and-character-variables.md b/tasks/prd-collaboration-and-character-variables.md new file mode 100644 index 0000000..f4bc2cf --- /dev/null +++ b/tasks/prd-collaboration-and-character-variables.md @@ -0,0 +1,530 @@ +# PRD: Real-time Collaboration & Character/Variable Management + +## Introduction + +Two major features to enhance the Visual Novel Flowchart Editor: + +1. **Real-time Collaboration** - Enable multiple users to work on the same flowchart simultaneously with presence indicators, live cursors, CRDT-based conflict-free synchronization, and a full audit trail with revert capability. + +2. **Character & Variable Management** - Replace free-text fields for speaker names and variable names with a centralized management system. Users define characters and variables once per project, then select from dropdowns throughout the flowchart, preventing typos and ensuring consistency. + +## Goals + +- Enable simultaneous multi-user editing of flowcharts with zero data loss +- Provide real-time awareness of collaborators (presence, cursors, editing state) +- Maintain a full audit trail of all changes with the ability to revert +- Centralize character and variable definitions per project +- Replace free-text inputs with searchable dropdowns for characters/variables +- Auto-migrate existing projects with free-text values to the new system +- Allow importing characters/variables from other projects + +--- + +## User Stories + +--- + +### FEATURE 1: Real-time Collaboration + +--- + +### US-043: Database schema for collaboration sessions and audit trail +**Description:** As a developer, I need database tables to track active collaboration sessions and store the full change history for projects. + +**Acceptance Criteria:** +- [ ] Create migration adding `project_collaborators` table: id (uuid), project_id (references projects), user_id (references profiles), role ('owner' | 'editor' | 'viewer'), invited_at (timestamptz), accepted_at (timestamptz) +- [ ] Create `collaboration_sessions` table: id (uuid), project_id, user_id, cursor_position (jsonb), selected_node_id (text nullable), connected_at (timestamptz), last_heartbeat (timestamptz) +- [ ] Create `audit_trail` table: id (uuid), project_id, user_id, action_type (text: 'node_add' | 'node_update' | 'node_delete' | 'edge_add' | 'edge_update' | 'edge_delete'), entity_id (text), previous_state (jsonb), new_state (jsonb), created_at (timestamptz) +- [ ] Add RLS policies: collaborators can access sessions/audit for projects they belong to +- [ ] Add index on audit_trail(project_id, created_at) for efficient history queries +- [ ] Typecheck passes + +**Complexity:** L + +--- + +### US-044: Project sharing and collaborator management +**Description:** As a project owner, I want to invite other users to collaborate on my project so that we can work together. + +**Acceptance Criteria:** +- [ ] Add "Share" button in the editor toolbar +- [ ] Share modal displays current collaborators with roles (owner/editor/viewer) +- [ ] Owner can invite users by email with a selected role +- [ ] Owner can change collaborator roles or remove collaborators +- [ ] Invited users see shared projects on their dashboard with a "Shared with me" indicator +- [ ] RLS policies updated so collaborators can read/write projects based on their role +- [ ] Typecheck passes +- [ ] Verify in browser using dev-browser skill + +**Dependencies:** US-043 +**Complexity:** M + +--- + +### US-045: Supabase Realtime channel and connection management +**Description:** As a developer, I need a WebSocket connection layer using Supabase Realtime so that clients can exchange presence and change events in real time. + +**Acceptance Criteria:** +- [ ] Create `lib/collaboration/realtime.ts` module +- [ ] On editor mount, join a Supabase Realtime channel scoped to the project ID +- [ ] Track connection state (connecting, connected, disconnected, reconnecting) +- [ ] Implement heartbeat mechanism (update `last_heartbeat` every 30 seconds) +- [ ] Auto-reconnect on network interruption with exponential backoff +- [ ] Clean up session record on disconnect/unmount +- [ ] Show connection status indicator in editor toolbar (green=connected, yellow=reconnecting, red=disconnected) +- [ ] Typecheck passes +- [ ] Verify in browser using dev-browser skill + +**Dependencies:** US-043 +**Complexity:** M + +--- + +### US-046: Presence indicators for active collaborators +**Description:** As a user, I want to see who else is currently viewing or editing the project so that I am aware of my collaborators. + +**Acceptance Criteria:** +- [ ] Display a row of avatar circles in the editor toolbar showing connected users +- [ ] Each avatar shows the user's display_name on hover (tooltip) +- [ ] Each user is assigned a consistent color (derived from user ID hash) +- [ ] Avatars appear when users join and disappear when they leave +- [ ] Maximum 5 avatars shown with "+N" overflow indicator +- [ ] Own avatar not shown in the list +- [ ] Typecheck passes +- [ ] Verify in browser using dev-browser skill + +**Dependencies:** US-045 +**Complexity:** S + +--- + +### US-047: Live cursor positions on canvas +**Description:** As a user, I want to see other collaborators' cursor positions on the canvas so that I can understand where they are working. + +**Acceptance Criteria:** +- [ ] Broadcast local cursor position to the Realtime channel (throttled to 50ms) +- [ ] Render remote cursors as colored arrows/pointers on the canvas with user name labels +- [ ] Cursor color matches the user's assigned presence color +- [ ] Remote cursors smoothly interpolate between position updates (no jumping) +- [ ] Remote cursors fade out after 5 seconds of inactivity +- [ ] Cursors are rendered in screen coordinates and properly transform with canvas zoom/pan +- [ ] Typecheck passes +- [ ] Verify in browser using dev-browser skill + +**Dependencies:** US-045, US-046 +**Complexity:** M + +--- + +### US-048: Integrate Yjs CRDT for conflict-free node/edge synchronization +**Description:** As a developer, I need to integrate a CRDT library so that concurrent edits from multiple users merge automatically without data loss. + +**Acceptance Criteria:** +- [ ] Install and configure Yjs with a Supabase-compatible provider (or WebSocket provider) +- [ ] Create `lib/collaboration/crdt.ts` module wrapping Yjs document setup +- [ ] Model flowchart nodes as a Y.Map keyed by node ID +- [ ] Model flowchart edges as a Y.Map keyed by edge ID +- [ ] Local React Flow state changes are synced to the Yjs document +- [ ] Remote Yjs document changes update local React Flow state +- [ ] Initial load populates Yjs document from database state +- [ ] Periodic persistence of Yjs document state to Supabase (debounced 2 seconds) +- [ ] Typecheck passes + +**Dependencies:** US-045 +**Complexity:** L + +--- + +### US-049: Node editing lock indicators +**Description:** As a user, I want to see when another collaborator is actively editing a specific node so that I can avoid conflicts and wait for them to finish. + +**Acceptance Criteria:** +- [ ] When a user focuses/opens a node for editing, broadcast the node ID to the channel +- [ ] Nodes being edited by others show a colored border matching the editor's presence color +- [ ] A small label with the editor's name appears on the locked node +- [ ] Other users can still view but see a "Being edited by [name]" indicator if they try to edit +- [ ] Lock is released when the user clicks away, closes the node, or disconnects +- [ ] Lock auto-expires after 60 seconds of inactivity as a safety measure +- [ ] Typecheck passes +- [ ] Verify in browser using dev-browser skill + +**Dependencies:** US-045, US-048 +**Complexity:** M + +--- + +### US-050: Join/leave notifications +**Description:** As a user, I want to be notified when collaborators join or leave the editing session so that I stay aware of the team's activity. + +**Acceptance Criteria:** +- [ ] Show a toast notification when a collaborator joins: "[Name] joined" +- [ ] Show a toast notification when a collaborator leaves: "[Name] left" +- [ ] Notifications use the collaborator's assigned color as an accent +- [ ] Notifications auto-dismiss after 3 seconds (matches existing Toast behavior) +- [ ] No notification shown for own join/leave events +- [ ] Typecheck passes +- [ ] Verify in browser using dev-browser skill + +**Dependencies:** US-045, US-046 +**Complexity:** S + +--- + +### US-051: Audit trail recording +**Description:** As a developer, I need all node and edge changes to be recorded in the audit trail so that users can review history and revert changes. + +**Acceptance Criteria:** +- [ ] Every node add/update/delete operation writes a record to `audit_trail` table +- [ ] Every edge add/update/delete operation writes a record to `audit_trail` table +- [ ] Records include `previous_state` (null for additions) and `new_state` (null for deletions) +- [ ] Records include the acting user's ID and timestamp +- [ ] Writes are batched/debounced to avoid excessive DB calls (max 1 write per second per entity) +- [ ] Audit writes do not block the user's editing flow (fire-and-forget with error logging) +- [ ] Typecheck passes + +**Dependencies:** US-043, US-048 +**Complexity:** M + +--- + +### US-052: Activity history sidebar +**Description:** As a user, I want to view a history of all changes made to the project so that I can see what collaborators have done and when. + +**Acceptance Criteria:** +- [ ] Add "History" button to editor toolbar that opens a right sidebar panel +- [ ] Sidebar displays a chronological list of changes with: user name, action type, entity description, timestamp +- [ ] Entries are grouped by time period (Today, Yesterday, Earlier) +- [ ] Each entry shows the user's presence color as an accent +- [ ] Clicking an entry highlights/selects the affected node or edge on the canvas +- [ ] Paginated loading (20 entries per page) with "Load more" button +- [ ] Typecheck passes +- [ ] Verify in browser using dev-browser skill + +**Dependencies:** US-051 +**Complexity:** M + +--- + +### US-053: Revert changes from audit trail +**Description:** As a user, I want to revert a specific change from the history so that I can undo mistakes made by myself or collaborators. + +**Acceptance Criteria:** +- [ ] Each entry in the activity history sidebar has a "Revert" button +- [ ] Clicking "Revert" shows a confirmation dialog with before/after preview +- [ ] Reverting a node addition deletes the node +- [ ] Reverting a node update restores the previous state +- [ ] Reverting a node deletion re-creates the node with its previous state +- [ ] Reverting an edge change follows the same add/update/delete logic +- [ ] The revert itself is recorded as a new audit trail entry +- [ ] Reverted state is synced to all connected clients via CRDT +- [ ] Typecheck passes +- [ ] Verify in browser using dev-browser skill + +**Dependencies:** US-052, US-048 +**Complexity:** L + +--- + +--- + +### FEATURE 2: Character & Variable Management + +--- + +### US-054: Character and Variable TypeScript types +**Description:** As a developer, I need TypeScript types for Character and Variable models so that the rest of the feature can be built with type safety. + +**Acceptance Criteria:** +- [ ] Add `Character` type to `types/flowchart.ts`: id (string), name (string), color (string, hex), description (string, optional) +- [ ] Add `Variable` type to `types/flowchart.ts`: id (string), name (string), type ('numeric' | 'string' | 'boolean'), initialValue (number | string | boolean), description (string, optional) +- [ ] Update `FlowchartData` type to include `characters: Character[]` and `variables: Variable[]` +- [ ] Update `DialogueNodeData` to add optional `characterId: string` field (alongside existing `speaker` for migration) +- [ ] Update `Condition` type to add optional `variableId: string` field (alongside existing `variableName` for migration) +- [ ] Update `VariableNodeData` to add optional `variableId: string` field (alongside existing `variableName` for migration) +- [ ] Typecheck passes + +**Complexity:** S + +--- + +### US-055: Database schema update for characters and variables +**Description:** As a developer, I need the database schema to store characters and variables as part of the project's flowchart data. + +**Acceptance Criteria:** +- [ ] Create migration that documents the new JSONB structure (characters/variables arrays stored within `flowchart_data`) +- [ ] Update the default value for `flowchart_data` column to include `characters: []` and `variables: []` +- [ ] Existing projects with no characters/variables arrays continue to load (handled as empty arrays in app code) +- [ ] Typecheck passes + +**Dependencies:** US-054 +**Complexity:** S + +--- + +### US-056: Character management UI in project settings +**Description:** As a user, I want a dedicated page to manage my project's characters so that I can define them once and reuse them throughout the flowchart. + +**Acceptance Criteria:** +- [ ] Add "Project Settings" button to editor toolbar +- [ ] Project settings opens as a modal with "Characters" and "Variables" tabs +- [ ] Characters tab shows a list of defined characters with name, color swatch, and description +- [ ] "Add Character" button opens inline form with: name (required), color picker (required, defaults to random), description (optional) +- [ ] Each character row has Edit and Delete buttons +- [ ] Deleting a character that is referenced by nodes shows a warning: "This character is used in N nodes. Removing it will leave those nodes without a speaker." +- [ ] Character names must be unique within the project (show validation error if duplicate) +- [ ] Changes are saved to the flowchart data (same save mechanism as nodes/edges) +- [ ] Typecheck passes +- [ ] Verify in browser using dev-browser skill + +**Dependencies:** US-054, US-055 +**Complexity:** M + +--- + +### US-057: Variable management UI in project settings +**Description:** As a user, I want a dedicated page to manage my project's variables so that I can define them with types and initial values for use throughout the flowchart. + +**Acceptance Criteria:** +- [ ] Variables tab in project settings modal shows a list of defined variables +- [ ] Each variable displays: name, type badge (numeric/string/boolean), initial value, description +- [ ] "Add Variable" button opens inline form with: name (required), type dropdown (required), initial value (required, input type matches selected type), description (optional) +- [ ] Each variable row has Edit and Delete buttons +- [ ] Deleting a variable that is referenced by nodes/edges shows a warning with usage count +- [ ] Variable names must be unique within the project +- [ ] Changes are saved to the flowchart data +- [ ] Typecheck passes +- [ ] Verify in browser using dev-browser skill + +**Dependencies:** US-054, US-055 +**Complexity:** M + +--- + +### US-058: Dialogue node speaker dropdown +**Description:** As a user, I want to select a character from a dropdown in the dialogue node instead of typing a name so that I avoid typos and maintain consistency. + +**Acceptance Criteria:** +- [ ] Replace the speaker text input in DialogueNode with a searchable dropdown (combobox) +- [ ] Dropdown lists all characters defined in the project, showing color swatch + name +- [ ] Selecting a character sets `characterId` on the node data +- [ ] Dropdown includes an "Add new character..." option at the bottom +- [ ] Clicking "Add new character..." opens a mini form inline (name + color) that creates the character and selects it +- [ ] If node has a `characterId` that doesn't match any defined character, show orange warning border on the dropdown +- [ ] Empty/unset speaker shows placeholder "Select speaker..." +- [ ] Typecheck passes +- [ ] Verify in browser using dev-browser skill + +**Dependencies:** US-056 +**Complexity:** M + +--- + +### US-059: Variable node variable dropdown +**Description:** As a user, I want to select a variable from a dropdown in the variable node instead of typing a name so that I avoid typos and maintain consistency. + +**Acceptance Criteria:** +- [ ] Replace the variableName text input in VariableNode with a searchable dropdown +- [ ] Dropdown lists all variables defined in the project, showing type badge + name +- [ ] Selecting a variable sets `variableId` on the node data +- [ ] Dropdown includes "Add new variable..." option that opens inline creation form +- [ ] If node references a variableId that doesn't match any defined variable, show orange warning border +- [ ] Operation options (set/add/subtract) are filtered based on selected variable's type (add/subtract only for numeric) +- [ ] Typecheck passes +- [ ] Verify in browser using dev-browser skill + +**Dependencies:** US-057 +**Complexity:** M + +--- + +### US-060: Edge condition variable dropdown +**Description:** As a user, I want to select a variable from a dropdown when setting edge conditions so that I reference valid variables consistently. + +**Acceptance Criteria:** +- [ ] Replace the variableName text input in ConditionEditor with a searchable dropdown +- [ ] Dropdown lists all variables defined in the project, showing type badge + name +- [ ] Selecting a variable sets `variableId` on the condition object +- [ ] Dropdown includes "Add new variable..." option +- [ ] If condition references an undefined variableId, show orange warning indicator +- [ ] Operator options are filtered based on variable type (comparison operators for numeric, == and != for string/boolean) +- [ ] Value input adapts to variable type (number input for numeric, text for string, checkbox for boolean) +- [ ] Typecheck passes +- [ ] Verify in browser using dev-browser skill + +**Dependencies:** US-057 +**Complexity:** M + +--- + +### US-061: Choice option condition variable dropdown +**Description:** As a user, I want to select a variable from a dropdown when setting choice option conditions so that I reference valid variables consistently. + +**Acceptance Criteria:** +- [ ] Replace the variableName text input in OptionConditionEditor with a searchable dropdown +- [ ] Dropdown lists all variables defined in the project, showing type badge + name +- [ ] Selecting a variable sets `variableId` on the option's condition object +- [ ] Dropdown includes "Add new variable..." option +- [ ] If condition references an undefined variableId, show orange warning indicator +- [ ] Operator and value inputs adapt to variable type (same behavior as US-060) +- [ ] Typecheck passes +- [ ] Verify in browser using dev-browser skill + +**Dependencies:** US-057 +**Complexity:** S + +--- + +### US-062: Auto-migration of existing free-text values +**Description:** As a user, I want my existing projects to automatically create character and variable definitions from free-text values so that I don't have to manually re-enter them. + +**Acceptance Criteria:** +- [ ] On project load, if `characters` array is empty but nodes have `speaker` values, auto-create Character entries from unique speaker names +- [ ] Auto-created characters get randomly assigned colors and the speaker text as name +- [ ] On project load, if `variables` array is empty but nodes/edges have `variableName` values, auto-create Variable entries (default type: numeric, initial value: 0) +- [ ] After auto-creation, update all nodes to set `characterId`/`variableId` references pointing to the new entries +- [ ] Show a toast notification: "Auto-imported N characters and M variables from existing data" +- [ ] Migration only runs once (presence of characters/variables arrays, even if empty, means migration already happened) +- [ ] Typecheck passes + +**Dependencies:** US-054, US-058, US-059 +**Complexity:** M + +--- + +### US-063: Import characters/variables from another project +**Description:** As a user, I want to import character and variable definitions from another project so that I can reuse them without redefining everything. + +**Acceptance Criteria:** +- [ ] Add "Import from project" button in both Characters and Variables tabs of project settings +- [ ] Button opens a modal listing the user's other projects +- [ ] Selecting a project shows its characters (or variables) with checkboxes for selection +- [ ] User can select which entries to import (select all / none / individual) +- [ ] Imported entries are added to the current project (duplicates by name are skipped with a warning) +- [ ] Imported characters keep their colors; imported variables keep their types and initial values +- [ ] Typecheck passes +- [ ] Verify in browser using dev-browser skill + +**Dependencies:** US-056, US-057 +**Complexity:** M + +--- + +### US-064: Export validation for undefined references +**Description:** As a user, I want to be warned before exporting if any nodes reference undefined characters or variables so that I can fix issues before generating output. + +**Acceptance Criteria:** +- [ ] Before export, scan all nodes and edges for `characterId`/`variableId` references that don't match defined entries +- [ ] If issues found, show a warning modal listing: node type, node content snippet, and the undefined reference +- [ ] Modal offers "Export anyway" and "Cancel" options +- [ ] Nodes with undefined references are highlighted on the canvas with orange warning borders when modal is shown +- [ ] If no issues found, export proceeds normally +- [ ] Typecheck passes +- [ ] Verify in browser using dev-browser skill + +**Dependencies:** US-058, US-059, US-060, US-061 +**Complexity:** M + +--- + +### US-065: Searchable combobox component +**Description:** As a developer, I need a reusable searchable combobox component so that all character/variable dropdowns share consistent behavior and styling. + +**Acceptance Criteria:** +- [ ] Create `components/editor/Combobox.tsx` - a reusable searchable dropdown component +- [ ] Props: items (id, label, color?, badge?), value, onChange, placeholder, onAddNew (optional callback) +- [ ] Typing in the input filters the list by name (case-insensitive) +- [ ] Keyboard navigation: arrow keys to move, Enter to select, Escape to close +- [ ] Shows color swatch and/or badge next to item labels when provided +- [ ] "Add new..." option rendered at bottom when `onAddNew` prop is provided +- [ ] Dropdown positions itself above or below input based on available space +- [ ] Matches existing editor styling (TailwindCSS, dark mode support) +- [ ] Typecheck passes +- [ ] Verify in browser using dev-browser skill + +**Complexity:** M + +--- + +## Functional Requirements + +### Real-time Collaboration + +- FR-1: The system must establish a WebSocket connection via Supabase Realtime when a user opens a project in the editor +- FR-2: The system must broadcast user presence (cursor position, selected node, connected status) to all collaborators on the same project +- FR-3: The system must display up to 5 collaborator avatars in the toolbar with overflow count +- FR-4: The system must render remote cursors on the canvas with color-coded labels +- FR-5: The system must use Yjs CRDT documents to synchronize node and edge state across clients without data loss +- FR-6: The system must show a colored border and name label on nodes being actively edited by another user +- FR-7: The system must display toast notifications when collaborators join or leave the session +- FR-8: The system must record all node/edge mutations to an audit_trail table with previous and new state +- FR-9: The system must provide a history sidebar showing chronological change entries grouped by date +- FR-10: The system must allow reverting any individual change from the history, restoring the previous state +- FR-11: The system must handle network disconnection gracefully with automatic reconnection and state re-sync +- FR-12: The system must allow project owners to invite collaborators by email with a specific role (editor/viewer) + +### Character & Variable Management + +- FR-13: The system must store Character definitions (id, name, color, description) in the project's flowchart_data +- FR-14: The system must store Variable definitions (id, name, type, initialValue, description) in the project's flowchart_data +- FR-15: The system must provide a project settings modal with tabs for managing characters and variables +- FR-16: The system must replace all speaker text inputs with searchable combobox dropdowns populated from the characters list +- FR-17: The system must replace all variableName text inputs with searchable combobox dropdowns populated from the variables list +- FR-18: The system must allow inline creation of new characters/variables from within dropdowns +- FR-19: The system must show orange warning indicators on nodes/edges referencing undefined characters or variables +- FR-20: The system must validate references before export and warn the user of any undefined references +- FR-21: The system must auto-migrate existing free-text speaker/variableName values to Character/Variable definitions on first load +- FR-22: The system must allow importing characters/variables from other projects owned by the user +- FR-23: The system must filter operator options and value inputs based on the selected variable's type + +## Non-Goals (Out of Scope) + +- No voice/video chat between collaborators +- No commenting/annotation system on nodes +- No permission system beyond owner/editor/viewer roles +- No offline-first editing (requires active connection for collaboration) +- No version branching or forking (linear history only) +- No global character/variable library shared across all users +- No AI-assisted character or variable suggestions +- No real-time text editing within a single node's text field (CRDT operates at node level) +- No variable dependencies or computed values + +## Design Considerations + +- Presence avatars should follow existing toolbar button styling (compact, consistent height) +- Remote cursors should be semi-transparent to avoid visual clutter +- Node lock indicators should use the existing node border styling with color override +- Project settings modal should match existing modal patterns (ProjectCard rename/delete modals) +- Combobox dropdown should match existing input styling with dark mode support +- Character colors should be distinguishable in both light and dark modes +- History sidebar width should not exceed 320px to preserve canvas space + +## Technical Considerations + +- **Supabase Realtime** for presence and broadcast channels (already available in the stack) +- **Yjs** CRDT library for conflict-free document synchronization +- **y-supabase** or custom provider to bridge Yjs with Supabase Realtime +- Cursor position broadcasts should be throttled (50ms) to avoid overwhelming the channel +- Audit trail writes should be debounced and batched to minimize database load +- Auto-migration logic must be idempotent (safe to run multiple times) +- Combobox component should use React portal for dropdown to avoid z-index/overflow issues +- Character/variable ID references enable renaming without breaking node associations +- The `flowchart_data` JSONB column grows with characters/variables arrays; monitor size for large projects + +## Success Metrics + +- Multiple users can simultaneously edit a flowchart with all changes reflected within 500ms +- No data loss during concurrent edits (CRDT guarantees) +- Collaborators can see each other's presence and activity within 1 second of connection +- Any historical change can be reverted without side effects on other nodes +- Zero typo-related issues in character names and variable references after migration to dropdowns +- New characters/variables can be created inline without leaving the canvas (under 3 clicks) +- Existing projects auto-migrate to the new system seamlessly on first load + +## Open Questions + +- Should the Yjs document be persisted separately from the Supabase JSONB column (dedicated storage)? +- What is the maximum number of concurrent collaborators to support per project? +- Should viewer-role users see other viewers' cursors, or only editors'? +- How long should audit trail entries be retained (indefinitely, or time-based cleanup)? +- Should the combobox support creating characters/variables with full details inline, or just name + essentials? +- Should variable type changes be allowed after creation if the variable is already in use?