Skip to content

ui: drop blueprintjs 6#1073

Merged
undefined-moe merged 6 commits into
masterfrom
no-bp6
Nov 4, 2025
Merged

ui: drop blueprintjs 6#1073
undefined-moe merged 6 commits into
masterfrom
no-bp6

Conversation

@undefined-moe

@undefined-moe undefined-moe commented Oct 30, 2025

Copy link
Copy Markdown
Member

Summary by CodeRabbit

  • UI/Design Updates

    • Migrated many UI components to a modern component library, added a compatibility progress theme, simplified tag visuals, and removed legacy icon/dark-theme style overrides.
    • Removed datepicker open shadow and explicit icon sizing/color overrides.
  • Improvements

    • Wrapped key pages with the new UI provider for consistent theming.
    • Added confirmation for subtask deletion and improved testcase add/move deduplication.
  • Bug Fixes

    • Fixed output field binding in the add-testcase dialog and removed an unintended navigable link.

@coderabbitai

coderabbitai Bot commented Oct 30, 2025

Copy link
Copy Markdown

Walkthrough

This PR migrates many UI components from Blueprint.js to Mantine across packages/ui-default and some frontend parts, replaces Popover/Modal/Tabs/Inputs with Mantine equivalents, and wraps roots with MantineProvider. It removes and adds multiple CSS/stylus rules, introduces postcss-preset-mantine and bp6-compat.css, updates package dependencies (React/ReactDOM, Mantine, mantine-contextmenu), and refactors several components (history, reaction, notification, upload, problemconfig tree/forms/testcase management). Reducer logic for testcase moves and __cases deduplication was adjusted. No public API signatures were added or removed.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

  • Heterogeneous changes: UI-library swap, styling removals/additions, component structure refactors, state/data-fetch pattern changes, and reducer logic updates.
  • Files/areas needing extra attention:
    • packages/ui-default/components/problemconfig/reducer/config.ts (testcase deduplication and __cases synchronization)
    • packages/ui-default/components/problemconfig/ProblemConfigTree.tsx and related tree files (AddTestcase, SubtaskSettings, Testcase) — UI and deletion/move semantics
    • packages/ui-default/components/discussion/history.page.tsx and reaction.page.tsx (data fetch and popover behavior changes)
    • packages/ui-default/components/notification/index.ts and packages/ui-default/components/upload.tsx (notification API and upload UI/state refactor)
    • packages/ui-default/postcss.config.js, packages/ui-default/theme/bp6-compat.css, and packages/ui-default/theme/default.js (build/style integration and new PostCSS plugins)
    • packages/ui-default/package.json (dependency and React version upgrades)

Possibly related PRs

Suggested reviewers

  • pandadtdyy

Pre-merge checks and finishing touches

✅ 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 "ui: drop blueprintjs 6" accurately and concisely summarizes the primary objective of this changeset, which involves systematically replacing Blueprint.js 6 components with Mantine equivalents across multiple UI components, templates, and theme files. The title is specific and clear, avoiding vague terminology, and clearly communicates the main change from a developer's perspective without requiring additional context or explanation.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch no-bp6

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 13

Caution

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

⚠️ Outside diff range comments (2)
packages/ui-default/components/discussion/reaction.page.tsx (1)

56-66: Add keyboard support to emoji items.

The emoji selection items are <div> elements with onClick handlers but lack keyboard accessibility. Users navigating with keyboard cannot select emoji reactions.

Apply this diff to add keyboard support:

               <div
                 key={emoji}
                 className={`medium-${12 / elesPerRow} small-${12 / elesPerRow} columns popover-reaction-item`}
+                role="button"
+                tabIndex={0}
+                aria-label={`React with ${emoji}`}
                 onClick={() => handleEmojiClick(payload, emoji, ele).then(() => {
                   updateOpen(false);
                   updateTrigger(false);
                 })}
+                onKeyDown={(e) => {
+                  if (e.key === 'Enter' || e.key === ' ') {
+                    e.preventDefault();
+                    handleEmojiClick(payload, emoji, ele).then(() => {
+                      updateOpen(false);
+                      updateTrigger(false);
+                    });
+                  }
+                }}
               >
packages/ui-default/components/problemconfig/tree/AddTestcase.tsx (1)

21-54: Block saving until both files are valid

Right now each effect overwrites valid with only its own lookup. If a user picks the output first, valid becomes truthy while input is still empty, so the Save button activates and we end up dispatching a testcase with no input file. Please derive validity from both sides at once so we never emit an incomplete pair. One way is to drop the per-effect assignments and add a combined check, e.g.:

@@
-    setValid(inputValid);
     if (inputValid && autoOutput) {
       const filename = input.substring(0, input.lastIndexOf('.'));
       let outputFile = '';
@@
   }, [input]);
@@
-    setValid(outputValid);
     if (outputValid && autoInput) {
       const filename = output.substring(0, output.lastIndexOf('.'));
       if (testdata.find((i) => i.name === `${filename}.in`)) {
@@
   }, [output]);
+
+  useEffect(() => {
+    const inputValid = testdata.some((i) => i.name === input);
+    const outputValid = testdata.some((i) => i.name === output);
+    setValid(inputValid && outputValid);
+  }, [input, output, testdata]);
🧹 Nitpick comments (2)
packages/ui-default/components/problemconfig/tree/Testcase.tsx (2)

25-33: Redundant condition in prefix check.

The condition c.input.startsWith(prefix) is always true since prefix is derived from c.input.slice(0, i). Only the c.output.startsWith(prefix) check is necessary.

Apply this diff to simplify the logic:

   const minlength = Math.min(c.input.length, c.output.length);
   for (let i = minlength; i >= 0; i--) {
     const prefix = c.input.slice(0, i);
-    if (c.input.startsWith(prefix) && c.output.startsWith(prefix)) {
+    if (c.output.startsWith(prefix)) {
       display = `${prefix}(${c.input.substring(i)}/${c.output.substring(i)})`;
       break;
     }

67-74: Consider optimizing dependency array.

Using JSON.stringify(cases) in the dependency array works but is not optimal for performance. Consider using a stable reference or useMemo to track changes more efficiently.

Example optimization:

+  const casesKey = React.useMemo(() => cases.map(c => `${c.input}|${c.output}`).join(','), [cases]);
   const [collected, drag] = useDrag(() => ({
     type: 'cases',
     collect: (monitor) => ({
       isDragging: monitor.isDragging(),
     }),
     canDrag: props.selected,
     item: { cases, subtaskId },
-  }), [JSON.stringify(cases), subtaskId]);
+  }), [casesKey, subtaskId]);
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6d2442c and a950374.

