## 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 - Character/Variable types (`Character`, `Variable`) and extracted node data types (`DialogueNodeData`, `VariableNodeData`) are in `src/types/flowchart.ts` - `EditorContext` at `src/components/editor/EditorContext.tsx` provides shared state (characters, onAddCharacter) to all custom node components via React context - Use `useEditorContext()` in node components to access project-level characters and variables without prop drilling through React Flow node data - New JSONB fields (characters, variables) must be defaulted to `[]` when reading from DB in page.tsx to handle pre-existing data - Reusable `Combobox` component at `src/components/editor/Combobox.tsx` - use for all character/variable dropdowns. Props: items (ComboboxItem[]), value, onChange, placeholder, onAddNew - `ProjectSettingsModal` at `src/components/editor/ProjectSettingsModal.tsx` manages characters/variables. Receives state + callbacks from FlowchartEditor - Characters and variables state is managed in `FlowchartEditorInner` with `useState` hooks, passed down to the modal - For settings-style modals, use `max-w-2xl h-[80vh]` with overflow-y-auto content area and fixed header/tabs - `EditorContext` provides both characters (onAddCharacter) and variables (onAddVariable) to node components. Use `useEditorContext()` to access them. - In FlowchartEditor, `handleAddVariable` adds a variable *node* to the canvas; `handleAddVariableDefinition` creates a variable *definition* in project data. Avoid naming collisions between "add node" and "add definition" callbacks. - Edge interactions use `onEdgeClick` on ReactFlow component. ConditionEditor opens as a modal overlay since React Flow edges don't support inline panels. - `Condition.value` supports `number | string | boolean` — always check variable type before rendering value inputs for edge conditions. - `OptionConditionEditor` at `src/components/editor/OptionConditionEditor.tsx` handles choice option conditions. Same pattern as `ConditionEditor` but with simpler props (no edgeId). - `ChoiceOption` type includes optional `condition?: Condition`. When counting variable usage, check variable nodes + edge conditions + choice option conditions. - React Compiler lint forbids `setState` in effects and reading `useRef().current` during render. Use `useState(() => computeValue())` lazy initializer pattern for one-time initialization logic. - For detecting legacy data shape (pre-migration), pass a flag from the server component (page.tsx) to the client component, since only the server reads raw DB data. - Collaboration tables: `project_collaborators` (roles), `collaboration_sessions` (presence), `audit_trail` (history) — all with RLS scoped by project ownership or collaborator membership - RLS pattern for shared resources: check `projects.user_id = auth.uid()` OR `project_collaborators.user_id = auth.uid()` to cover both owners and collaborators - `RealtimeConnection` class at `src/lib/collaboration/realtime.ts` manages Supabase Realtime channel lifecycle (connect, heartbeat, reconnect, disconnect). Instantiate with (projectId, userId, callbacks). - FlowchartEditor receives `userId` prop from page.tsx server component for collaboration features - Toolbar accepts optional `connectionState` prop to show green/yellow/red connection indicator - `collaboration_sessions` table has UNIQUE(project_id, user_id) constraint to support upsert-based session management - Server actions for project-specific operations go in `src/app/editor/[projectId]/actions.ts` — use `'use server'` directive and return `{ success: boolean; error?: string }` pattern - Editor page.tsx supports both owner and collaborator access: first checks ownership, then falls back to `project_collaborators` lookup. Pass `isOwner` prop to client component. - `ShareModal` at `src/components/editor/ShareModal.tsx` manages collaborator invites/roles/removal via server actions. Only owners see invite form. - Dashboard shared projects use Supabase join query: `project_collaborators.select('role, projects(id, name, updated_at)')` to fetch projects shared with the user - `ProjectCard` supports optional `shared` and `sharedRole` props — when `shared=true`, hide edit/delete buttons and show role badge instead - `PresenceAvatars` at `src/components/editor/PresenceAvatars.tsx` renders connected collaborator avatars. Receives `PresenceUser[]` from `RealtimeConnection.onPresenceSync`. - `RealtimeConnection` constructor takes `(projectId, userId, displayName, callbacks)` — `displayName` is broadcast via Supabase Realtime presence tracking - User color for presence is derived from a hash of their userId, ensuring consistency across sessions. Use `getUserColor(userId)` pattern from PresenceAvatars. - `CRDTManager` at `src/lib/collaboration/crdt.ts` wraps a Yjs Y.Doc with Y.Map for nodes and edges. Connects to Supabase Realtime channel for broadcasting updates. - CRDT sync pattern: local React Flow changes → `updateNodes`/`updateEdges` on CRDTManager → Yjs broadcasts to channel; remote broadcasts → Yjs applies update → callbacks set React Flow state. Use `isRemoteUpdateRef` to prevent echo loops. - For Supabase Realtime broadcast of binary data (Yjs updates), convert `Uint8Array` → `Array.from()` for JSON payload, and `new Uint8Array()` on receive. --- ## 2026-01-23 - US-054 - What was implemented: Character and Variable TypeScript types added to `src/types/flowchart.ts` - Files changed: - `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:** - 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. --- ## 2026-01-23 - US-055 - What was implemented: Database migration to update flowchart_data JSONB default to include `characters: []` and `variables: []` - Files changed: - `supabase/migrations/20260123000000_add_characters_variables_to_flowchart_data.sql` - New migration that alters the default value for the flowchart_data column and documents the expected JSONB structure - **Learnings for future iterations:** - Since characters and variables are stored within the existing flowchart_data JSONB column (not as separate tables), schema changes are minimal - just updating the column default. The real data integrity is handled at the application layer. - The app-side defaults in page.tsx (from US-054) already handle existing projects gracefully, so no data migration of existing rows is needed. - For JSONB-embedded arrays, the pattern is: update the DB default for new rows + handle missing fields in app code for old rows. --- ## 2026-01-23 - US-065 - What was implemented: Reusable searchable combobox component at `src/components/editor/Combobox.tsx` - Files changed: - `src/components/editor/Combobox.tsx` - New component with searchable dropdown, keyboard navigation, color swatches, badges, "Add new..." option, and auto-positioning - **Learnings for future iterations:** - The Combobox exports both the default component and the `ComboboxItem` type for consumers to use - Props: `items` (ComboboxItem[]), `value` (string | undefined), `onChange` (id: string) => void, `placeholder` (string), `onAddNew` (() => void, optional) - ComboboxItem shape: `{ id: string, label: string, color?: string, badge?: string }` - The component uses neutral zinc colors for borders/backgrounds (not blue/green/orange) so it can be reused across different node types - Dropdown auto-positions above or below based on available viewport space (200px threshold) - Keyboard: ArrowDown/Up navigate, Enter selects, Escape closes - The component is designed to be a drop-in replacement for text inputs in node components (same `w-full` and `text-sm` sizing) --- ## 2026-01-23 - US-056 - What was implemented: Character management UI in the project settings modal - Files changed: - `src/components/editor/ProjectSettingsModal.tsx` - New modal component with Characters and Variables tabs; Characters tab has full CRUD (add, edit, delete with usage warnings), name uniqueness validation, color picker, inline forms - `src/components/editor/Toolbar.tsx` - Added `onProjectSettings` prop and "Project Settings" button to the right side of the toolbar - `src/app/editor/[projectId]/FlowchartEditor.tsx` - Added `characters` and `variables` state management, `showSettings` modal state, usage count helpers (`getCharacterUsageCount`, `getVariableUsageCount`), and ProjectSettingsModal rendering - **Learnings for future iterations:** - The ProjectSettingsModal receives `onCharactersChange` and `onVariablesChange` callbacks that directly set state in FlowchartEditor. When save is implemented, it should read from this state. - The Variables tab is a read-only placeholder in US-056; US-057 will implement the full CRUD for variables using the same patterns (inline forms, validation, delete warnings). - Modal pattern: fixed inset-0 z-50 with backdrop click to close, max-w-2xl for settings modals (larger than max-w-md used for simple dialogs). - Character usage count checks dialogue nodes for `data.characterId`; variable usage count checks both variable nodes and edge conditions. - The `randomHexColor()` utility picks from a curated list of 12 vibrant colors for character defaults. - No browser testing tools are available; manual verification is needed. --- ## 2026-01-23 - US-057 - What was implemented: Variable management UI with full CRUD in the project settings modal Variables tab - Files changed: - `src/components/editor/ProjectSettingsModal.tsx` - Replaced placeholder VariablesTab with full implementation: add/edit/delete with inline forms, type dropdown (numeric/string/boolean), type-adaptive initial value input (number input for numeric, text for string, select for boolean), name uniqueness validation, delete warnings with usage count, colored type badges - **Learnings for future iterations:** - The VariableForm uses a `handleTypeChange` helper that resets the initial value to the type's default when the type changes, preventing invalid state (e.g., "hello" as a numeric value) - Initial values are stored as strings in form state and parsed to the correct type (number/string/boolean) on save via `parseInitialValue()` - Type badges use distinct colors: blue for numeric, green for string, purple for boolean - making variable types instantly recognizable in the list - The same form patterns from CharactersTab apply: inline form within the list for editing, appended form below the list for adding - No browser testing tools are available; manual verification is needed. --- ## 2026-01-23 - US-059 - What was implemented: Variable node variable dropdown using Combobox component, replacing the free-text input - Files changed: - `src/components/editor/nodes/VariableNode.tsx` - Replaced text input with Combobox for variable selection, added inline "Add new variable" form with name + type, added orange warning border for invalid references, filtered operation options (add/subtract only for numeric type) - `src/components/editor/EditorContext.tsx` - Extended context to include `variables: Variable[]` and `onAddVariable` callback - `src/app/editor/[projectId]/FlowchartEditor.tsx` - Added `handleAddVariableDefinition` callback and passed variables + onAddVariable through EditorContext - **Learnings for future iterations:** - The existing `handleAddVariable` in FlowchartEditor adds a variable *node* to the canvas (toolbar action). The new `handleAddVariableDefinition` creates a variable *definition* in the project's data. Name carefully to avoid collisions. - EditorContext is the shared context for node components to access project-level characters and variables. Extend it when new entity types need to be accessible from custom node components. - The VariableNode follows the same pattern as DialogueNode for Combobox integration: items derived via useMemo, handleSelect sets both variableId and variableName, inline add form for quick creation, hasInvalidReference for warning state. - Operations filtering uses `isNumeric` flag: if no variable is selected (undefined) or type is 'numeric', all operations are shown; otherwise only 'set' is available. When selecting a non-numeric variable, operation is auto-reset to 'set' if it was 'add' or 'subtract'. - No browser testing tools are available; manual verification is needed. --- ## 2026-01-23 - US-060 - What was implemented: Edge condition variable dropdown using Combobox component, replacing free-text input with a type-aware condition editor modal - Files changed: - `src/types/flowchart.ts` - Updated `Condition.value` type from `number` to `number | string | boolean` to support all variable types - `src/components/editor/ConditionEditor.tsx` - New component: modal-based condition editor with Combobox for variable selection, type-aware operator filtering, type-adaptive value inputs, inline "Add new variable" form, orange warning for invalid references, and "Remove condition" action - `src/app/editor/[projectId]/FlowchartEditor.tsx` - Added `onEdgeClick` handler to open ConditionEditor, `handleConditionChange` to update edge condition data, `selectedEdgeId` state, and ConditionEditor rendering - **Learnings for future iterations:** - Edge interactions in React Flow use `onEdgeClick` prop on the ReactFlow component (not on individual edges). The handler receives `(event: React.MouseEvent, edge: Edge)`. - The ConditionEditor is rendered as a modal overlay (fixed z-50), not as part of the edge itself — since edges don't have built-in panel/popover support in React Flow. - `Condition.value` was originally typed as just `number` but needed broadening to `number | string | boolean` to support string/boolean variables in conditions. This change didn't break existing code since the VariableNode's `value` field is a separate type. - Operator filtering for non-numeric types: only `==` and `!=` are available for string/boolean variables. When switching from a numeric variable to a string/boolean, the operator auto-resets to `==` if it was a comparison operator. - Value input adapts to type: number input for numeric, text input for string, boolean dropdown for boolean. - The `selectedEdge` is derived via `useMemo` from `edges` state and `selectedEdgeId`, so it always reflects the latest condition data. - No browser testing tools are available; manual verification is needed. --- ## 2026-01-23 - US-061 - What was implemented: Choice option condition variable dropdown using OptionConditionEditor component with Combobox - Files changed: - `src/types/flowchart.ts` - Added `condition?: Condition` to `ChoiceOption` type; moved `Condition` type definition before `ChoiceOption` for correct reference order - `src/components/editor/OptionConditionEditor.tsx` - New component: modal-based condition editor for choice options with Combobox variable selection, type-aware operators, type-adaptive value inputs, inline "Add new variable" form, orange warning for invalid references - `src/components/editor/nodes/ChoiceNode.tsx` - Added condition button per option (clipboard icon), condition summary text below options, OptionConditionEditor integration, EditorContext usage for variables, invalid reference detection with orange warning styling - `src/app/editor/[projectId]/FlowchartEditor.tsx` - Extended `getVariableUsageCount` to also count variable references in choice option conditions - **Learnings for future iterations:** - The `OptionConditionEditor` follows the same pattern as `ConditionEditor` but with a simpler API: it doesn't need an edgeId since it works with a single option's condition via `onChange(condition | undefined)` callback - The `ChoiceOption` type in `flowchart.ts` now references `Condition`, which required reordering type definitions (Condition must be defined before ChoiceOption) - Each choice option shows a small clipboard icon button that turns blue when a condition is set, or orange when the referenced variable is invalid/deleted - A condition summary line (e.g., "if score > 10") appears below each option label when a condition is active - The `getVariableUsageCount` in FlowchartEditor now counts three sources: variable nodes, edge conditions, and choice option conditions - No browser testing tools are available; manual verification is needed. --- ## 2026-01-23 - US-062 - What was implemented: Auto-migration of existing free-text speaker/variable values to character/variable definitions on project load - Files changed: - `src/app/editor/[projectId]/page.tsx` - Added `needsMigration` flag that detects whether raw DB data has characters/variables arrays - `src/app/editor/[projectId]/FlowchartEditor.tsx` - Added `computeMigration()` helper function and `needsMigration` prop; migration result initializes state directly via lazy `useState` to avoid React Compiler lint issues - `src/components/editor/nodes/DialogueNode.tsx` - Included pre-existing US-058 changes (speaker dropdown with Combobox) that were not previously committed - **Learnings for future iterations:** - React Compiler lint (`react-hooks/set-state-in-effect`) forbids calling `setState` synchronously within `useEffect`. For one-time initialization logic, compute the result and use it directly in state initializers instead. - React Compiler lint (`react-hooks/refs`) forbids reading `useRef().current` during render. Use `useState(() => ...)` lazy initializer pattern instead of `useRef` for values computed once at mount. - The migration detection relies on `rawData.characters` being `undefined` (old projects) vs `[]` (migrated projects). The `page.tsx` server component passes `needsMigration` flag to the client component since only the server has access to the raw DB shape. - `computeMigration` is a pure function called outside the component render cycle (via lazy useState). It uses `nanoid()` for IDs, so it must only be called once — lazy `useState` ensures this. - The toast message for migration is set as initial state, so it shows immediately on first render without needing an effect. - No browser testing tools are available; manual verification is needed. --- ## 2026-01-23 - US-063 - What was implemented: Import characters/variables from another project via modal in project settings - Files changed: - `src/components/editor/ImportFromProjectModal.tsx` - New component: project list modal with checkbox selection for characters or variables, duplicate-by-name skipping with warnings, select all/none controls - `src/components/editor/ProjectSettingsModal.tsx` - Added `projectId` prop, `ImportFromProjectModal` integration, and "Import from project" buttons in both Characters and Variables tabs - `src/app/editor/[projectId]/FlowchartEditor.tsx` - Passed `projectId` through to `ProjectSettingsModal` - **Learnings for future iterations:** - The `ImportFromProjectModal` uses `z-[60]` to layer above the `ProjectSettingsModal` (which uses `z-50`), since it's rendered as a child of that modal - Imported characters/variables get new IDs via `nanoid()` to avoid ID collisions between projects. The original colors, types, and initial values are preserved. - Duplicate detection is case-insensitive by name. Duplicates are skipped (not overwritten) with a warning message shown to the user. - The `LoadingSpinner` component mentioned in Codebase Patterns doesn't exist; used inline text loading indicators instead. - Supabase client-side fetching from `createClient()` (browser) automatically scopes by the logged-in user's RLS policies, so fetching other projects just uses `.neq('id', currentProjectId)` and RLS handles ownership filtering. - No browser testing tools are available; manual verification is needed. --- ## 2026-01-23 - US-064 - What was implemented: Export validation that scans nodes/edges for undefined character/variable references before exporting, with a warning modal and canvas highlighting - Files changed: - `src/components/editor/ExportValidationModal.tsx` - New component: warning modal listing validation issues by type (character/variable), showing node type badge and content snippet, with "Export anyway" and "Cancel" buttons - `src/app/editor/[projectId]/FlowchartEditor.tsx` - Updated `handleExport` to scan nodes (dialogue, variable, choice) and edges for characterId/variableId references not matching defined entries; added `performExport`, `handleExportAnyway`, `handleExportCancel` callbacks; added `validationIssues` and `warningNodeIds` state; applied `export-warning-node` className to affected nodes via `styledNodes` memo - `src/app/globals.css` - Added `.react-flow__node.export-warning-node` CSS with orange outline and pulse animation for highlighted nodes - **Learnings for future iterations:** - React Flow nodes support a `className` prop that gets applied to the `.react-flow__node` wrapper div, so custom styling can be applied via CSS without modifying node component internals. - The validation scans four sources of references: dialogue nodes (characterId), variable nodes (variableId), choice node option conditions (variableId), and edge conditions (variableId). - The `handleExport` is the validation gate; `performExport` is where actual export logic (US-035) will go. When no issues are found, `performExport` is called directly. Otherwise the modal is shown and the user decides. - Warning highlighting is cleared either by canceling the modal or by proceeding with "Export anyway". - Pre-existing lint issues in `ConditionEditor.tsx` and `OptionConditionEditor.tsx` (React Compiler `preserve-manual-memoization` errors) are from prior stories and not related to this change. - No browser testing tools are available; manual verification is needed. --- ## 2026-01-23 - US-043 - What was implemented: Database migration adding project_collaborators, collaboration_sessions, and audit_trail tables with RLS policies and indexes - Files changed: - `supabase/migrations/20260123100000_add_collaboration_and_audit_trail.sql` - New migration with three tables, RLS policies, indexes, and updated projects RLS for collaborator access - **Learnings for future iterations:** - `project_collaborators` has a UNIQUE constraint on (project_id, user_id) to prevent duplicate invitations - RLS policies for collaboration tables use subqueries to check either project ownership (via `projects.user_id`) or collaboration membership (via `project_collaborators.user_id`) - The audit_trail insert policy requires both `auth.uid() = user_id` AND project access (owner or editor role) to prevent unauthorized audit writes - New RLS policies were added to the existing `projects` table to allow collaborators to SELECT and UPDATE (editors/owners only) shared projects - The audit_trail index uses `created_at DESC` for efficient reverse-chronological pagination in the history sidebar - `collaboration_sessions.cursor_position` is JSONB to store flexible coordinate data (x, y, and potentially viewport info) - `collaboration_sessions.selected_node_id` is nullable text since a user may not have any node selected --- ## 2026-01-23 - US-045 - What was implemented: Supabase Realtime channel and connection management with connection lifecycle, heartbeat, auto-reconnect, and toolbar status indicator - Files changed: - `src/lib/collaboration/realtime.ts` - New module: `RealtimeConnection` class with connect/disconnect, heartbeat (30s interval), exponential backoff reconnect, session upsert/delete, presence sync callback - `src/components/editor/Toolbar.tsx` - Added `connectionState` optional prop with color-coded indicator (green=connected, yellow=connecting/reconnecting, red=disconnected) - `src/app/editor/[projectId]/FlowchartEditor.tsx` - Added `userId` prop, imported `RealtimeConnection`, added `connectionState` state + `useEffect` for connection lifecycle, passed `connectionState` to Toolbar - `src/app/editor/[projectId]/page.tsx` - Passed `userId={user.id}` to FlowchartEditor component - `supabase/migrations/20260123200000_add_collaboration_sessions_unique_constraint.sql` - Added UNIQUE(project_id, user_id) constraint for upsert support - **Learnings for future iterations:** - Supabase Realtime channel subscription statuses are: `SUBSCRIBED`, `CHANNEL_ERROR`, `TIMED_OUT`, `CLOSED`. Handle each for connection state tracking. - The `RealtimeConnection` class creates its own Supabase client instance via `createClient()` since the browser client is stateless and cheap to create - `useRef` is used to hold the connection instance across renders without triggering re-renders. The `useEffect` cleanup calls `disconnect()` to properly clean up. - The Supabase channel name is `project:{projectId}` — future stories (presence, CRDT) should join the same channel via `realtimeRef.current.getChannel()` - The `collaboration_sessions` table needed a UNIQUE constraint on (project_id, user_id) for the upsert pattern to work; this was added as a separate migration - Pre-existing lint errors in ConditionEditor.tsx and OptionConditionEditor.tsx (React Compiler `preserve-manual-memoization`) are unrelated to this story - No browser testing tools are available; manual verification is needed. --- ## 2026-01-23 - US-044 - What was implemented: Project sharing and collaborator management with Share button, modal, server actions, collaborator access to editor, and dashboard shared projects section - Files changed: - `src/app/editor/[projectId]/actions.ts` - New server actions module: `getCollaborators`, `inviteCollaborator`, `updateCollaboratorRole`, `removeCollaborator` with ownership verification and profile lookups - `src/components/editor/ShareModal.tsx` - New modal component: invite form (email + role), collaborator list with role change and remove actions, error/success messaging - `src/components/editor/Toolbar.tsx` - Added `onShare` prop and "Share" button between "Project Settings" and "Save" - `src/app/editor/[projectId]/FlowchartEditor.tsx` - Added `isOwner` prop, `showShare` state, ShareModal rendering, passed `onShare` to Toolbar - `src/app/editor/[projectId]/page.tsx` - Updated project fetching to support collaborator access (owner-first lookup, fallback to project_collaborators check), pass `isOwner` prop - `src/app/dashboard/page.tsx` - Added query for shared projects via `project_collaborators` join, rendered "Shared with me" section with ProjectCard - `src/components/ProjectCard.tsx` - Made `onDelete`/`onRename` optional, added `shared`/`sharedRole` props, conditionally shows role badge instead of edit/delete actions for shared projects - **Learnings for future iterations:** - Supabase join queries with `profiles(display_name, email)` return the type as an array due to generated types. Cast through `unknown` to the expected shape: `c.profiles as unknown as { display_name: string | null; email: string | null } | null` - The RLS policies from US-043 migration already handle collaborator access to projects (SELECT and UPDATE for editors). No new migration was needed for this story. - For editor page.tsx, checking collaborator access requires two queries: first try `.eq('user_id', user.id)` for ownership, then check `project_collaborators` table. RLS on projects allows collaborators to SELECT, so the second `.from('projects')` query works. - Server actions in App Router can be co-located with page.tsx in the same route directory (e.g., `src/app/editor/[projectId]/actions.ts`) - The `accepted_at` field is auto-set on invite (auto-accept pattern). A pending invitation flow would require leaving `accepted_at` null and adding an accept action. - No browser testing tools are available; manual verification is needed. --- ## 2026-01-23 - US-046 - What was implemented: Presence indicators showing connected collaborators as avatar circles in the editor toolbar - Files changed: - `src/lib/collaboration/realtime.ts` - Added `PresenceUser` type, `displayName` constructor param, presence tracking via `channel.track()`, presence sync parsing that excludes own user - `src/components/editor/PresenceAvatars.tsx` - New component: avatar circles with user initials, consistent color from user ID hash, tooltip with display_name, max 5 visible with "+N" overflow - `src/components/editor/Toolbar.tsx` - Added `presenceUsers` prop and `PresenceAvatars` rendering before connection indicator - `src/app/editor/[projectId]/FlowchartEditor.tsx` - Added `userDisplayName` prop, `presenceUsers` state, updated `RealtimeConnection` instantiation with `displayName` and `onPresenceSync` callback - `src/app/editor/[projectId]/page.tsx` - Fetches user's `display_name` from profiles table and passes as `userDisplayName` prop - **Learnings for future iterations:** - Supabase Realtime presence uses `channel.track({ ...data })` after subscription to broadcast user info. The presence key is set in channel config: `config: { presence: { key: userId } }`. - The `presenceState()` returns a `Record` where each key maps to an array of presence objects. Filter out own key to exclude self. - Consistent user colors are generated via a simple hash of the userId string, indexed into a fixed palette of 12 colors. This ensures the same user always gets the same color across sessions. - The `PresenceAvatars` component shows initials (first letter of first two words, or first two chars for single-word names) with the computed background color. - Pre-existing lint errors in ConditionEditor.tsx, OptionConditionEditor.tsx, and ShareModal.tsx are from prior stories and unrelated to this change. - No browser testing tools are available; manual verification is needed. --- ## 2026-01-23 - US-048 - What was implemented: Yjs CRDT integration for conflict-free node/edge synchronization across multiple collaborators - Files changed: - `package.json` / `package-lock.json` - Added `yjs` dependency - `src/lib/collaboration/crdt.ts` - New module: `CRDTManager` class wrapping Y.Doc with Y.Map for nodes (keyed by node ID) and Y.Map for edges (keyed by edge ID), broadcast via Supabase Realtime channel, debounced persistence (2s) - `src/lib/collaboration/realtime.ts` - Added `onChannelSubscribed` callback to `RealtimeCallbacks` type, invoked after channel subscription to allow CRDT connection - `src/app/editor/[projectId]/FlowchartEditor.tsx` - Added CRDTManager initialization from DB data, channel connection via `onChannelSubscribed`, bidirectional sync (local→CRDT via useEffect on nodes/edges, remote→local via callbacks), Supabase persistence on debounced timer - **Learnings for future iterations:** - Yjs updates are serialized as `Uint8Array`. For Supabase Realtime broadcast (which uses JSON), convert to `Array.from(update)` for sending and `new Uint8Array(data)` for receiving. - The CRDT uses an `isApplyingRemote` flag to prevent echo loops: when applying remote updates, observers skip re-notifying React state (since the state setter is what triggers the change). Similarly, `isRemoteUpdateRef` in FlowchartEditor prevents local→CRDT sync when the change originated from a remote update. - Y.Map stores serialized JSON strings (not objects) as values to avoid Yjs's nested type complexity. This means each node/edge is independently merge-able at the entry level. - The `onPersist` callback captures `characters` and `variables` from the effect closure — this means the persisted data always uses the character/variable state at the time the effect was created. For full correctness in a production system, these should be passed dynamically. - The CRDT document is initialized once from `migratedData` (the lazy-computed initial state). The `initializeFromData` call uses an 'init' origin to distinguish initialization from local edits. - The `onChannelSubscribed` callback pattern allows the CRDT to connect to the channel only after it's fully subscribed, avoiding race conditions with broadcast messages arriving before the listener is set up. - Pre-existing lint errors in ConditionEditor.tsx, OptionConditionEditor.tsx, and ShareModal.tsx are from prior stories and unrelated to this change. ---