feat: session quick wins (#51-#55) (#72)

* feat: add session quick wins (#51-#55)

- Session timer showing elapsed time in header (#51)
- Tab keyboard shortcut to focus notes textarea (#52)
- Repeat Last Session button on tree library page (#53)
- Auto-recovery banner for incomplete sessions (#54)
- Copy individual step to clipboard on session detail (#55)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: add missing delete button to table and list tree views

The onDeleteTree prop was accepted but never used in TreeTableView and
TreeListView. Now both views show a trash icon (permission-gated) matching
the existing grid view behavior.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit was merged in pull request #72.
This commit is contained in:
chihlasm
2026-02-10 19:40:45 -05:00
committed by GitHub
parent 84fa554a7a
commit 402cdea063
7 changed files with 271 additions and 52 deletions

View File

@@ -82,6 +82,13 @@ export function useTreeNavigationShortcuts({
handler: onContinue,
enabled: canContinue,
},
// Tab to focus notes
{
key: 'Tab',
handler: () => {
document.getElementById('session-notes')?.focus()
},
},
]
useKeyboardShortcuts(shortcuts)

View File

@@ -0,0 +1,33 @@
import { useState, useEffect, useRef } from 'react'
export function useSessionTimer(startedAt: string | undefined | null): string | null {
const [elapsed, setElapsed] = useState<string | null>(null)
const intervalRef = useRef<ReturnType<typeof setInterval> | null>(null)
useEffect(() => {
if (!startedAt) {
setElapsed(null)
return
}
const startTime = new Date(startedAt).getTime()
const tick = () => {
const diff = Math.max(0, Math.floor((Date.now() - startTime) / 1000))
const hours = Math.floor(diff / 3600)
const minutes = Math.floor((diff % 3600) / 60)
const seconds = diff % 60
const pad = (n: number) => String(n).padStart(2, '0')
setElapsed(hours > 0 ? `${pad(hours)}:${pad(minutes)}:${pad(seconds)}` : `${pad(minutes)}:${pad(seconds)}`)
}
tick()
intervalRef.current = setInterval(tick, 1000)
return () => {
if (intervalRef.current) clearInterval(intervalRef.current)
}
}, [startedAt])
return elapsed
}