📒 Files selected for processing (25)
  • packages/components/frontend/autocomplete/autocomplete.scss (0 hunks)
  • packages/ui-default/components/datepicker/datepicker.styl (0 hunks)
  • packages/ui-default/components/discussion/history.page.tsx (3 hunks)
  • packages/ui-default/components/discussion/reaction.page.tsx (3 hunks)
  • packages/ui-default/components/notification/index.ts (2 hunks)
  • packages/ui-default/components/notification/notification.page.styl (1 hunks)
  • packages/ui-default/components/problem/tag.page.styl (1 hunks)
  • packages/ui-default/components/problemconfig/ProblemConfigForm.tsx (4 hunks)
  • packages/ui-default/components/problemconfig/ProblemConfigTree.tsx (4 hunks)
  • packages/ui-default/components/problemconfig/ProblemType.tsx (2 hunks)
  • packages/ui-default/components/problemconfig/index.tsx (2 hunks)
  • packages/ui-default/components/problemconfig/reducer/config.ts (2 hunks)
  • packages/ui-default/components/problemconfig/tree/AddTestcase.tsx (3 hunks)
  • packages/ui-default/components/problemconfig/tree/SubtaskSettings.tsx (3 hunks)
  • packages/ui-default/components/problemconfig/tree/Testcase.tsx (4 hunks)
  • packages/ui-default/components/upload.tsx (3 hunks)
  • packages/ui-default/package.json (2 hunks)
  • packages/ui-default/pages/problem_config.page.styl (0 hunks)
  • packages/ui-default/pages/problem_config.page.tsx (4 hunks)
  • packages/ui-default/postcss.config.js (1 hunks)
  • packages/ui-default/templates/contest_detail.html (1 hunks)
  • packages/ui-default/templates/problem_detail.html (1 hunks)
  • packages/ui-default/theme/bp6-compat.css (1 hunks)
  • packages/ui-default/theme/dark.styl (0 hunks)
  • packages/ui-default/theme/default.js (2 hunks)
💤 Files with no reviewable changes (4)
  • packages/components/frontend/autocomplete/autocomplete.scss
  • packages/ui-default/theme/dark.styl
  • packages/ui-default/components/datepicker/datepicker.styl
  • packages/ui-default/pages/problem_config.page.styl
🧰 Additional context used
🧬 Code graph analysis (12)
packages/ui-default/components/upload.tsx (2)
packages/ui-default/components/dialog/index.tsx (1)
  • Dialog (11-63)
packages/ui-default/utils/base.ts (2)
  • tpl (39-58)
  • i18n (14-17)
packages/ui-default/components/problemconfig/reducer/config.ts (1)
framework/utils/lib/common.ts (1)
  • sortFiles (216-238)
packages/ui-default/components/problemconfig/tree/SubtaskSettings.tsx (3)
packages/ui-default/utils/base.ts (1)
  • i18n (14-17)
framework/utils/lib/common.ts (2)
  • parseTimeMS (164-170)
  • parseMemoryMB (172-178)
packages/ui-default/components/autocomplete/CustomSelectAutoComplete.tsx (1)
  • CustomSelectAutoComplete (8-23)
packages/ui-default/components/problemconfig/index.tsx (3)
packages/ui-default/utils/base.ts (1)
  • i18n (14-17)
packages/ui-default/components/problemconfig/ProblemConfigForm.tsx (1)
  • ProblemConfigForm (89-103)
packages/ui-default/components/problemconfig/ProblemConfigTree.tsx (1)
  • ProblemConfigTree (147-163)
packages/ui-default/pages/problem_config.page.tsx (1)
packages/ui-default/components/problemconfig/index.tsx (1)
  • ProblemConfig (15-57)
packages/ui-default/components/notification/index.ts (1)
packages/ui-default/utils/base.ts (1)
  • tpl (39-58)
packages/ui-default/components/problemconfig/ProblemConfigForm.tsx (1)
packages/ui-default/components/problemconfig/BasicForm.tsx (1)
  • FormItem (8-20)
packages/ui-default/components/problemconfig/ProblemConfigTree.tsx (5)
packages/ui-default/utils/base.ts (1)
  • i18n (14-17)
packages/ui-default/components/dialog/index.tsx (1)
  • confirm (253-258)
packages/ui-default/components/problemconfig/tree/SubtaskSettings.tsx (2)
  • SubtaskSettings (17-126)
  • GlobalSettings (128-180)
packages/ui-default/components/problemconfig/tree/SelectionManager.tsx (1)
  • SelectionManager (21-130)
packages/ui-default/components/problemconfig/tree/AddTestcase.tsx (1)
  • AddTestcase (9-137)
packages/ui-default/components/problemconfig/tree/AddTestcase.tsx (2)
packages/ui-default/utils/base.ts (1)
  • i18n (14-17)
packages/ui-default/components/autocomplete/FileSelectAutoComplete.tsx (1)
  • FileSelectAutoComplete (11-26)
packages/ui-default/components/discussion/history.page.tsx (2)
packages/ui-default/utils/base.ts (1)
  • i18n (14-17)
packages/ui-default/misc/Page.ts (1)
  • AutoloadPage (39-46)
packages/ui-default/components/problemconfig/ProblemType.tsx (3)
packages/ui-default/utils/base.ts (1)
  • i18n (14-17)
packages/ui-default/components/problemconfig/BasicForm.tsx (3)
  • FormItem (8-20)
  • SingleFileSelect (59-92)
  • ManagedSelect (43-57)
packages/ui-default/components/monaco/schema/problemconfig.ts (1)
  • testlibCheckers (3-7)
packages/ui-default/components/problemconfig/tree/Testcase.tsx (1)
packages/ui-default/utils/base.ts (1)
  • i18n (14-17)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: build
  • GitHub Check: Analyze (javascript)
  • GitHub Check: build
🔇 Additional comments (27)
packages/ui-default/templates/contest_detail.html (1)

15-23: Verify CSS classes exist and styling migrates cleanly.

The migration from Blueprint.js (bp6-tag, bp6-large, bp6-minimal) to generic classes (problem__tag-item, interactive) removes all bp6-specific styling. Ensure the new CSS rules provide equivalent visual appearance and spacing.

Key verifications needed:

  • Confirm .problem__tag-item is defined with baseline styling (padding, margin, display, font-size)
  • Verify .interactive class maintains link-like appearance (cursor, color, hover states)
  • Ensure icon placement and sizing work correctly with combined icon icon-* classes

Can you confirm that the CSS for .problem__tag-item and .interactive classes is properly defined and that the visual appearance matches the Blueprint.js styling?

packages/ui-default/templates/problem_detail.html (2)

93-107: Verify .problem__tag-item styling provides consistent appearance across diverse contexts.

The migration replaces multiple bp6 classes with .problem__tag-item across different semantic contexts: IDs, types, durations, memory, links, and difficulty. Ensure the unified class provides appropriate styling without losing context-specific visual differentiation.

Specific concerns:

  • Line 93: ID tag vs other tags—ensure sufficient visual distinction if needed
  • Lines 104–105: <a> elements with .interactive class—verify link styling, underlines, and hover states
  • Line 107: "Uploaded By" with .interactive—confirm interactive styling is consistent with link tags above

Please verify that .problem__tag-item and .interactive classes provide adequate styling for both static (<span>) and interactive (<a>) elements without visual regressions or loss of interactivity indicators.


110-112: Confirm tag display logic works with simplified classes.

Lines 110 and 112 handle conditional tag display (show/hide logic). The nojs--hide and hasjs--hide classes are preserved, but verify the combined .problem__tag-item.interactive.nojs--hide.icon.icon-tag selectors are correctly styled, particularly for the toggle button on line 110.

Verify that CSS selectors for combined classes (.problem__tag-item.interactive.nojs--hide.icon.icon-tag) are defined and that the tag toggle button renders correctly with proper styling and interactivity.

