Commit graph

109 commits

Author SHA1 Message Date
Jan-Henrik Bruhn
93a5f38112 Omit handle fields from serialization entirely
Since edges use floating calculations that ignore handle positions,
the handle IDs (like 'top-source', 'right-target') should never be
persisted. They're only used to define clickable areas for connections.

This ensures consistency: both migrated old edges and newly created
edges will have no handle fields in saved JSON files.

Addresses PR review comment about serialization inconsistency.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-24 16:33:17 +01:00
Jan-Henrik Bruhn
4b865762a1 Address PR review comments
Edge calculation improvements:
- Add zero radius/radii guards in circle and ellipse intersection functions
- Add clamping for pill straight edge intersections to prevent overflow
- Ensure intersection points stay within valid pill boundaries

Handle improvements:
- Add bidirectional connection support with overlapping source/target handles
- Each edge now has both source and target handles (8 total per node)
- Allows edges to connect in any direction from any side
- Fixes handle type restrictions that prevented flexible connections

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-24 16:17:23 +01:00
Jan-Henrik Bruhn
318cdee15c Fix lint errors: change @ts-ignore to @ts-expect-error and fix type assertions 2026-01-24 16:05:50 +01:00
Jan-Henrik Bruhn
8d71da76b2 Add shape-aware edge connections and edge-only handles
Improvements:
- Edges now follow actual node shape contours (circle, ellipse, pill, rectangle)
- Smooth arrow rotation using normal vectors at intersection points
- Custom bezier curves with control points aligned to shape normals
- Edge-only handles (30px strips) leaving center free for node dragging
- Proper offset calculations to prevent edge-shape overlap

Technical changes:
- Add getCircleIntersection() for perfect circle geometry
- Add getEllipseIntersection() with gradient-based normals
- Add getPillIntersection() for stadium shape (rounded caps + straight sides)
- Update getFloatingEdgeParams() to accept and use node shapes
- CustomEdge determines shapes from nodeType config and creates custom bezier paths
- Replace full-node handles with 4 edge-positioned handles (top/right/bottom/left)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-24 16:03:34 +01:00
Jan-Henrik Bruhn
c9c888d0ac Implement whole-node easy-connect handle system with floating edges
Migrated from 4-position handle system (top/right/bottom/left) to React Flow's
easy-connect pattern where the entire node surface is connectable and edges
dynamically route to the nearest point on the node border.

Key changes:
- Migration utility removes old 4-position handle references for backwards compatibility
- Full-coverage invisible handles on CustomNode and GroupNode (maximized state)
- Floating edges use node.measured dimensions and node.internals.positionAbsolute
- useInternalNode hook for correct absolute positioning of nodes in groups
- All edges now omit handle fields, allowing dynamic border calculations

This improves UX by making nodes easier to connect (whole surface vs tiny handles)
and edges intelligently route to optimal connection points.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-24 13:01:04 +01:00
Jan-Henrik Bruhn
ae552a9fbd Fix infinite loop in TUIO connection and remove conflicting keyboard shortcut
- Use ref for callbacks in useTuioConnection to prevent infinite re-renders when entering presentation mode
- Remove disabled deselect-all shortcut that conflicted with exit-presentation-mode (both using Escape)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-20 15:10:12 +01:00
Jan-Henrik Bruhn
2ffebb9eb7 Add TUIO connection in tangible config dialog and hardware ID suggestion
Features:
- TUIO connection now starts when tangible config dialog is open
- Connection closes when dialog is closed
- Last detected tangible ID is suggested for Hardware ID field
- "Use: [ID]" link appears next to Hardware ID field when tangible detected
- Clicking the link auto-fills the Hardware ID field

Technical Changes:
- Created useTuioConnection hook for shared TUIO connection management
- Refactored useTuioIntegration to use new useTuioConnection hook
- Added suggestedHardwareId prop to TangibleForm component
- Updated QuickAddTangibleForm to get and pass suggested ID
- Updated EditTangibleInline to get and pass suggested ID
- TangibleConfig modal now uses useTuioConnection when isOpen is true

UI Improvements:
- Hardware ID suggestion link styled like auto-zoom toggle
- Shows truncated ID if longer than 8 characters (e.g., "Use: abc123...")
- Full ID shown in tooltip on hover

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-19 20:27:50 +01:00
Jan-Henrik Bruhn
1c56066f47 Remove 'About Tangibles' helper text section
Removed the blue info box with tangible explanations from the
TangibleConfig modal to streamline the interface.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-19 20:22:46 +01:00
Jan-Henrik Bruhn
3e2a7b6b20 Add multi-filter tangible support with presentation mode filtering
Features:
- Extended tangible filters to support labels, actor types, and relation types
- Added configurable combine mode (OR/AND) for filter logic
- Separated presentation mode filters from editing mode filters
- Implemented backward compatibility with legacy filterLabels format

Filter Behavior:
- OR mode (default for tangibles): Show items matching ANY filter category
- AND mode (default for editing): Show items matching ALL filter categories
- Presentation mode uses tuioStore.presentationFilters
- Editing mode uses searchStore filters

