Skip to content

Migrate submission view to use monaco editor in read-only mode#565

Open
jon-bell wants to merge 4 commits into
stagingfrom
grade-in-monaco
Open

Migrate submission view to use monaco editor in read-only mode#565
jon-bell wants to merge 4 commits into
stagingfrom
grade-in-monaco

Conversation

@jon-bell
Copy link
Copy Markdown
Contributor

@jon-bell jon-bell commented Jan 12, 2026

Summary by CodeRabbit

Release Notes

New Features

  • File tree sidebar with collapsible folder navigation for code submissions
  • Command palette for searching files and symbols (Cmd/Ctrl+P, Cmd/Ctrl+Shift+O)
  • Java language support with go-to-definition and find references
  • Multiple file tabs with keyboard navigation (Cmd/Ctrl+Tab/Shift+Tab, Cmd/Ctrl+W to close)
  • Deep-link support via line anchors for direct code navigation
  • Enhanced inline code commenting with rubric-based annotations

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jan 12, 2026

Important

Review skipped

Too many files!

This PR contains 299 files, which is 149 over the limit of 150.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: d40cc910-0175-439b-b1b1-d166decb7a9e

📥 Commits

Reviewing files that changed from the base of the PR and between 932786a and d28af6f.

📒 Files selected for processing (299)
  • .dockerignore
  • .env.local.staging
  • .github/workflows/deploy-impl.yml
  • .github/workflows/deploy.yml
  • .github/workflows/update-docs.yml
  • .gitignore
  • AGENTS.md
  • CLAUDE.md
  • Dockerfile
  • app/admin/AdminDashboardContent.tsx
  • app/admin/classes/ClassManagementTable.tsx
  • app/admin/classes/SectionManagementModal.tsx
  • app/admin/layout.tsx
  • app/admin/notifications/NotificationsTable.tsx
  • app/admin/page.tsx
  • app/api/cache/revalidate-tags/route.ts
  • app/api/llm-hint/route.ts
  • app/auth/reset-password/page.tsx
  • app/course/[course_id]/UserMenu.tsx
  • app/course/[course_id]/assignments/[assignment_id]/commitHistory.tsx
  • app/course/[course_id]/assignments/[assignment_id]/finalizeSubmissionEarly.tsx
  • app/course/[course_id]/assignments/[assignment_id]/manageGroupWidget.tsx
  • app/course/[course_id]/assignments/[assignment_id]/page.tsx
  • app/course/[course_id]/assignments/[assignment_id]/submissions/[submissions_id]/files/page.tsx
  • app/course/[course_id]/assignments/[assignment_id]/submissions/[submissions_id]/layout.tsx
  • app/course/[course_id]/assignments/[assignment_id]/submissions/[submissions_id]/page.tsx
  • app/course/[course_id]/assignments/[assignment_id]/submissions/[submissions_id]/repo-analytics/page.tsx
  • app/course/[course_id]/assignments/[assignment_id]/submissions/[submissions_id]/results/page.tsx
  • app/course/[course_id]/assignments/[assignment_id]/submissions/[submissions_id]/utils.ts
  • app/course/[course_id]/assignments/[assignment_id]/submissions/layout.tsx
  • app/course/[course_id]/assignments/createStudentReposButton.tsx
  • app/course/[course_id]/assignments/loading.tsx
  • app/course/[course_id]/assignments/page.tsx
  • app/course/[course_id]/discussion/[root_id]/page.tsx
  • app/course/[course_id]/discussion/discussion_thread.tsx
  • app/course/[course_id]/discussion/layout.tsx
  • app/course/[course_id]/discussion/loading.tsx
  • app/course/[course_id]/discussion/new/page.tsx
  • app/course/[course_id]/discussion/page.tsx
  • app/course/[course_id]/dynamicCourseNav.tsx
  • app/course/[course_id]/flashcards/FlashcardsDecksBody.tsx
  • app/course/[course_id]/flashcards/page.tsx
  • app/course/[course_id]/grade/assignments/[assignment_id]/submissions/[submissions_id]/page.tsx
  • app/course/[course_id]/grade/assignments/[assignment_id]/submissions/[submissions_id]/repo-analytics/page.tsx
  • app/course/[course_id]/grade/loading.tsx
  • app/course/[course_id]/gradebook/whatIf.tsx
  • app/course/[course_id]/instructorDashboard.tsx
  • app/course/[course_id]/layout.tsx
  • app/course/[course_id]/loading.tsx
  • app/course/[course_id]/manage/assignments/ManageAssignmentsTable.tsx
  • app/course/[course_id]/manage/assignments/ManageAssignmentsToolbar.tsx
  • app/course/[course_id]/manage/assignments/[assignment_id]/ManageAssignmentNav.tsx
  • app/course/[course_id]/manage/assignments/[assignment_id]/assignmentDashboard.tsx
  • app/course/[course_id]/manage/assignments/[assignment_id]/assignmentsTable.tsx
  • app/course/[course_id]/manage/assignments/[assignment_id]/due-date-exceptions/page.tsx
  • app/course/[course_id]/manage/assignments/[assignment_id]/edit/page.tsx
  • app/course/[course_id]/manage/assignments/[assignment_id]/grading-progress/page.tsx
  • app/course/[course_id]/manage/assignments/[assignment_id]/groups/GroupManagementContext.tsx
  • app/course/[course_id]/manage/assignments/[assignment_id]/groups/bulkCreateGroupModal.tsx
  • app/course/[course_id]/manage/assignments/[assignment_id]/groups/bulkModifyGroup.tsx
  • app/course/[course_id]/manage/assignments/[assignment_id]/groups/createNewGroupModal.tsx
  • app/course/[course_id]/manage/assignments/[assignment_id]/groups/groupMembershipUtils.ts
  • app/course/[course_id]/manage/assignments/[assignment_id]/groups/page.tsx
  • app/course/[course_id]/manage/assignments/[assignment_id]/page.tsx
  • app/course/[course_id]/manage/assignments/[assignment_id]/regrade-requests/RegradeRequestsTable.tsx
  • app/course/[course_id]/manage/assignments/[assignment_id]/repositories/page.tsx
  • app/course/[course_id]/manage/assignments/[assignment_id]/rerun-autograder/page.tsx
  • app/course/[course_id]/manage/assignments/[assignment_id]/reviewAssignmentsTable.tsx
  • app/course/[course_id]/manage/assignments/[assignment_id]/reviews/GradingProgressDashboard.tsx
  • app/course/[course_id]/manage/assignments/[assignment_id]/reviews/ReviewsTable.tsx
  • app/course/[course_id]/manage/assignments/[assignment_id]/reviews/assignmentCalculator.tsx
  • app/course/[course_id]/manage/assignments/[assignment_id]/reviews/bulk-assign/page.tsx
  • app/course/[course_id]/manage/assignments/[assignment_id]/reviews/page.tsx
  • app/course/[course_id]/manage/assignments/[assignment_id]/reviews/reassign/page.tsx
  • app/course/[course_id]/manage/assignments/[assignment_id]/rubric/page.tsx
  • app/course/[course_id]/manage/assignments/[assignment_id]/security/page.tsx
  • app/course/[course_id]/manage/assignments/[assignment_id]/test-insights/page.tsx
  • app/course/[course_id]/manage/assignments/[assignment_id]/test/page.tsx
  • app/course/[course_id]/manage/assignments/new/form.tsx
  • app/course/[course_id]/manage/assignments/new/page.tsx
  • app/course/[course_id]/manage/assignments/page.tsx
  • app/course/[course_id]/manage/course/audit/page.tsx
  • app/course/[course_id]/manage/course/due-date-extensions/tables/assignmentExceptionsTable.tsx
  • app/course/[course_id]/manage/course/due-date-extensions/tables/studentExtensionsTable.tsx
  • app/course/[course_id]/manage/course/emails/historyList.tsx
  • app/course/[course_id]/manage/course/emails/page.tsx
  • app/course/[course_id]/manage/course/enrollments/enrollmentsTable.tsx
  • app/course/[course_id]/manage/course/enrollments/importStudentsCSVModal.tsx
  • app/course/[course_id]/manage/course/enrollments/page.tsx
  • app/course/[course_id]/manage/course/lab-roster/page.tsx
  • app/course/[course_id]/manage/course/lab-sections/page.tsx
  • app/course/[course_id]/manage/discussion-engagement/ExportButton.tsx
  • app/course/[course_id]/manage/discussion-engagement/TrophyIcon.tsx
  • app/course/[course_id]/manage/discussion-engagement/page.tsx
  • app/course/[course_id]/manage/discussion-topics/modals/createTopicModal.tsx
  • app/course/[course_id]/manage/discussion-topics/modals/editTopicModal.tsx
  • app/course/[course_id]/manage/discussion-topics/page.tsx
  • app/course/[course_id]/manage/gradebook/GradebookLayoutClient.tsx
  • app/course/[course_id]/manage/gradebook/gradebookCell.tsx
  • app/course/[course_id]/manage/gradebook/gradebookTable.tsx
  • app/course/[course_id]/manage/gradebook/importGradebookColumn.tsx
  • app/course/[course_id]/manage/gradebook/layout.tsx
  • app/course/[course_id]/manage/gradebook/overrideScoreForm.tsx
  • app/course/[course_id]/manage/loading.tsx
  • app/course/[course_id]/manage/office-hours/all-requests/page.tsx
  • app/course/[course_id]/manage/office-hours/feedback.tsx
  • app/course/[course_id]/manage/office-hours/helpQueueManagement.tsx
  • app/course/[course_id]/manage/office-hours/helpQueuesDashboard.tsx
  • app/course/[course_id]/manage/office-hours/helpRequestList.tsx
  • app/course/[course_id]/manage/office-hours/layout-client.tsx
  • app/course/[course_id]/manage/office-hours/modals/createHelpQueueModal.tsx
  • app/course/[course_id]/manage/office-hours/modals/editHelpQueueModal.tsx
  • app/course/[course_id]/manage/office-hours/settings/queues/page.tsx
  • app/course/[course_id]/manage/polls/PollsTable.tsx
  • app/course/[course_id]/manage/student/[student_id]/page.tsx
  • app/course/[course_id]/manage/surveys/ManageSurveysBody.tsx
  • app/course/[course_id]/manage/surveys/SurveysHeader.tsx
  • app/course/[course_id]/manage/surveys/SurveysTable.tsx
  • app/course/[course_id]/manage/surveys/[survey_id]/edit/page.tsx
  • app/course/[course_id]/manage/surveys/[survey_id]/responses/SurveyResponsesView.tsx
  • app/course/[course_id]/manage/surveys/[survey_id]/responses/[response_id]/page.tsx
  • app/course/[course_id]/manage/surveys/[survey_id]/responses/page.tsx
  • app/course/[course_id]/manage/surveys/new/form.tsx
  • app/course/[course_id]/manage/surveys/new/page.tsx
  • app/course/[course_id]/manage/surveys/page.tsx
  • app/course/[course_id]/manage/surveys/series/page.tsx
  • app/course/[course_id]/notifications/loading.tsx
  • app/course/[course_id]/office-hours/[queue_id]/new/newRequestForm.tsx
  • app/course/[course_id]/office-hours/[queue_id]/new/page.tsx
  • app/course/[course_id]/office-hours/layout.tsx
  • app/course/[course_id]/office-hours/loading.tsx
  • app/course/[course_id]/office-hours/page.tsx
  • app/course/[course_id]/office-hours/request/[request_id]/page.tsx
  • app/course/[course_id]/page.tsx
  • app/course/[course_id]/polls/loading.tsx
  • app/course/[course_id]/regrade-requests/loading.tsx
  • app/course/[course_id]/regrade-requests/page.tsx
  • app/course/[course_id]/studentDashboard.tsx
  • app/course/[course_id]/surveys/[survey_id]/page.tsx
  • app/course/[course_id]/surveys/loading.tsx
  • app/course/[course_id]/surveys/page.tsx
  • app/course/canvas-classes/page.tsx
  • app/course/loading.tsx
  • app/course/page.tsx
  • app/layout.tsx
  • app/loading.tsx
  • components/TimeZoneAwareDate.tsx
  • components/TimeZoneModal.tsx
  • components/TimeZoneSelector.tsx
  • components/ai-help/AIHelpButton.tsx
  • components/ai-help/AIHelpFeedbackPanel.tsx
  • components/ai-help/AIHelpSubmissionErrorButton.tsx
  • components/calendar/CalendarColorContext.tsx
  • components/calendar/calendar-day-view.tsx
  • components/calendar/calendar-schedule-summary.tsx
  • components/calendar/calendar-subscribe-button.tsx
  • components/calendar/calendar-utils.ts
  • components/calendar/office-hours-schedule.tsx
  • components/calendar/queue-weekly-schedule.tsx
  • components/chat-message.tsx
  • components/discord/discord-discussion-message-link.tsx
  • components/discussion/AssignedLabSections.tsx
  • components/discussion/DiscussionSummary.tsx
  • components/discussion/ErrorPinCallout.tsx
  • components/discussion/ErrorPinIndicator.tsx
  • components/discussion/ErrorPinManageModal.tsx
  • components/discussion/ErrorPinModal.tsx
  • components/discussion/KarmaBadge.tsx
  • components/discussion/PostRow.tsx
  • components/discussion/StaffThreadActions.tsx
  • components/discussion/VirtualizedPostRowList.tsx
  • components/help-queue/floating-help-request-widget.tsx
  • components/help-queue/help-drawer.tsx
  • components/help-queue/help-request-chat.tsx
  • components/help-queue/help-request-resolution-modal.tsx
  • components/help-queue/help-request-search.tsx
  • components/help-queue/help-request-sidebar.tsx
  • components/help-queue/help-request-teaser.tsx
  • components/help-queue/office-hours-discussion-browser.tsx
  • components/help-queue/office-hours-header.tsx
  • components/help-queue/office-hours-status-card.tsx
  • components/help-queue/queue-card.tsx
  • components/help-queue/request-list-controls.tsx
  • components/help-queue/request-row.tsx
  • components/notifications/notification-permission-warning.tsx
  • components/notifications/notification-preferences.tsx
  • components/realtime-chat.tsx
  • components/settings/MCPTokensMenu.tsx
  • components/survey/AnalyticsConfigEditor.tsx
  • components/survey/SeriesManagementModal.tsx
  • components/survey/SurveyAnalytics.tsx
  • components/survey/SurveySeriesList.tsx
  • components/survey/SurveyTemplateLibraryModal.tsx
  • components/survey/analytics/AlertsPanel.tsx
  • components/survey/analytics/ChartAxisTick.tsx
  • components/survey/analytics/ChoiceDistributionChart.tsx
  • components/survey/analytics/ComparisonTable.tsx
  • components/survey/analytics/ConsistentAxesChart.tsx
  • components/survey/analytics/DistributionChart.tsx
  • components/survey/analytics/DivergingStackedChart.tsx
  • components/survey/analytics/DivergingStackedChartMultiSeries.tsx
  • components/survey/analytics/GroupDetailPanel.tsx
  • components/survey/analytics/GroupMemberRoster.tsx
  • components/survey/analytics/GroupSummaryCards.tsx
  • components/survey/analytics/GroupViewContent.tsx
  • components/survey/analytics/LikertHeatmap.tsx
  • components/survey/analytics/MeanDotStripChart.tsx
  • components/survey/analytics/NormalizedStackedChart.tsx
  • components/survey/analytics/ScaleGroupCharts.tsx
  • components/survey/analytics/SeriesComparisonBlock.tsx
  • components/survey/analytics/SeriesComparisonCheckboxes.tsx
  • components/survey/analytics/SummaryCards.tsx
  • components/survey/analytics/TrendChart.tsx
  • components/survey/analytics/ViewSelector.tsx
  • components/survey/analytics/groupSectionUtils.ts
  • components/survey/analytics/index.ts
  • components/survey/analytics/likert-chart-utils.ts
  • components/survey/analytics/useContainerWidth.ts
  • components/survey/analytics/utils.ts
  • components/ui/annotation-comment-dialog.tsx
  • components/ui/assignment-due-date.tsx
  • components/ui/assignment-grading-toolbar.tsx
  • components/ui/assignment-leaderboard.tsx
  • components/ui/binary-file-preview.tsx
  • components/ui/code-file-monaco.tsx
  • components/ui/code-file-plain.tsx
  • components/ui/code-file-shared.tsx
  • components/ui/code-file-starry-night.tsx
  • components/ui/code-file.tsx
  • components/ui/command-palette.tsx
  • components/ui/discussion-post-summary.tsx
  • components/ui/file-tree.tsx
  • components/ui/group-member-select-option.tsx
  • components/ui/markdown-file-preview.tsx
  • components/ui/markdown.tsx
  • components/ui/md-editor.tsx
  • components/ui/message-input.tsx
  • components/ui/monaco-rubric-context-menu.tsx
  • components/ui/navigation-progress.tsx
  • components/ui/plain-rubric-line-menu.tsx
  • components/ui/recharts-wrapper.tsx
  • components/ui/regrade-request-wrapper.tsx
  • components/ui/repo-analytics-chart.tsx
  • components/ui/request-regrade-dialog.tsx
  • components/ui/route-loading-skeleton.tsx
  • components/ui/rubric-marking-menu.tsx
  • components/ui/rubric-quick-pick.tsx
  • components/ui/rubric-sidebar.tsx
  • components/ui/self-review-due-date-information.tsx
  • components/ui/self-review-notice.tsx
  • components/ui/student-summary.tsx
  • components/ui/submission-review-toolbar.tsx
  • components/ui/survey-status-banner.tsx
  • docs/grafana-dashboard-vacuum-health.json
  • docs/seed-course-assignments.md
  • eslint.config.mjs
  • hooks/useActiveHelpRequest.tsx
  • hooks/useAssignment.tsx
  • hooks/useCalendarEvents.tsx
  • hooks/useClassProfiles.tsx
  • hooks/useCourseController.tsx
  • hooks/useDiscussionThreadRootController.tsx
  • hooks/useErrorPinMatches.tsx
  • hooks/useGradebook.tsx
  • hooks/useHelpDrawer.tsx
  • hooks/useMessageNotifications.tsx
  • hooks/useOfficeHoursRealtime.tsx
  • hooks/useRealtimeChat.tsx
  • hooks/useRubricAnnotationTargetMeta.ts
  • hooks/useSubmission.tsx
  • hooks/useSubmissionReview.tsx
  • hooks/useSurveyAnalytics.ts
  • hooks/useTestInsights.tsx
  • hooks/useUserPreferences.ts
  • hooks/useUserProfiles.tsx
  • instrumentation-client.ts
  • jest.setup.ts
  • lib/OfficeHoursRealTimeController.ts
  • lib/PreviewAssignmentController.ts
  • lib/TableController.ts
  • lib/TimeZoneProvider.tsx
  • lib/edgeFunctions.ts
  • lib/githubIdentity.ts
  • lib/java-language-service.ts
  • lib/next-cache-tags.ts
  • lib/notifications/NotificationManager.ts
  • lib/notifications/index.ts
  • lib/rehype-source-positions.ts
  • lib/remark-escape-html.ts
  • lib/revalidateCourseDerivedCachesClient.ts
  • lib/rubricCommentTargetStudentProfileId.ts
  • lib/rubricGradingCompletion.ts
  • lib/rubricUnsavedChanges.ts
  • lib/ssr-course-dashboard.ts
  • lib/ssr-platform-data.ts
  • lib/ssrUtils.ts
  • lib/studentFacingErrorMessages.ts
  • lib/submissionHasGraderOutput.ts
  • lib/test-insights/AIHelpTestErrorButton.tsx

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Walkthrough

