Skip to content

Conversation

@hansoojeongsj
Copy link
Member

@hansoojeongsj hansoojeongsj commented Oct 14, 2025

📌 Related Issues

✅ 체크 리스트

  • PR 제목의 형식을 잘 작성했나요? e.g. [Feat] PR 템플릿 작성
  • 빌드가 성공했나요? (pnpm build)
  • 컨벤션을 지켰나요?

📄 Tasks

📷 Screenshot

🔔 ETC

Summary by CodeRabbit

  • New Features

    • Simple Whiteboard page with drawing, eraser, color/size controls, image upload, clear and save-as-PNG; new protected route.
    • Global Toast component and integrated toast flows (reviews, profile action, download guard, login loading UI).
  • Refactor

    • Removed inline action buttons from chat messages, simplifying chat UI.
  • Style

    • Removed chat button styles; increased bottom padding in login/signup footers.
  • Chores

    • Added runtime dependencies for whiteboard and icon support.

@hansoojeongsj hansoojeongsj linked an issue Oct 14, 2025 that may be closed by this pull request
2 tasks
@coderabbitai
Copy link

coderabbitai bot commented Oct 14, 2025

Walkthrough

Removed chat button support and styles; added a Fabric.js SimpleWhiteboard (component + styles + route); introduced a Toast component and styles and wired toasts into My and ReviewNotes; replaced LoginCallback empty render with Loading; adjusted bottom paddings; and added runtime deps (fabric, react-icons, @types/fabric).

Changes

Cohort / File(s) Summary
Chat UI update
src/pages/solve/ChatManager.tsx
Removed buttons?: { label: string; onClick: () => void }[] from type Chat and deleted UI rendering for chat.buttons.
Styles cleanup (solve)
src/pages/solve/solve.css.ts
Removed exported styles chatButtons and chatButton and their declarations.
Whiteboard feature
src/pages/simpleWhiteboard/SimpleWhiteboard.tsx, src/pages/simpleWhiteboard/simpleWhiteboard.css.ts
Added a new Fabric.js whiteboard component (pen/eraser/color/size, image upload, clear, save) and corresponding styles.
Routing / Lazy loading
src/routes/globalRoutes.tsx, src/routes/lazy.tsx, src/routes/routePath.ts
Added SIMPLE_WHITEBOARD route path, lazy export SimpleWhiteboardPage, and registered protected route for the whiteboard.
Toast component
src/shared/components/toast/Toast.tsx, src/shared/components/toast/toast.css.ts
Added Toast component with enter/exit animations, duration/onClose handling, and related styles/keyframes.
Toast usage
src/pages/my/My.tsx, src/pages/reviewNotes/ReviewNotes.tsx
Added toast state/queue and showToast helper; My: added 연습장 button with goWhiteboard logic (navigate or show toast); ReviewNotes: show toast when attempting PDF download with no selection.
Login loading UI
src/pages/loginCallback/LoginCallback.tsx
Replaced empty render with <Loading /> while login callback processes.
Minor padding tweaks
src/pages/login/login.css.ts, src/pages/signup/signup.css.ts
Increased bottom padding of buttonWrapper from 1.6rem to 2.4rem.
Package manifest
package.json
Added runtime deps fabric, react-icons and dev types @types/fabric.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant U as User
  participant Router as App Router
  participant WB as SimpleWhiteboardPage
  participant Canvas as Fabric Canvas
  participant FI as FileInput (hidden)
  participant DL as Download action

  U->>Router: Navigate to /simple-whiteboard
  Router->>WB: mount SimpleWhiteboardPage
  WB->>Canvas: init Fabric canvas & PencilBrush
  U->>WB: select tool / color / size
  WB->>Canvas: update brush/eraser settings
  U->>FI: choose image
  FI-->>WB: file data
  WB->>Canvas: add image (non-selectable)
  U->>WB: click "Save"
  WB->>Canvas: export to PNG
  Canvas-->>WB: data URL
  WB->>DL: trigger download
  DL-->>U: PNG file
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Potential attention points:

  • SimpleWhiteboard: Fabric initialization/disposal, responsive resizing, image handling, export correctness.
  • Toast: timing, enter/exit animation sync, queue and onClose behavior.
  • Routing: lazy import and protected-route registration.
  • package.json deps: ensure build and type integration.

Possibly related PRs