UI Improvements:
- Replaced radio buttons with horizontal button layout for mode selection
- Replaced dropdown with horizontal buttons for combine mode selection
- Consolidated Name and Hardware ID fields into two-column layout
- More compact and consistent interface

Technical Changes:
- Added FilterConfig type with combineMode field
- Created tangibleMigration.ts for backward compatibility
- Created tangibleValidation.ts for multi-format validation
- Added useActiveFilters hook for mode-aware filter access
- Added nodeMatchesFilters and edgeMatchesFilters helper functions
- Updated cascade cleanup for node/edge type deletions
- Removed all TUIO debug logging

Tests:
- Added 44 comprehensive tests for useActiveFilters hook
- Added tests for tangibleMigration utility
- All 499 tests passing

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-19 20:20:34 +01:00
Jan-Henrik Bruhn
010d8a558c Update tests for new activeStateTangibles tracking
Changes:
- Replace lastStateChangeSource with activeStateTangibles in tests
- Update tuioStore.test.ts to test array-based tracking
- Add tests for adding/removing multiple state tangibles
- Add test for duplicate prevention
- Update TUIO integration tests to use new API
- Pass fromTangible=true parameter to switchToState in tests

All 447 tests now passing.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-19 11:50:48 +01:00
Jan-Henrik Bruhn
68ca121b19 Add automatic fullscreen mode when entering presentation mode
Changes:
- setPresentationMode now requests fullscreen when enabled
- Automatically exits fullscreen when presentation mode is disabled
- Sync presentation mode when user manually exits fullscreen (ESC key)
- Add fullscreenchange event listener in App.tsx
- Add debug logging for fullscreen state changes

Behavior:
- Clicking "Presentation Mode" in menu or pressing F11 -> enters fullscreen
- Pressing ESC in presentation mode -> exits both presentation and fullscreen
- Clicking exit presentation mode -> exits both modes
- Manual fullscreen exit (browser button) -> also exits presentation mode

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-19 11:44:23 +01:00
Jan-Henrik Bruhn
520eef879e Fix state tangible tracking and manual state switch behavior
Issues fixed:
1. State tangibles not working after manual state switch
2. No support for multiple simultaneous state tangibles

Changes:
- Replace lastStateChangeSource with activeStateTangibles array
- Track active state tangibles in order of placement
- When removing a state tangible, switch to the last remaining one
- Clear activeStateTangibles on manual state switch
- Add fromTangible parameter to switchToState to distinguish sources
- Always switch to newly placed tangible's state (last added wins)

New behavior:
- Place tangible A -> switch to state A
- Manually switch to state B -> clears active tangibles list
- Place tangible A again -> switches back to state A
- Place tangible A and B simultaneously -> shows state B (last wins)
- Remove tangible B -> switches to state A
- Remove tangible A -> stays in current state

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-19 11:42:13 +01:00
Jan-Henrik Bruhn
d5450610f1 Fix state management bugs in tangible configuration
1. Fix state dropdown not updating when new states are added:
   - Replace useMemo with proper Zustand selector subscription
   - Component now re-renders when timeline states change

2. Add auto-save trigger to state operations:
   - createState now triggers auto-save after 1 second
   - updateState now triggers auto-save after 1 second
   - deleteState now triggers auto-save after 1 second
   - Consistent with label and tangible operations

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-19 11:37:05 +01:00
Jan-Henrik Bruhn
efc93c8acb Fix TUIO tangible detection and add comprehensive debug logging
- Fix connection settings to display all detected tangibles, not just configured ones
- Add visual indicators for configured vs unconfigured tangibles
- Add extensive debug logging throughout TUIO stack (WebSocket, client, handlers, integration)
- Add validation for TUIO 1.1 symbolId field to prevent errors
- Add warning message when unconfigured tangibles are detected

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-19 11:33:05 +01:00
Jan-Henrik Bruhn
f002e1660d Add TUIO protocol integration for tangible hardware detection
Implements WebSocket-based TUIO protocol support to connect physical tangibles
to presentation mode. When tangibles are placed on/removed from a touch screen,
they trigger configured actions (label filtering or state switching).

Features:
- TUIO 1.1 and 2.0 protocol support with version selection
- WebSocket connection management with real-time status
- Test connection feature in configuration dialog
- Persistent settings (WebSocket URL and protocol version)
- Multi-tangible handling: union for filters, last-wins for states
- Automatic connection in presentation mode

Implementation:
- TuioClientManager: Wrapper for tuio-client library with dual protocol support
- WebsocketTuioReceiver: Custom OSC/WebSocket transport layer
- useTuioIntegration: React hook bridging TUIO events to app stores
- TuioConnectionConfig: Settings UI with real-time tangible detection
- tuioStore: Zustand store with localStorage persistence

