diff --git a/prd.json b/prd.json index 29d01a7..3c6e280 100644 --- a/prd.json +++ b/prd.json @@ -1,18 +1,19 @@ { "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", + "branchName": "ralph/collaboration-and-character-variables", + "description": "Real-time Collaboration & Character/Variable Management - Enable multi-user editing with CRDT sync, presence indicators, audit trail, plus centralized character/variable definitions with dropdown selectors", "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.", + "id": "US-054", + "title": "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.", "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/", + "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" ], "priority": 1, @@ -20,680 +21,401 @@ "notes": "" }, { - "id": "US-002", - "title": "Define TypeScript types for flowchart data", - "description": "As a developer, I need TypeScript types for nodes, connections, and conditions.", + "id": "US-055", + "title": "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.", "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", + "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" ], "priority": 2, - "passes": true, - "notes": "" + "passes": false, + "notes": "Dependencies: US-054" }, { - "id": "US-003", - "title": "Supabase schema for users and projects", - "description": "As a developer, I need database tables to store users and their projects.", + "id": "US-065", + "title": "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.", "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" + "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" ], "priority": 3, - "passes": true, + "passes": false, "notes": "" }, { - "id": "US-004", - "title": "Supabase client configuration", - "description": "As a developer, I need Supabase client utilities for auth and database access.", + "id": "US-056", + "title": "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.", "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" + "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 referenced by nodes shows warning with usage count", + "Character names must be unique within the project (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" ], "priority": 4, - "passes": true, - "notes": "" + "passes": false, + "notes": "Dependencies: US-054, US-055" }, { - "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.", + "id": "US-057", + "title": "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.", "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" + "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 adapts to type), description (optional)", + "Each variable row has Edit and Delete buttons", + "Deleting a variable referenced by nodes/edges shows 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" ], "priority": 5, - "passes": true, - "notes": "" + "passes": false, + "notes": "Dependencies: US-054, US-055" }, { - "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.", + "id": "US-058", + "title": "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.", "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", + "Replace the speaker text input in DialogueNode with the Combobox component", + "Dropdown lists all characters defined in the project, showing color swatch + name", + "Selecting a character sets characterId on the node data", + "Dropdown includes '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" ], "priority": 6, - "passes": true, - "notes": "" + "passes": false, + "notes": "Dependencies: US-056, US-065" }, { - "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.", + "id": "US-059", + "title": "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.", "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", + "Replace the variableName text input in VariableNode with the Combobox component", + "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" ], "priority": 7, - "passes": true, - "notes": "" + "passes": false, + "notes": "Dependencies: US-057, US-065" }, { - "id": "US-008", - "title": "Logout functionality", - "description": "As a user, I want to log out so that I can secure my session.", + "id": "US-060", + "title": "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.", "acceptanceCriteria": [ - "Create components/LogoutButton.tsx component", - "Button calls Supabase signOut", - "On success, redirect to /login", + "Replace the variableName text input in ConditionEditor with the Combobox component", + "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" ], "priority": 8, - "passes": true, - "notes": "" + "passes": false, + "notes": "Dependencies: US-057, US-065" }, { - "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.", + "id": "US-061", + "title": "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.", "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", + "Replace the variableName text input in OptionConditionEditor with the Combobox component", + "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" ], "priority": 9, - "passes": true, - "notes": "" + "passes": false, + "notes": "Dependencies: US-057, US-065" }, { - "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.", + "id": "US-062", + "title": "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.", "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" + "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" ], "priority": 10, - "passes": true, - "notes": "" + "passes": false, + "notes": "Dependencies: US-054, US-058, US-059" }, { - "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.", + "id": "US-063", + "title": "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.", "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", + "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" ], "priority": 11, - "passes": true, - "notes": "" + "passes": false, + "notes": "Dependencies: US-056, US-057" }, { - "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.", + "id": "US-064", + "title": "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.", "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", + "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" ], "priority": 12, - "passes": true, - "notes": "" + "passes": false, + "notes": "Dependencies: US-058, US-059, US-060, US-061" }, { - "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.", + "id": "US-043", + "title": "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.", "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" + "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" ], "priority": 13, - "passes": true, + "passes": false, "notes": "" }, { - "id": "US-014", - "title": "Delete project", - "description": "As a user, I want to delete a project I no longer need.", + "id": "US-045", + "title": "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.", "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", + "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" ], "priority": 14, - "passes": true, - "notes": "" + "passes": false, + "notes": "Dependencies: US-043" }, { - "id": "US-015", - "title": "Rename project", - "description": "As a user, I want to rename a project to keep my work organized.", + "id": "US-044", + "title": "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.", "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", + "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" ], "priority": 15, - "passes": true, - "notes": "" + "passes": false, + "notes": "Dependencies: US-043" }, { - "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.", + "id": "US-046", + "title": "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.", "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", + "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" ], "priority": 16, - "passes": true, - "notes": "" + "passes": false, + "notes": "Dependencies: US-045" }, { - "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.", + "id": "US-048", + "title": "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.", "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" + "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" ], "priority": 17, - "passes": true, - "notes": "" + "passes": false, + "notes": "Dependencies: US-045" }, { - "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.", + "id": "US-047", + "title": "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.", "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", + "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" ], "priority": 18, - "passes": true, - "notes": "" + "passes": false, + "notes": "Dependencies: US-045, US-046" }, { - "id": "US-019", - "title": "Editor toolbar", - "description": "As a user, I want a toolbar with actions for adding nodes and saving/exporting.", + "id": "US-050", + "title": "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.", "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", + "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" ], "priority": 19, - "passes": true, - "notes": "" + "passes": false, + "notes": "Dependencies: US-045, US-046" }, { - "id": "US-020", - "title": "Create custom dialogue node component", - "description": "As a user, I want dialogue nodes to display and edit character speech.", + "id": "US-049", + "title": "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.", "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", + "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" ], "priority": 20, - "passes": true, - "notes": "" + "passes": false, + "notes": "Dependencies: US-045, US-048" }, { - "id": "US-021", - "title": "Add dialogue node from toolbar", - "description": "As a user, I want to add dialogue nodes by clicking the toolbar button.", + "id": "US-051", + "title": "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.", "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" + "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" ], "priority": 21, - "passes": true, - "notes": "" + "passes": false, + "notes": "Dependencies: US-043, US-048" }, { - "id": "US-022", - "title": "Create custom choice node component", - "description": "As a user, I want choice nodes to display branching decisions.", + "id": "US-052", + "title": "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.", "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", + "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" ], "priority": 22, - "passes": true, - "notes": "" + "passes": false, + "notes": "Dependencies: US-051" }, { - "id": "US-023", - "title": "Add choice node from toolbar", - "description": "As a user, I want to add choice nodes by clicking the toolbar button.", + "id": "US-053", + "title": "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.", "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", + "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" ], "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": false, - "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": false, - "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": false, - "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": false, - "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": false, - "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": false, - "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": false, - "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": false, - "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": false, - "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": false, - "notes": "" + "notes": "Dependencies: US-052, US-048" } ] } diff --git a/progress.txt b/progress.txt index dab9d29..89cf9ce 100644 --- a/progress.txt +++ b/progress.txt @@ -24,412 +24,21 @@ - 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 +- Character/Variable types (`Character`, `Variable`) and extracted node data types (`DialogueNodeData`, `VariableNodeData`) are in `src/types/flowchart.ts` +- New JSONB fields (characters, variables) must be defaulted to `[]` when reading from DB in page.tsx to handle pre-existing data --- -## 2026-01-21 - US-001 -- What was implemented: Project scaffolding and configuration +## 2026-01-23 - US-054 +- What was implemented: Character and Variable TypeScript types added to `src/types/flowchart.ts` - 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 + - `src/types/flowchart.ts` - Added `Character`, `Variable`, `DialogueNodeData`, `VariableNodeData` types; updated `FlowchartData`, `DialogueNode`, `VariableNode`, `Condition` types + - `src/app/editor/[projectId]/page.tsx` - Updated FlowchartData initialization to include `characters: []` and `variables: []` defaults - **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 + - The node components (`DialogueNode.tsx`, `VariableNode.tsx`, `ChoiceNode.tsx`) define their own local data types that mirror the global types. When adding fields, both the global type and local component type may need updating in later stories. + - `flowchart_data` is a JSONB column in Supabase, so it comes as `any` type. Always provide defaults for new fields when reading from DB to handle existing data without those fields. + - The new `characterId` and `variableId` fields are optional alongside existing `speaker`/`variableName` fields to support migration from free-text to referenced-entity pattern. --- diff --git a/src/app/editor/[projectId]/page.tsx b/src/app/editor/[projectId]/page.tsx index 8117932..0750c85 100644 --- a/src/app/editor/[projectId]/page.tsx +++ b/src/app/editor/[projectId]/page.tsx @@ -31,10 +31,13 @@ export default async function EditorPage({ params }: PageProps) { notFound() } - const flowchartData = (project.flowchart_data || { - nodes: [], - edges: [], - }) as FlowchartData + const rawData = project.flowchart_data || {} + const flowchartData: FlowchartData = { + nodes: rawData.nodes || [], + edges: rawData.edges || [], + characters: rawData.characters || [], + variables: rawData.variables || [], + } return (
diff --git a/src/types/flowchart.ts b/src/types/flowchart.ts index 21a5c54..94ed51d 100644 --- a/src/types/flowchart.ts +++ b/src/types/flowchart.ts @@ -4,15 +4,35 @@ export type Position = { y: number; }; +// Character type: represents a defined character in the project +export type Character = { + id: string; + name: string; + color: string; // hex color + description?: string; +}; + +// Variable type: represents a defined variable in the project +export type Variable = { + id: string; + name: string; + type: 'numeric' | 'string' | 'boolean'; + initialValue: number | string | boolean; + description?: string; +}; + // DialogueNode type: represents character speech/dialogue +export type DialogueNodeData = { + speaker?: string; + characterId?: string; + text: string; +}; + export type DialogueNode = { id: string; type: 'dialogue'; position: Position; - data: { - speaker?: string; - text: string; - }; + data: DialogueNodeData; }; // Choice option type for ChoiceNode @@ -33,15 +53,18 @@ export type ChoiceNode = { }; // VariableNode type: represents variable operations +export type VariableNodeData = { + variableName: string; + variableId?: string; + operation: 'set' | 'add' | 'subtract'; + value: number; +}; + export type VariableNode = { id: string; type: 'variable'; position: Position; - data: { - variableName: string; - operation: 'set' | 'add' | 'subtract'; - value: number; - }; + data: VariableNodeData; }; // Union type for all node types @@ -50,6 +73,7 @@ export type FlowchartNode = DialogueNode | ChoiceNode | VariableNode; // Condition type for conditional edges export type Condition = { variableName: string; + variableId?: string; operator: '>' | '<' | '==' | '>=' | '<=' | '!='; value: number; }; @@ -70,4 +94,6 @@ export type FlowchartEdge = { export type FlowchartData = { nodes: FlowchartNode[]; edges: FlowchartEdge[]; + characters: Character[]; + variables: Variable[]; };