This pull request introduces a comprehensive file navigation and code annotation system to the submissions viewer. It adds a file tree sidebar, command palette with fuzzy search, Monaco editor integration with Java language support, inline comment system with rubric annotations, and keyboard shortcuts for efficient navigation and tab management.

Changes

Cohort / File(s) Summary
Navigation & Sidebar UI
components/ui/file-tree.tsx, components/ui/command-palette.tsx
New FileTreeSidebar component renders hierarchical file structure with comment count badges and active file highlighting; CommandPalette component provides dual-mode (file/symbol) fuzzy-searchable dialog with keyboard navigation.
Code Editor & Multi-file Support
components/ui/code-file.tsx
Refactors CodeFile from simple component to forwardRef-based editor with multi-file tab support, Monaco integration, view zones for inline comments, glyph decorations, and Java symbol providers (go-to-definition, references, outline). Exposes CodeFileHandle with scrollToLine method.
Rubric & Annotation System
components/ui/annotation-comment-dialog.tsx, components/ui/monaco-rubric-context-menu.tsx, components/ui/rubric-quick-pick.tsx
New dialog for submitting line-scoped comments with rubric checks and visibility controls; context menu integration for Monaco editor to apply rubric checks; quick-pick UI for nested rubric selection with comment-required indicators.
Submissions Page
app/course/.../submissions/.../files/page.tsx
Integrates FileTreeSidebar and CommandPalette for navigation; adds openFileIds tracking with auto-open behavior; implements line-anchor support (L<number>); adds keyboard shortcuts (Cmd/Ctrl+P for palette, Cmd/Ctrl+Shift+O for symbols, Cmd/Ctrl+Tab for tab cycling, Cmd/Ctrl+W to close); wires Java parsing and symbol state.
Global Styling
app/globals.css
Adds Monaco-specific CSS rules: .monaco-line-highlight for semi-transparent yellow background with fade transition, and .monaco-comment-glyph::before for inline comment glyph rendering.