Technical details:
- TUIO 1.1 uses symbolId for hardware identification
- TUIO 2.0 uses token.cId for hardware identification
- Filter mode: Activates labels, union of all active tangibles
- State mode: Switches timeline state, last tangible wins
- Cleanup: Removes only labels no longer in use by any tangible
- Unknown hardware IDs are silently ignored

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-16 23:32:54 +01:00
copilot-swe-agent[bot]
b93b36664e Fix executeTypeTransaction type definition to include documentId parameter
The type definition in workspace/types.ts was missing the optional documentId parameter that was added to the implementation.

Co-authored-by: jhbruhn <1036566+jhbruhn@users.noreply.github.com>
2026-01-16 12:45:53 +00:00
copilot-swe-agent[bot]
8051a466f8 Add comprehensive tests for auto-save behavior
- Added tests to verify auto-save triggers after label/tangible/type updates
- Uses fake timers to verify 1-second auto-save delay
- Tests verify dirty flag is cleared after auto-save completes
- Covers label, tangible, node type, and edge type operations

Co-authored-by: jhbruhn <1036566+jhbruhn@users.noreply.github.com>
2026-01-16 11:17:38 +00:00
copilot-swe-agent[bot]
1deb0b2631 Add auto-save trigger to label and tangible operations
- Modified executeTypeTransaction to accept documentId and trigger auto-save
- Added 1-second timeout before saving (consistent with undo/redo)
- Updated all label, tangible, and type management operations to pass documentId
- This ensures dirty indicator clears after save completes

Co-authored-by: jhbruhn <1036566+jhbruhn@users.noreply.github.com>
2026-01-16 11:16:17 +00:00
Jan-Henrik Bruhn
23c65ffbb1 fix import for ConstellationState 2026-01-16 10:04:40 +01:00
Jan-Henrik Bruhn
00c7adc41d Fix lint in unit test 2026-01-16 09:58:08 +01:00
Jan-Henrik Bruhn
8dcca18008 add tangible configuration interface and internal model for tangible configuration 2026-01-16 09:50:10 +01:00
Jan-Henrik Bruhn
9ffd62d54a feat: implement presentation mode for touch table displays
Add comprehensive presentation/viewer mode optimized for touch table
interactions with clean UI and touch-friendly timeline navigation.

State Management:
- Add presentationMode toggle to settingsStore with localStorage persistence
- Add preferPresentationMode to DocumentMetadata for per-document preferences
- Auto-enter presentation mode when opening documents that prefer it
- Add setDocumentPresentationPreference() helper to workspaceStore

UI Components:
- Create PresentationTimelineOverlay component with floating timeline control
  - Previous/Next navigation buttons with chevron icons
  - Horizontal scrollable state list
  - Only shows when document has 2+ states
  - Proper vertical alignment using flex items-stretch and centered content
  - Scales to ~10 states with max-w-screen-md (768px) container
- Create presentation.css for touch optimizations (60px+ touch targets)

UI Modifications:
- App.tsx: Conditional rendering hides editing chrome in presentation mode
- GraphEditor: Disable editing interactions, keep pan/zoom enabled
- MenuBar: Add "Presentation Mode" menu item
- Global shortcuts: F11 to toggle, Escape to exit presentation mode

Tests:
- Add presentation mode tests to settingsStore.test.ts
- Add document preference tests to workspaceStore.test.ts
- All 376 tests passing

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-15 17:36:00 +01:00
Jan-Henrik Bruhn
63ec8eb2e3 feat: increase zoom range to support larger charts
Allow zooming out to 10% (previously 50%) to better accommodate large constellation analyses. Added zoom constants (MIN_ZOOM=0.1, MAX_ZOOM=2.5) for consistency across fitView and ReactFlow component.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-15 11:54:02 +01:00
Jan-Henrik Bruhn
a554aa156f fix: resolve build and type errors
Fixes TypeScript compilation errors that prevented production build:

- Fix invalid node shape type in integration test (diamond → circle)
- Export WorkspaceSettings type from main types module
- Exclude test files from production build TypeScript check

