Skip to content

feat: Make board angle part of session state for synchronized angle changes#664

Open
marcodejongh wants to merge 6 commits intomainfrom
claude/configurable-board-angle-E4IS6
Open

feat: Make board angle part of session state for synchronized angle changes#664
marcodejongh wants to merge 6 commits intomainfrom
claude/configurable-board-angle-E4IS6

Conversation

@marcodejongh
Copy link
Owner

  • Add angle field to Session type in GraphQL schema (computed from boardPath)
  • Add AngleChanged event to SessionEvent union for broadcasting angle changes
  • Add updateSessionAngle mutation for changing session angle
  • Add updateSessionAngle method to RoomManager that updates boardPath in both Postgres and Redis
  • Add updateBoardPath method to RedisSessionStore
  • Update PersistentSessionContext to handle AngleChanged events and update URL for all users
  • Update AngleSelector to call updateSessionAngle mutation when in a session

When a user changes the board angle in a session, all session members' UIs
now update to reflect the new angle. The angle is stored as part of the
session's boardPath, ensuring consistency when joining a session.

https://claude.ai/code/session_01XKTGqyTM9634sT2tL1khNM

…hanges

- Add `angle` field to Session type in GraphQL schema (computed from boardPath)
- Add `AngleChanged` event to SessionEvent union for broadcasting angle changes
- Add `updateSessionAngle` mutation for changing session angle
- Add `updateSessionAngle` method to RoomManager that updates boardPath in both Postgres and Redis
- Add `updateBoardPath` method to RedisSessionStore
- Update PersistentSessionContext to handle AngleChanged events and update URL for all users
- Update AngleSelector to call updateSessionAngle mutation when in a session

When a user changes the board angle in a session, all session members' UIs
now update to reflect the new angle. The angle is stored as part of the
session's boardPath, ensuring consistency when joining a session.

https://claude.ai/code/session_01XKTGqyTM9634sT2tL1khNM
@vercel
Copy link

vercel bot commented Feb 5, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
boardsesh Building Building Preview, Comment Feb 6, 2026 9:50am

Request Review

@claude
Copy link

claude bot commented Feb 5, 2026

Claude Review

⚠️ Needs attention - URL path matching could incorrectly replace non-angle numeric segments, missing error handling for user initiator, and no tests for new functionality.

Issues

  1. Bug: URL angle matching is too greedy (packages/web/app/components/persistent-session/persistent-session-context.tsx:459-465)

    • The regex /^\d+$/ will match any numeric segment, not just the angle. For paths like /kilter/1/12/1,2,3/40/play/123e4567-e89b-..., it could incorrectly match a UUID segment or other numeric IDs if the path structure varies.
    • The non-session path in angle-selector.tsx:81 uses findIndex to match the exact current angle value, which is safer.
  2. Missing error feedback (packages/web/app/components/board-page/angle-selector.tsx:75-77)

    • When updateSessionAngle fails, the error is only logged to console. Users in a session won't know why the angle didn't change. Consider showing a user-facing notification.
  3. Race condition potential (packages/web/app/components/persistent-session/persistent-session-context.tsx:453-478)

    • The URL navigation happens outside the setSession callback, using stale pathname from closure. If multiple rapid angle changes occur, the navigation could use outdated path state.
  4. No tests added

    • New backend mutation updateSessionAngle, Redis updateBoardPath, and frontend event handling lack test coverage. The backend has existing test files (e.g., session-persistence.test.ts) that should be extended.
  5. Documentation not updated

    • The docs/websocket-implementation.md documents session events and queue synchronization but doesn't mention the new AngleChanged event or angle synchronization behavior. The session events table should be updated.

When the session angle is updated, the backend now:
- Fetches updated climb stats (difficulty, quality, ascent count) for each queue item at the new angle
- Updates the queue state with the refreshed climb data
- Sends a FullSync event so all clients update their queue display

This ensures that when someone changes the board angle during a session,
all users see the correct difficulty grades for climbs in the queue.

https://claude.ai/code/session_01XKTGqyTM9634sT2tL1khNM
@claude
Copy link

claude bot commented Feb 5, 2026

Claude Review

Ready to merge - Minor issues noted below, but nothing blocking.

Issues

  1. packages/backend/src/graphql/resolvers/sessions/mutations.ts:346 - Angle validation is too restrictive. Kilter boards support angles from 0° to 70°, but some boards (like Tension) may go higher. The hardcoded 90 limit is fine for now, but the validation message could be misleading if board-specific limits differ.

  2. packages/web/app/components/persistent-session/persistent-session-context.tsx:448-463 - The URL navigation logic for AngleChanged uses a regex /^\d+$/ to find the angle segment, but this will match the first numeric segment from the end - which could be wrong if there are trailing numeric segments after the angle in future URL structures. The code assumes the last numeric segment is always the angle.

  3. packages/backend/src/graphql/resolvers/sessions/mutations.ts:363-409 - When boardParams is null (due to invalid boardPath), the angle still gets updated in the database but queue items keep stale stats. Consider logging a warning when parseBoardPath returns null but the session has queue items.