Sequence Diagram

sequenceDiagram
    participant User
    participant CommandPalette
    participant FileTree
    participant CodeFile
    participant Monaco
    participant RubricMenu

    User->>FileTree: Click file / use sidebar
    FileTree->>CodeFile: onFileSelect(fileId)
    CodeFile->>Monaco: Switch active model/render file
    Monaco-->>CodeFile: Editor ready

    User->>CommandPalette: Press Cmd+P (file search)
    CommandPalette->>CommandPalette: Fuzzy filter files
    User->>CommandPalette: Select/Enter file
    CommandPalette->>CodeFile: onSelectFile(fileId)
    CodeFile->>Monaco: Load & render file

    User->>CommandPalette: Press Cmd+Shift+O (symbols)
    CommandPalette->>CodeFile: Request currentFileSymbols
    CodeFile->>CodeFile: parseJavaFile + buildSymbolIndex
    CommandPalette->>CommandPalette: Display symbols
    User->>CommandPalette: Select symbol
    Monaco->>Monaco: scrollToLine + highlight

    User->>Monaco: Right-click (context menu)
    Monaco->>RubricMenu: Show rubric options
    User->>RubricMenu: Select rubric check
    RubricMenu->>CodeFile: Trigger annotation dialog
    CodeFile->>CodeFile: AnnotationCommentDialog opens
    User->>CodeFile: Submit comment + line range
    CodeFile-->>User: Save & display inline comment
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested labels