All tests (368), linting, and build now pass successfully.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-11 09:46:44 +01:00
Jan-Henrik Bruhn
b748862865 investigate the current linting errors and fix them (vibe-kanban b099eb14) 2025-11-10 12:29:37 +01:00
Jan-Henrik Bruhn
7fa0965001 there is a __tests__ folder and a test folder. That seems confusing, is that a SotA TypeScript/REact/Vite testing pattern? (vibe-kanban c7c8b21b) 2025-11-10 12:26:34 +01:00
Jan-Henrik Bruhn
650819a083 create some first integration tests, determining where it actually makes sense. (vibe-kanban d736b771) 2025-11-10 12:22:30 +01:00
Jan-Henrik Bruhn
28719d8953 Running the CI Tests results in errors (see the run here: https://github.com/OFFIS-ESC/constellation-analyzer/actions/runs/19229156468 ). Why is that? (vibe-kanban b6717985) 2025-11-10 12:13:07 +01:00
Jan-Henrik Bruhn
343dcd090a feat: add comprehensive test infrastructure and CI/CD pipelines
Implements complete testing setup with Vitest and Testing Library,
including unit tests for all Zustand stores and CI/CD automation.

Test Infrastructure:
- Vitest configuration with JSDOM environment
- Testing Library for React component testing
- Test setup with mocks for React Flow and browser APIs
- Comprehensive test suite for all 10 Zustand stores

Store Tests Added:
- bibliographyStore.test.ts: Bibliography entry management
- editorStore.test.ts: Document editor state and operations
- graphStore.test.ts: Graph state and node/edge operations
- historyStore.test.ts: Undo/redo functionality
- panelStore.test.ts: Panel visibility and state management
- searchStore.test.ts: Search functionality and filters
- settingsStore.test.ts: Application settings persistence
- timelineStore.test.ts: Timeline state management
- toastStore.test.ts: Toast notification system
- workspaceStore.test.ts: Workspace and document operations

CI/CD Pipelines:
- New CI workflow for PRs and pushes to main/develop
- Enhanced deployment workflow with test execution
- Automated linting, testing, and type checking
- GitHub Actions integration with artifact deployment

Build Configuration:
- Updated Vite config for test support
- Test scripts in package.json (test:run, test:ui, test:watch)
- Type checking integrated into build process

Documentation:
- Architecture review with recommendations
- Test documentation and patterns guide

All tests passing with comprehensive coverage of store functionality,
edge cases, and error handling scenarios.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-10 11:52:40 +01:00
Jan-Henrik Bruhn
60d13eda19 fix: remove unused eslint-disable directives
Remove three unnecessary eslint-disable comments for @typescript-eslint/no-unused-vars
that were no longer needed, as the destructured variables are actually used.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-21 11:49:33 +02:00
Jan-Henrik Bruhn
59096a5644 feat: maximize minimized groups on double-click
Add double-click handler for minimized groups to quickly maximize them,
providing a faster alternative to using the context menu.

Implementation:
- Add handleNodeDoubleClick callback in GraphEditor
- Check if double-clicked node is a minimized group
- Call toggleGroupMinimized to maximize the group
- Wire handler to ReactFlow's onNodeDoubleClick prop

This improves UX by allowing users to quickly expand minimized groups
to see and edit individual relations within them.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-21 11:47:58 +02:00
Jan-Henrik Bruhn
ace816f2a5 feat: aggregate multiple relations between minimized groups
Implements visual aggregation of edges when multiple relations exist
between two minimized groups, preventing overlapping edges and improving
graph readability.

Key features:
- Aggregate edges between minimized groups using normalized keys (sorted
  node IDs) so bidirectional edges (A->B and B->A) merge together
- Display single neutral-styled edge (dark gray, solid, no arrows) with
  relation counter badge showing "X relations"
- Disable context menu and use special info panel for aggregated edges
- Hide individual edge type labels when aggregated
- Store all original relation data in aggregatedRelations array

Implementation details:
- GraphEditor: Use Map to deduplicate edges with bidirectional key for
  group-to-group connections, attach aggregatedCount metadata
- CustomEdge: Detect aggregated edges and apply neutral styling (#4b5563
  color, undirected/no arrows, no type label)
- RightPanel: Show "Aggregated Relations" info panel instead of
  EdgeEditorPanel for aggregated edges
- Context menu: Skip aggregated edges to prevent editing synthetic edges

Users can maximize groups to see and edit individual relations. This
keeps the graph clean when working with minimized groups while preserving
all underlying relationship data.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-21 11:45:27 +02:00
Jan-Henrik Bruhn
7c49ad0baa feat: render edges between minimized groups with floating edges
Implements proper edge rendering when both source and target actors are
in minimized groups. Edges are now correctly rerouted to connect the
minimized group nodes with dynamic floating edge calculations.

Key changes:
- Add edge rerouting logic to redirect edges from hidden actors to their
  parent minimized groups
- Implement edge deduplication to prevent multiple edges between same
  groups when multiple actors have connections
- Fix floating edge calculations with fallback dimensions to prevent NaN
- Use canonical parentId property instead of actorIds array for accuracy
- Create constants.ts for MINIMIZED_GROUP_WIDTH/HEIGHT (220x80)
- Replace magic numbers throughout codebase with named constants
- Remove sourceHandle/targetHandle properties when routing to groups to
  avoid React Flow validation errors

Technical details:
- GraphEditor: Build actor-to-group mapping using parentId, deduplicate
  edges with Map keyed by source_target, strip handle properties
- CustomEdge: Use floating edge params for minimized groups on both ends
- edgeUtils: Add fallback dimensions and division-by-zero guards in
  getNodeIntersection and getEdgePosition
- graphStore: Use constants for minimized group dimensions

Edges between minimized groups now render with proper Bezier curves and
dynamic connection points that adapt to group positions.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-21 11:28:48 +02:00
Jan-Henrik Bruhn
3b7497ec99 feat: add multi-select properties panel with bulk operations
Implements comprehensive multi-selection support for bulk operations on
actors, relations, and groups.

Features:
- New MultiSelectProperties panel for 2+ selected elements
- Bulk operations: group, ungroup, delete, minimize/maximize
- Add actors to existing groups (expands group bounds)
- Reverse relation directions
- Change directionality for multiple relations
- Immediate UI feedback with local state for directionality
- Standardized panel layout with scrollable content and footer

Fixes:
- Group positioning: actors stay at absolute positions when added/removed
- Minimize/maximize: properly stores/restores dimensions and visibility
- Position conversion between absolute and relative coordinates
- Type-safe width/height handling for group dimensions

UI Consistency:
- All property panels use fragment wrapper pattern
- Scrollable content area with px-3 py-3 padding
- Fixed footer section with action buttons
- Consistent button styles across panels

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-21 10:57:07 +02:00
Jan-Henrik Bruhn
5bfd3029e1 fix: ensure edge labels stay above selected groups
Edge labels were appearing behind edges when a group was selected
because React Flow elevates selected nodes to z-index ~1000, bringing
their child edges above the edge labels.

Solution:
- Increased edge label z-index from 1000 to 1100
- Ensures labels always render on top of edges, even in selected groups

The issue occurred because:
1. Edge labels use EdgeLabelRenderer (separate overlay layer)
2. When groups are selected, React Flow increases their z-index
3. Group edges (SVG elements) inherit the elevated z-index
4. Edge labels at z-index 1000 were below the elevated edges

By setting edge labels to z-index 1100, they now consistently
render above all edges regardless of selection state.

Benefits:
-  Edge labels always readable, even with selected groups
-  Consistent label visibility across all interaction states
-  Better UX for understanding relationships in groups

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 15:24:41 +02:00
Jan-Henrik Bruhn
178292435f feat: apply custom background colors to maximized groups
Maximized groups now display their assigned background color instead
of using the hardcoded default gray color.

Changes:

GroupNode.tsx:
- Added background color overlay div in maximized state
- Uses data.color property (same as minimized groups)
- Positioned absolutely to fill entire group area
- Set pointerEvents: 'none' to allow interaction with children

index.css:
- Changed .react-flow__node-group background to transparent
- Changed .react-flow__node-group.selected background to transparent
- Inner div now controls the background color

This ensures visual consistency between minimized and maximized
groups - both now respect the user-selected color from the group
properties panel.

Benefits:
-  Visual consistency between group states
-  User-selected colors are always visible
-  Better visual organization with color-coded groups
-  Matches user expectations for group appearance

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 15:23:01 +02:00
Jan-Henrik Bruhn
4b60c4b7b2 docs: add comprehensive JSDoc for all state sync points
Implements Phase 6.1 of the state management refactoring plan.

Added detailed documentation for all 5 critical sync points in the
state management architecture, making data flow explicit and traceable.

Sync Points Documented:

1. Document → graphStore (useActiveDocument.ts:67-80)
   - When: Active document switches
   - What: Loads timeline state into editor
   - Direction: ConstellationDocument → graphStore

2. graphStore → timeline current state (useActiveDocument.ts:197-214)
   - When: Graph changes detected
   - What: Updates timeline's current state
   - Direction: graphStore → timeline.states[currentStateId]

3. timeline → document (workspaceStore.ts:789-818)
   - When: Document save
   - What: Serializes timeline to storage
   - Direction: timelineStore → document.timeline → localStorage

4. document types → graphStore (workspaceStore.ts:1018-1034)
   - When: Type management operations
   - What: Syncs types to editor if active
   - Direction: document → graphStore (if active)

5. timeline → graphStore (timelineStore.ts:286-311)
   - When: Timeline state switch
   - What: Loads state's graph into editor
   - Direction: timeline.states[targetId] → graphStore

Each sync point includes:
-  Visual separator for easy identification
-  Trigger condition (when it occurs)
-  Data being synchronized (what changes)
-  Source of truth clarification
-  Data flow direction
-  Context and rationale

Benefits:
- Clear understanding of data flow through the system
- Easier onboarding for new developers
- Prevents accidental breaking of sync relationships
- Makes debugging state issues much faster

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 15:17:36 +02:00
Jan-Henrik Bruhn
f29c55a1b8 fix: improve minimized group label contrast and typography
Applied luminosity-based contrast color calculation to minimized
group labels, matching the same accessibility approach used for
actor nodes.

Changes:
- Added rgbToHex() helper to convert rgb/rgba colors to hex format
- Import getContrastColor() from colorUtils
- Calculate text color based on background luminosity
- Apply calculated color to both label and subtitle

Typography updates to match actor nodes:
- Main label: text-base font-bold (was text-sm font-semibold)
- Subtitle: text-xs font-medium with 0.7 opacity (was text-gray-600)
- Both use dynamic textColor instead of fixed gray values

Benefits:
-  Readable labels on both light and dark group backgrounds
-  Consistent typography between actor nodes and group nodes
-  Follows WCAG 2.0 luminance contrast standards
-  Professional appearance across all color choices

The minimized group labels now automatically switch between white
and black text based on the background color's luminosity, ensuring
optimal readability in all cases.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 15:12:42 +02:00
Jan-Henrik Bruhn
60748a2235 feat: improve label deletion atomicity with immutable timeline updates
Implements Phase 5 of the state management refactoring plan.

Problem:
The previous implementation mutated timeline states in place when
removing label references from nodes and edges. This created a risk
of partial state corruption if an error occurred mid-operation.

Solution:
Refactored deleteLabelFromDocument to use immutable updates:

1. Build new timeline states Map with cleaned labels
   - Map over all states, nodes, and edges immutably
   - Create new objects instead of mutating existing ones

2. Atomic timeline swap
   - Replace entire timeline structure at once
   - Either fully succeeds or fully fails (no partial state)

3. Simplified rollback
   - Capture entire original timeline (shallow copy)
   - Restore complete timeline on failure
   - No need to patch individual nodes/edges

Benefits:
-  True atomicity - timeline updates are all-or-nothing
-  Cleaner rollback - restore entire structure, not individual items
-  Reduced mutation risk - immutable pattern throughout
-  Better data integrity across timeline states
-  Consistent with React/Zustand best practices

The operation now builds a completely new timeline structure and
swaps it atomically, ensuring that label deletion either fully
succeeds across all timeline states or fully rolls back.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 15:10:14 +02:00
Jan-Henrik Bruhn
d03be68860 fix: update group minimize/maximize button label in real-time
The group property panel's minimize/maximize button label was not
updating after clicking because it relied on the selectedGroup prop
passed from the parent, which was set once on selection and never
updated when the group's minimized state changed in the store.

Changes:
- Added currentGroup lookup from the store's groups array
- Use currentGroup (from store) for minimized state checks
- Button label now reflects the actual current state

This ensures the button always shows the correct action based on
the group's actual minimized state in the store, not the stale
prop value.

Fixes the issue where clicking "Minimize Group" would minimize the
group visually but the button would still say "Minimize Group"
instead of changing to "Maximize Group".

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 15:07:54 +02:00
Jan-Henrik Bruhn
1059c05242 feat: add atomic transaction pattern to type management operations
Implements Phase 3.1 of the state management refactoring plan.

Changes:
- Added executeTypeTransaction() helper in workspaceStore for atomic
  operations with automatic rollback on failure
- Refactored all 9 type/label operations to use transaction pattern:
  * Node types: add, update, delete
  * Edge types: add, update, delete
  * Labels: add, update, delete
- Each operation now captures original state and can rollback if:
  * localStorage quota is exceeded (QuotaExceededError)
  * Any other error occurs during the multi-step process

Transaction pattern ensures atomicity:
1. Capture original state (for rollback)
2. Execute operation (update doc, save storage, mark dirty, sync)
3. If error: rollback to original state and show error toast
4. Rollback restores: document state, isDirty flag, graphStore sync

Label deletion includes comprehensive rollback:
- Restores label array
- Restores label references in all timeline states (nodes and edges)
- Syncs graphStore if active document

Benefits:
- Prevents partial failures leaving state inconsistent
- Handles storage quota errors gracefully
- Provides user feedback via toast notifications
- Maintains data integrity across all stores

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 15:05:19 +02:00
Jan-Henrik Bruhn
6a56b94477 refactor: move snapshot creation to historyStore (architectural fix)
Fixes architectural issue where snapshot logic was duplicated between
hook and store layers.

Problem Identified:
- useDocumentHistory (hook) created snapshots with duplicate logic
- timelineStore (store) created snapshots with duplicate logic
- timelineStore couldn't call useDocumentHistory (hooks can't be used in stores)
- Snapshot creation logic scattered across codebase