Documentation

The docs/websocket-implementation.md documents session path handling (lines 201-232) and mentions angle is part of boardPath. The new AngleChanged event type should be added to the Event Types table in the Session Management section for completeness, but this is minor.

- Add warning log when boardPath cannot be parsed but queue items exist
- Fix URL navigation to use fixed position (index 5) for angle segment
  instead of finding last numeric segment which could match wrong segments
- Add debug logging when angle segment cannot be found in pathname

https://claude.ai/code/session_01XKTGqyTM9634sT2tL1khNM
@claude
Copy link

claude bot commented Feb 5, 2026

Claude Review

Ready to merge - Minor issues noted below, but nothing blocking.

Issues

  1. Missing error handling for non-existent sessions (packages/backend/src/graphql/resolvers/sessions/mutations.ts:338-343)

    • updateSessionAngle checks if ctx.sessionId exists but doesn't verify the session actually exists before calling roomManager.updateSessionAngle(). The error would come from room-manager, but a more informative error at the mutation level would be better UX.
  2. Race condition potential in queue update (packages/backend/src/graphql/resolvers/sessions/mutations.ts:378-394)

    • Gets queue state, updates items in parallel, then saves with original version. If another client modifies the queue between getQueueState and updateQueueState, the version check could cause issues. The version mismatch should be handled gracefully.
  3. Hardcoded angle validation range (packages/backend/src/graphql/resolvers/sessions/mutations.ts:346)

    • Angle validation 0-90 degrees may not match actual board angle ranges (some boards support negative angles or angles > 90). Consider using board-specific validation or fetching valid angles from the database.
  4. Default angle magic number (packages/backend/src/graphql/resolvers/sessions/type-resolvers.ts:17)

    • Falls back to 40 when parsing fails. This should be documented or made configurable, and ideally log a warning when defaulting.
  5. Missing tests for the new updateSessionAngle mutation and AngleChanged event handling. Given the existing test coverage in packages/backend/src/__tests__/session-persistence.test.ts, tests for angle synchronization should be added.

  6. Documentation not updated - docs/websocket-implementation.md documents session events but doesn't mention the new AngleChanged event. The "Session Path Continuity" section mentions angle doesn't trigger reconnection, but should note angle changes are now synced via AngleChanged events.

@claude
Copy link

claude bot commented Feb 6, 2026

Claude Review

Ready to merge - Minor issues noted below, but nothing blocking.

Issues

  1. Missing rate limiting on updateSessionAngle mutation (packages/backend/src/graphql/resolvers/sessions/mutations.ts:340) - Other mutations like joinSession and createSession apply rate limiting with applyRateLimit(ctx, 10), but updateSessionAngle does not. This could allow abuse through rapid angle changes.

  2. No authentication check on updateSessionAngle (packages/backend/src/graphql/resolvers/sessions/mutations.ts:340) - The mutation only checks for session membership (ctx.sessionId), but doesn't verify if the user is authenticated. Consider whether unauthenticated users in a session should be able to change the angle for everyone.

  3. Race condition potential in angle updates (packages/backend/src/graphql/resolvers/sessions/mutations.ts:357-393) - The code reads queue state, updates items, then saves. If another mutation modifies the queue between read and write, changes could be lost. The version parameter is passed but concurrency conflict handling isn't visible in this diff.

  4. Missing error handling for user in AngleSelector (packages/web/app/components/board-page/angle-selector.tsx:73-77) - The catch block only logs the error. User should get feedback when the angle change fails (e.g., toast notification).

Documentation

The docs/websocket-implementation.md describes session events but should be updated to include:

  • The new AngleChanged event in the SessionEvent union
  • How angle changes propagate to all session members
  • The queue update flow when angle changes

Tests

No tests were added for the new updateSessionAngle mutation. Consider adding tests for:

  • Angle validation (0-90 range)
  • Queue item stats update when angle changes
  • AngleChanged event broadcasting
  • Error cases (not in session, invalid angle)

…r angle changes

Backend changes:
- Add rate limiting to updateSessionAngle mutation (limit: 10 per window)
- Import VersionConflictError and MAX_RETRIES for retry logic
- Implement retry loop with optimistic locking for queue updates
- Cache already-updated items to avoid redundant database queries on retry
- Broadcast AngleChanged event before queue update to ensure URL updates happen
  even if queue update fails

Frontend changes:
- Add toast notification when angle change fails in session
- Keep drawer open on error so user can retry

The retry logic handles race conditions where another mutation modifies the
queue between reading and writing. When a version conflict occurs, the code
re-fetches the queue state and only updates new items that weren't in our cache.