enhancement, frontend

Poem

Files now dance in a sidebar tree 🌳
Symbols leap through Monaco's spree ✨
Comments bloom on lines with glee 💬
Rubric guides to set you free 📝
Navigation flows—watch code flow free! 🎯

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 31.25% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the primary change: migrating the submission view to use Monaco editor in read-only mode, which is the core objective across all modified files.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (4)
app/course/[course_id]/assignments/[assignment_id]/submissions/[submissions_id]/files/page.tsx (3)

91-150: Remove unused FilePicker component.

The FilePicker component is defined but never used — it's been replaced by FileTreeSidebar. The linter correctly flags this.

-function FilePicker({ curFile, onSelect }: { curFile: number; onSelect: (fileId: number) => void }) {
-  const submission = useSubmission();
-  const comments = useSubmissionFileComments({});
-  const showCommentsFeature = true; //submission.released !== null || isGraderOrInstructor;
-  return (
-    <Box
-      maxH="250px"
-      ... (entire component)
-    </Box>
-  );
-}

1173-1173: Remove unused variable filePickerDisplayIndex.

This variable is assigned but never used (FilePicker was replaced).

-  const filePickerDisplayIndex = curFileIndex === -1 ? 0 : curFileIndex;

1258-1382: Critical: Hooks called after early returns violate React's Rules of Hooks.