Root Cause:
Timeline operations happen in the store layer but history was managed
via a hook. This architectural mismatch forced duplication.

Solution:
- Add pushToHistory() method to historyStore
- Single source of truth for snapshot creation
- Both hook and store layers call historyStore.pushToHistory()
- Eliminates all snapshot creation duplication

Changes:
- Add historyStore.pushToHistory() - high-level API with snapshot creation
- Keep historyStore.pushAction() - low-level API (for future use)
- Update useDocumentHistory to call historyStore.pushToHistory()
- Update timelineStore pushDocumentHistory() to call historyStore.pushToHistory()
- Remove createDocumentSnapshot import from useDocumentHistory
- Remove createDocumentSnapshot import from timelineStore

Architecture:
Before:
  Hook (useDocumentHistory) → creates snapshot → historyStore.pushAction()
  Store (timelineStore) → creates snapshot → historyStore.pushAction()
  [Duplication at 2 call sites]

After:
  Hook (useDocumentHistory) → historyStore.pushToHistory() → creates snapshot
  Store (timelineStore) → historyStore.pushToHistory() → creates snapshot
  [Single implementation in historyStore]

Benefits:
-  Zero snapshot creation duplication
-  Proper architectural separation (store handles snapshots, not hook/store)
-  Single source of truth (historyStore.pushToHistory)
-  Timeline and graph operations use identical snapshot logic
-  Easy to modify snapshot behavior in future (one place)