https://claude.ai/code/session_01XKTGqyTM9634sT2tL1khNM
@claude
Copy link

claude bot commented Feb 6, 2026

Claude Review

Ready to merge - Minor issues noted below, but nothing blocking.

Issues

  1. Inconsistent angle parsing logic - type-resolvers.ts:23-26 parses angle from the last path segment, but parseBoardPath() in mutations.ts:67 parses it from index 4. If boardPath has trailing segments (e.g., from view routes), the type resolver would return wrong angle.

  2. Missing authentication check - updateSessionAngle mutation at mutations.ts:341 uses applyRateLimit() but does not call requireAuthenticated() unlike joinSession and createSession. Any connected user (authenticated or not) can change the angle.

  3. Hardcoded angle index in URL parsing - persistent-session-context.tsx:459 uses a hardcoded ANGLE_INDEX = 5 which may not work for all route structures. The existing angle-selector.tsx uses findIndex() to locate the current angle, which is more resilient.

  4. No tests - This feature adds significant new functionality (mutation, event broadcasting, queue updates) but includes no tests.

Documentation

The PR changes affect the session system documented in docs/websocket-implementation.md. The doc mentions Different angle as adjustable during session but does not describe the new AngleChanged event or updateSessionAngle mutation. Consider adding a section about synchronized angle changes.

…ests

Fixes for code review feedback:

1. Consistent angle parsing:
   - Updated type-resolvers.ts to use index 4 (same as parseBoardPath)
   - Both now use filter(Boolean) to handle leading slashes consistently

2. Authentication check:
   - Use requireSession() helper for consistency with other mutations
   - Removed duplicate sessionId assignment

3. URL navigation robustness:
   - Use boardPath from event to reconstruct URL instead of hardcoded index
   - Properly handles trailing path segments (e.g., /list, /climb/uuid)
   - Added detailed debug logging when URL update fails

4. Added tests:
   - Test sessionTypeResolver.angle for various boardPath formats
   - Test URL segment extraction and reconstruction logic

https://claude.ai/code/session_01XKTGqyTM9634sT2tL1khNM
@claude
Copy link

claude bot commented Feb 6, 2026

Claude Review

Ready to merge - Minor issues noted below, but nothing blocking.

Issues

  1. packages/backend/src/services/room-manager.ts:631 - Assumes angle is always the last path segment. If boardPath has trailing slashes or extra segments, this will update the wrong segment. Consider using parseBoardPath logic for consistency.

  2. packages/backend/src/graphql/resolvers/sessions/mutations.ts:67-71 - parseBoardPath doesn't validate that layoutId, sizeId, and angle are valid numbers (could be NaN if parsing fails on non-numeric segments).

  3. packages/web/app/components/persistent-session/persistent-session-context.tsx:463 - URL update condition currentPathSegments.length > 5 silently fails for paths with exactly 5 segments (e.g., /kilter/1/10/1/40). The test at line 95-106 shows this works but the client code requires length > 5.

  4. Missing test coverage - Tests cover unit behavior of type resolvers and URL manipulation, but no integration tests for the actual updateSessionAngle mutation flow including database updates, Redis updates, and queue item stat updates.

Documentation

  • The WebSocket documentation (docs/websocket-implementation.md) already states angle changes don't trigger reconnection (line 221), which aligns with this feature. No documentation updates required.

- Fix room-manager to use index 4 with filter(Boolean) for angle parsing
- Add NaN validation to parseBoardPath in mutations
- Fix URL update condition from > 5 to >= 6 segments
- Add parseBoardPath helper tests with full coverage
- Add integration test stubs for updateSessionAngle mutation
- Improve debug logging in URL update warning

https://claude.ai/code/session_01XKTGqyTM9634sT2tL1khNM
@claude
Copy link

claude bot commented Feb 6, 2026

Claude Review

Ready to merge - Minor issues noted below, but nothing blocking.

Issues

  1. packages/backend/src/tests/session-angle.test.ts:17-51: Test duplicates the parseBoardPath implementation rather than importing and testing the actual function. Consider exporting parseBoardPath from mutations.ts and testing it directly.

  2. packages/backend/src/tests/session-angle.test.ts:213-218: Integration tests are marked as todo - the actual mutation logic (Postgres/Redis updates, version conflict handling, rate limiting) lacks test coverage.

  3. packages/backend/src/graphql/resolvers/sessions/mutations.ts:354-355: Angle validation hardcodes 0-90 range, but some boards may support different angle ranges. Consider making this configurable per board type.

Documentation

The docs/websocket-implementation.md documents session events and mentions that "angle is adjustable during session" (line 221), but does not document the new AngleChanged event or the updateSessionAngle mutation. Consider adding the new event to the Session Events table and documenting the angle sync behavior.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants