Conversation
…s, and proper form label associations
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughSummary by CodeRabbit
Walkthroughアップロード中心の単一画面をフルビューポートのワークスペースに再構築。トップバー、プレビュー領域(空状態含む)、メタデータ用サイドバーを導入し、画像選択に再入防止と明示的な読み込み待ちを追加。フォームのアクセシビリティ属性を整備。 Changes
Sequence Diagram(s)sequenceDiagram
participant User as User
participant App as App(UI)
participant FP as FilePicker (browser)
participant IMG as Image Decoder
User->>App: Click "Open/Change Photo"
App->>FP: open file dialog
FP-->>App: return File / DataURL
App->>IMG: create Image(src=DataURL) and await load
IMG-->>App: onload / onerror
App->>App: set imageObj, imageLoaded, isSelecting=false
App-->>User: render preview or error
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes 🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Pull request overview
This PR improves the frontend UI layout to address issue #6 (“UIがダサい”), moving the app toward a more modern “workspace” style interface.
Changes:
- Replaces the centered header/upload layout with a top bar (Open/Export actions) + main workspace.
- Adds an empty-state preview area and a right-hand sidebar for metadata inputs.
- Updates App.css to support the new layout, button styles, and panel styling.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
| frontend/src/App.tsx | Restructures the UI into top-bar/workspace layout, adds empty-state and sidebar inputs. |
| frontend/src/App.css | Adds new styling tokens and layout styles for top bar, workspace, preview area, sidebar, and buttons. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| max-width: 100%; | ||
| max-height: 60vh; | ||
| max-height: 100%; | ||
| object-fit: contain; |
There was a problem hiding this comment.
object-fit doesn't apply to <canvas> elements, so object-fit: contain; here is ineffective and can be misleading. Either remove it or replace it with explicit CSS sizing (e.g., controlling width/height) that achieves the intended containment behavior.
| object-fit: contain; |
| onClick={handleSelectImage} | ||
| role="button" | ||
| tabIndex={0} | ||
| aria-label="Click or press Enter to open a photo" |
There was a problem hiding this comment.
aria-label says "Click or press Enter" but the key handler also supports Space. Please update the label text (or the handler) so the announced instructions match the actual supported keyboard interactions.
| aria-label="Click or press Enter to open a photo" | |
| aria-label="Click or press Enter or Space to open a photo" |
| <div | ||
| className="empty-state" | ||
| onClick={handleSelectImage} | ||
| role="button" | ||
| tabIndex={0} | ||
| aria-label="Click or press Enter to open a photo" |
There was a problem hiding this comment.
The empty state is implemented as a <div role="button">. For better native accessibility (keyboard behavior, semantics, screen reader hints) use a real <button type="button"> and style it to look the same, instead of recreating button behavior manually.
| onKeyDown={(e) => { | ||
| if (e.key === 'Enter' || e.key === ' ') { | ||
| e.preventDefault(); // Prevent page scroll for Space | ||
| handleSelectImage(); | ||
| } | ||
| }} |
There was a problem hiding this comment.
onKeyDown will fire repeatedly when a key is held (key repeat), which can trigger multiple OpenImage() calls/file dialogs. Consider guarding against e.repeat, moving this to onKeyUp, or using a native <button> (which avoids most of this manual handling).
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@frontend/src/App.css`:
- Around line 147-156: The .sidebar rule currently uses a fixed width of 320px
which breaks on smaller screens; update the CSS to add a media query (e.g.,
`@media` (max-width: 768px)) that overrides .sidebar to width: 100%, removes or
changes the left border (e.g., border-left: none; add border-top instead), and
adjust related layout containers such as .workspace to stack vertically
(flex-direction: column) so the sidebar becomes responsive on tablets and
phones.
- Around line 123-124: Add a prefers-reduced-motion media query that disables
transitions for interactive/animated selectors so users who prefer reduced
motion don't see animations; specifically, target the selectors used near the
transition declaration (e.g., .btn, .empty-state, .input-group input) and set
transition: none (and any related animation properties) inside `@media`
(prefers-reduced-motion: reduce) to override the existing transition: all 0.2s
ease; rule.
- Around line 60-92: Add explicit disabled-state styles for buttons: update the
.btn selector family (.btn, .btn-primary, .btn-secondary) to include rules for
:disabled (and [disabled]) that set cursor: not-allowed, lower opacity (e.g.
0.5), disable pointer-events/hover visual changes, and remove accent
outlines/hover backgrounds (or keep a muted focus-visible) so disabled buttons
visibly differ and do not react to hover/focus the same as active buttons.
In `@frontend/src/App.tsx`:
- Around line 213-217: The decorative SVG in App.tsx (the inline <svg> element
inside the App component JSX) should include aria-hidden="true" to prevent
screen readers from duplicating the already-present parent aria-label; update
the SVG element definition to add the aria-hidden="true" attribute (keep other
attributes unchanged) so the icon is ignored by assistive technologies.
🪄 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: e1cc188e-7f89-4288-9eec-058b1e009475
📒 Files selected for processing (2)
frontend/src/App.cssfrontend/src/App.tsx
| .btn { | ||
| padding: 0.4rem 1rem; | ||
| border-radius: 4px; | ||
| font-size: 0.85rem; | ||
| font-weight: 500; | ||
| cursor: pointer; | ||
| transition: all 0.2s ease; | ||
| border: 1px solid transparent; | ||
| } | ||
| .btn:focus-visible, | ||
| .empty-state:focus-visible { | ||
| outline: 2px solid var(--accent-color); | ||
| outline-offset: 2px; | ||
| } | ||
|
|
||
| .main-content { | ||
| .btn-secondary { | ||
| background-color: transparent; | ||
| color: var(--text-primary); | ||
| border-color: var(--border-color); | ||
| } | ||
|
|
||
| .btn-secondary:hover { | ||
| background-color: rgba(255, 255, 255, 0.05); | ||
| } | ||
|
|
||
| .btn-primary { | ||
| background-color: var(--accent-color); | ||
| color: white; | ||
| } | ||
|
|
||
| .btn-primary:hover { | ||
| background-color: var(--accent-hover); | ||
| } |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
ボタンの無効状態スタイルの追加を検討してください。
将来的にボタンを無効化する必要がある場合(例:エクスポート処理中)に備えて、:disabled状態のスタイルを追加することを推奨します。
💅 無効状態のスタイル追加案
.btn-primary:hover {
background-color: var(--accent-hover);
}
+
+.btn:disabled {
+ opacity: 0.5;
+ cursor: not-allowed;
+}📝 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.
| .btn { | |
| padding: 0.4rem 1rem; | |
| border-radius: 4px; | |
| font-size: 0.85rem; | |
| font-weight: 500; | |
| cursor: pointer; | |
| transition: all 0.2s ease; | |
| border: 1px solid transparent; | |
| } | |
| .btn:focus-visible, | |
| .empty-state:focus-visible { | |
| outline: 2px solid var(--accent-color); | |
| outline-offset: 2px; | |
| } | |
| .main-content { | |
| .btn-secondary { | |
| background-color: transparent; | |
| color: var(--text-primary); | |
| border-color: var(--border-color); | |
| } | |
| .btn-secondary:hover { | |
| background-color: rgba(255, 255, 255, 0.05); | |
| } | |
| .btn-primary { | |
| background-color: var(--accent-color); | |
| color: white; | |
| } | |
| .btn-primary:hover { | |
| background-color: var(--accent-hover); | |
| } | |
| .btn { | |
| padding: 0.4rem 1rem; | |
| border-radius: 4px; | |
| font-size: 0.85rem; | |
| font-weight: 500; | |
| cursor: pointer; | |
| transition: all 0.2s ease; | |
| border: 1px solid transparent; | |
| } | |
| .btn:focus-visible, | |
| .empty-state:focus-visible { | |
| outline: 2px solid var(--accent-color); | |
| outline-offset: 2px; | |
| } | |
| .btn-secondary { | |
| background-color: transparent; | |
| color: var(--text-primary); | |
| border-color: var(--border-color); | |
| } | |
| .btn-secondary:hover { | |
| background-color: rgba(255, 255, 255, 0.05); | |
| } | |
| .btn-primary { | |
| background-color: var(--accent-color); | |
| color: white; | |
| } | |
| .btn-primary:hover { | |
| background-color: var(--accent-hover); | |
| } | |
| .btn:disabled { | |
| opacity: 0.5; | |
| cursor: not-allowed; | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@frontend/src/App.css` around lines 60 - 92, Add explicit disabled-state
styles for buttons: update the .btn selector family (.btn, .btn-primary,
.btn-secondary) to include rules for :disabled (and [disabled]) that set cursor:
not-allowed, lower opacity (e.g. 0.5), disable pointer-events/hover visual
changes, and remove accent outlines/hover backgrounds (or keep a muted
focus-visible) so disabled buttons visibly differ and do not react to
hover/focus the same as active buttons.
| transition: all 0.2s ease; | ||
| box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); | ||
| } |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
prefers-reduced-motionメディアクエリの追加を検討してください。
アニメーションを減らすことを好むユーザーのために、prefers-reduced-motionメディアクエリを追加することで、アクセシビリティがさらに向上します。
♿ reduced-motion対応の追加案
`@media` (prefers-reduced-motion: reduce) {
.btn,
.empty-state,
.input-group input {
transition: none;
}
}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@frontend/src/App.css` around lines 123 - 124, Add a prefers-reduced-motion
media query that disables transitions for interactive/animated selectors so
users who prefer reduced motion don't see animations; specifically, target the
selectors used near the transition declaration (e.g., .btn, .empty-state,
.input-group input) and set transition: none (and any related animation
properties) inside `@media` (prefers-reduced-motion: reduce) to override the
existing transition: all 0.2s ease; rule.
| /* Sidebar */ | ||
| .sidebar { | ||
| width: 320px; | ||
| background-color: var(--bg-panel); | ||
| border-left: 1px solid var(--border-color); | ||
| display: flex; | ||
| flex-direction: column; | ||
| gap: 1rem; | ||
| box-sizing: border-box; | ||
| overflow-y: auto; | ||
| flex-shrink: 0; | ||
| } |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
レスポンシブ対応の検討をお勧めします。
サイドバーの幅が320pxに固定されています。小さな画面サイズ(タブレットなど)での表示を考慮して、メディアクエリでの調整を検討してください。
📱 レスポンシブ対応の追加案
`@media` (max-width: 768px) {
.sidebar {
width: 100%;
border-left: none;
border-top: 1px solid var(--border-color);
}
.workspace {
flex-direction: column;
}
}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@frontend/src/App.css` around lines 147 - 156, The .sidebar rule currently
uses a fixed width of 320px which breaks on smaller screens; update the CSS to
add a media query (e.g., `@media` (max-width: 768px)) that overrides .sidebar to
width: 100%, removes or changes the left border (e.g., border-left: none; add
border-top instead), and adjust related layout containers such as .workspace to
stack vertically (flex-direction: column) so the sidebar becomes responsive on
tablets and phones.
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@frontend/src/App.tsx`:
- Around line 186-210: Add a reentrancy guard to prevent handleSelectImage from
being invoked concurrently: introduce a boolean React state like isSelecting,
have handleSelectImage immediately return if isSelecting is true, set
isSelecting = true at start and ensure it is set false in every completion path
(use finally or ensure in both success/error branches). Also update UI triggers
(the "Open Photo"/"Change Photo" button, the conditional Export button
activation if needed, and the empty-state onClick/onKeyDown handlers) to be
disabled/no-op while isSelecting is true (e.g., set disabled on the button and
short-circuit the event handlers). Reference: handleSelectImage, imageLoaded,
empty-state click/onKeyDown handlers.
🪄 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: ac297066-391a-448b-a06a-5bd41f516099
📒 Files selected for processing (1)
frontend/src/App.tsx
…sabling UI buttons during operation
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
frontend/src/App.tsx (1)
30-74:⚠️ Potential issue | 🟠 Major選択ガードの解除タイミングが早すぎて競合が再発します。
Line [71]〜Line [73] で
isSelectingを解除していますが、画像デコード完了(Line [58]〜Line [66])前に再操作可能になります。結果として複数選択のonload完了順で古い画像が後勝ちする可能性があります。デコード完了までawaitしてから解除してください。🔧 修正案(デコード完了まで await)
+ const loadImageFromBase64 = (src: string) => + new Promise<HTMLImageElement>((resolve, reject) => { + const img = new Image(); + img.onload = () => resolve(img); + img.onerror = () => reject(new Error("Failed to decode or render the selected image")); + img.src = src; + }); + const handleSelectImage = async () => { if (isSelectingRef.current) return; isSelectingRef.current = true; setIsSelecting(true); try { const result = await OpenImage(); @@ - // Update EXIF state (leave empty if not found) - setExif({ - camera: result.camera || "", - lens: result.lens || "", - focalLength: result.focalLength || "", - aperture: result.aperture || "", - shutterSpeed: result.shutterSpeed || "", - iso: result.iso || "" - }); - - // Load the Base64 image - const img = new Image(); - img.onload = () => { - setImageObj(img); - setImageLoaded(true); - }; - img.onerror = () => { - console.error("Failed to decode or render the selected image"); - setImageObj(null); - setImageLoaded(false); - }; - img.src = result.imageBase64; + const img = await loadImageFromBase64(result.imageBase64); + setExif({ + camera: result.camera || "", + lens: result.lens || "", + focalLength: result.focalLength || "", + aperture: result.aperture || "", + shutterSpeed: result.shutterSpeed || "", + iso: result.iso || "" + }); + setImageObj(img); + setImageLoaded(true); } catch (err) { console.error("Failed to open image:", err); + setImageObj(null); + setImageLoaded(false); } finally { isSelectingRef.current = false; setIsSelecting(false); } };🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/App.tsx` around lines 30 - 74, The selection guard is being released in the finally block before the image decode/render finishes (img.onload/img.onerror), allowing concurrent selections to race; modify handleSelectImage to await image decoding by wrapping the img.onload/img.onerror sequence in a Promise (e.g., create a loadPromise that resolves on onload and rejects on onerror), await that promise before reaching the finally/unsetting isSelectingRef.current and setIsSelecting(false), and continue using setImageObj/setImageLoaded inside the promise handlers so the guard only clears after the image has successfully loaded or failed.
♻️ Duplicate comments (3)
frontend/src/App.css (3)
141-147:⚠️ Potential issue | 🟡 Minor
prefers-reduced-motionの考慮を追加してください。
.spinnerの常時アニメーションと各種transitionに対して、@media (prefers-reduced-motion: reduce)で無効化オーバーライドを入れるとアクセシビリティが改善します。🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/App.css` around lines 141 - 147, Add a media query for users who prefer reduced motion and override the spinner animation and any related transitions: specifically target the `@keyframes` spin consumer (.spinner) and set animation: none (and animation-duration/iteration if desired) and also disable transition (transition: none) inside `@media` (prefers-reduced-motion: reduce) to ensure the spin is stopped for accessibility.
60-92:⚠️ Potential issue | 🟡 Minor無効化ボタンの見た目と挙動を明示してください。
disabled時の専用スタイルがないため、通常/hoverとの差が弱く、cursor: pointerも残ります。.btn:disabled(および[disabled])で視覚差分と非操作性を明示してください。🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/App.css` around lines 60 - 92, The CSS lacks disabled-state rules so disabled buttons still look interactive; add selectors for .btn:disabled and .btn[disabled] to clearly show non-interactive state (reduce opacity or use muted background/border), remove or override cursor: pointer (set cursor: default or not-allowed), and ensure both .btn-primary and .btn-secondary disabled variants are covered (e.g., muted colors, no hover effects) so disabled buttons are visually distinct and non-clickable; also consider matching focus/aria-disabled behavior for .empty-state if it can be disabled.
166-174:⚠️ Potential issue | 🟡 Minorサイドバー固定幅のレスポンシブ対応が必要です。
.sidebar { width: 320px; }固定のままだと狭い画面でワークスペースが圧迫されます。ブレークポイントで.workspaceの縦積み化と.sidebarの幅調整を追加してください。🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/App.css` around lines 166 - 174, The .sidebar uses a fixed width (320px) which breaks small screens; update the CSS to be responsive by replacing the hard width with a flexible rule (e.g., use max-width or a flex-basis like flex: 0 0 320px) and add media queries (breakpoints) that at narrow widths change .workspace to column stacking (flex-direction: column) and adjust .sidebar to a narrower or full-width value (e.g., width: 100% or max-width: 240px) and ensure overflow-y behavior remains correct; target the .sidebar and .workspace selectors in the stylesheet and add the media-query overrides so the layout stacks and sidebar shrinks on small viewports.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Outside diff comments:
In `@frontend/src/App.tsx`:
- Around line 30-74: The selection guard is being released in the finally block
before the image decode/render finishes (img.onload/img.onerror), allowing
concurrent selections to race; modify handleSelectImage to await image decoding
by wrapping the img.onload/img.onerror sequence in a Promise (e.g., create a
loadPromise that resolves on onload and rejects on onerror), await that promise
before reaching the finally/unsetting isSelectingRef.current and
setIsSelecting(false), and continue using setImageObj/setImageLoaded inside the
promise handlers so the guard only clears after the image has successfully
loaded or failed.
---
Duplicate comments:
In `@frontend/src/App.css`:
- Around line 141-147: Add a media query for users who prefer reduced motion and
override the spinner animation and any related transitions: specifically target
the `@keyframes` spin consumer (.spinner) and set animation: none (and
animation-duration/iteration if desired) and also disable transition
(transition: none) inside `@media` (prefers-reduced-motion: reduce) to ensure the
spin is stopped for accessibility.
- Around line 60-92: The CSS lacks disabled-state rules so disabled buttons
still look interactive; add selectors for .btn:disabled and .btn[disabled] to
clearly show non-interactive state (reduce opacity or use muted
background/border), remove or override cursor: pointer (set cursor: default or
not-allowed), and ensure both .btn-primary and .btn-secondary disabled variants
are covered (e.g., muted colors, no hover effects) so disabled buttons are
visually distinct and non-clickable; also consider matching focus/aria-disabled
behavior for .empty-state if it can be disabled.
- Around line 166-174: The .sidebar uses a fixed width (320px) which breaks
small screens; update the CSS to be responsive by replacing the hard width with
a flexible rule (e.g., use max-width or a flex-basis like flex: 0 0 320px) and
add media queries (breakpoints) that at narrow widths change .workspace to
column stacking (flex-direction: column) and adjust .sidebar to a narrower or
full-width value (e.g., width: 100% or max-width: 240px) and ensure overflow-y
behavior remains correct; target the .sidebar and .workspace selectors in the
stylesheet and add the media-query overrides so the layout stacks and sidebar
shrinks on small viewports.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 7302ff52-5222-4603-b026-afe05c9d0401
📒 Files selected for processing (2)
frontend/src/App.cssfrontend/src/App.tsx
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@frontend/src/App.tsx`:
- Around line 56-68: On image decode failure the onerror handler immediately
clears state (setImageObj / setImageLoaded) which removes an existing preview;
change the logic so the onerror does not mutate existing state and only the
onload commits updates: in the Image promise (created in the async block) remove
calls to setImageObj(null) and setImageLoaded(false) from img.onerror and
instead reject without touching state, and ensure only img.onload calls
setImageObj(img) and setImageLoaded(true) so the current preview remains if the
new image fails to decode.
🪄 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: c1013e0b-ade4-4a15-ad02-56845e4f7251
📒 Files selected for processing (1)
frontend/src/App.tsx
There was a problem hiding this comment.
♻️ Duplicate comments (1)
frontend/src/App.tsx (1)
46-69:⚠️ Potential issue | 🟠 MajorEXIF と画像は成功時にまとめて反映してください。
Line 47-54 の
setExifがデコード完了前に走るため、Change Photo中に新画像の読み込みが失敗すると、古いプレビューに新しい EXIF だけが残ります。成功時にExifDataとHTMLImageElementをまとめてコミットしないと、失敗時の不整合と読み込み中の一時的な誤表示が発生します。🔧 修正案
- // Update EXIF state (leave empty if not found) - setExif({ - camera: result.camera || "", - lens: result.lens || "", - focalLength: result.focalLength || "", - aperture: result.aperture || "", - shutterSpeed: result.shutterSpeed || "", - iso: result.iso || "" - }); - - // Load the Base64 image and await its decoding - await new Promise<void>((resolve, reject) => { + const nextExif: ExifData = { + camera: result.camera || "", + lens: result.lens || "", + focalLength: result.focalLength || "", + aperture: result.aperture || "", + shutterSpeed: result.shutterSpeed || "", + iso: result.iso || "" + }; + + const nextImage = await new Promise<HTMLImageElement>((resolve, reject) => { const img = new Image(); img.onload = () => { - setImageObj(img); - setImageLoaded(true); - resolve(); + resolve(img); }; img.onerror = () => { console.error("Failed to decode or render the selected image"); reject(new Error("Failed to decode image")); }; img.src = result.imageBase64; }); + + setExif(nextExif); + setImageObj(nextImage); + setImageLoaded(true);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/App.tsx` around lines 46 - 69, The EXIF state (setExif) is being committed before the image decode finishes, causing mismatches if image loading fails; change the flow so you only update both EXIF and image state together after the image successfully loads: move the setExif call into the img.onload handler (or resolve with both result and image and then call setExif, setImageObj, setImageLoaded together), and ensure img.onerror does not mutate state (keep previous preview) and rejects using result.imageBase64’s decode failure; key symbols to update are setExif, setImageObj, setImageLoaded, img.onload/img.onerror and result.imageBase64.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Duplicate comments:
In `@frontend/src/App.tsx`:
- Around line 46-69: The EXIF state (setExif) is being committed before the
image decode finishes, causing mismatches if image loading fails; change the
flow so you only update both EXIF and image state together after the image
successfully loads: move the setExif call into the img.onload handler (or
resolve with both result and image and then call setExif, setImageObj,
setImageLoaded together), and ensure img.onerror does not mutate state (keep
previous preview) and rejects using result.imageBase64’s decode failure; key
symbols to update are setExif, setImageObj, setImageLoaded,
img.onload/img.onerror and result.imageBase64.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: f3a4555e-6be8-4c46-beba-ea5264c8fd4a
📒 Files selected for processing (1)
frontend/src/App.tsx
UIの強化
resolved #6