Impact:
- No breaking changes
- No behavior changes
- Better code organization
- Easier maintenance

Related: Phase 2.1 architectural improvement

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 12:24:52 +02:00
Jan-Henrik Bruhn
0ef81260cc refactor: centralize snapshot creation logic (Phase 2.1)
Eliminates duplicate snapshot logic between useDocumentHistory and timelineStore.

Problem:
- useDocumentHistory created snapshots reading types from document (correct)
- timelineStore created snapshots reading types from graphStore (incorrect)
- ~30 lines of duplicated snapshot creation logic
- Risk of inconsistent behavior between graph and timeline operations

Solution:
- Created createDocumentSnapshot() in documentUtils.ts
- Single source of truth for snapshot creation
- Always reads types/labels from document (correct source)
- Syncs timeline current state before snapshot (ensures latest data)

Changes:
- Add createDocumentSnapshot() to src/stores/workspace/documentUtils.ts
- Update useDocumentHistory.ts to use centralized helper
- Update timelineStore.ts pushDocumentHistory() to use centralized helper
- Remove duplicate snapshot creation code (~30 lines)

Benefits:
-  Consistent snapshot behavior everywhere
-  Types always read from document (source of truth)
-  Easier to maintain (single implementation)
-  Fixes potential bug where timelineStore had stale types
-  Better code organization