Poem

I am a rabbit, nibbling code by light,
Buttons cleared away to set the UI right.
A whiteboard blooms where sketches come to play,
Toasts softly whisper what the user may.
Hopping forward — routes and pixels bright. 🐇✨

Pre-merge checks and finishing touches

❌ Failed checks (4 warnings, 1 inconclusive)
Check name Status Explanation Resolution
Linked Issues Check ⚠️ Warning Issue #57 specifically requests: fixing mobile photo capture issues in the solve feature, enabling PDF downloads in in-app browsers via direct URLs, and unifying token-related HTTP errors to 401 status codes. While the PR adds a SimpleWhiteboard component and Toast notifications which could address mobile solve issues, the code changes do not demonstrate implementation of the critical token error unification (HTTP 401) or PDF download URL handling that were explicitly requested. Without evidence of these requirements being met, the PR's alignment with the linked issue objectives is unclear. Verify that the changes address the specific requirements from issue #57. If the token error unification and PDF download handling were delegated to the backend, explicitly document this in the PR description. If these features are in this PR, clarify where they are implemented in the code. Ensure the Tasks section connects each implementation to the corresponding requirement from issue #57.
Out of Scope Changes Check ⚠️ Warning Several changes appear to be outside the scope of issue #57. The adjustments to login.css.ts (buttonWrapper padding: '1.6rem' → '2.4rem') and signup.css.ts (similar padding change) are styling tweaks unrelated to the reported mobile photo issue, PDF download visibility, or token error handling. Additionally, while the SimpleWhiteboard and Toast components could be in-scope if they're solutions to the reported issues, their relationship to the specific requirements is not clearly documented. Review and justify the inclusion of unrelated styling changes (login.css.ts and signup.css.ts padding adjustments). If these changes are necessary, explain their connection to issue #57 in the PR description. If they are unrelated, move them to a separate PR or combine this PR into a comprehensive fix that only includes changes directly addressing the reported issues.
Description Check ⚠️ Warning The PR description follows the template structure and includes the Related Issues section referencing #57 and a completed checklist. However, the critical Tasks section—which should describe what work was actually performed—is completely empty with no description of the implemented changes. Given the substantial nature of the changes (new components, routing, dependencies), this section is essential for explaining what was accomplished and should not be left blank. Fill in the Tasks section with a clear description of the work completed. This should explain the key changes implemented, such as adding the SimpleWhiteboard component with Fabric.js integration, implementing Toast notifications across multiple pages, updating routing, and adding required dependencies. This will provide clarity on what was actually done to address issue #57.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Title Check ❓ Inconclusive The title "[Feat/#57] 수정 필요한 부분 수정하는 PR" (translating to "fixing parts that need fixing") is vague and generic. It does reference issue #57, indicating a connection to the linked issue, but the description uses non-specific phrasing that doesn't clearly convey what was actually implemented. The changeset includes substantial additions like a new SimpleWhiteboard component, Toast notifications, new routing, and dependencies, but the title provides no meaningful insight into these changes. Revise the title to be more specific about the primary change. Instead of generic language, describe the main feature or fix being implemented, such as "[Feat/#57] Add SimpleWhiteboard component for mobile solving" or similar, to clearly communicate the scope of the PR to reviewers scanning the history.
✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/#57/solve

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 83166e1 and 9c53d1c.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (1)
  • package.json (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • package.json

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.

@github-actions
Copy link

🎩 빌드에 성공했습니다!

@github-actions
Copy link

✖️ 빌드에 실패했습니다.

Copy link

@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: 2

🧹 Nitpick comments (4)
src/pages/reviewNotes/ReviewNotes.tsx (1)

111-119: Consider using stable keys for toast list.

Using array indices as keys can cause React to lose track of which toast is which when toasts are dismissed out of order, potentially leading to incorrect animations or state updates.

Consider generating unique IDs for each toast:

-  const [toasts, setToasts] = useState<string[]>([]);
+  const [toasts, setToasts] = useState<{ id: number; message: string }[]>([]);
+  let toastIdCounter = 0;

  const showToast = (msg: string) => {
-    setToasts((prev) => [...prev, msg]);
+    setToasts((prev) => [...prev, { id: toastIdCounter++, message: msg }]);
  };

Then update the rendering:

-      {toasts.map((msg, i) => (
+      {toasts.map((toast) => (
        <Toast
-          key={i}
-          message={msg}
+          key={toast.id}
+          message={toast.message}
          onClose={() =>
-            setToasts((prev) => prev.filter((_, index) => index !== i))
+            setToasts((prev) => prev.filter((t) => t.id !== toast.id))
          }
        />
      ))}
src/shared/components/toast/toast.css.ts (1)

14-29: Consider adding responsive width constraints.

The fixed minWidth: '32rem' (320px) could cause horizontal overflow on very small mobile devices (e.g., iPhone SE with 320px viewport width accounting for padding).

Add responsive width handling:

export const toast = style({
  position: 'fixed',
  bottom: '2rem',
  left: '50%',
  transform: 'translateX(-50%)',
  padding: '1rem 2rem',
  borderRadius: '10px',
-  minWidth: '32rem',
+  minWidth: '28rem',
+  maxWidth: 'calc(100vw - 4rem)',
  textAlign: 'center',

  backgroundColor: themeVars.color.gray800,
  color: themeVars.color.white000,
  ...themeVars.font.bodySmall,
  zIndex: themeVars.zIndex.five,
  whiteSpace: 'pre-line',
});

This ensures the toast fits within the viewport while maintaining a reasonable minimum width.

src/pages/simpleWhiteboard/SimpleWhiteboard.tsx (2)

173-191: Simplify handleAddImage logic.

The function handles both event-based and direct file input access, making it harder to follow. Since the function is only called from onChange (line 291), the extra fallback logic adds unnecessary complexity.

Apply this diff to simplify:

-  const handleAddImage = (e?: React.ChangeEvent<HTMLInputElement>) => {
-    const input = e?.target ?? fileInputRef.current;
-    const file = input && 'files' in input ? input.files?.[0] : undefined;
+  const handleAddImage = (e: React.ChangeEvent<HTMLInputElement>) => {
+    const file = e.target.files?.[0];
     if (!file) {
       return;
     }
 
     const reader = new FileReader();
     reader.onload = () => {
       const result = reader.result;
       if (typeof result === 'string') {
         setUploadedImage(result);
       }
     };
     reader.readAsDataURL(file);
-    if (input) {
-      input.value = '';
-    }
+    e.target.value = '';
   };

294-305: Consider using a distinct icon for the clear drawing action.

Both the eraser tool (line 220) and the clear drawing button (line 304) use <FaEraser />, which may confuse users about their different functions—one toggles eraser mode, the other clears all drawings.

Consider using FaTrash or FaBroom from react-icons/fa for the clear action:

 import {
   FaPen,
   FaEraser,
   FaRegImage,
   FaRegWindowClose,
   FaSave,
+  FaTrash,
 } from 'react-icons/fa';
         onClick={handleClearDrawing}
         title="풀이 전체 지우기 (드로잉만)"
       >
-        <FaEraser size={18} />
+        <FaTrash size={18} />
       </button>
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fa2fed4 and ab26659.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (13)
  • package.json (1 hunks)
  • src/pages/login/login.css.ts (1 hunks)
  • src/pages/loginCallback/LoginCallback.tsx (2 hunks)
  • src/pages/my/My.tsx (5 hunks)
  • src/pages/reviewNotes/ReviewNotes.tsx (4 hunks)
  • src/pages/signup/signup.css.ts (1 hunks)
  • src/pages/simpleWhiteboard/SimpleWhiteboard.tsx (1 hunks)
  • src/pages/simpleWhiteboard/simpleWhiteboard.css.ts (1 hunks)
  • src/routes/globalRoutes.tsx (2 hunks)
  • src/routes/lazy.tsx (1 hunks)
  • src/routes/routePath.ts (1 hunks)
  • src/shared/components/toast/Toast.tsx (1 hunks)
  • src/shared/components/toast/toast.css.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (4)
src/routes/globalRoutes.tsx (2)
src/routes/routePath.ts (1)
  • routePath (1-11)
src/routes/lazy.tsx (1)
  • SimpleWhiteboardPage (17-19)
src/pages/simpleWhiteboard/SimpleWhiteboard.tsx (1)
src/pages/simpleWhiteboard/simpleWhiteboard.css.ts (1)
  • uploadedImage (80-86)
src/pages/simpleWhiteboard/simpleWhiteboard.css.ts (1)
src/shared/styles/theme.css.ts (1)
  • themeVars (21-21)
src/pages/my/My.tsx (2)
src/routes/routePath.ts (1)
  • routePath (1-11)
src/pages/my/my.css.ts (1)
  • button (187-200)
🪛 GitHub Actions: CI/CD Pipeline
src/pages/simpleWhiteboard/SimpleWhiteboard.tsx

[error] 25-25: TypeScript error: 'canvasOffset' is declared but its value is never read. (TS6133) during build (pnpm build).

🪛 GitHub Check: build
src/pages/simpleWhiteboard/SimpleWhiteboard.tsx

[failure] 25-25:
'canvasOffset' is declared but its value is never read.

🔇 Additional comments (19)
src/pages/signup/signup.css.ts (1)

33-33: LGTM! Improved bottom spacing.

The increased bottom padding from 1.6rem to 2.4rem provides better visual breathing room for the fixed button container, which is a good UX refinement for mobile layouts.

src/pages/login/login.css.ts (1)

34-34: LGTM! Consistent spacing adjustment.

The padding change matches the signup page adjustment, maintaining design consistency across authentication flows.

src/pages/loginCallback/LoginCallback.tsx (1)

6-6: Good UX improvement!

Rendering a <Loading /> component instead of an empty div provides better visual feedback to users during the OAuth callback flow.

Also applies to: 97-97

package.json (1)

26-26: LGTM!

Adding react-icons provides a convenient icon library for the UI enhancements.

src/routes/lazy.tsx (1)

17-19: LGTM!

The lazy-loaded SimpleWhiteboardPage follows the established pattern for route-based code splitting.

src/routes/routePath.ts (1)

10-10: LGTM!

The new route path follows the existing naming convention and integrates cleanly with the type system.

src/routes/globalRoutes.tsx (1)

10-10: LGTM!

The SimpleWhiteboardPage is correctly registered as a protected route, ensuring authentication is required before access.

Also applies to: 50-53

src/pages/my/My.tsx (4)

8-8: LGTM!

Toast component import and state initialization are correctly implemented.

Also applies to: 25-25


37-43: LGTM!

The viewport-based conditional navigation appropriately restricts the whiteboard feature to larger screens and provides user feedback via toast on smaller devices.


194-197: LGTM!

The new "연습장" button maintains consistency with the existing button styling and layout.


218-223: LGTM!

Toast rendering is properly gated by state and the onClose callback correctly resets the visibility. The multi-line message formatting will render correctly given the whiteSpace: 'pre-line' styling.

src/pages/reviewNotes/ReviewNotes.tsx (2)

7-7: LGTM!

The toast queuing system is correctly implemented to support multiple concurrent toast notifications.

Also applies to: 28-28, 30-32


44-44: Good UX improvement!

Providing user feedback when no questions are selected for PDF export is more helpful than a silent no-op.

src/shared/components/toast/toast.css.ts (2)

4-12: LGTM!

The slide animations maintain horizontal centering during vertical motion, ensuring smooth visual transitions.


31-37: LGTM!

Animation classes are correctly configured with appropriate durations and easing functions.

src/pages/simpleWhiteboard/SimpleWhiteboard.tsx (1)

147-169: Verify that saving only the canvas (not the uploaded image) aligns with requirements.

The save function captures only the canvas drawing area as PNG, excluding the uploaded problem image displayed in the separate imageDisplayWrapper div. The button title indicates this is intentional ("풀이 영역만"), but confirm this matches the expected behavior described in issue #57.

src/shared/components/toast/Toast.tsx (1)

1-41: LGTM! Clean toast implementation.

The component correctly manages its lifecycle with proper cleanup, coordinates animation timing (300ms exit matches CSS), and follows React best practices. The implementation is straightforward and effective.

src/pages/simpleWhiteboard/simpleWhiteboard.css.ts (2)

65-78: Verify responsive behavior on mobile devices.

The imageDisplayWrapper uses a fixed width of 34rem (~544px), which may not work well on smaller mobile screens. Given that issue #57 mentions mobile problems with the solve feature, ensure this layout has been tested on mobile devices.

If mobile support is required, consider using a responsive approach:

 export const imageDisplayWrapper = style({
   position: 'relative',
   display: 'flex',
   justifyContent: 'flex-start',
   alignItems: 'flex-start',
-  width: '34rem',
+  width: '100%',
+  maxWidth: '34rem',
   height: '100%',
   padding: '0.8rem',
   boxSizing: 'border-box',
   pointerEvents: 'none',
   borderRight: '2px solid #ddd',
   backgroundColor: '#f9f9f9',
   zIndex: themeVars.zIndex.one,
+  '@media': {
+    '(max-width: 768px)': {
+      maxWidth: '100%',
+    },
+  },
 });

1-97: LGTM! Well-structured vanilla-extract styles.

The styling tokens are correctly organized and properly use theme variables. The layout structure (toolbar, canvas wrapper, image display) is clear and maintainable.

@github-actions
Copy link

🎩 빌드에 성공했습니다!

Copy link

@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: 0

🧹 Nitpick comments (8)
src/pages/simpleWhiteboard/SimpleWhiteboard.tsx (8)

64-73: Drawings don’t scale with container on resize → overlay will drift relative to the image.

setWidth/setHeight updates the canvas size but existing paths keep original coordinates. When the container resizes, the image scales while strokes don’t, causing misalignment. Scale the viewport based on an initial baseline.

Apply this diff (add a baseline ref and scale viewport in resize):

@@
   const problemAreaRef = useRef<HTMLDivElement | null>(null);
+  const baseSizeRef = useRef<{ w: number; h: number } | null>(null);
@@
-    const resize = () => {
+    const resize = () => {
       if (!canvas || !currentWrapper || !currentProblemArea) {
         return;
       }
-      const rect = currentWrapper.getBoundingClientRect();
-      canvas.setWidth(Math.floor(rect.width));
-      canvas.setHeight(Math.floor(rect.height));
-      drawBackground(canvas);
+      const rect = currentWrapper.getBoundingClientRect();
+      const w = Math.floor(rect.width);
+      const h = Math.floor(rect.height);
+      canvas.setWidth(w);
+      canvas.setHeight(h);
+      if (!baseSizeRef.current) {
+        baseSizeRef.current = { w, h };
+      } else {
+        const scaleX = w / baseSizeRef.current.w;
+        const scaleY = h / baseSizeRef.current.h;
+        canvas.setViewportTransform([scaleX, 0, 0, scaleY, 0, 0]);
+      }
+      drawBackground(canvas);

Optionally, keep stroke thickness visually consistent by dividing brush.width by the current scale in the effect that updates brush props. I can provide that patch if you want.

Please verify overlay alignment by drawing, rotating the device, and checking if strokes stay on the same image features across breakpoints.


41-44: Prevent page scroll while drawing on touch devices.

Setting touch-action: none on the canvas avoids scroll/zoom interference during strokes.

   canvasEl.style.width = '100%';
   canvasEl.style.height = '100%';
   canvasEl.style.display = 'block';
+  canvasEl.style.touchAction = 'none';

144-166: More robust export: use toBlob with a fallback; handle in‑app browsers where download may be ignored.

is often blocked/ignored in in‑app browsers (KakaoTalk, Instagram). Use toBlob when available, fall back to dataURL, and open a new tab if download isn’t supported.

   const handleSave = () => {
     const canvas = canvasRef.current;
     if (!canvas) {
       return;
     }

     canvas.discardActiveObject();
     canvas.renderAll();

-    const dataUrl = canvas.toDataURL({
-      format: 'png',
-      multiplier: 2,
-      left: 0,
-      top: 0,
-      width: canvas.getWidth(),
-      height: canvas.getHeight(),
-    });
-
-    const link = document.createElement('a');
-    link.href = dataUrl;
-    link.download = 'whiteboard-solution.png';
-    link.click();
+    const el = (canvas as any).lowerCanvasEl as HTMLCanvasElement;
+    const downloadOrOpen = (url: string) => {
+      const a = document.createElement('a');
+      if ('download' in a) {
+        a.href = url;
+        a.download = 'whiteboard-solution.png';
+        document.body.appendChild(a);
+        a.click();
+        a.remove();
+      } else {
+        window.open(url, '_blank');
+      }
+    };
+    if (el && el.toBlob) {
+      el.toBlob((blob) => {
+        if (!blob) return;
+        const url = URL.createObjectURL(blob);
+        downloadOrOpen(url);
+        setTimeout(() => URL.revokeObjectURL(url), 1000);
+      }, 'image/png');
+    } else {
+      const dataUrl = canvas.toDataURL({ format: 'png', multiplier: 2 });
+      downloadOrOpen(dataUrl);
+    }
   };

Please test in KakaoTalk/Naver in‑app browsers on Android/iOS.


194-206: A11y and semantics: add aria-pressed/aria-label and type="button" to icon buttons.

Improves screen‑reader UX and prevents accidental form submission defaults.

Example for the first two buttons (replicate to others):

-<button
+<button
+  type="button"
   className={css.iconButton}
   style={{ /* … */ }}
   onClick={togglePenMode}
-  title={isPenActive ? '펜 끄기' : '펜 켜기'}
+  title={isPenActive ? '펜 끄기' : '펜 켜기'}
+  aria-label={isPenActive ? '펜 끄기' : '펜 켜기'}
+  aria-pressed={isPenActive}
 >
   <FaPen size={18} />
 </button>

-<button
+<button
+  type="button"
   className={css.iconButton}
   style={{ /* … */ }}
   onClick={toggleEraserMode}
-  title={isEraser ? '지우개 끄기' : '지우개 켜기'}
+  title={isEraser ? '지우개 끄기' : '지우개 켜기'}
+  aria-label={isEraser ? '지우개 끄기' : '지우개 켜기'}
+  aria-pressed={isEraser}
>
  <FaEraser size={18} />
</button>

For the other icon buttons, add type="button" and aria-label matching the title.

Also applies to: 207-218, 263-269, 270-282, 292-302, 307-318


33-40: Remove unused problemAreaRef dependency in resize guard.

The guard checks currentProblemArea but never reads its dimensions anymore. Simplify.

-    const currentProblemArea = problemAreaRef.current;
+    // const currentProblemArea = problemAreaRef.current; // no longer needed
@@
-      if (!canvas || !currentWrapper || !currentProblemArea) {
+      if (!canvas || !currentWrapper) {
         return;
       }

Also applies to: 64-67


284-289: Mobile camera capture hint; confirm single vs multi image requirement.

If this component is used in the “solve” flow, input currently supports one image. If multiple images are desired (per linked issue), the state/model must change. Regardless, adding capture="environment" improves mobile UX.

Do we need multi-image here? If yes, I can draft a state + UI change.

 <input
   ref={fileInputRef}
   type="file"
   accept="image/*"
+  capture="environment"
   style={{ display: 'none' }}
   onChange={handleAddImage}
 />

153-160: Large export multiplier can OOM on mobile.

multiplier: 2 can create very large bitmaps on tablets. Consider capping by max dimension.

const maxSide = Math.max(canvas.getWidth(), canvas.getHeight());
const scale = Math.min(2, 4096 / Math.max(1, maxSide)); // cap by ~4K
const dataUrl = canvas.toDataURL({ format: 'png', multiplier: scale });

2-2: Optional: lazy‑load Fabric to reduce initial bundle and avoid SSR pitfalls.

If SSR or code‑splitting matters, import Fabric inside the effect:

useEffect(() => {
  let disposed = false;
  (async () => {
    const { fabric } = await import('fabric');
    // init with fabric here...
    if (disposed) return;
    // ...
  })();
  return () => { disposed = true; /* cleanup */ };
}, []);

This also sidesteps window access at import time in SSR environments.

Is SSR or route-level code-splitting a goal for this page?

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ab26659 and 83166e1.

📒 Files selected for processing (1)
  • src/pages/simpleWhiteboard/SimpleWhiteboard.tsx (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/pages/simpleWhiteboard/SimpleWhiteboard.tsx (1)
src/pages/simpleWhiteboard/simpleWhiteboard.css.ts (1)
  • uploadedImage (80-86)
🔇 Additional comments (1)
src/pages/simpleWhiteboard/SimpleWhiteboard.tsx (1)

53-63: Non-interactive objects: confirm future needs.

Forcing selectable:false and evented:false on all added objects disables any future select/erase-by-object features. If only freehand strokes + white “eraser” are intended, this is fine. Otherwise consider scoping this rule to specific types.

@github-actions
Copy link

🎩 빌드에 성공했습니다!

@hansoojeongsj hansoojeongsj merged commit 6706475 into develop Oct 26, 2025
3 checks passed
@coderabbitai coderabbitai bot mentioned this pull request Oct 27, 2025
3 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feat] solve..........

2 participants