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

@@ -4,15 +4,18 @@ import { treesApi } from '@/api/trees'
import { sessionsApi } from '@/api/sessions'
import { useTreeNavigationShortcuts } from '@/hooks/useKeyboardShortcuts'
import { useCustomStepFlow } from '@/hooks/useCustomStepFlow'
import { useSessionTimer } from '@/hooks/useSessionTimer'
import type { Tree, Session, DecisionRecord, TreeStructure } from '@/types'
import { cn, safeGetItem } from '@/lib/utils'
import { cn, safeGetItem, safeSetItem } from '@/lib/utils'
import { MarkdownContent } from '@/components/ui/MarkdownContent'
import { CustomStepModal } from '@/components/step-library/CustomStepModal'
import { PostStepActionModal, ContinuationModal, ForkTreeModal, ScratchpadSidebar } from '@/components/session'
import { Plus, CheckCircle, ArrowRight } from 'lucide-react'
import { Plus, CheckCircle, ArrowRight, Clock } from 'lucide-react'
interface LocationState {
sessionId?: string
prefillClientName?: string
prefillTicketNumber?: string
}
export function TreeNavigationPage() {
@@ -31,11 +34,14 @@ export function TreeNavigationPage() {
const [error, setError] = useState<string | null>(null)
const [isCompleting, setIsCompleting] = useState(false)
// Session metadata
const [ticketNumber, setTicketNumber] = useState<string>('')
const [clientName, setClientName] = useState<string>('')
// Session metadata (prefill from Repeat Last Session)
const [ticketNumber, setTicketNumber] = useState<string>(locationState?.prefillTicketNumber || '')
const [clientName, setClientName] = useState<string>(locationState?.prefillClientName || '')
const [showMetadataForm, setShowMetadataForm] = useState(true)
// Session timer
const timerDisplay = useSessionTimer(session?.started_at)
// Scratchpad state
const [scratchpadOpen, setScratchpadOpen] = useState(() => {
return safeGetItem('scratchpad-collapsed') === 'false'
@@ -120,6 +126,13 @@ export function TreeNavigationPage() {
})
setSession(newSession)
setShowMetadataForm(false)
// Save for "Repeat Last Session"
safeSetItem('last-session', JSON.stringify({
tree_id: tree.id,
tree_name: tree.name,
client_name: clientName || '',
ticket_number: ticketNumber || '',
}))
} catch (err) {
setError('Failed to start session')
console.error(err)
@@ -368,7 +381,15 @@ export function TreeNavigationPage() {
{/* Header */}
<div className="mb-6 flex items-center justify-between">
<div>
<h1 className="text-xl font-bold text-white">{tree.name}</h1>
<div className="flex items-center gap-3">
<h1 className="text-xl font-bold text-white">{tree.name}</h1>
{timerDisplay && (
<span className="flex items-center gap-1 text-sm text-white/40">
<Clock className="h-3.5 w-3.5" />
{timerDisplay}
</span>
)}
</div>
{(ticketNumber || clientName) && (
<p className="text-sm text-white/40">
{ticketNumber && `Ticket: ${ticketNumber}`}
@@ -665,6 +686,7 @@ export function TreeNavigationPage() {
Notes (optional)
</label>
<textarea
id="session-notes"
value={notes}
onChange={(e) => setNotes(e.target.value)}
placeholder="Add any notes for this step..."
@@ -698,6 +720,7 @@ export function TreeNavigationPage() {
{(currentNode.type === 'action' || currentNode.type === 'solution') && (
<span>, Enter {currentNode.type === 'solution' ? 'complete' : 'continue'}</span>
)}
<span>, Tab notes</span>
</div>
)}
</div>