Impact:
- No breaking changes
- Undo/redo behavior unchanged for users
- Timeline operations now have correct type information
- Safer - impossible for snapshot logic to diverge

Testing:
- TypeScript compilation passes
- No runtime behavior changes expected
- Snapshot structure unchanged

Related: Phase 2.1 of STATE_MANAGEMENT_REFACTORING_PLAN.md

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 12:19:58 +02:00
Jan-Henrik Bruhn
0ac15353ae refactor: remove legacy persistence code and migration system (Phase 1)
Removes ~350 lines of legacy code from single-document system.

Changes:
- Delete src/stores/persistence/loader.ts (legacy load functions)
- Delete src/stores/persistence/saver.ts (legacy save functions)
- Delete src/stores/workspace/migration.ts (no migration support needed)
- Create src/stores/workspace/documentUtils.ts (consolidated utilities)

Extracted and consolidated:
- validateDocument() - document structure validation
- getCurrentGraphFromDocument() - extract current graph from timeline
- deserializeGraphState() - convert storage to runtime format
- serializeActors/Relations/Groups() - convert runtime to storage format
- createDocument() - create new document with initial timeline state

Updated imports:
- workspaceStore.ts: use documentUtils instead of loader/saver
- useActiveDocument.ts: use documentUtils.getCurrentGraphFromDocument
- fileIO.ts: use documentUtils for serialization/validation
- persistence.ts: use documentUtils.validateDocument
- graphStore.ts: remove legacy loadGraphState, start with empty state

Removed legacy storage keys:
- LEGACY_GRAPH_STATE from workspace/persistence.ts
- hasLegacyData() function (no longer needed)
- References to LEGACY_GRAPH_STATE in cleanupStorage.ts

Impact:
- No breaking changes for existing users (workspace format unchanged)
- Cleaner code organization (all document utils in one place)
- No migration support (old documents will not be loaded)
- Reduced technical debt (~350 lines removed)

Related: Phase 1 of STATE_MANAGEMENT_REFACTORING_PLAN.md

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 12:15:25 +02:00
Jan-Henrik Bruhn
3f24e4be0b fix: correct history timing in createGroupWithActors for proper undo behavior
Fixes incorrect undo behavior when creating groups with actors.

Previously, history was captured AFTER mutations, causing undo to restore
a state that included the group. This fix moves history capture to BEFORE
mutations, making it consistent with all other operations (addNode,
deleteNode, addEdge, etc.).

Changes:
- Move pushToHistory() call before mutations in createGroupWithActors
- Add detailed comment explaining the timing
- Update refactoring plan with implementation status

Impact:
- Undo now correctly removes groups and ungroups actors
- Behavior consistent with all other operations
- No breaking changes to existing functionality

Testing:
- TypeScript compilation verified (npx tsc --noEmit)
- Manual testing plan documented in PHASE_4_1_TEST_PLAN.md
- 6 test cases defined for comprehensive verification

Related: Phase 4.1 of STATE_MANAGEMENT_REFACTORING_PLAN.md

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 12:09:09 +02:00
Jan-Henrik Bruhn
b1e634d3c4 feat: add group minimize/maximize with floating edges and React Flow v12
Implements comprehensive group minimize/maximize functionality and migrates
to React Flow v12 (@xyflow/react) with improved edge routing.

## Group Minimize/Maximize Features:
- Minimized groups render as compact 220×80px solid rectangles
- Original dimensions preserved in metadata and restored on maximize
- Child actors hidden (not filtered) to prevent React Flow state issues
- Solid color backgrounds (transparency removed for minimized state)
- Internal edges filtered out when group is minimized
- Dimension sync before minimize ensures correct size on maximize

## Floating Edges:
- Dynamic edge routing for connections to/from minimized groups
- Edges connect to closest point on minimized group border
- Regular actors maintain fixed handle connections
- Smooth transitions when toggling group state