The useEffect (lines 1269, 1293), useCallback (lines 1350, 1358), and useMemo (line 1371) are called after conditional returns at lines 1258 and 1264. This violates React's requirement that hooks must be called in the same order on every render.

This will cause unpredictable behavior and React will throw errors in development mode.

🐛 Required fix: Move all hooks before early returns

Move all the hooks currently after line 1266 to before the early returns. The pattern should be:

+  // Handle sidebar resize - MOVE BEFORE EARLY RETURNS
+  useEffect(() => {
+    if (!isResizing) return;
+    // ... resize logic
+  }, [isResizing]);
+
+  // Keyboard shortcuts - MOVE BEFORE EARLY RETURNS  
+  useEffect(() => {
+    const handleKeyDown = (e: KeyboardEvent) => {
+      // ... shortcut logic
+    };
+    window.addEventListener("keydown", handleKeyDown);
+    return () => window.removeEventListener("keydown", handleKeyDown);
+  }, [openFileIds, selectedFileId, handleSelectFile, handleCloseFile]);
+
+  const handleCommandPaletteSelectFile = useCallback(/* ... */);
+  const handleCommandPaletteSelectSymbol = useCallback(/* ... */);
+  const currentFileSymbols = useMemo(/* ... */);
+
   if (isLoading) {
     return <Spinner />;
   }

-  const submission = submissionData;
-
   if (!submission) {
     return <NotFound />;
   }

-  // Handle sidebar resize  <-- REMOVE FROM HERE
-  useEffect(() => { ... }, [isResizing]);
-  
-  // Keyboard shortcuts  <-- REMOVE FROM HERE
-  useEffect(() => { ... }, [...]);
-  
-  // etc...

Note: You'll need to adjust the hooks to handle the case where submission might be undefined (e.g., use optional chaining or early-exit guards inside the hooks).

components/ui/code-file.tsx (1)

3-10: Remove unused imports to fix linter errors.

Several imports are unused and flagged by the linter:

  • useRubricChecksByRubric, useRubricCriteriaByRubric, useRubricWithParts (lines 6-9)
  • Separator (line 28)
  • chakraComponents, Select, SelectComponentsConfig, SelectInstance (line 31)
  • StudentVisibilityIndicator (line 52)
🧹 Remove unused imports
 import {
   useGraderPseudonymousMode,
   useRubricCheck,
-  useRubricChecksByRubric,
   useRubricCriteria,
-  useRubricCriteriaByRubric,
-  useRubricWithParts
 } from "@/hooks/useAssignment";
-import { Badge, Box, Button, Flex, HStack, Icon, Separator, Tag, Text, VStack } from "@chakra-ui/react";
+import { Badge, Box, Button, Flex, HStack, Icon, Tag, Text, VStack } from "@chakra-ui/react";
-import { chakraComponents, Select, SelectComponentsConfig, SelectInstance } from "chakra-react-select";
-import { CommentActions, ReviewRoundTag, StudentVisibilityIndicator } from "./rubric-sidebar";
+import { CommentActions, ReviewRoundTag } from "./rubric-sidebar";

Also applies to: 28-28, 31-31, 52-52

🤖 Fix all issues with AI agents
In @components/ui/annotation-comment-dialog.tsx:
- Around line 124-139: The object construction for values uses non-null
assertions on file.class_id and authorProfileId which can crash at runtime;
before building values in annotation-comment-dialog.tsx, add defensive checks
(e.g., if (!file.class_id) or if (!authorProfileId)) and handle them by
returning early or throwing a clear error, or use a safe fallback value if
appropriate; update any callers to handle the early return or propagate the
error so you don't rely on the `!` assertions in values.

In @components/ui/code-file.tsx:
- Line 1010: Remove the unused local variable by deleting the assignment to
browserEvent (the line declaring "const browserEvent = e.event.browserEvent") in
the code handling the event inside the CodeFile component; if the original
intent was to use the native browser event, either reference browserEvent where
needed (e.g., preventDefault/stopPropagation or read properties) or remove the
declaration entirely to eliminate the unused variable warning.
- Around line 989-996: Rename the unused catch parameters to indicate
intentional non-use: change the inner catch parameter `err` to `_err` (in the
map callback surrounding the JSON.parse/transform logic) and change the outer
catch parameter `error` to `_error` (the outer try/catch that returns
`symbols`), so both catch blocks use prefixed names instead of unused
identifiers.

In @components/ui/monaco-rubric-context-menu.tsx:
- Around line 96-99: The computed isDisabled (derived from
existingAnnotationsForCheck, check.max_annotations and check.id) is unused;
update the action-building logic that consumes check to either skip checks that
are disabled (filter out when mapping checks to menu actions) or set a disabled
flag/property on the created action object (e.g., action.disabled = isDisabled
and add an explanatory tooltip/title) so the UI respects max_annotations; locate
where checks are transformed into menu items and apply one of these two fixes
using the isDisabled variable.
🧹 Nitpick comments (14)
app/globals.css (1)