packages/ui-default/components/problem/tag.page.styl (2)

18-36: Verify intentional property overrides.

Several properties are re-declared with different values compared to earlier lines:

  • padding: Line 18 (0 10px !important) vs. line 34 (6px 8px)
  • margin-bottom: Line 20 (rem(5px)) vs. line 46 (implicitly overridden by child spacing rules)
  • line-height: Line 21 (30px !important) vs. line 30 (18px)

Confirm these overrides are intentional for the Mantine migration, or consolidate the rules to avoid confusion.


49-57: Good accessibility and UX improvements.

The focus outline styling (lines 49-52) and interactive variant hover state (lines 53-57) enhance accessibility and user experience. The outline color and offset look appropriate.

packages/ui-default/package.json (4)

23-25: Mantine package versions are consistent and well-chosen.

All Mantine packages use the same version (^8.3.6), ensuring compatibility across the UI library ecosystem.


27-27: Verify the purpose of the @sentry/webpack-plugin version update.

Line 27 shows @sentry/webpack-plugin updated to ^4.6.0 with a ~ marker. This change appears separate from the BP6→Mantine migration. Please confirm whether this update is intentional and related to the migration, or if it should be a separate commit.


70-70: Clarify mantine-contextmenu placement.

According to the AI summary, mantine-contextmenu was added to both devDependencies and dependencies. However, the provided code shows it only in devDependencies (line 70). Since this is a UI context menu component, verify whether it should be a runtime dependency (dependencies) rather than or in addition to a dev dependency, and update the placement if needed.


13-150: Blueprint.js packages successfully removed.

