Skip to content

Conversation

@marcodejongh
Copy link
Owner

@marcodejongh marcodejongh commented Jan 23, 2026

Summary

  • Enable saving Moonboard climbs to the PostgreSQL database (requires authentication)
  • Add frames encoding utility to convert holds to database format
  • Update create form and bulk import to use API instead of IndexedDB
  • Add direct database query for listing Moonboard climbs (GraphQL doesn't support Moonboard yet)
  • Enable MOONBOARD_ENABLED feature flag by default
  • Fix Moonboard climb preview rendering in list view

Changes

Database & API

  • moonboard-config.ts: Add encodeMoonBoardHoldsToFrames utility and MOONBOARD_HOLD_STATE_CODES constants
  • saveClimb route: Accept Moonboard requests with holds format, convert to frames
  • moonboard-climbs-db.ts: Remove IndexedDB functions (keep only convertOcrHoldsToMap utility)

Create & Import UI

  • moonboard-create-climb-form.tsx: Require auth, use API, restructure UI with header containing save button
  • moonboard-bulk-import.tsx: Similar auth and API changes for batch saves

List View & Rendering

  • list/page.tsx: Add direct database query for Moonboard climbs with frames parsing
  • list/layout.tsx & consolidated-board-config.tsx: Add Moonboard handling with getMoonBoardDetails
  • board-renderer.tsx: Delegate to MoonBoardRenderer when board_name is 'moonboard'
  • moonboard-renderer.tsx: Update to use standard LitUpHoldsMap type with state field
  • types.ts: Add layoutFolder and holdSetImages fields to BoardDetails

Type Standardization

  • moonboard-renderer/types.ts: Remove MoonBoardLitUpHoldsMap, use standard LitUpHoldsMap
  • use-moonboard-create-climb.ts: Update to use HoldState ('STARTING', 'HAND', 'FINISH')
  • moonboard-import-card.tsx & moonboard-edit-modal.tsx: Update to use LitUpHoldsMap

Context & Data Fetching

  • QueueContext.tsx & use-queue-data-fetching.tsx: Skip GraphQL operations for Moonboard

Test plan

  • Create a Moonboard climb while logged in - verify it saves to database
  • Create a Moonboard climb while logged out - verify it prompts to log in
  • Use bulk import while logged in - verify batch saves to database
  • After saving, verify navigation back to list page
  • Verify saved climbs appear in the list with working previews
  • Verify Moonboard climb previews render correctly in list view
  • Verify Aurora boards (Kilter/Tension) still work correctly
  • Run build to ensure no type errors

🤖 Generated with Claude Code

- Add frames encoding utility (`encodeMoonBoardHoldsToFrames`) to convert
  holds to the `p{holdId}r{roleCode}` format used by the database
- Update saveClimb API to accept Moonboard requests with holds format
  and convert to frames before saving
- Update MoonBoardCreateClimbForm to require authentication and save
  climbs via the API instead of IndexedDB
- Update MoonBoardBulkImport similarly for batch saves
- Add direct database query for Moonboard climbs in list page since
  GraphQL backend doesn't support Moonboard yet
- Skip GraphQL search and playlist operations for Moonboard to prevent
  errors
- Add getMoonBoardDetails helper throughout codebase to handle Moonboard
  board details separately from Aurora boards
- Enable MOONBOARD_ENABLED feature flag by default
- Remove IndexedDB saving functions (no longer needed)
- Restructure create form UI with header containing save button

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@vercel
Copy link

vercel bot commented Jan 23, 2026

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

Project Deployment Review Updated (UTC)
boardsesh Building Building Preview, Comment Jan 24, 2026 1:55am

Request Review

@claude
Copy link

claude bot commented Jan 23, 2026

Claude Review

⚠️ Needs attention - Incorrect Ant Design Alert prop usage will cause runtime issues.

Issues

  1. moonboard-bulk-import.tsx:249,313,333: Uses title prop on <Alert> but Ant Design Alert uses message prop for the title. This will cause the title text not to display.

  2. list/page.tsx:101-109 & list/layout.tsx:44-52: Duplicated getBoardDetailsForBoard helper function - consider extracting to a shared module.

  3. moonboard-bulk-import.tsx:160-189: Sequential API calls in a loop for bulk save - consider using Promise.all with batching for better performance, or providing user feedback during the save process (progress indicator).

  4. No tests: New database saving functionality and frames encoding lack test coverage.

Integrate MoonBoardRenderer into BoardRenderer to fix Moonboard climb
previews. BoardRenderer now delegates to MoonBoardRenderer when
board_name is 'moonboard', using the new layoutFolder and holdSetImages
fields in BoardDetails.

Standardize on LitUpHoldsMap type across all Moonboard components,
replacing the separate MoonBoardLitUpHoldsMap type. This ensures
consistent hold state representation using 'STARTING', 'HAND', 'FINISH'
instead of 'start', 'hand', 'finish'.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@claude
Copy link

claude bot commented Jan 23, 2026

Claude Review

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

Issues

  1. Missing CSS class - moonboard-create-climb-form.tsx:267 references styles.holdCountsBar but this class is missing from the CSS diff. The class exists in create-climb-form.module.css so this should work, but the class appears to have been added without being explicitly shown in the diff as a new addition.

  2. Duplicate helper function - getBoardDetailsForBoard is duplicated in both list/layout.tsx:44-52 and list/page.tsx:101-109. Consider extracting to a shared utility.

  3. Sequential API calls in bulk import - moonboard-bulk-import.tsx:165-185 saves climbs one at a time in a loop. For large imports, this could be slow. Consider batching or parallel requests with a limit.

  4. No setter username for Moonboard climbs - list/page.tsx:166 defaults to 'Unknown' for setterUsername. The user's display name from the session could be used instead when saving/displaying.

  5. Unused import - TextArea is imported at moonboard-create-climb-form.tsx:4 but the component using it is now hidden in the form at the bottom and could use the standard Input.TextArea inline.

Notes

  • The IndexedDB removal and migration to PostgreSQL looks correct
  • Type standardization from MoonBoardLitUpHoldsMap to LitUpHoldsMap is consistent throughout
  • No documentation updates needed as Moonboard is not covered in existing docs

All unified tables (board_climbs, board_climb_stats, board_difficulty_grades)
require board_type filtering since they store data for all board types.

Changes:
- create-climb-filters.ts: Add board_type to baseConditions and climbStats join
- search-climbs.ts: Add board_type to difficultyGrades join, remove unused imports
- get-climb.ts: Add board_type to climbs, climbStats, and difficultyGrades queries
- playlists/queries.ts: Add board_type to all unified table joins
- ticks/queries.ts: Fix TypeScript errors for nullable boolean fields

This fixes climb searches returning incorrect or missing difficulty grades.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@claude
Copy link

claude bot commented Jan 23, 2026

Claude Review

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

Issues

  1. Missing pagination for Moonboard climbs (packages/web/app/[board_name]/[layout_id]/[size_id]/[set_ids]/[angle]/list/page.tsx:229-241): The getMoonboardClimbs function ignores page offset and always returns results from the beginning. hasMore is always false and totalCount equals the returned count. Pagination controls will not work for Moonboard list view.

  2. N+1 API calls in bulk import (packages/web/app/components/moonboard-import/moonboard-bulk-import.tsx:163-193): Sequential fetch calls in a loop - consider batching or showing progress to user for large imports.

  3. No authentication check on saveClimb API route (packages/web/app/api/v1/[board_name]/proxy/saveClimb/route.ts:47-84): The route accepts any user_id in the request body without validating that the caller owns that session. An attacker could create climbs attributed to other users. Consider verifying session.user.id matches options.user_id.

  4. Type assertion safety (packages/web/app/lib/moonboard-config.ts:255,261,267): coord as MoonBoardCoordinate - if caller passes invalid coordinates, this will silently fail in coordinateToHoldId.

Tests

No tests included for the new functionality (grade conversion, frames encoding, bulk import, direct database queries).

Documentation

No documentation changes required - Moonboard is a new feature and docs/ focuses on WebSocket implementation which is unaffected.

marcodejongh and others added 4 commits January 24, 2026 11:22
- Unified Aurora and MoonBoard create climb forms into single component
- Added unified header with back button, title input, settings, and save
- Added grade selector for MoonBoard with proper difficulty IDs
- Added angle selector for MoonBoard (25° and 40° only)
- Added climb stats creation for MoonBoard climbs with grades
- Consolidated BOULDER_GRADES constant for all boards
- Added draft toggle support for MoonBoard
- Auth alert now shows for both board types

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Join with boardClimbStats and boardDifficultyGrades tables to show
the actual grade for MoonBoard climbs in the list view instead of
displaying 'V?' for all climbs.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Use getGradeByDifficultyId to get full grade info with V-grade
- Format difficulty as "6a/V3" style so ClimbTitle can extract V-grade
- Set quality_average to "3" when grade exists (required by hasGrade check)
- Removed redundant join with boardDifficultyGrades table

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The displayDifficulty column is stored as doublePrecision in the database
which may return values like 16.0 instead of 16. Round to nearest integer
for the lookup.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@claude
Copy link

claude bot commented Jan 24, 2026

Claude Review

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

Issues Found

  1. Missing error handling for unauthenticated MoonBoard save attempt - packages/web/app/components/create-climb/create-climb-form.tsx:1036-1040

    • When !isLoggedIn and boardType === "moonboard", the code just returns without showing any feedback to the user (unlike Aurora which shows auth modal). The button state prevents this path, but the function behavior is inconsistent.
  2. Hardcoded color values in CSS - packages/web/app/components/create-climb/create-climb-form.module.css:606,612,627

    • Uses hardcoded hex colors (#F9FAFB, #FFFFFF, #E5E7EB) instead of design tokens from theme-config.ts as per CLAUDE.md guidelines.
  3. Inline style usage - packages/web/app/components/create-climb/create-climb-form.tsx:1184-1189

    • Uses style property for grade text styling. CLAUDE.md advises avoiding style property.
  4. No pagination for MoonBoard climbs - packages/web/app/[board_name]/[layout_id]/[size_id]/[set_ids]/[angle]/list/page.tsx:404-410

    • getMoonboardClimbs returns climbs with hasMore: false regardless of actual count. The query uses a limit but there is no offset/cursor support for pagination.
  5. Potential race condition in bulk import - packages/web/app/components/moonboard-import/moonboard-bulk-import.tsx:162-195

    • Sequential API calls in a loop without batching. If one fails mid-way, partial saves occur with no rollback. Consider adding transaction-like behavior or batch endpoint.
  6. Duplicated helper function - getBoardDetailsForBoard is defined identically in both:

    • packages/web/app/[board_name]/[layout_id]/[size_id]/[set_ids]/[angle]/list/layout.tsx:12-20
    • packages/web/app/[board_name]/[layout_id]/[size_id]/[set_ids]/[angle]/list/page.tsx:281-289

    Should be extracted to a shared utility.

Temporary logging to debug why grades aren't showing in list view:
- Log user_grade and difficultyId in save API
- Log saveClimbStats call
- Log displayDifficulty and gradeInfo in list query

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@claude
Copy link

claude bot commented Jan 24, 2026

Claude Review

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

Issues Found

  1. Duplicated helper function - getBoardDetailsForBoard is defined identically in both packages/web/app/[board_name]/[layout_id]/[size_id]/[set_ids]/[angle]/list/layout.tsx:13-22 and packages/web/app/[board_name]/[layout_id]/[size_id]/[set_ids]/[angle]/list/page.tsx:26-34. Consider extracting to a shared utility.

  2. Missing pagination for Moonboard climbs - getMoonboardClimbs in page.tsx:65-125 always returns hasMore: false regardless of result count, and doesn't support offset-based pagination. Users with many climbs won't be able to load more.

  3. Inconsistent grade case handling - fontGradeToDifficultyId in board-data.ts:191-195 normalizes to lowercase, but MOONBOARD_GRADES uses uppercase values (e.g., '6A'). This could cause lookup mismatches if the input is already uppercase.

  4. No test coverage - This PR adds significant new functionality (database saving, frame encoding, bulk import API calls) but includes no tests for the new code paths.

Documentation Check

  • No documented systems in docs/ appear to be affected by these changes.

marcodejongh and others added 2 commits January 24, 2026 11:37
Grade saving and display is now working correctly.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add setter_username field to SaveClimbOptions interface
- Update API schema to accept setter field from client
- Save setterUsername to database in saveClimb function
- Send setter, user_grade, and is_benchmark from bulk import client
- Fix fontGradeToDifficultyId to handle OCR grade formats like "6A/V3"

The OCR returns grades in combined format (e.g., "7B+/V8") which wasn't
being parsed correctly. Now extracts just the Font grade portion before
looking up the difficulty ID.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@claude
Copy link

claude bot commented Jan 24, 2026

Claude Review

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

Issues

  1. Missing authorization check on saveClimb API (packages/web/app/api/v1/[board_name]/proxy/saveClimb/route.ts:48-50)

    • The API route accepts user_id from the request body without validating it against the authenticated session. A malicious user could save climbs as another user by passing a different user_id. Consider validating user_id matches the current session.
  2. Duplicated helper function (packages/web/app/[board_name]/[layout_id]/[size_id]/[set_ids]/[angle]/list/layout.tsx:21-29 and list/page.tsx:26-34)

    • getBoardDetailsForBoard is defined identically in both files. Consider extracting to a shared utility.
  3. MoonBoard climb search ignores pagination/sorting (packages/web/app/[board_name]/[layout_id]/[size_id]/[set_ids]/[angle]/list/page.tsx:223-236)

    • The Moonboard query always returns hasMore: false regardless of actual count, and ignores search filters, sort order, and page offset. Users may not see all their climbs if count exceeds pageSize.
  4. Unused import (packages/web/app/components/create-climb/create-climb-form.tsx:11)

    • MoonBoardRenderer is imported but the component already uses it via conditional rendering inside boardContainer. This is fine but slightly redundant since BoardRenderer handles delegation.

Add ability to upload OCR screenshots and parsed results to S3 for
improving the OCR library. Includes:

- Backend handler at /api/ocr-test-data for multipart upload
- Frontend service for fire-and-forget uploads
- Opt-in checkbox in bulk import UI (default enabled)
- S3 storage in moonboard-ocr-test-data/{timestamp}-{uuid}/

Upload is fire-and-forget - errors don't affect main save flow.
When S3 is not configured, upload is silently skipped.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@claude
Copy link

claude bot commented Jan 24, 2026

Claude Review

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

Issues

  1. Missing authentication check in saveClimb API route - packages/web/app/api/v1/[board_name]/proxy/saveClimb/route.ts:48-50: The route accepts a user_id from the request body without validating that the caller is actually that user. The MoonBoard client sends session.user.id, but the server does not verify the session. Any caller could submit climbs attributed to another user.

  2. Moonboard list query ignores search filters - packages/web/app/[board_name]/[layout_id]/[size_id]/[set_ids]/[angle]/list/page.tsx:230-234: The getMoonboardClimbs function only uses layoutId, angle, and limit. All other search parameters (minGrade, maxGrade, name, setter) passed via searchParamsObject are ignored.

  3. Hardcoded default quality_average - packages/web/app/[board_name]/[layout_id]/[size_id]/[set_ids]/[angle]/list/page.tsx:118: Setting quality_average to "3" as a fallback when grade exists seems like a workaround. Comment explains it is for ClimbTitle to show the grade, but this returns potentially misleading data.

  4. No pagination for Moonboard - list/page.tsx:238-239: hasMore is always false and totalCount equals the returned count, so infinite scroll will not work for Moonboard. Users with many climbs will not be able to page through them.

  5. Duplicate type mapping - The stateToMoonboard mapping object is defined inline in multiple places (use-moonboard-create-climb.ts:80-84, moonboard-edit-modal.tsx:67-71). Consider extracting to a shared constant.

- Add moonboard to BoardNameSchema validation in backend
- Create SUPPORTED_BOARDS constant in shared-schema as single source of truth
- Update backend resolvers to use SUPPORTED_BOARDS (ticks, climbs, playlists)
- Update isValidBoardName to include moonboard
- Add moonboard to HOLD_STATE_MAP in playlists resolver
- Update web validation to use centralized SUPPORTED_BOARDS
- Update profile page to show moonboard ascents
- Hide unsupported features (playlists, classify holds, fork) for moonboard
- Update tests to expect moonboard as valid board type

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@claude
Copy link

claude bot commented Jan 24, 2026

Claude Review

Ready to merge - No critical issues found. This is a substantial feature addition with good architectural decisions.

Minor observations (non-blocking):

  1. packages/web/app/[board_name]/[layout_id]/[size_id]/[set_ids]/[angle]/list/page.tsx:119-126 - The getMoonboardClimbs function has no pagination support - it always returns hasMore: false regardless of result count. This may cause issues if many climbs are added.

  2. packages/web/app/components/create-climb/create-climb-form.tsx:412 - isAuthenticated in renderSaveButton() uses the Aurora auth check instead of the unified isLoggedIn variable used elsewhere in the component. Consider using isLoggedIn for consistency.

  3. packages/backend/src/handlers/ocr-test-data.ts - New S3 upload endpoint added but no rate limiting. Consider adding rate limiting for production use to prevent abuse.

  4. Missing documentation - Significant changes to MoonBoard climb saving/loading behavior, but no updates to docs/ directory. Consider documenting the MoonBoard-specific database flow.

marcodejongh and others added 2 commits January 24, 2026 12:22
MoonBoard uses grid-based rendering with its own configuration in
moonboard-config.ts, not the Aurora board product-sizes-data.ts.
Updated route pages to call getMoonBoardDetails() for MoonBoard
instead of getBoardDetails() which only supports Aurora boards.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
MoonBoard uses green for starting holds and red for finish holds,
which is the opposite of what was previously configured.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@claude
Copy link

claude bot commented Jan 24, 2026

Claude Review

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

Issues

  1. Missing null check in database query - packages/web/app/[board_name]/[layout_id]/[size_id]/[set_ids]/[angle]/list/page.tsx:827 - row.displayDifficulty may be null (from left join) but getGradeByDifficultyId is called without checking, which could pass null to a function expecting a number.

  2. Potential runtime error with undefined boardDetails - packages/web/app/components/create-climb/create-climb-form.tsx:1642 - When boardType === "aurora" but boardDetails is undefined, auroraClimb hook is initialized with boardDetails?.board_name || "kilter" as fallback, which may not match actual context.

  3. Missing error handling for concurrent save failures - packages/web/app/components/moonboard-import/moonboard-bulk-import.tsx:178-217 - The bulk save loops through climbs sequentially and collects errors, but if the first N saves succeed and later ones fail, the router navigates away without showing which climbs failed.

  4. SUPPORTED_BOARDS mutable array export - packages/shared-schema/src/types.ts:117 - SUPPORTED_BOARDS is declared as BoardName[] which is mutable. Should be as const or readonly BoardName[] to prevent accidental mutation.

  5. Deprecated type still exported - packages/backend/src/db/queries/util/table-select.ts:137-138 - UnifiedBoardName is marked deprecated but still exported. Consider removing in next major version cleanup.

marcodejongh and others added 2 commits January 24, 2026 12:30
…feed

MoonBoard ticks now show proper layout names (e.g., "MoonBoard 2024")
instead of generic fallback names like "Moonboard (Layout 3)".

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Remove duplicate color definitions by deriving HOLD_STATE_MAP.moonboard
and parseMoonboardFrames() colors from MOONBOARD_HOLD_STATES in
moonboard-config.ts, ensuring consistency across the codebase.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@claude
Copy link

claude bot commented Jan 24, 2026

Claude Review

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

Issues

  1. Duplicated helper function - getBoardDetailsForBoard is defined identically in 7+ page files (list/layout.tsx, list/page.tsx, play/[climb_uuid]/page.tsx, playlist/[playlist_uuid]/page.tsx, playlists/page.tsx, view/[climb_uuid]/page.tsx). Consider extracting to a shared utility.

  2. Inconsistent pattern in play/layout.tsx - Unlike other files that use the helper function, play/layout.tsx:29-35 uses inline if/else instead of the helper function pattern.

  3. Type narrowing could be cleaner - In create-climb-form.tsx:253, 'as BoardName' cast on nextState may indicate the type definition could be improved to avoid the cast.

  4. Test coverage - No new tests were added for:

    • encodeMoonBoardHoldsToFrames utility function
    • fontGradeToDifficultyId utility function
    • saveClimbStats database function
    • The new OCR test data upload endpoint
  5. Unused import - MoonBoardRenderer is imported twice in create-climb-form.tsx (lines 10 and 11) - once via BoardRenderer delegation and once directly.

marcodejongh and others added 2 commits January 24, 2026 12:38
- Extract getBoardDetailsForBoard to shared utility (board-utils.ts)
  removing duplicate implementations from 6 page files
- Add warning log for unexpected MoonBoard role codes in parseMoonboardFrames
- Add clarifying comment for grade difficulty float-to-int conversion
- Improve OCR upload warning logs to include climb name for traceability
- Document MoonBoard hold state codes (42, 43, 44) and Aurora compatibility
- Add type guard and validation for OCR test data metadata in backend

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add default configurations for all MoonBoard layouts (1-5) in
  default-board-configs.ts to enable thumbnail rendering
- Use getBoardDetailsForBoard in ascent-thumbnail.tsx which correctly
  routes MoonBoard requests to getMoonBoardDetails
- Inline MoonBoard hold state values (42, 43, 44) in HOLD_STATE_MAP
  to avoid computed property issues with bundlers

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@claude
Copy link

claude bot commented Jan 24, 2026

Claude Review

⚠️ Needs attention - Missing error handling for undefined session.user.id, potential runtime errors when converting grades.

Issues

  1. packages/web/app/components/create-climb/create-climb-form.tsx:266 - session?.user?.id is used without null check in the API call. If session.user.id is undefined, the API will receive an invalid user_id.

  2. packages/web/app/components/moonboard-import/moonboard-bulk-import.tsx:177-180 - Same issue: session.user.id is accessed after the guard check but could still be undefined if session.user exists without an id property.

  3. packages/web/app/lib/board-data.ts:183 - fontGradeToDifficultyId returns null for invalid grades, but callers in saveClimb/route.ts:60-67 only check for truthy difficultyId, which means grade "0" would incorrectly be filtered out (though unlikely in practice).

  4. packages/web/app/[board_name]/.../list/page.tsx:82-88 - The getMoonboardClimbs function catches all database errors via the outer try/catch that calls notFound(), but a database connection issue would show as "not found" rather than a proper error message.

  5. packages/backend/src/handlers/ocr-test-data.ts - New endpoint added but no rate limiting. Authenticated users could potentially upload large volumes of test data.

  6. packages/web/app/components/moonboard-import/moonboard-bulk-import.tsx:35-36 - contributeImages defaults to true for opt-in data collection. Consider defaulting to false and making it an explicit opt-in for privacy.

Minor

  • packages/web/app/lib/moonboard-config.ts:101-103 - The color scheme comment says "Red" for start but the actual color is green (#00FF00). Comment is stale/incorrect.
  • Unused imports: SetIdList imported in list/page.tsx:5 but not used.

- Extract userId before guard checks in create-climb-form and bulk-import
  for proper TypeScript type narrowing
- Use explicit null check (difficultyId !== null) in saveClimb route
- Update stale comment in moonboard-config to match actual color scheme
- Remove unused imports (SetIdList, BoardName, HoldState) from list page

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@claude
Copy link

claude bot commented Jan 24, 2026

Claude Review

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

Issues Found

  1. packages/web/app/lib/board-data.ts:154 - TENSION_KILTER_GRADES alias points to readonly BOULDER_GRADES but original was a mutable array. This is fine but could cause issues if any code mutates it (unlikely).

  2. packages/web/app/api/v1/[board_name]/proxy/saveClimb/route.ts:49-58 - No validation that userGrade was successfully converted before saving. If fontGradeToDifficultyId returns null for an unrecognized grade, no stats are saved but no warning is logged. This is acceptable for now but could make debugging harder.

  3. packages/web/app/[board_name]/[layout_id]/[size_id]/[set_ids]/[angle]/list/page.tsx:90-110 - The getMoonboardClimbs function queries with a limit parameter but doesn't implement pagination. The function always returns hasMore: false and ignores the page offset from searchParamsObject, meaning pagination won't work for Moonboard climbs.

  4. packages/web/app/components/create-climb/create-climb-form.tsx:289-290 - selectedAngle state is initialized from angle prop but is only used for Moonboard. For Aurora boards, the original angle prop is used. This is intentional but could cause confusion if someone changes the angle in settings for Aurora boards (which currently can't happen since angle selection is Moonboard-only in settings panel).

  5. packages/backend/src/handlers/ocr-test-data.ts - New endpoint added but no rate limiting. Users could potentially flood the S3 bucket with uploads. Consider adding rate limiting in a follow-up.

  6. Unused imports - packages/web/app/[board_name]/[layout_id]/[size_id]/[set_ids]/[angle]/list/page.tsx imports BoardDetails and ParsedBoardRouteParameters types but they appear to be inherited from the already-used types and aren't directly referenced.

Notes

  • No tests added for the new Moonboard database saving functionality, but this is consistent with the existing test coverage (noted as "not yet implemented" in CLAUDE.md).
  • No documentation updates needed - Moonboard is not yet documented in the docs/ directory.

The climb view/details page doesn't properly support MoonBoard yet.
Hide the viewDetails action in ClimbCard for MoonBoard climbs until
the view page is adapted for MoonBoard's grid-based rendering.

Relates to #617

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@claude
Copy link

claude bot commented Jan 24, 2026

Claude Review

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

Issues

  1. Missing pagination for Moonboard list queries (packages/web/app/[board_name]/[layout_id]/[size_id]/[set_ids]/[angle]/list/page.tsx:817-851)

    • getMoonboardClimbs ignores pageSize offset parameter and always returns first N results
    • hasMore is always false regardless of actual results, breaking infinite scroll
  2. Potential null reference in board Bluetooth hook (packages/web/app/components/board-bluetooth-control/use-board-bluetooth.ts:77)

    • boardDetails is now optional but sendFramesToBoard callback depends on it for placementPositions without the null guard in the callback dependencies
  3. OCR test data handler returns 200 on upload error (packages/backend/src/handlers/ocr-test-data.ts:573-577)

    • When S3 upload fails, returns success: true with skipped: true - inconsistent semantics
  4. Unused import (packages/web/app/components/board-renderer/board-renderer.tsx:6)

    • MoonBoardRenderer import added but routing is correct - just flagging as cosmetic

Documentation

No documentation updates needed - changes do not affect documented WebSocket architecture.

Tests

Existing tests were updated for the new Moonboard board type. No new test coverage added for the Moonboard-specific code paths (save climb, list queries, OCR upload), but this is consistent with the repo's current test coverage patterns.

Add moonboard entries to HOLD_STATE_MAP and PRODUCT_SIZES records to
satisfy Record<BoardName, ...> type requirements now that moonboard
is included in BoardName union type.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@marcodejongh marcodejongh merged commit bb85a10 into main Jan 24, 2026
4 of 7 checks passed
@claude
Copy link

claude bot commented Jan 24, 2026

Claude Review

⚠️ Needs attention - Potential runtime issues with undefined properties; tests missing for new backend endpoint.

Issues

  1. packages/web/app/[board_name]/[layout_id]/[size_id]/[set_ids]/[angle]/list/page.tsx:856-918 - The getMoonboardClimbs function directly queries the database but doesn't filter by set_ids. This differs from Aurora boards which filter by set, potentially returning climbs that shouldn't be visible for the selected hold set.

  2. packages/web/app/components/board-renderer/board-renderer.tsx:21-35 - When delegating to MoonBoardRenderer, the code accesses boardDetails.layoutFolder and boardDetails.holdSetImages which are optional fields. While there's a guard for layoutFolder, if somehow holdSetImages is undefined, it would pass an undefined value where an array is expected.

  3. packages/backend/src/handlers/ocr-test-data.ts - New endpoint lacks test coverage. The PR adds a new API endpoint for OCR test data uploads, but no tests are provided for authentication, validation, or upload logic.

  4. packages/web/app/components/create-climb/create-climb-form.tsx:1908 - In doSaveAuroraClimb, there's a guard if (\!boardDetails || \!generateFramesString) return; which silently returns without any error feedback to the user.

  5. packages/backend/src/graphql/resolvers/playlists/queries.ts:44-48 - Moonboard hold state codes (1, 2, 3) in the playlist resolver don't match the codes used elsewhere (42, 43, 44 in MOONBOARD_HOLD_STATE_CODES). This inconsistency could cause incorrect hold rendering when loading playlist climbs.

  6. packages/web/app/components/moonboard-import/moonboard-bulk-import.tsx:104-131 - The batch save logic reports success before verifying all uploads completed. If the OCR test data upload fails after navigating away, users won't see any error.

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