24-28: Emoji glyph may render inconsistently across platforms.

Using content: "💬" is functional but emoji rendering varies by OS/browser. Consider using an SVG icon or a font icon for more consistent visuals if cross-platform consistency is important. Otherwise, this is acceptable for a quick implementation.

components/ui/rubric-quick-pick.tsx (1)

59-84: Unused index variable in map callback.

The index parameter is declared but never used. Consider removing it to keep the code clean.

🧹 Proposed fix
-          {items.map((action, index) => (
+          {items.map((action) => (
components/ui/file-tree.tsx (2)

3-3: Unused import: useSubmission.

The useSubmission hook is imported but never used in this file. Remove it to avoid confusion.

🧹 Proposed fix
-import { useSubmission, useSubmissionFileComments } from "@/hooks/useSubmission";
+import { useSubmissionFileComments } from "@/hooks/useSubmission";

83-98: handleClick and handleDoubleClick have identical logic.

Both handlers perform the same actions. The double-click handler only adds e.stopPropagation() but the logic is duplicated. Consider extracting to a shared helper or removing the double-click handler if single-click already covers the use case.

♻️ Proposed consolidation
+  const performAction = () => {
+    if (isFile && node.file) {
+      onFileSelect(node.file.id);
+    } else if (hasChildren) {
+      onCollapseChange(node.path, !isCollapsed);
+    }
+  };
+
   const handleClick = () => {
-    if (isFile && node.file) {
-      onFileSelect(node.file.id);
-    } else if (hasChildren) {
-      onCollapseChange(node.path, !isCollapsed);
-    }
+    performAction();
   };

   const handleDoubleClick = (e: React.MouseEvent) => {
     e.stopPropagation();
-    if (isFile && node.file) {
-      onFileSelect(node.file.id);
-    } else if (hasChildren) {
-      onCollapseChange(node.path, !isCollapsed);
-    }
+    performAction();
   };
components/ui/command-palette.tsx (2)

68-77: Consider using a ref callback instead of setTimeout for focus.

The 50ms delay is a pragmatic solution, but it can be flaky in slower environments. A more robust approach is to use a callback ref or requestAnimationFrame.

♻️ Alternative approach
   useEffect(() => {
     if (isOpen) {
       setQuery("");
       setSelectedIndex(0);
-      // Focus input after a brief delay to ensure modal is rendered
-      setTimeout(() => {
-        inputRef.current?.focus();
-      }, 50);
+      // Focus input after paint to ensure modal is rendered
+      requestAnimationFrame(() => {
+        inputRef.current?.focus();
+      });
     }
   }, [isOpen]);

233-237: Potential key collision for symbols on the same line.

The key ${item.symbol.fileId}-${item.symbol.line} could collide if multiple symbols (e.g., method overloads) exist on the same line in the same file. Consider adding the symbol name or index to ensure uniqueness.

🔑 Proposed fix
-                    key={item.type === "file" ? item.file.id : `${item.symbol.fileId}-${item.symbol.line}`}
+                    key={item.type === "file" ? item.file.id : `${item.symbol.fileId}-${item.symbol.line}-${item.symbol.name}`}
components/ui/monaco-rubric-context-menu.tsx (2)

168-175: Empty conditional block is dead code.

The if (lastMousePositionRef.current) block contains only a comment and no actual logic. Either remove this check or add the intended logic.

🧹 Remove the empty block
       const { startLine, endLine } = getSelectionLines();
-        // Capture current mouse position when action is triggered
-        if (lastMousePositionRef.current) {
-          // Position already captured from mousemove/contextmenu
-        }
         onAddComment(startLine, endLine);

277-302: Global mousemove listener adds continuous overhead.

Tracking mouse position on every move is expensive. Since you already capture position on contextmenu (line 285-291) and Monaco's onContextMenu (line 180-192), the mousemove listener may be unnecessary.

♻️ Remove redundant mousemove tracking
   useEffect(() => {
-    const handleMouseMove = (e: MouseEvent) => {
-      lastMousePositionRef.current = {
-        top: e.clientY,
-        left: e.clientX
-      };
-    };
-    
     const handleContextMenu = (e: MouseEvent) => {
       // Capture position when context menu is triggered
       lastMousePositionRef.current = {
         top: e.clientY,
         left: e.clientX
       };
     };
     
-    // Track mouse movement to always have a recent position
-    document.addEventListener("mousemove", handleMouseMove, { passive: true });
-    // Also capture on context menu (in case mouse hasn't moved)
     document.addEventListener("contextmenu", handleContextMenu, { passive: true });
     
     return () => {
-      document.removeEventListener("mousemove", handleMouseMove);
       document.removeEventListener("contextmenu", handleContextMenu);
     };
   }, []);
components/ui/annotation-comment-dialog.tsx (2)

77-95: Consider simplifying focus logic with a single retry approach.

The multiple nested timeouts (100ms, 300ms) can be hard to maintain. A cleaner approach might be a single retry loop with requestAnimationFrame.

🔧 Suggested simplification
-      const focusInput = () => {
-        if (messageInputRef.current) {
-          // For single-line mode (textarea), focus directly
-          messageInputRef.current.focus();
-        } else {
-          // Fallback: try to find textarea in the dialog
-          const textarea = document.querySelector('[role="dialog"] textarea') as HTMLTextAreaElement;
-          if (textarea) {
-            textarea.focus();
-          }
-        }
-      };
-      
-      // Try focusing after a short delay to ensure DOM is ready
-      requestAnimationFrame(() => {
-        setTimeout(focusInput, 100);
-        // Also try again after a longer delay as fallback
-        setTimeout(focusInput, 300);
-      });
+      let attempts = 0;
+      const maxAttempts = 5;
+      const tryFocus = () => {
+        if (messageInputRef.current) {
+          messageInputRef.current.focus();
+          return;
+        }
+        const textarea = document.querySelector('[role="dialog"] textarea') as HTMLTextAreaElement;
+        if (textarea) {
+          textarea.focus();
+          return;
+        }
+        if (attempts++ < maxAttempts) {
+          requestAnimationFrame(tryFocus);
+        }
+      };
+      requestAnimationFrame(tryFocus);

247-261: Redundant null check inside onClick handler.

The if (onImmediateApply) check on line 252 is redundant since the button only renders when onImmediateApply is truthy (line 247).

♻️ Remove redundant check
               <Button
                 variant="solid"
                 colorPalette="green"
                 onClick={() => {
-                  if (onImmediateApply) {
-                    onImmediateApply();
-                  }
+                  onImmediateApply();
                   onClose();
                 }}
                 disabled={isLoading}
               >
app/course/[course_id]/assignments/[assignment_id]/submissions/[submissions_id]/files/page.tsx (1)

1232-1249: Duplicate Java parsing logic with code-file.tsx.

Java file parsing is implemented both here (lines 1232-1249) and in code-file.tsx (lines 719-738). Consider consolidating into a shared hook to avoid duplication.

♻️ Extract to shared hook
// hooks/useJavaSymbols.ts
export function useJavaSymbols(files: SubmissionFile[] | undefined) {
  const [fileSymbols, setFileSymbols] = useState<Map<number, JavaFileSymbols>>(new Map());
  
  useEffect(() => {
    if (!files) return;
    const javaFiles = files.filter((f) => f.name.endsWith(".java"));
    const parsed = new Map<number, JavaFileSymbols>();
    
    for (const file of javaFiles) {
      try {
        const symbols = parseJavaFile(file.contents, file.id, file.name);
        parsed.set(file.id, symbols);
      } catch (error) {
        console.warn(`Failed to parse Java file ${file.name}:`, error);
      }
    }
    setFileSymbols(parsed);
  }, [files]);
  
  return fileSymbols;
}
components/ui/code-file.tsx (3)

719-738: Consider using a proper logger instead of console.warn.

Line 731 uses console.warn which triggers linter warnings. While acceptable for debugging, a structured logger would be more consistent.


877-1001: Consider simplifying URI validation logic.

The document symbol provider has extensive defensive checks for URI scheme validation (lines 886-912). While robust, this suggests working around Monaco quirks. If these edge cases are truly needed, a comment explaining why would help future maintainers.

If you've encountered specific Monaco URI issues, consider adding a brief comment explaining the scenario, e.g.:

// Monaco sometimes returns URIs without scheme property during hot reload - validate before use

223-1381: Large component could benefit from modularization.

The CodeFile component spans ~1150 lines with multiple concerns:

  • Monaco editor setup and lifecycle
  • Java language providers (definition, reference, symbols)
  • View zone management for inline comments
  • Comment dialog state management

Consider extracting into focused modules in a future refactor (e.g., useMonacoJavaProviders, useViewZoneManager).

📜 Review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between eb8dd7a and 932786a.

📒 Files selected for processing (8)
  • app/course/[course_id]/assignments/[assignment_id]/submissions/[submissions_id]/files/page.tsx
  • app/globals.css
  • components/ui/annotation-comment-dialog.tsx
  • components/ui/code-file.tsx
  • components/ui/command-palette.tsx
  • components/ui/file-tree.tsx
  • components/ui/monaco-rubric-context-menu.tsx
  • components/ui/rubric-quick-pick.tsx
🧰 Additional context used
🧬 Code graph analysis (3)
components/ui/rubric-quick-pick.tsx (1)
components/ui/monaco-rubric-context-menu.tsx (1)
  • RubricContextMenuAction (12-19)
components/ui/annotation-comment-dialog.tsx (9)
utils/supabase/DatabaseTypes.d.ts (4)
  • SubmissionFileComment (119-125)
  • SubmissionWithGraderResultsAndFiles (239-245)
  • SubmissionFile (165-165)
  • RubricCheck (686-686)
hooks/useSubmission.tsx (1)
  • useSubmissionController (782-788)
hooks/useClassProfiles.tsx (2)
  • useClassProfiles (24-30)
  • useIsGraderOrInstructor (53-56)
hooks/useAssignment.tsx (1)
  • useGraderPseudonymousMode (71-74)
components/ui/toaster.tsx (1)
  • toaster (5-8)
components/ui/code-file.tsx (1)
  • formatPoints (1767-1772)
components/ui/rubric-sidebar.tsx (1)
  • StudentVisibilityIndicator (696-755)
components/ui/message-input.tsx (1)
  • MessageInput (54-750)
components/ui/checkbox.tsx (1)
  • Checkbox (11-19)
components/ui/command-palette.tsx (2)
utils/supabase/DatabaseTypes.d.ts (1)
  • SubmissionFile (165-165)
components/ui/input.tsx (1)
  • Input (46-46)
🪛 Biome (2.1.2)
app/course/[course_id]/assignments/[assignment_id]/submissions/[submissions_id]/files/page.tsx

[error] 1269-1269: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

Hooks should not be called after an early return.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 1293-1293: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

Hooks should not be called after an early return.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 1350-1350: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

Hooks should not be called after an early return.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 1358-1358: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

Hooks should not be called after an early return.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 1371-1371: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

Hooks should not be called after an early return.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)

🪛 GitHub Actions: Lint
components/ui/code-file.tsx

[error] 3-3: 'useRubricChecksByRubric' is defined but never used. (no-unused-vars)


[error] 8-8: 'useRubricCriteriaByRubric' is defined but never used. (no-unused-vars)


[error] 9-9: 'useRubricWithParts' is defined but never used. (no-unused-vars)


[error] 28-28: 'Separator' is defined but never used. (no-unused-vars)


[error] 31-31: 'chakraComponents' is defined but never used. (no-unused-vars)


[error] 31-31: 'Select' is defined but never used. (no-unused-vars)


[error] 31-31: 'SelectComponentsConfig' is defined but never used. (no-unused-vars)


[error] 31-31: 'SelectInstance' is defined but never used. (no-unused-vars)


[error] 52-52: 'StudentVisibilityIndicator' is defined but never used. (no-unused-vars)


[error] 989-989: 'err' is defined but never used. (no-unused-vars)


[error] 996-996: 'error' is defined but never used. (no-unused-vars)


[error] 1010-1010: 'browserEvent' is assigned a value but never used. (no-unused-vars)


[warning] 731-731: Unexpected console statement. (no-console)


[error] Multiple ESLint errors in code-file.tsx detected (no-unused-vars).


[warning] Need to disable some ESLint rules? See Next.js ESLint docs.

app/course/[course_id]/assignments/[assignment_id]/submissions/[submissions_id]/files/page.tsx

[error] 91-91: 'FilePicker' is defined but never used. (no-unused-vars)


[error] 1173-1173: 'filePickerDisplayIndex' is assigned a value but never used. (no-unused-vars)


[error] 1269-1269: React Hook 'useEffect' is called conditionally. Hooks must be called unconditionally in the same order. (react-hooks/rules-of-hooks)


[error] 1293-1293: React Hook 'useEffect' is called conditionally. Hooks must be called unconditionally in the same order. (react-hooks/rules-of-hooks)


[error] 1350-1350: React Hook 'useCallback' is called conditionally. Hooks must be called in the same order on every render. (react-hooks/rules-of-hooks)


[error] 1358-1358: React Hook 'useCallback' is called conditionally. Hooks must be called in the same order on every render. (react-hooks/rules-of-hooks)


[error] 1371-1371: React Hook 'useMemo' is called conditionally. Hooks must be called in the same order on every render. (react-hooks/rules-of-hooks)

⏰ Context from checks skipped due to timeout of 180000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: deploy
🔇 Additional comments (14)
app/globals.css (1)

19-22: Opacity transition without a defined end state.

The .monaco-line-highlight class has a transition: opacity 2s ease-out but no change in opacity value is defined in CSS. This suggests the opacity change is controlled via JavaScript (e.g., adding/removing a class or inline style). If that's the case, the implementation is fine. Just ensure the JS side properly triggers the fade by modifying the opacity.

components/ui/rubric-quick-pick.tsx (1)

27-35: LGTM on the modal structure and positioning logic.

The backdrop + menu pattern with fixed positioning and appropriate zIndex layering is well implemented. The centering via translate(-50%, -50%) correctly positions the menu at the provided coordinates or viewport center.

Also applies to: 37-52

components/ui/file-tree.tsx (2)

26-58: Well-structured tree building logic.

The buildFileTree function correctly constructs a hierarchical structure from flat file paths. The path splitting and incremental node creation is clean and handles nested directories properly.


178-211: Clean external/internal state pattern for collapse management.

The fallback from externalCollapsed to internalCollapsed and the corresponding handler pattern provides good flexibility for both controlled and uncontrolled usage. Well done.

components/ui/command-palette.tsx (2)

19-53: Fuzzy match scoring logic is solid.

The scoring system with bonuses for consecutive matches, word boundaries, and exact substrings provides good ranking. One minor note: the exact substring bonus (lines 48-50) is applied after the fuzzy match score is potentially zeroed (lines 43-45), meaning a query that exists as an exact substring but wasn't matched by the greedy fuzzy algorithm would still score 2. This is actually a good fallback behavior.


127-148: Keyboard navigation implementation is clean.

The handling of Escape, Arrow keys, and Enter is well-structured with proper preventDefault() calls to avoid scroll behavior on arrow keys.

components/ui/monaco-rubric-context-menu.tsx (2)

196-205: Separator using dash characters is a workaround.

Monaco doesn't natively support visual separators in context menus, so this is an acceptable workaround. The empty run() function is intentional.


345-373: Two-level quick pick integration looks good.

The flow from selecting a rubric check to choosing "Apply" or "Apply with comment..." is well-structured. The RubricQuickPick components are properly wired with state and callbacks.

components/ui/annotation-comment-dialog.tsx (1)

1-28: LGTM! Clean imports and proper "use client" directive.

The imports are well-organized and the component is correctly marked as a client component for Next.js 15.

app/course/[course_id]/assignments/[assignment_id]/submissions/[submissions_id]/files/page.tsx (1)

1194-1230: Approve scroll retry logic with observation.

The retry approach (60 attempts × 50ms ≈ 3 seconds) is reasonable for handling async Monaco rendering. The implementation is sound.

components/ui/code-file.tsx (4)

210-244: Clean API design with backward compatibility.

Good approach supporting both legacy single-file mode (file prop) and new multi-file mode (files prop) with graceful fallbacks. The CodeFileHandle provides a clean imperative API for parent components.


464-508: Good optimization: debounced view zone height updates.

The approach of pausing height updates during scrolling (isScrollingRef) and batching with requestAnimationFrame is a solid pattern to prevent layout thrashing. The 10px threshold for "significant" height changes is reasonable.


542-582: Nice UX: Line highlight with fade-out animation.

The scrollToLine implementation correctly reveals the line in center and adds a temporary highlight that fades after 2 seconds. Previous highlights are properly cleaned up before adding new ones.


1105-1107: LGTM - early return is correctly placed after all hooks.

Unlike the page.tsx issue, this early return for missing currentFile is correctly positioned after all hook calls.

Comment thread components/ui/annotation-comment-dialog.tsx
Comment thread components/ui/code-file.tsx Outdated
Comment thread components/ui/code-file.tsx Outdated
Comment thread components/ui/monaco-rubric-context-menu.tsx Outdated
coderabbitai[bot]
coderabbitai Bot previously approved these changes Feb 22, 2026
@argos-ci
Copy link
Copy Markdown

argos-ci Bot commented Apr 12, 2026

The latest updates on your projects. Learn more about Argos notifications ↗︎

Build Status Details Updated (UTC)
default (Inspect) ⚠️ Changes detected (Review) 89 changed Apr 12, 2026, 10:06 PM

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.

1 participant