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:
@@ -30,6 +30,7 @@ export function SessionDetailPage() {
|
||||
const [showRatingModal, setShowRatingModal] = useState(false)
|
||||
const [isSavingRatings, setIsSavingRatings] = useState(false)
|
||||
const [librarySteps, setLibrarySteps] = useState<Step[]>([])
|
||||
const [copiedStepIndex, setCopiedStepIndex] = useState<number | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
if (id) {
|
||||
@@ -217,6 +218,21 @@ export function SessionDetailPage() {
|
||||
}
|
||||
}
|
||||
|
||||
const handleCopyStep = async (decision: Session['decisions'][number], index: number) => {
|
||||
const lines: string[] = []
|
||||
if (decision.question) lines.push(`Question: ${decision.question}`)
|
||||
if (decision.answer) lines.push(`Answer: ${decision.answer}`)
|
||||
if (decision.action_performed) lines.push(`Action: ${decision.action_performed}`)
|
||||
if (decision.notes) lines.push(`Notes: ${decision.notes}`)
|
||||
try {
|
||||
await navigator.clipboard.writeText(lines.join('\n'))
|
||||
setCopiedStepIndex(index)
|
||||
setTimeout(() => setCopiedStepIndex(null), 2000)
|
||||
} catch {
|
||||
// Clipboard access denied
|
||||
}
|
||||
}
|
||||
|
||||
const formatDate = (dateString: string) => {
|
||||
return new Date(dateString).toLocaleString()
|
||||
}
|
||||
@@ -367,25 +383,40 @@ export function SessionDetailPage() {
|
||||
<div className="relative">
|
||||
<span className="absolute -left-[1.625rem] top-1 h-2 w-2 rounded-full bg-white/20" />
|
||||
<div className="glass-card rounded-xl p-4">
|
||||
{decision.question && (
|
||||
<p className="font-medium text-white">{decision.question}</p>
|
||||
)}
|
||||
{decision.answer && (
|
||||
<p className="mt-1 text-sm text-white">Answer: {decision.answer}</p>
|
||||
)}
|
||||
{decision.action_performed && (
|
||||
<p className="mt-1 text-sm text-white/40">
|
||||
Action: {decision.action_performed}
|
||||
</p>
|
||||
)}
|
||||
{decision.notes && (
|
||||
<p className="mt-2 rounded bg-white/5 p-2 text-sm text-white/40">
|
||||
Notes: {decision.notes}
|
||||
</p>
|
||||
)}
|
||||
<p className="mt-2 text-xs text-white/40">
|
||||
{formatDate(decision.timestamp)}
|
||||
</p>
|
||||
<div className="flex items-start justify-between gap-2">
|
||||
<div className="flex-1">
|
||||
{decision.question && (
|
||||
<p className="font-medium text-white">{decision.question}</p>
|
||||
)}
|
||||
{decision.answer && (
|
||||
<p className="mt-1 text-sm text-white">Answer: {decision.answer}</p>
|
||||
)}
|
||||
{decision.action_performed && (
|
||||
<p className="mt-1 text-sm text-white/40">
|
||||
Action: {decision.action_performed}
|
||||
</p>
|
||||
)}
|
||||
{decision.notes && (
|
||||
<p className="mt-2 rounded bg-white/5 p-2 text-sm text-white/40">
|
||||
Notes: {decision.notes}
|
||||
</p>
|
||||
)}
|
||||
<p className="mt-2 text-xs text-white/40">
|
||||
{formatDate(decision.timestamp)}
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => handleCopyStep(decision, index)}
|
||||
title="Copy step to clipboard"
|
||||
className="rounded p-1 text-white/30 hover:bg-white/10 hover:text-white"
|
||||
>
|
||||
{copiedStepIndex === index ? (
|
||||
<Check className="h-4 w-4 text-emerald-400" />
|
||||
) : (
|
||||
<Copy className="h-4 w-4" />
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user