Skip to content

feat: add success toast notification after saving image (#14)#16

Merged
amemya merged 4 commits into
mainfrom
feat/save-success-feedback
May 17, 2026
Merged

feat: add success toast notification after saving image (#14)#16
amemya merged 4 commits into
mainfrom
feat/save-success-feedback

Conversation

@amemya
Copy link
Copy Markdown
Owner

@amemya amemya commented May 15, 2026

Closes #14

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 15, 2026

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 203eccbd-e53c-403b-b7c0-0c273d87abdb

📥 Commits

Reviewing files that changed from the base of the PR and between e0466f1 and 3b2f137.

📒 Files selected for processing (2)
  • frontend/src/App.css
  • frontend/src/App.tsx

📝 Walkthrough

Summary by CodeRabbit

リリースノート

  • 新機能
    • トースト通知機能を追加しました。画像をエクスポートした際に、成功メッセージが画面下部に表示されるようになります。通知はアニメーション付きで、一定時間後に自動的に消失します。

Walkthrough

画像保存成功時に短時間表示されるトースト通知を追加しました。CSS(配置・アニメーション)、コンポーネントの状態/タイマー管理、保存成功時のトースト表示呼び出し、および条件付きのARIA対応レンダリングを実装しています。

Changes

トースト通知機能

Layer / File(s) Summary
Toast styling and animations
frontend/src/App.css
.toast-container を画面下部に固定し、.toast の初期状態(透明度・位置)と @keyframes toast-fade-in-out(フェードイン/アウト、上下移動)を定義。.toast.success で成功時の左ボーダー色を指定。CSS ブロックの閉じも追加。
Toast state management, integration, and rendering
frontend/src/App.tsx
TOAST_DURATION_MS 定数、toastMessage 状態、showToast(再表示制御・自動非表示)、タイマー/RAF の参照とアンマウント時クリーンアップを追加。画像保存成功時に showToast("Image saved successfully") を呼び出し、toastMessage があるときのみ ARIA 属性付きトーストをレンダリング。

Sequence Diagram

sequenceDiagram
  participant User
  participant App as App Component
  participant SaveAPI as Save API
  participant Toast as Toast State
  participant Render as UI Render

  User->>App: Click save image
  App->>SaveAPI: POST image
  SaveAPI-->>App: Success response
  App->>Toast: showToast("Image saved successfully")
  Toast->>Toast: setToastMessage(message)
  Toast->>Render: Update state
  Render->>Render: Render toast-container with message
  Note over Render: Toast animates in, fades out after delay
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed プルリクエストのタイトルはトースト通知機能の追加という主要な変更内容を明確かつ簡潔に表現しており、変更セットの主要な目的と一致しています。
Description check ✅ Passed プルリクエストの説明はCloses #14で参照されている関連イシューを明確に示しており、変更内容と関連しています。
Linked Issues check ✅ Passed プルリクエストはイシュー#14で要求された保存成功時のトースト通知機能を完全に実装しており、画像保存後に成功メッセージを表示する仕様を満たしています。
Out of Scope Changes check ✅ Passed すべての変更はイシュー#14で要求されたトースト通知機能の実装に限定されており、スコープ外の変更は含まれていません。

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/save-success-feedback

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

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds a lightweight success notification after exporting an image, addressing the lack of positive feedback noted in #14.

Changes:

  • Introduces toast state + helper in App.tsx and triggers it on successful save.
  • Adds toast UI markup to the main layout.
  • Adds toast styling + fade-in/out animation in App.css.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.

File Description
frontend/src/App.tsx Adds toast state/helper, shows toast on successful save, renders toast markup.
frontend/src/App.css Styles toast container/toast and defines fade-in/out animation.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread frontend/src/App.tsx
Comment on lines +34 to 40

const showToast = (message: string) => {
setToastMessage(message);
setTimeout(() => setToastMessage(null), 3000);
};

useEffect(() => {
Comment thread frontend/src/App.tsx Outdated

{toastMessage && (
<div className="toast-container">
<div className="toast success">{toastMessage}</div>
Comment thread frontend/src/App.tsx
Comment on lines +35 to +38
const showToast = (message: string) => {
setToastMessage(message);
setTimeout(() => setToastMessage(null), 3000);
};
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: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@frontend/src/App.tsx`:
- Around line 350-353: The success toast currently rendered when toastMessage is
truthy does not announce to assistive tech; update the toast markup in App.tsx
(the JSX that uses toastMessage and renders the divs with className
"toast-container" and "toast success") to make it accessible by adding an ARIA
live region and status role (e.g., aria-live="polite" aria-atomic="true"
role="status") to the container or the inner toast element, and ensure any
visual-only styling still exposes the text to screen readers (or include a
visually-hidden element inside the toast for announcement) so screen readers are
reliably notified of the save-success message.
- Around line 35-38: The showToast implementation causes timer races and missed
re-renders; fix by tracking the timeout in a ref (e.g., toastTimerRef =
useRef<number | null>(null)), clearing any existing timer before scheduling a
new one (window.clearTimeout(toastTimerRef.current)), force a reflow/re-render
by resetting state to null before setting the message using
requestAnimationFrame, schedule the hide with setTimeout and save its id into
toastTimerRef, and add a cleanup useEffect that clears toastTimerRef on unmount;
update uses of setToastMessage, showToast, toastTimerRef and the useEffect
cleanup accordingly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 6485dae6-5d4a-4235-afb7-34d4b7b446dc

📥 Commits

Reviewing files that changed from the base of the PR and between 686aec0 and b42781c.

📒 Files selected for processing (2)
  • frontend/src/App.css
  • frontend/src/App.tsx

Comment thread frontend/src/App.tsx
Comment thread frontend/src/App.tsx
- Use useRef to track toast timer and clear previous timers to prevent
  overlapping or racing toasts.
- Reset state to null and use requestAnimationFrame before showing a new
  toast to force a React re-render and trigger the CSS animation.
- Add aria-live="polite", aria-atomic="true", and role="status" to the
  toast container for screen reader accessibility.
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.

♻️ Duplicate comments (1)
frontend/src/App.tsx (1)

36-48: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

requestAnimationFrame の未管理でトースト競合が残っています

Line 42 で requestAnimationFrame を登録していますが、前回分をキャンセルしていないため、同一フレーム内の連続 showToast 呼び出しで古いコールバック由来の timeout が残り、トーストが早く消える競合が再発します。RAFref で追跡してクリアしてください。

修正案
@@
-    const toastTimerRef = useRef<number | null>(null);
+    const toastTimerRef = useRef<number | null>(null);
+    const toastRafRef = useRef<number | null>(null);
@@
     const showToast = (message: string) => {
         if (toastTimerRef.current !== null) {
             window.clearTimeout(toastTimerRef.current);
+            toastTimerRef.current = null;
+        }
+        if (toastRafRef.current !== null) {
+            window.cancelAnimationFrame(toastRafRef.current);
+            toastRafRef.current = null;
         }
         // Force a re-render by clearing the state first
         setToastMessage(null);
-        requestAnimationFrame(() => {
+        toastRafRef.current = window.requestAnimationFrame(() => {
+            toastRafRef.current = null;
             setToastMessage(message);
             toastTimerRef.current = window.setTimeout(() => {
                 setToastMessage(null);
                 toastTimerRef.current = null;
             }, 3000);
         });
     };
@@
     useEffect(() => {
         return () => {
             if (toastTimerRef.current !== null) {
                 window.clearTimeout(toastTimerRef.current);
             }
+            if (toastRafRef.current !== null) {
+                window.cancelAnimationFrame(toastRafRef.current);
+            }
         };
     }, []);

Also applies to: 51-57

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/App.tsx` around lines 36 - 48, The showToast implementation
schedules a requestAnimationFrame without canceling prior RAFs, causing stale
callbacks to set timeouts and prematurely clear toasts; add a new ref (e.g.,
toastRafRef) to store the RAF id, call cancelAnimationFrame on any existing id
before assigning a new one in showToast, clear toastRafRef after the callback
runs, and also update the component cleanup (useEffect return) to cancel both
the pending RAF and the toastTimerRef timeout to avoid leaks.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In `@frontend/src/App.tsx`:
- Around line 36-48: The showToast implementation schedules a
requestAnimationFrame without canceling prior RAFs, causing stale callbacks to
set timeouts and prematurely clear toasts; add a new ref (e.g., toastRafRef) to
store the RAF id, call cancelAnimationFrame on any existing id before assigning
a new one in showToast, clear toastRafRef after the callback runs, and also
update the component cleanup (useEffect return) to cancel both the pending RAF
and the toastTimerRef timeout to avoid leaks.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: cfeb5754-f2bb-44b1-ad63-0206d9531284

📥 Commits

Reviewing files that changed from the base of the PR and between b42781c and e0466f1.

📒 Files selected for processing (1)
  • frontend/src/App.tsx

If showToast was called multiple times within the same animation frame,
the previous requestAnimationFrame callback was not cancelled. This caused
multiple callbacks to fire in the next frame, overwriting the toastTimerRef
with a new timeout ID while leaking the older timeout, which would then
prematurely clear the toast.

This commit adds a toastRafRef to track and cancel any pending RAF
callbacks, ensuring only the most recent showToast call schedules a timeout.
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

Comment thread frontend/src/App.tsx
Comment on lines +36 to +48

const showToast = (message: string) => {
if (toastTimerRef.current !== null) {
window.clearTimeout(toastTimerRef.current);
toastTimerRef.current = null;
}
if (toastRafRef.current !== null) {
window.cancelAnimationFrame(toastRafRef.current);
toastRafRef.current = null;
}

// Force a re-render by clearing the state first
setToastMessage(null);
Comment thread frontend/src/App.tsx
Comment on lines +42 to +47
if (toastRafRef.current !== null) {
window.cancelAnimationFrame(toastRafRef.current);
toastRafRef.current = null;
}

// Force a re-render by clearing the state first
Hardcoding the duration in both CSS (animation) and JS (setTimeout)
creates a risk of timing drift and cutoff bugs if only one side is
updated. This commit introduces a single TOAST_DURATION_MS constant
in JS and passes it to CSS via the inline animationDuration style
to ensure they are always perfectly synchronized.
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

Comment thread frontend/src/App.tsx
Comment on lines +39 to +58
const showToast = (message: string) => {
if (toastTimerRef.current !== null) {
window.clearTimeout(toastTimerRef.current);
toastTimerRef.current = null;
}
if (toastRafRef.current !== null) {
window.cancelAnimationFrame(toastRafRef.current);
toastRafRef.current = null;
}

// Force a re-render by clearing the state first
setToastMessage(null);
toastRafRef.current = requestAnimationFrame(() => {
setToastMessage(message);
toastTimerRef.current = window.setTimeout(() => {
setToastMessage(null);
toastTimerRef.current = null;
}, TOAST_DURATION_MS);
toastRafRef.current = null;
});
@amemya
Copy link
Copy Markdown
Owner Author

amemya commented May 17, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 17, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@amemya amemya merged commit 504691a into main May 17, 2026
2 checks passed
@amemya amemya deleted the feat/save-success-feedback branch May 17, 2026 05:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

保存成功時のフィードバックがない

2 participants