Verification confirms complete removal of all @blueprintjs/* packages from both devDependencies and dependencies, with no remaining Blueprint imports or references in the codebase.

packages/ui-default/components/notification/notification.page.styl (1)

68-72: LGTM! Clean Mantine styling addition.

The closing brace properly closes the @media print block, and the new hover styling for Mantine's notification close button provides appropriate visual feedback with a subtle background color change.

packages/ui-default/components/notification/index.ts (3)

1-5: LGTM! Proper imports for Mantine migration.

The imports correctly include all necessary Mantine dependencies (core, notifications) and React for creating elements. This aligns well with the Blueprint.js to Mantine migration.


7-35: LGTM! Well-structured Mantine theme configuration.

The custom theme properly extends Mantine's Notification component with project-specific styling, using CSS variables for flexibility and maintaining consistent visual design across the notification system.


37-41: LGTM! Correct React rendering approach.

The MantineProvider and Notifications are properly rendered using tpl with the true flag, which ensures they're rendered with ReactDOM.createRoot rather than static markup. This is necessary for Mantine's context and state management to work correctly.

packages/ui-default/components/discussion/reaction.page.tsx (2)

4-4: @mantine/core is properly declared—no action needed.

Verification confirms that @mantine/core@^8.3.6, @mantine/hooks@^8.3.6, and @mantine/notifications@^8.3.6 are all properly declared in packages/ui-default/package.json. The import statement is valid and the packages are available as dependencies.


69-73: Clarify the purpose of the emojiSuggest input.

The emojiSuggest input (line 71) has no onChange handler, value attribute, or any emoji suggestion/filtering logic. It only manages focus state to keep the Popover open while focused. What is the intended functionality—is this a placeholder for future emoji search/filtering, or is the focus-state-only behavior intentional?

packages/ui-default/theme/default.js (1)

7-9: LGTM! Clean migration to Mantine stylesheets.

The CSS imports correctly reflect the migration from Blueprint.js to Mantine, with the addition of bp6-compat.css for backward compatibility with any remaining bp6-styled components.

Also applies to: 31-31

packages/ui-default/theme/bp6-compat.css (5)

1-9: LGTM! Well-structured progress bar container.

The base styles for the progress bar container are clean and follow standard patterns.


11-20: LGTM! Smooth progress meter implementation.

The meter styles include a nice striped gradient effect and smooth width transitions for progress updates.


26-28: LGTM! Clean no-stripes variant.

The override correctly removes the striped background for solid progress bars.


30-36: LGTM! Appropriate dark theme adjustments.

The dark theme colors provide good contrast and follow standard dark mode conventions.


38-52: Intent color styles are correctly defined but unused in the codebase.

The four intent color variants (primary, success, warning, danger) are properly defined with appropriate semantic colors. However, verification shows these classes exist only in the compatibility CSS file with no active usage in any component code, indicating these are orphaned styles in a deprecated compatibility layer. No action is required for this code section.

packages/ui-default/components/problemconfig/tree/Testcase.tsx (1)

64-91: LGTM! Context menu integration is well implemented.

The context menu for moving test cases is correctly implemented with proper target filtering and Redux action dispatching. The fallback for no available targets is a good UX touch.

packages/ui-default/pages/problem_config.page.tsx (2)

118-137: LGTM! Auto-config guard logic is correct.

The autoConfigTriggered flag correctly prevents repeated auto-configuration runs. The logic ensures that auto-configuration happens once and then the guard prevents further executions.


146-150: LGTM! MantineProvider integration is correct.

The MantineProvider wrapper is correctly applied to provide Mantine theme and context to the component tree. This follows the standard Mantine integration pattern.

packages/ui-default/components/problemconfig/index.tsx (1)

1-57: LGTM! Blueprint to Mantine migration is well executed.

The migration successfully replaces Blueprint.js components with Mantine equivalents while maintaining the same functionality. The Tabs and Modal implementations follow Mantine best practices, and the ContextMenuProvider integration enables proper context menu support.

packages/ui-default/components/problemconfig/ProblemConfigTree.tsx (2)

56-97: LGTM! SubtaskNode migration to Mantine is well implemented.

The refactoring successfully migrates from Blueprint to Mantine while maintaining functionality. The delete confirmation pattern with confirm().then() is correct, and the inline styles are appropriate for the Mantine migration.


117-143: LGTM! SubtaskConfigTree successfully migrated to Mantine Tree.

The refactored tree implementation uses Mantine's Tree component effectively with a custom renderNode function. The memoized rootNodes structure and type-based rendering logic are clean and maintainable.

Comment thread packages/ui-default/components/discussion/history.page.tsx
Comment on lines +44 to +51
<span
className="icon icon-emoji"
onMouseEnter={() => updateTrigger(true)}
onMouseLeave={() => {
setTimeout(() => updateTrigger(false), 300);
}}
/>
</Popover.Target>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Add cleanup for setTimeout and improve accessibility.

Two issues with the trigger element:

  1. Memory leak risk: The setTimeout on line 48 is not cleaned up if the component unmounts before the timeout fires. This can cause attempted state updates on unmounted components.

  2. Accessibility blocker: The <span> element with mouse handlers is not keyboard-accessible. Users who rely on keyboard navigation cannot interact with the emoji reactions.

Apply this diff to address both issues:

-  const [open, updateOpen] = React.useState(false);
-  const [trigger, updateTrigger] = React.useState(false);
+  const [open, updateOpen] = React.useState(false);
+  const [trigger, updateTrigger] = React.useState(false);
+  const timeoutRef = React.useRef<number>();
+
+  React.useEffect(() => {
+    return () => {
+      if (timeoutRef.current) clearTimeout(timeoutRef.current);
+    };
+  }, []);
+
   return (
     <Popover opened={trigger || open || focus}>
       <Popover.Target>
         <span
           className="icon icon-emoji"
+          role="button"
+          tabIndex={0}
+          aria-label="Add reaction"
           onMouseEnter={() => updateTrigger(true)}
           onMouseLeave={() => {
-            setTimeout(() => updateTrigger(false), 300);
+            timeoutRef.current = window.setTimeout(() => updateTrigger(false), 300);
           }}
+          onKeyDown={(e) => {
+            if (e.key === 'Enter' || e.key === ' ') {
+              e.preventDefault();
+              updateTrigger((prev) => !prev);
+            }
+          }}
         />
       </Popover.Target>

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In packages/ui-default/components/discussion/reaction.page.tsx around lines 44
to 51, the span using setTimeout for mouseLeave must clear the timeout on
unmount and before scheduling another one to avoid state updates on unmounted
components; use a ref to store the timeout id and call clearTimeout(ref.current)
before setting a new timeout and in a useEffect cleanup. Also make the span
keyboard-accessible by adding tabIndex={0}, onFocus/onBlur handlers that mirror
onMouseEnter/onMouseLeave, and an onKeyDown handler to open the popover when
Enter or Space is pressed.

Comment thread packages/ui-default/components/discussion/reaction.page.tsx
Comment thread packages/ui-default/components/notification/index.ts Outdated
Comment thread packages/ui-default/components/notification/index.ts Outdated
Comment thread packages/ui-default/components/problemconfig/tree/AddTestcase.tsx Outdated
@@ -1,4 +1,4 @@
import { Classes } from '@blueprintjs/core';
import { MantineProvider, Progress } from '@mantine/core';

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Critical: Module-level state causes race conditions with concurrent uploads.

The state variables (fileLabel, uploadLabel, fileProgress, uploadProgress, render) are declared at module scope, making them shared across all concurrent invocations of uploadFiles. If multiple upload dialogs are opened simultaneously, their progress updates will interfere with each other, causing incorrect UI state and progress display.

Move all state management into the ProgressDialog component and pass update callbacks as props or use a context/ref pattern:

-import { MantineProvider, Progress } from '@mantine/core';
+import { MantineProvider, Progress } from '@mantine/core';
 
 import $ from 'jquery';
 import React from 'react';
 import { Dialog } from 'vj/components/dialog/index';
 import Notification from 'vj/components/notification';
 import {
   delay, i18n, pjax, request, tpl,
 } from 'vj/utils';
 
 function onBeforeUnload(e) {
   e.returnValue = '';
 }
 
 interface UploadOptions {
   type?: string;
   pjax?: boolean;
   sidebar?: boolean;
   singleFileUploadCallback?: (file: File) => any;
   filenameCallback?: (file: File) => string;
 }
+
+interface ProgressState {
+  fileLabel: string;
+  uploadLabel: string;
+  fileProgress: number;
+  uploadProgress: number;
+}
+
 export default async function uploadFiles(endpoint = '', files: File[] | FileList = [], options: UploadOptions = {}) {
-  let fileLabel = '';
-  let uploadLabel = '';
-  let fileProgress = 0;
-  let uploadProgress = 0;
-  let render = () => { };
+  const stateRef = React.createRef<{
+    updateProgress: (state: Partial<ProgressState>) => void;
+  }>();
 
-  function ProgressDialog() {
-    const [, setRender] = React.useState(0);
+  function ProgressDialog() {
+    const [state, setState] = React.useState<ProgressState>({
+      fileLabel: '',
+      uploadLabel: '',
+      fileProgress: 0,
+      uploadProgress: 0,
+    });
+    
     React.useEffect(() => {
-      render = () => setRender((r) => r + 1);
+      if (stateRef.current) {
+        stateRef.current.updateProgress = (newState) => {
+          setState((prev) => ({ ...prev, ...newState }));
+        };
+      }
     }, []);
+    
     return <MantineProvider>
       <div
         style={{
           textAlign: 'center', marginBottom: '5px', color: 'gray', fontSize: 'small',
         }}
-      >{uploadLabel}</div>
-      <Progress value={uploadProgress} />
+      >{state.uploadLabel}</div>
+      <Progress value={state.uploadProgress} />
       <div
         style={{
           textAlign: 'center', margin: '5px 0', color: 'gray', fontSize: 'small',
         }}
-      >{fileLabel}</div>
-      <Progress value={fileProgress} />
+      >{state.fileLabel}</div>
+      <Progress value={state.fileProgress} />
     </MantineProvider>;
   }

Then update the xhr event handlers to use stateRef.current.updateProgress:

           xhr.upload.addEventListener('loadstart', () => {
-            uploadLabel = `[${+i + 1}/${files.length}] ${file.name} `;
-            uploadProgress = Math.round((+i + 1) / files.length * 100);
-            fileLabel = i18n('Uploading... ({0}%)', 0);
-            fileProgress = 0;
-            render();
+            stateRef.current?.updateProgress({
+              uploadLabel: `[${+i + 1}/${files.length}] ${file.name} `,
+              uploadProgress: Math.round((+i + 1) / files.length * 100),
+              fileLabel: i18n('Uploading... ({0}%)', 0),
+              fileProgress: 0,
+            });
           });
           xhr.upload.addEventListener('progress', (e) => {
             if (e.lengthComputable) {
               const percentComplete = Math.round((e.loaded / e.total) * 100);
-              if (percentComplete === 100) fileLabel = i18n('Processing...');
-              else fileLabel = i18n('Uploading... ({0}%)', percentComplete);
-              fileProgress = percentComplete;
-              render();
+              stateRef.current?.updateProgress({
+                fileLabel: percentComplete === 100 ? i18n('Processing...') : i18n('Uploading... ({0}%)', percentComplete),
+                fileProgress: percentComplete,
+              });
             }
           }, false);

Also applies to: 22-26

🤖 Prompt for AI Agents
packages/ui-default/components/upload.tsx around lines 1 and 22-26: module-level
state variables (fileLabel, uploadLabel, fileProgress, uploadProgress, render)
are declared at module scope causing race conditions when multiple upload
dialogs run concurrently; move all state into the ProgressDialog component (use
useState/useRef inside the component), remove the module-level declarations at
lines 22-26, and expose update callbacks or a stateRef (e.g.,
stateRef.current.updateProgress) passed into the uploadFiles/XHR handlers so the
handlers update the correct dialog instance instead of shared module state.

Comment on lines +28 to +47
function ProgressDialog() {
const [, setRender] = React.useState(0);
React.useEffect(() => {
render = () => setRender((r) => r + 1);
}, []);
return <MantineProvider>
<div
className="file-label"
style={{
textAlign: 'center', marginBottom: '5px', color: 'gray', fontSize: 'small',
}}
></div>
<div className={`${Classes.PROGRESS_BAR} ${Classes.INTENT_PRIMARY} bp5-no-stripes`}>
<div className={`file-progress ${Classes.PROGRESS_METER}`} style={{ width: 0 }}></div>
</div>
>{uploadLabel}</div>
<Progress value={uploadProgress} />
<div
className="upload-label"
style={{
textAlign: 'center', margin: '5px 0', color: 'gray', fontSize: 'small',
}}
></div>
<div className={`${Classes.PROGRESS_BAR} ${Classes.INTENT_PRIMARY} bp5-no-stripes`}>
<div className={`upload-progress ${Classes.PROGRESS_METER}`} style={{ width: 0 }}></div>
</div>
</>)),
>{fileLabel}</div>
<Progress value={fileProgress} />
</MantineProvider>;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Race condition: render() may be called before component mounts.

The render function is assigned inside useEffect, but it's invoked in the loadstart and progress event handlers (lines 72, 80) which can fire immediately after request.postFile is called on line 64. If these events fire before the component mounts and useEffect runs, render() will be a no-op, and the initial progress updates will be lost.

Consider one of these approaches:

  1. Use the ref-based pattern suggested in the previous comment to ensure the update function is available when needed
  2. Add a check to ensure the component is mounted before calling render:
  function ProgressDialog() {
    const [, setRender] = React.useState(0);
+   const mountedRef = React.useRef(false);
    React.useEffect(() => {
+     mountedRef.current = true;
-     render = () => setRender((r) => r + 1);
+     render = () => {
+       if (mountedRef.current) {
+         setRender((r) => r + 1);
+       }
+     };
+     return () => { mountedRef.current = false; };
    }, []);

Note: The ref-based solution in the previous comment is preferred as it eliminates the race condition entirely.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function ProgressDialog() {
const [, setRender] = React.useState(0);
React.useEffect(() => {
render = () => setRender((r) => r + 1);
}, []);
return <MantineProvider>
<div
className="file-label"
style={{
textAlign: 'center', marginBottom: '5px', color: 'gray', fontSize: 'small',
}}
></div>
<div className={`${Classes.PROGRESS_BAR} ${Classes.INTENT_PRIMARY} bp5-no-stripes`}>
<div className={`file-progress ${Classes.PROGRESS_METER}`} style={{ width: 0 }}></div>
</div>
>{uploadLabel}</div>
<Progress value={uploadProgress} />
<div
className="upload-label"
style={{
textAlign: 'center', margin: '5px 0', color: 'gray', fontSize: 'small',
}}
></div>
<div className={`${Classes.PROGRESS_BAR} ${Classes.INTENT_PRIMARY} bp5-no-stripes`}>
<div className={`upload-progress ${Classes.PROGRESS_METER}`} style={{ width: 0 }}></div>
</div>
</>)),
>{fileLabel}</div>
<Progress value={fileProgress} />
</MantineProvider>;
}
function ProgressDialog() {
const [, setRender] = React.useState(0);
const mountedRef = React.useRef(false);
React.useEffect(() => {
mountedRef.current = true;
render = () => {
if (mountedRef.current) {
setRender((r) => r + 1);
}
};
return () => { mountedRef.current = false; };
}, []);
return <MantineProvider>
<div
style={{
textAlign: 'center', marginBottom: '5px', color: 'gray', fontSize: 'small',
}}
>{uploadLabel}</div>
<Progress value={uploadProgress} />
<div
style={{
textAlign: 'center', margin: '5px 0', color: 'gray', fontSize: 'small',
}}
>{fileLabel}</div>
<Progress value={fileProgress} />
</MantineProvider>;
}
🤖 Prompt for AI Agents
In packages/ui-default/components/upload.tsx around lines 28-47, replace the
current render assignment via useEffect with a ref-based updater to avoid the
race: create a useRef for the render function, set renderRef.current = () =>
setRender(r => r + 1) synchronously (not only inside useEffect), and update the
event handlers elsewhere to call renderRef.current?.() instead of render(); this
ensures the updater exists immediately and eliminates the race where
loadstart/progress fire before the effect runs.

Comment thread packages/ui-default/postcss.config.js
Comment thread packages/ui-default/theme/bp6-compat.css Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 0

Caution

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

⚠️ Outside diff range comments (2)
packages/ui-default/components/problem/tag.page.styl (1)

18-33: CSS specificity conflict: !important rules prevent new styles from applying.

Lines 18 and 21 declare padding and line-height with !important, while lines 29 and 33 add conflicting declarations without !important. In CSS, !important rules always win—this means the new styles on lines 29 and 33 will never apply.

Given this is a migration away from Blueprint.js, the intent is likely to use the new values (line-height: 18px, padding: 6px 8px). To fix this, either remove !important from the earlier declarations or add !important to the new ones.

Apply this diff (removing !important from lines 18 and 21):

 .problem__tag-item
-  padding: 0 10px !important
+  padding: 0 10px
   margin-left: rem(3px)
   margin-bottom: rem(5px)
-  line-height: 30px !important
+  line-height: 30px
   display: inline-flex

Alternatively, if you prefer to keep the !important flags, apply !important to lines 29 and 33:

-  font-size: 14px
-  line-height: 18px
+  font-size: 14px !important
+  line-height: 18px !important
-  padding: 6px 8px
+  padding: 6px 8px !important
packages/ui-default/components/problemconfig/tree/AddTestcase.tsx (1)

84-101: Add keyboard accessibility to clickable elements.

These divs act as buttons but lack semantic HTML and keyboard support, preventing keyboard-only users from accessing "Auto detect" and "Add testcase" features.

Consider using Mantine's Button component with variant="subtle" or add proper ARIA attributes:

-    <div
-      style={{ cursor: 'pointer', padding: '6px 0' }}
-      onClick={auto}
-    >
-      <Text><i className="icon icon-wrench" /> {i18n('Auto detect')}</Text>
-    </div>
-    <div
-      style={{ cursor: 'pointer', padding: '6px 0' }}
+    <Button
+      variant="subtle"
+      onClick={auto}
+      style={{ padding: '6px 0' }}
+      leftSection={<i className="icon icon-wrench" />}
+    >
+      {i18n('Auto detect')}
+    </Button>
+    <Button
+      variant="subtle"
+      style={{ padding: '6px 0' }}
       onClick={() => {
         setInput('');
         setOutput('');
         setAutoInput(true);
         setAutoOutput(true);
         setOpen(true);
       }}
+      leftSection={<i className="icon icon-add" />}
     >
-      <Text><i className="icon icon-add" /> {i18n('Add testcase')}</Text>
-    </div>
+      {i18n('Add testcase')}
+    </Button>
♻️ Duplicate comments (1)
packages/ui-default/components/discussion/reaction.page.tsx (1)

44-51: Add setTimeout cleanup and keyboard accessibility.

The issues flagged in the previous review remain unaddressed:

  1. Memory leak risk: The setTimeout on line 48 is not cleaned up if the component unmounts before the timeout fires, potentially causing state updates on unmounted components.

  2. Accessibility blocker: The <span> element with mouse handlers is not keyboard-accessible, preventing users who rely on keyboard navigation from interacting with emoji reactions.

Apply this diff to address both issues:

  const [focus, updateFocus] = React.useState(false);
  const [open, updateOpen] = React.useState(false);
  const [trigger, updateTrigger] = React.useState(false);
+  const timeoutRef = React.useRef<number>();
+
+  React.useEffect(() => {
+    return () => {
+      if (timeoutRef.current) clearTimeout(timeoutRef.current);
+    };
+  }, []);
+
  return (
    <Popover opened={trigger || open || focus}>
      <Popover.Target>
        <span
          className="icon icon-emoji"
+          role="button"
+          tabIndex={0}
+          aria-label="Add reaction"
          onMouseEnter={() => updateTrigger(true)}
          onMouseLeave={() => {
-            setTimeout(() => updateTrigger(false), 300);
+            if (timeoutRef.current) clearTimeout(timeoutRef.current);
+            timeoutRef.current = window.setTimeout(() => updateTrigger(false), 300);
          }}
+          onKeyDown={(e) => {
+            if (e.key === 'Enter' || e.key === ' ') {
+              e.preventDefault();
+              updateTrigger((prev) => !prev);
+            }
+          }}
        />
      </Popover.Target>
🧹 Nitpick comments (3)
packages/ui-default/theme/bp6-compat.css (1)

11-24: Add animation for striped progress meter (or confirm if static stripes are intentional).

The CSS includes a striped pattern via linear-gradient (line 12) with background-size: 30px 30px (line 14), but lacks the @keyframes animation to move the stripes and create the characteristic "crawling" effect typical of Blueprint progress bars.

A previous review flagged this missing animation. If animated stripes are desired (recommended for better UX feedback), apply this diff:

+@keyframes linear-progress-bar-stripes {
+    from {
+        background-position: 0 0;
+    }
+    to {
+        background-position: 30px 0;
+    }
+}
+
 .bp6-progress-bar .bp6-progress-meter {
     background: linear-gradient(-45deg, rgba(255, 255, 255, 0.2) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.2) 50%, rgba(255, 255, 255, 0.2) 75%, transparent 75%);
     background-color: rgba(95, 107, 124, 0.8);
     background-size: 30px 30px;
     border-radius: 40px;
     height: 100%;
     position: absolute;
     transition: width 200ms cubic-bezier(0.4, 1, 0.75, 0.9);
     width: 100%;
+    animation: linear-progress-bar-stripes 300ms linear infinite;
 }
 
 .bp6-progress-bar.bp6-no-stripes .bp6-progress-meter {

Confirm whether animated stripes should be included to match Blueprint.js behavior or if the current static stripes are intentional for this migration.

packages/ui-default/components/problemconfig/tree/AddTestcase.tsx (1)

85-132: Consider extracting repeated inline styles.

Several inline styles are duplicated across the component. While not critical, extracting these to CSS classes or Mantine's sx prop would improve maintainability.

For example, the repeated cursor: 'pointer', padding: '6px 0' pattern could be extracted to a shared style.

packages/ui-default/components/notification/index.ts (1)

95-133: Consider using CSS variables or theme colors for consistency.

The static methods use hardcoded color values ('#238551', '#2d72d2', '#fbb360', '#cd4246') while the theme configuration (lines 11-35) uses CSS variables extensively. This creates inconsistency and prevents the theme from controlling these notification colors.

Consider defining these colors in the theme or using CSS variables for better maintainability and theming consistency.


Verify the empty message field pattern is intentional.

All static methods pass an empty string for the message parameter and place the actual notification text in the title field. In Mantine's design system, title is typically a heading and message is the body text. If this is intentional for single-line notifications, it's fine, but please confirm this is the desired UX pattern.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a950374 and 30073e2.

📒 Files selected for processing (7)
  • packages/ui-default/components/discussion/reaction.page.tsx (3 hunks)
  • packages/ui-default/components/notification/index.ts (2 hunks)
  • packages/ui-default/components/notification/notification.page.styl (1 hunks)
  • packages/ui-default/components/problem/tag.page.styl (1 hunks)
  • packages/ui-default/components/problemconfig/tree/AddTestcase.tsx (3 hunks)
  • packages/ui-default/package.json (3 hunks)
  • packages/ui-default/theme/bp6-compat.css (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/ui-default/package.json
  • packages/ui-default/components/notification/notification.page.styl
🧰 Additional context used
🧬 Code graph analysis (3)
packages/ui-default/components/problemconfig/tree/AddTestcase.tsx (2)
packages/ui-default/utils/base.ts (1)
  • i18n (14-17)
packages/ui-default/components/autocomplete/FileSelectAutoComplete.tsx (1)
  • FileSelectAutoComplete (11-26)
packages/ui-default/components/notification/index.ts (1)
packages/ui-default/utils/base.ts (1)
  • tpl (39-58)
packages/ui-default/components/discussion/reaction.page.tsx (1)
packages/ui-default/misc/Page.ts (1)
  • AutoloadPage (39-46)
🔇 Additional comments (7)
packages/ui-default/components/problemconfig/tree/AddTestcase.tsx (1)

102-135: LGTM! Modal migration looks good.

The Mantine Modal correctly replaces the Blueprint.js Dialog with proper localization on the title (line 105). The overflow styles are necessary for the autocomplete dropdowns to display properly outside the modal bounds.

packages/ui-default/components/notification/index.ts (2)

1-5: LGTM!

The imports are correctly updated for the Mantine migration, including the necessary React import for createElement calls in the static methods.


7-41: LGTM!

The Mantine theme configuration is well-structured with proper use of CSS variables for styling consistency. The notification provider is correctly mounted to the document body using the tpl utility.

packages/ui-default/components/discussion/reaction.page.tsx (4)

4-4: LGTM!

The import migration from Blueprint.js to Mantine is correct and aligns with the PR objectives.


39-40: LGTM!

The state management for controlling Popover visibility is well-structured and clear.


52-74: LGTM!

The Popover.Dropdown structure correctly implements Mantine's pattern with proper mouse handlers and state management. The emoji selection logic cleanly closes the popover after the async operation completes.


82-84: LGTM!

The rendering correctly wraps each Reaction component with MantineProvider, and the JSX syntax is valid (no trailing comma).

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (6)
packages/ui-default/components/problemconfig/ProblemType.tsx (6)

19-24: Consider clarifying the category initialization logic.

The useEffect early-returns when category is truthy, but category isn't in the dependency array. While this appears intentional (to initialize category only once based on the initial checker value), the implicit one-shot behavior could be confusing for maintainers.

Consider adding a comment explaining the intended behavior or refactoring to make the one-time initialization more explicit.


43-71: Extract repeated condition into a helper for maintainability.

The condition (['strict', 'default'].includes(checkerType) || !checkerType) appears on both lines 48 and 62. Consider extracting this into a named helper function like isDefaultCheckerType(checkerType) to improve maintainability and reduce duplication.

Apply this pattern:

+const isDefaultCheckerType = (type) => ['strict', 'default'].includes(type) || !type;
+
 export default function ProblemType() {
   const Type = useSelector((state: RootState) => state.config.type);
   const checkerType = useSelector((state: RootState) => state.config.checker_type);
   ...
           <SegmentedControl
-            value={(['strict', 'default'].includes(checkerType) || !checkerType)
-              ? 'default' : (checkerType !== 'testlib' ? 'other' : 'testlib')}
+            value={isDefaultCheckerType(checkerType) ? 'default' : (checkerType !== 'testlib' ? 'other' : 'testlib')}
             onChange={(value) => {
               ...
             }}
           />
         </div>

-        {((['strict', 'default'].includes(checkerType) || !checkerType)) && (
+        {isDefaultCheckerType(checkerType) && (
           <Switch

73-102: Simplify the condition for testlib checker.

Line 73's condition (!['strict', 'default'].includes(checkerType) && checkerType && checkerType === 'testlib') is overly complex. Since checking checkerType === 'testlib' already implies it's truthy and not in ['strict', 'default'], the condition can be simplified.

Apply this diff:

-{(!['strict', 'default'].includes(checkerType) && checkerType && checkerType === 'testlib') && (
+{checkerType === 'testlib' && (
   <div className="row">

104-111: Consider a helper function for checker type checks.

Similar to the testlib section, this condition could benefit from a helper function like isCustomCheckerType(checkerType) for improved readability. This would make the intent clearer: checking for custom checker interfaces like 'syzoj', 'hustoj', etc.

Example:

const isCustomCheckerType = (type) => type && !['strict', 'default', 'testlib'].includes(type);

Then use:

-{(!['strict', 'default'].includes(checkerType) && checkerType && checkerType !== 'testlib') && (
+{isCustomCheckerType(checkerType) && (
   <div className="row">

124-131: Consider using controlled input for state consistency.

Line 126 uses defaultValue instead of value, making this an uncontrolled input. If numProcesses changes in the Redux store (e.g., through external updates or reset actions), the input field won't reflect the updated value. Consider switching to a controlled input pattern.

Apply this diff:

 <input
-  defaultValue={numProcesses || 2}
+  value={numProcesses || 2}
   placeholder="2"
   onChange={(ev) => dispatch(({ type: 'CONFIG_FORM_UPDATE', key: 'num_processes', value: +ev.currentTarget.value }))}
   className="textbox"
 />

Note: The extra parentheses in line 128's dispatch(({...})) are redundant but harmless—you may optionally simplify to dispatch({...}).


147-155: Consider using controlled input for filename field.

Similar to the num_processes input, line 149 uses defaultValue instead of value, creating an uncontrolled input. This can lead to UI/state inconsistencies if the Redux state is updated externally.

Apply this diff:

 <input
-  defaultValue={filename || '#.txt'}
+  value={filename || '#.txt'}
   placeholder="#.txt"
   disabled={subType !== 'multi'}
   onChange={(ev) => dispatch(({ type: 'CONFIG_FORM_UPDATE', key: 'filename', value: ev.currentTarget.value }))}
   className="textbox"
 />
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 30073e2 and 3e9ea1b.

📒 Files selected for processing (7)
  • packages/common/subtask.ts (2 hunks)
  • packages/ui-default/components/problemconfig/ProblemConfigTree.tsx (4 hunks)
  • packages/ui-default/components/problemconfig/ProblemType.tsx (2 hunks)
  • packages/ui-default/components/problemconfig/index.tsx (2 hunks)
  • packages/ui-default/package.json (5 hunks)
  • packages/ui-default/pages/problem_config.page.styl (1 hunks)
  • packages/ui-default/pages/problem_main.page.tsx (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • packages/ui-default/pages/problem_main.page.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/ui-default/components/problemconfig/index.tsx
🧰 Additional context used
🧬 Code graph analysis (3)
packages/ui-default/components/problemconfig/ProblemType.tsx (3)
packages/ui-default/utils/base.ts (1)
  • i18n (14-17)
packages/ui-default/components/problemconfig/BasicForm.tsx (3)
  • FormItem (8-20)
  • SingleFileSelect (59-92)
  • ManagedSelect (43-57)
packages/ui-default/components/monaco/schema/problemconfig.ts (1)
  • testlibCheckers (3-7)
packages/ui-default/components/problemconfig/ProblemConfigTree.tsx (5)
packages/ui-default/utils/base.ts (1)
  • i18n (14-17)
packages/ui-default/components/dialog/index.tsx (1)
  • confirm (253-258)
packages/ui-default/components/problemconfig/tree/SubtaskSettings.tsx (2)
  • SubtaskSettings (17-126)
  • GlobalSettings (128-180)
packages/ui-default/components/problemconfig/tree/SelectionManager.tsx (1)
  • SelectionManager (21-130)
packages/ui-default/components/problemconfig/tree/AddTestcase.tsx (1)
  • AddTestcase (9-137)
packages/common/subtask.ts (1)
framework/utils/lib/common.ts (1)
  • sortFiles (216-238)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: build
  • GitHub Check: Analyze (javascript)
  • GitHub Check: build
🔇 Additional comments (13)
packages/common/subtask.ts (3)

1-1: LGTM! Import addition supports natural sorting.

The sortFiles import is correctly added to support the natural/numeric sorting of test cases on line 130.


121-123: Good addition: Prevents duplicate test cases.

The duplicate check ensures that cases with identical input and output filenames aren't added multiple times to a subtask. This improves data integrity and aligns with the testcase deduplication mentioned in the PR summary.


130-130: Good addition: Ensures consistent test case ordering.

Sorting cases by input filename using sortFiles provides predictable, natural ordering (e.g., "1.in", "2.in", "10.in" instead of lexicographic "1.in", "10.in", "2.in"). This improves consistency across the system.

packages/ui-default/components/problemconfig/ProblemConfigTree.tsx (5)

3-3: LGTM! Clean migration to Mantine components.

The import changes correctly replace Blueprint.js components with Mantine equivalents and add the confirmation dialog utility.

Also applies to: 9-9


48-76: LGTM! Safe deletion flow with user confirmation.

The delete confirmation logic properly prevents accidental deletions by requiring explicit user confirmation before dispatching the deleteSubtask action. The stopPropagation correctly prevents the parent click handler from firing.


56-97: LGTM! Successful migration to Mantine layout.

The SubtaskNode component has been cleanly migrated from Blueprint TreeNode to a Mantine-based layout using Text and Button components. The expand/collapse logic and drag-and-drop functionality remain intact.


104-144: LGTM! Clean Mantine Tree implementation.

The SubtaskConfigTree has been successfully migrated to use Mantine's Tree component with proper memoization and custom rendering logic. The autoConfigure callback dependencies are correct, and the rootNodes structure properly defines the tree hierarchy.


155-158: LGTM! Simplified layout structure.

The right pane has been appropriately simplified by replacing the Blueprint Tree structure with a plain div container, aligning with the Mantine migration.

packages/ui-default/components/problemconfig/ProblemType.tsx (3)

27-41: LGTM!

The Problem Type selector UI is well-structured with proper i18n labels and clean state binding.


115-119: LGTM!

Interactive type configuration is straightforward and properly implemented.


159-161: LGTM!

Appropriate messaging for unsupported objective type configuration.

packages/ui-default/package.json (2)

23-26: Well-coordinated dependency migration to Mantine + React 19 ecosystem.

The additions and upgrades align with the broader Blueprint.js → Mantine migration documented across the codebase. All Mantine packages (core, hooks, notifications) are pinned to the same version (8.3.6) for consistency, and React is upgraded to 19.2.0 with matching @types (19.2.2). PostCSS and build tooling (postcss-preset-mantine, mantine-contextmenu) are properly integrated.

Also applies to: 39-40, 70-70, 86-86, 92-92, 96-96, 123-123


27-27: Remove React 19 compatibility concern; webpack plugin is runtime-agnostic.

The @sentry/webpack-plugin v4.6.0 upgrade is safe to merge. The @sentry/webpack-plugin itself is agnostic to the React runtime; compatibility with React 19 is determined by the Sentry React SDK (@sentry/react or @sentry/nextjs), not the webpack plugin. The minor version bump handles only release and source map management, which are unaffected by React versions. No React 19-specific verification is needed for this dependency.

Likely an incorrect or invalid review comment.

data={rootNodes}
levelOffset={12}
renderNode={({ node }) => {
const n: any = node;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Avoid using any type; define a proper union type.

The any type assertion defeats TypeScript's type safety. Since the node structure is known from rootNodes (lines 117-122), define a proper union type for better type safety.

Add a type definition before the component:

type TreeNodeType = 
  | { id: 'auto'; label: string; type: 'action' }
  | { id: 'global'; label: string; type: 'global' }
  | { id: string; label: string; type: 'subtask'; subtaskId: number }
  | { id: 'add'; label: string; type: 'add' };

Then use it:

-        const n: any = node;
+        const n = node as TreeNodeType;
🤖 Prompt for AI Agents
In packages/ui-default/components/problemconfig/ProblemConfigTree.tsx around
line 129, the code uses "const n: any = node;" which bypasses TypeScript safety;
replace this with a proper union type definition (e.g., a TreeNodeType union
that covers 'auto', 'global', 'subtask' with subtaskId, and 'add') declared
before the component, then change the assertion to use that type (const n:
TreeNodeType = node) and update any downstream property accesses to match the
union (n.type narrowings or type guards where needed) so the compiler enforces
correct fields instead of using any.

Comment thread packages/ui-default/package.json Outdated
Comment on lines +2 to +4
.main
.row
max-width: 100vw !important;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Avoid !important and prefer 100% over 100vw.

This rule has two issues:

  1. The !important flag makes the CSS harder to maintain and override. Consider removing it and ensuring proper specificity instead.
  2. Using 100vw can cause horizontal scrollbars when the vertical scrollbar is visible, as 100vw includes the scrollbar width while the viewport does not. Use max-width: 100% instead.

Apply this diff:

  .main
    .row
-      max-width: 100vw !important;
+      max-width: 100%;
🤖 Prompt for AI Agents
In packages/ui-default/pages/problem_config.page.styl around lines 2 to 4, the
rule uses "max-width: 100vw !important;" which is problematic: remove the
"!important" and replace "100vw" with "100%" so it becomes "max-width: 100%"; if
specificity issues remain, increase selector specificity or adjust stylesheet
order rather than using !important to enforce the rule.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
packages/ui-default/package.json (1)

123-123: Verify webpack-manifest-plugin ^6.0.1 exists in npm registry.

A previous review flagged that webpack-manifest-plugin version 6.0.0 does not exist in the npm registry (latest was 5.0.1). The version has been changed to 6.0.1, but this still needs verification to ensure the package can be resolved during installation.

webpack-manifest-plugin 6.0.1 npm package release
🧹 Nitpick comments (2)
packages/ui-default/components/discussion/history.page.tsx (2)

48-54: Display the actual error message.

The error message captured in state (line 36) is never shown to users. Line 51 only displays a generic "Loading failed." message, providing no insight into what went wrong.

Apply this diff to show the actual error:

        {(isLoading || error) && (
          <li className="menu__item">
            <a className="menu__link">
-             {isLoading ? i18n('Loading...') : i18n('Loading failed.')}
+             {isLoading ? i18n('Loading...') : `${i18n('Loading failed.')} ${error}`}
            </a>
          </li>
        )}

42-42: Consider resetting the load state on close for fresh data.

The load state is never reset to false, so closing and reopening the popover won't refetch the data. If the history might change and users expect fresh data on each open, consider adding an onClose handler.

If refetching is desired, add this:

-return <Popover onOpen={() => setLoad(true)}>
+return <Popover onOpen={() => setLoad(true)} onClose={() => setLoad(false)}>

Otherwise, the current caching behavior may be intentional and acceptable.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3e9ea1b and 182dc6e.

📒 Files selected for processing (2)
  • packages/ui-default/components/discussion/history.page.tsx (3 hunks)
  • packages/ui-default/package.json (5 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
packages/ui-default/components/discussion/history.page.tsx (2)
packages/ui-default/utils/base.ts (1)
  • i18n (14-17)
packages/ui-default/misc/Page.ts (1)
  • AutoloadPage (39-46)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Analyze (javascript)
  • GitHub Check: build
  • GitHub Check: build
🔇 Additional comments (2)
packages/ui-default/package.json (2)

92-92: Verify React 19.2.0 compatibility with the build and runtime.

The upgrade from React ^18.x to ^19.2.0 is a major version change with known breaking changes (ref prop handling, removed APIs like propTypes/defaultProps for function components, string refs removal, etc.). Ensure:

  1. All ref callbacks are properly refactored (React 19 disallows returning values from ref callbacks).
  2. TypeScript types are updated correctly (your @types/react and @types/react-dom are pinned to ^19.2.2, which is good).
  3. All JSX transform is using the modern 2020+ format.
  4. Any test utilities or legacy APIs have been migrated appropriately.

Also applies to: 96-96


23-25: Verify Mantine package version compatibility.

The Mantine packages are pinned to different versions:

  • @mantine/core, @mantine/hooks, @mantine/notifications: ^8.3.6
  • mantine-contextmenu: ^8.2.0
  • postcss-preset-mantine: ^1.18.0

Confirm these versions are compatible with each other and the project's build configuration. If postcss-preset-mantine is used in a PostCSS config, verify it is properly wired up in your Webpack/PostCSS pipeline.

Also applies to: 70-70, 86-86

Comment on lines +30 to +40
React.useEffect(() => {
if (!load) return;
setLoading(true);
request.get(`${payload}?all=1`).then(({ history }) => {
setData(history);
}).catch((e) => {
setError(e.message);
}).finally(() => {
setLoading(false);
});
}, [load]);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Reset error state before fetching and add request cleanup.

The error state is never cleared before making a new request. If a previous fetch failed, the stale error message persists in state even while isLoading is true, causing the condition on line 48 to show both loading and error simultaneously.

Apply this diff to reset the error state and add proper cleanup:

  React.useEffect(() => {
    if (!load) return;
+   setError('');
    setLoading(true);
-   request.get(`${payload}?all=1`).then(({ history }) => {
+   const abortController = new AbortController();
+   request.get(`${payload}?all=1`, { signal: abortController.signal }).then(({ history }) => {
      setData(history);
    }).catch((e) => {
+     if (e.name === 'AbortError') return;
      setError(e.message);
    }).finally(() => {
      setLoading(false);
    });
+   return () => abortController.abort();
  }, [load]);
🤖 Prompt for AI Agents
In packages/ui-default/components/discussion/history.page.tsx around lines 30 to
40, the effect does not clear previous error before starting a new fetch and
lacks cleanup to avoid updating state after unmount or when a later fetch
supersedes an earlier one; before calling request.get, clear the error state
(setError(null) or ''), then perform the fetch and on success/error update state
as now, and implement cleanup by using an AbortController (pass its signal to
request.get if supported) or a local "isMounted/isCurrent" flag to ignore
results from stale requests and prevent setState in the finally/then/catch when
the component is unmounted or the request was aborted.

@undefined-moe undefined-moe merged commit 3996f6f into master Nov 4, 2025
7 of 9 checks passed
@undefined-moe undefined-moe deleted the no-bp6 branch November 4, 2025 09:08
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