## React Flow v12 Migration:
- Updated package from 'reactflow' to '@xyflow/react'
- Changed imports to named imports (ReactFlow is now named)
- Updated CSS imports to '@xyflow/react/dist/style.css'
- Fixed NodeProps/EdgeProps to use full Node/Edge types
- Added Record<string, unknown> to data interfaces for v12 compatibility
- Replaced useStore(state => state.connectionNodeId) with useConnection()
- Updated nodeInternals to nodeLookup (renamed in v12)
- Fixed event handler types for v12 API changes

## Edge Label Improvements:
- Added explicit z-index (1000) to edge labels via EdgeLabelRenderer
- Labels now properly render above edge paths

## Type Safety & Code Quality:
- Removed all 'any' type assertions in useDocumentHistory
- Fixed missing React Hook dependencies
- Fixed unused variable warnings
- All ESLint checks passing (0 errors, 0 warnings)
- TypeScript compilation clean

## Bug Fixes:
- Group drag positions now properly persisted to store
- Minimized group styling (removed dotted border, padding)
- Node visibility using 'hidden' property instead of array filtering
- Dimension sync prevents actors from disappearing on maximize

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 11:52:44 +02:00
Jan-Henrik Bruhn
f5adbc8ead feat: add resizable actor grouping with full undo/redo support
Implements visual grouping of actors with context menu operations,
resizable containers, and complete history tracking integration.

Features:
- Create groups from multiple selected actors via context menu
- Groups visualized as resizable containers with child nodes
- Ungroup actors (non-destructive) or delete group with actors
- Right-click context menu with group-specific operations
- Dedicated GroupEditorPanel for group properties
- Smart minimum size constraint based on child node positions
- Full undo/redo support for group operations and resizes

Technical Implementation:
- GroupNode component with React Flow NodeResizer integration
- Atomic createGroupWithActors operation for consistent history
- Parent-child relationship using React Flow v11 parentId pattern
- Groups stored separately from actors in graphStore
- Fixed history tracking to sync graphStore before snapshots
- Resize tracking to prevent state sync loops during interaction
- Dynamic minimum dimensions to keep children inside bounds
- Sanitization of orphaned parentId references on state load

History Fixes:
- pushToHistory now syncs timeline with graphStore before snapshot
- Prevents missing groups/nodes in history states
- Ensures undo/redo correctly restores all graph elements
- Atomic state updates to avoid React Flow processing stale state

Storage & Persistence:
- Groups saved in timeline states and document structure
- Safe JSON serialization to prevent prototype pollution
- Cleanup utilities for removing __proto__ from localStorage

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-18 20:06:59 +02:00
Jan-Henrik Bruhn
59e30cca8a refactor: update Timeline dialogs to match application design system
Replaces Material-UI components in Timeline dialogs with custom Tailwind
CSS styling to match the application's standard dialog design.

Changes:
- RenameStateDialog: Replaced MUI Dialog, TextField, and Button components
  with custom styled elements matching ConfirmDialog/InputDialog pattern
- CreateStateDialog: Converted all MUI form components (TextField, Checkbox,
  Button) to native HTML elements with Tailwind styling
- Added consistent visual design with large icons, gray backgrounds, and
  proper spacing
- Improved keyboard handling (Enter/Escape) with proper event listeners
- Enhanced focus management with auto-select on input fields
- Maintained all existing functionality while improving UI consistency

Both dialogs now feature:
- Icon-based visual hierarchy with 48px icons
- Consistent button styling (gray cancel, blue confirm)
- Proper disabled states and hover effects
- Gray-50 background on action button areas
- Backdrop click to close

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-17 21:14:32 +02:00
Jan-Henrik Bruhn
ef16b9d060 feat: add PubMed and software citation format support
Extends bibliography smart import capabilities with additional citation.js plugins:
- @citation-js/plugin-pubmed: Support for PubMed IDs (PMID/PMCID)
- @citation-js/plugin-software-formats: Support for software citations (CFF, GitHub, npm, Zenodo)

Updates smart-parser.ts to recognize and validate Zenodo DOI format (10.5281/zenodo.xxxxx).
Improves input type detection for better user feedback.

Includes code formatting improvements to bibliographyStore.ts for consistency.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-17 15:18:09 +02:00
Jan-Henrik Bruhn
14ccb2da5b feat: expand smart import to support additional citation formats
Adds detection and parsing support for more input formats that
citation.js can handle, making the smart import feature more powerful.

New supported formats:
- RIS format (common export from EndNote, RefWorks, Zotero)
- PubMed Central IDs (PMCID) in addition to existing PMID support
- Wikidata IDs (e.g., Q12345 or wikidata:Q12345)
- Zenodo records (URLs and IDs like zenodo.1234567)
- Citation File Format (CFF) for software citations

Benefits:
- Users can now paste RIS exports from reference managers directly
- Support for Wikidata citations enables citing entities/concepts
- Zenodo support facilitates citing datasets and research outputs
- CFF support enables proper software citation
- Enhanced format detection provides better user feedback

The smart parser now detects all these formats automatically and
displays the appropriate format hint to users. All formats are
processed through citation.js's robust parsing engine.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-17 14:51:06 +02:00