Conversation
- useUndoable フック(useReducer ベース)で features の履歴管理 - Ctrl+Z / Cmd+Z でUndo、Ctrl+Shift+Z / Cmd+Shift+Z でRedo - DrawControlPanel に Undo/Redo ボタンを追加 - 最大50件の履歴を保持 - ユニットテスト10件、E2Eシナリオ5件追加 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
📝 WalkthroughWalkthroughUndo/Redo機能を地図描画アプリケーションに追加します。useUndoableカスタムフックで状態管理を実装し、キーボードショートカット(Ctrl+Z、Ctrl+Shift+Z)とUIボタンでUndo/Redo操作をサポートします。E2Eテストとユニットテストで動作を検証します。 Changes
Sequence DiagramsequenceDiagram
participant User
participant Keyboard
participant MapView
participant useUndoable
participant DrawControlPanel
participant UI
User->>Keyboard: Press Ctrl+Z
Keyboard->>MapView: Trigger keyboard event
MapView->>useUndoable: Call undo()
useUndoable->>useUndoable: Move current to future,<br/>restore from past
useUndoable-->>MapView: Return updated features
MapView->>DrawControlPanel: Update canUndo/canRedo props
DrawControlPanel->>UI: Render button states
UI-->>User: Display updated map
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
src/components/DrawControlPanel.tsx (1)
152-176: Undo/Redo ボタンに aria-label を追加してください。
アイコンのみのボタンはスクリーンリーダーで識別しづらいので、明示的なラベル付与が望ましいです。♿ 例: aria-label を追加
<button type='button' onClick={onUndo} disabled={!canUndo} + aria-label='元に戻す' title='元に戻す (Ctrl+Z)' className={`draw-control-panel__action-button${!canUndo ? ' draw-control-panel__action-button--disabled' : ''}`} > ... <button type='button' onClick={onRedo} disabled={!canRedo} + aria-label='やり直す' title='やり直す (Ctrl+Shift+Z)' className={`draw-control-panel__action-button${!canRedo ? ' draw-control-panel__action-button--disabled' : ''}`} >🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/DrawControlPanel.tsx` around lines 152 - 176, The undo/redo icon-only buttons in DrawControlPanel are missing accessible labels; update the two button elements that use onUndo/canUndo and onRedo/canRedo to include aria-label attributes (e.g., aria-label="元に戻す" for the onUndo button and aria-label="やり直す" for the onRedo button) so screen readers can identify the controls while keeping existing title, disabled and className behavior intact.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/components/MapView.tsx`:
- Around line 414-430: The global keyboard handler (handleKeyDown in the
useEffect that binds window keydown) currently triggers
undoFeatures/redoFeatures even when focus is in input fields; update
handleKeyDown to bail out early if the event target is an editable element
(e.g., tagName input, textarea, select, or an element with isContentEditable
true) or if the focused element has a role/attribute indicating an editor, so
the shortcut is ignored while editing; keep the existing mac vs ctrl logic and
existing undoFeatures/redoFeatures calls otherwise.
---
Nitpick comments:
In `@src/components/DrawControlPanel.tsx`:
- Around line 152-176: The undo/redo icon-only buttons in DrawControlPanel are
missing accessible labels; update the two button elements that use
onUndo/canUndo and onRedo/canRedo to include aria-label attributes (e.g.,
aria-label="元に戻す" for the onUndo button and aria-label="やり直す" for the onRedo
button) so screen readers can identify the controls while keeping existing
title, disabled and className behavior intact.
| // Undo/Redo キーボードショートカット | ||
| useEffect(() => { | ||
| const handleKeyDown = (e: KeyboardEvent) => { | ||
| const isMac = /mac/i.test(navigator.userAgent) | ||
| const ctrlOrCmd = isMac ? e.metaKey : e.ctrlKey | ||
| if (!ctrlOrCmd) return | ||
| if (e.key === 'z' && !e.shiftKey) { | ||
| e.preventDefault() | ||
| undoFeatures() | ||
| } else if ((e.key === 'z' && e.shiftKey) || e.key === 'y') { | ||
| e.preventDefault() | ||
| redoFeatures() | ||
| } | ||
| } | ||
| window.addEventListener('keydown', handleKeyDown) | ||
| return () => window.removeEventListener('keydown', handleKeyDown) | ||
| }, [undoFeatures, redoFeatures]) |
There was a problem hiding this comment.
入力欄での Ctrl/Cmd+Z を奪わないようにしてください。
現在の実装だと、プロパティ編集などの入力中でも Undo/Redo が発火し、編集体験を壊します。入力系要素ではショートカットを無視する条件を追加してください。
🛠 例: 編集可能要素を除外
const handleKeyDown = (e: KeyboardEvent) => {
const isMac = /mac/i.test(navigator.userAgent)
const ctrlOrCmd = isMac ? e.metaKey : e.ctrlKey
+ const target = e.target as HTMLElement | null
+ if (target && (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' || target.isContentEditable)) return
if (!ctrlOrCmd) return
if (e.key === 'z' && !e.shiftKey) {
e.preventDefault()
undoFeatures()
} else if ((e.key === 'z' && e.shiftKey) || e.key === 'y') {
e.preventDefault()
redoFeatures()
}
}📝 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.
| // Undo/Redo キーボードショートカット | |
| useEffect(() => { | |
| const handleKeyDown = (e: KeyboardEvent) => { | |
| const isMac = /mac/i.test(navigator.userAgent) | |
| const ctrlOrCmd = isMac ? e.metaKey : e.ctrlKey | |
| if (!ctrlOrCmd) return | |
| if (e.key === 'z' && !e.shiftKey) { | |
| e.preventDefault() | |
| undoFeatures() | |
| } else if ((e.key === 'z' && e.shiftKey) || e.key === 'y') { | |
| e.preventDefault() | |
| redoFeatures() | |
| } | |
| } | |
| window.addEventListener('keydown', handleKeyDown) | |
| return () => window.removeEventListener('keydown', handleKeyDown) | |
| }, [undoFeatures, redoFeatures]) | |
| // Undo/Redo キーボードショートカット | |
| useEffect(() => { | |
| const handleKeyDown = (e: KeyboardEvent) => { | |
| const isMac = /mac/i.test(navigator.userAgent) | |
| const ctrlOrCmd = isMac ? e.metaKey : e.ctrlKey | |
| const target = e.target as HTMLElement | null | |
| if (target && (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' || target.isContentEditable)) return | |
| if (!ctrlOrCmd) return | |
| if (e.key === 'z' && !e.shiftKey) { | |
| e.preventDefault() | |
| undoFeatures() | |
| } else if ((e.key === 'z' && e.shiftKey) || e.key === 'y') { | |
| e.preventDefault() | |
| redoFeatures() | |
| } | |
| } | |
| window.addEventListener('keydown', handleKeyDown) | |
| return () => window.removeEventListener('keydown', handleKeyDown) | |
| }, [undoFeatures, redoFeatures]) |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/components/MapView.tsx` around lines 414 - 430, The global keyboard
handler (handleKeyDown in the useEffect that binds window keydown) currently
triggers undoFeatures/redoFeatures even when focus is in input fields; update
handleKeyDown to bail out early if the event target is an editable element
(e.g., tagName input, textarea, select, or an element with isContentEditable
true) or if the focused element has a role/attribute indicating an editor, so
the shortcut is ignored while editing; keep the existing mac vs ctrl logic and
existing undoFeatures/redoFeatures calls otherwise.
Summary
useUndoableフック(useReducerベース)でfeatures状態の履歴管理を実装Ctrl+Z/Cmd+Zで Undo、Ctrl+Shift+Z/Cmd+Shift+Zで RedoDrawControlPanelに Undo/Redo ボタンを追加(無効時はグレーアウト)Test plan
useUndoable.test.ts(10件) —npm testでパスundo-redo.feature(5シナリオ) —npm run test:e2eで44シナリオすべてパスCloses #41
Summary by CodeRabbit
リリースノート
新機能
テスト