Major UI overhaul plans; Other random docs
This commit is contained in:
369
docs/plans/2026-02-13-export-phase-a-frontend.md
Normal file
369
docs/plans/2026-02-13-export-phase-a-frontend.md
Normal file
@@ -0,0 +1,369 @@
|
||||
# Export Improvements Phase A — Frontend Implementation Plan
|
||||
|
||||
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
|
||||
|
||||
**Goal:** Add `next_steps` to types/UI, add "Copy for Ticket" to TreeNavigationPage, add step cutoff control to SessionDetailPage, display next_steps in session detail + completion modal.
|
||||
|
||||
**Architecture:** Update TypeScript types to match backend schema changes. Add `next_steps` textarea to SessionOutcomeModal. Display next_steps on SessionDetailPage. Add mid-session "Copy for Ticket" button to TreeNavigationPage. Add step cutoff dropdown to SessionDetailPage export controls.
|
||||
|
||||
**Tech Stack:** React 19, TypeScript, Tailwind CSS, Zustand, Vite
|
||||
|
||||
**Backend dependency:** Phase A backend is complete on `feat/export-phase-a` branch.
|
||||
|
||||
---
|
||||
|
||||
## Task 1: Update TypeScript Types
|
||||
|
||||
**Files:**
|
||||
- Modify: `frontend/src/types/session.ts`
|
||||
|
||||
**Step 1: Add `next_steps` to Session interface** (line 59, after `scratchpad`)
|
||||
|
||||
```typescript
|
||||
next_steps: string
|
||||
```
|
||||
|
||||
**Step 2: Add `next_steps` to SessionUpdate** (line 68, after `scratchpad`)
|
||||
|
||||
```typescript
|
||||
next_steps?: string
|
||||
```
|
||||
|
||||
**Step 3: Add `next_steps` to SessionComplete** (line 83)
|
||||
|
||||
```typescript
|
||||
export interface SessionComplete {
|
||||
outcome: SessionOutcome
|
||||
outcome_notes?: string
|
||||
next_steps?: string
|
||||
}
|
||||
```
|
||||
|
||||
**Step 4: Update SessionExport** (line 77)
|
||||
|
||||
```typescript
|
||||
export interface SessionExport {
|
||||
format: 'text' | 'markdown' | 'html' | 'psa'
|
||||
include_timestamps?: boolean
|
||||
include_tree_info?: boolean
|
||||
include_outcome_notes?: boolean
|
||||
include_next_steps?: boolean
|
||||
max_step_index?: number
|
||||
}
|
||||
```
|
||||
|
||||
**Step 5: Build to verify no type errors**
|
||||
|
||||
Run: `cd /c/Dev/Projects/patherly/frontend && npx tsc --noEmit`
|
||||
|
||||
**Step 6: Commit**
|
||||
|
||||
```bash
|
||||
git add frontend/src/types/session.ts
|
||||
git commit -m "feat(frontend): add next_steps and export options to session types"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 2: Add Next Steps to Session Completion Modal
|
||||
|
||||
**Files:**
|
||||
- Modify: `frontend/src/components/session/SessionOutcomeModal.tsx`
|
||||
- Modify: `frontend/src/pages/TreeNavigationPage.tsx` (consumer callback)
|
||||
|
||||
**Step 1: Update the `onSubmit` prop type** (line 9)
|
||||
|
||||
Change the data shape to include `next_steps`:
|
||||
|
||||
```typescript
|
||||
onSubmit: (data: { outcome: SessionOutcome; outcome_notes?: string; next_steps?: string }) => Promise<void>
|
||||
```
|
||||
|
||||
**Step 2: Update `handleSubmit`** (line 28)
|
||||
|
||||
Add next_steps extraction from formData:
|
||||
|
||||
```typescript
|
||||
const handleSubmit = async () => {
|
||||
if (!formRef.current) return
|
||||
const formData = new FormData(formRef.current)
|
||||
const outcome = (formData.get('session-outcome') as SessionOutcome | null) ?? 'resolved'
|
||||
const outcomeNotes = ((formData.get('outcome-notes') as string | null) ?? '').trim()
|
||||
const nextSteps = ((formData.get('next-steps') as string | null) ?? '').trim()
|
||||
|
||||
await onSubmit({
|
||||
outcome,
|
||||
outcome_notes: outcomeNotes || undefined,
|
||||
next_steps: nextSteps || undefined,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
**Step 3: Add Next Steps textarea** after the outcome notes textarea (after line 115)
|
||||
|
||||
Add a second textarea block right before the closing `</form>`:
|
||||
|
||||
```tsx
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-white">Next Steps / Follow-Up (optional)</label>
|
||||
<textarea
|
||||
name="next-steps"
|
||||
defaultValue=""
|
||||
rows={3}
|
||||
placeholder="Actions to take after this session..."
|
||||
className={cn(
|
||||
'mt-1 block w-full rounded-md border border-white/10 bg-black/50 px-3 py-2',
|
||||
'text-sm text-white placeholder:text-white/40',
|
||||
'focus:border-white/30 focus:outline-none focus:ring-1 focus:ring-white/20'
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
```
|
||||
|
||||
**Step 4: Update the consumer callback in TreeNavigationPage.tsx** (line 341)
|
||||
|
||||
Change `handleSubmitOutcome` signature to match:
|
||||
|
||||
```typescript
|
||||
const handleSubmitOutcome = async (data: { outcome: SessionOutcome; outcome_notes?: string; next_steps?: string }) => {
|
||||
```
|
||||
|
||||
The body already passes `data` directly to `sessionsApi.complete(session.id, data)` so the `next_steps` field will propagate automatically.
|
||||
|
||||
**Step 5: Build to verify**
|
||||
|
||||
Run: `cd /c/Dev/Projects/patherly/frontend && npx tsc --noEmit`
|
||||
|
||||
**Step 6: Commit**
|
||||
|
||||
```bash
|
||||
git add frontend/src/components/session/SessionOutcomeModal.tsx frontend/src/pages/TreeNavigationPage.tsx
|
||||
git commit -m "feat(frontend): add next_steps field to session completion modal"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 3: Display Next Steps on SessionDetailPage
|
||||
|
||||
**Files:**
|
||||
- Modify: `frontend/src/pages/SessionDetailPage.tsx`
|
||||
|
||||
**Step 1: Display next_steps after outcome_notes** (after line 334)
|
||||
|
||||
Find the existing outcome_notes display:
|
||||
```tsx
|
||||
{session.outcome_notes && (
|
||||
<p className="mt-2 text-sm text-white/60">Outcome Notes: {session.outcome_notes}</p>
|
||||
)}
|
||||
```
|
||||
|
||||
Add next_steps display right after it. Use `whitespace-pre-wrap` to preserve line breaks (engineers often enter bullet lists):
|
||||
|
||||
```tsx
|
||||
{session.next_steps && (
|
||||
<div className="mt-2">
|
||||
<span className="text-sm text-white/40">Next Steps:</span>
|
||||
<p className="mt-0.5 text-sm text-white/60 whitespace-pre-wrap">{session.next_steps}</p>
|
||||
</div>
|
||||
)}
|
||||
```
|
||||
|
||||
**Step 2: Build to verify**
|
||||
|
||||
Run: `cd /c/Dev/Projects/patherly/frontend && npx tsc --noEmit`
|
||||
|
||||
**Step 3: Commit**
|
||||
|
||||
```bash
|
||||
git add frontend/src/pages/SessionDetailPage.tsx
|
||||
git commit -m "feat(frontend): display next_steps on session detail page"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 4: Add "Copy for Ticket" Button to TreeNavigationPage
|
||||
|
||||
**Files:**
|
||||
- Modify: `frontend/src/pages/TreeNavigationPage.tsx`
|
||||
|
||||
This adds a mid-session export button. It should be secondary/outline style — accessible but not prominent (engineers shouldn't think it ends their session).
|
||||
|
||||
**Step 1: Import necessary icons and API**
|
||||
|
||||
Check what's already imported. You'll need `Copy` and `Check` from lucide-react, and `sessionsApi` from `@/api`. Also need `toast` if not already imported.
|
||||
|
||||
**Step 2: Add state for copy feedback and loading** (near other state declarations)
|
||||
|
||||
```typescript
|
||||
const [copiedForTicket, setCopiedForTicket] = useState(false)
|
||||
const [isCopyingForTicket, setIsCopyingForTicket] = useState(false)
|
||||
```
|
||||
|
||||
**Step 3: Add the copy handler** (with loading guard to prevent double-clicks)
|
||||
|
||||
```typescript
|
||||
const handleCopyForTicket = async () => {
|
||||
if (!session || isCopyingForTicket) return
|
||||
setIsCopyingForTicket(true)
|
||||
try {
|
||||
const content = await sessionsApi.export(session.id, {
|
||||
format: 'psa',
|
||||
include_timestamps: true,
|
||||
include_tree_info: true,
|
||||
})
|
||||
if (content) {
|
||||
await navigator.clipboard.writeText(content)
|
||||
setCopiedForTicket(true)
|
||||
setTimeout(() => setCopiedForTicket(false), 2000)
|
||||
toast.success('Copied progress notes to clipboard')
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Copy for ticket failed:', err)
|
||||
toast.error('Failed to copy notes')
|
||||
} finally {
|
||||
setIsCopyingForTicket(false)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Step 4: Add the button to the page toolbar**
|
||||
|
||||
Find the toolbar/header area of TreeNavigationPage. Look for where other action buttons are (like scratchpad toggle, session timer, etc.). Add a secondary-style button with disabled state:
|
||||
|
||||
```tsx
|
||||
<button
|
||||
onClick={handleCopyForTicket}
|
||||
disabled={isCopyingForTicket}
|
||||
title="Copy progress notes for ticket"
|
||||
className={cn(
|
||||
'flex items-center gap-1.5 rounded-md border border-white/10 px-3 py-1.5 text-xs font-medium text-white/60',
|
||||
'hover:bg-white/10 hover:text-white transition-colors disabled:opacity-50'
|
||||
)}
|
||||
>
|
||||
{copiedForTicket ? <Check className="h-3.5 w-3.5 text-emerald-400" /> : <Copy className="h-3.5 w-3.5" />}
|
||||
{copiedForTicket ? 'Copied!' : 'Copy for Ticket'}
|
||||
</button>
|
||||
```
|
||||
|
||||
Place it near other toolbar actions but not in a position where it could be confused with session completion buttons.
|
||||
|
||||
**Step 5: Build to verify**
|
||||
|
||||
Run: `cd /c/Dev/Projects/patherly/frontend && npx tsc --noEmit`
|
||||
|
||||
**Step 6: Commit**
|
||||
|
||||
```bash
|
||||
git add frontend/src/pages/TreeNavigationPage.tsx
|
||||
git commit -m "feat(frontend): add mid-session Copy for Ticket button to navigation page"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 5: Add Step Cutoff Control to SessionDetailPage Export
|
||||
|
||||
**Files:**
|
||||
- Modify: `frontend/src/pages/SessionDetailPage.tsx`
|
||||
|
||||
**Step 1: Add state for step cutoff** (near other export state)
|
||||
|
||||
```typescript
|
||||
const [maxStepIndex, setMaxStepIndex] = useState<number | null>(null)
|
||||
```
|
||||
|
||||
**Step 2: Update `fetchExportContent` to include new options** (line 91)
|
||||
|
||||
```typescript
|
||||
const fetchExportContent = async () => {
|
||||
if (!session) return null
|
||||
const options: SessionExport = {
|
||||
format: exportFormat,
|
||||
include_timestamps: true,
|
||||
include_tree_info: true,
|
||||
...(maxStepIndex !== null && { max_step_index: maxStepIndex }),
|
||||
}
|
||||
return await sessionsApi.export(session.id, options)
|
||||
}
|
||||
```
|
||||
|
||||
**Step 3: Update `handleCopyForTicket` similarly** (line 135)
|
||||
|
||||
Add `max_step_index` to PSA export options too:
|
||||
|
||||
```typescript
|
||||
const options: SessionExport = {
|
||||
format: 'psa',
|
||||
include_timestamps: true,
|
||||
include_tree_info: true,
|
||||
...(maxStepIndex !== null && { max_step_index: maxStepIndex }),
|
||||
}
|
||||
```
|
||||
|
||||
**Step 4: Add step cutoff dropdown** in the export controls area
|
||||
|
||||
Find the export format `<select>` element (line 368). Add a step cutoff dropdown right after it, but only show it when the session has decisions:
|
||||
|
||||
```tsx
|
||||
{session.decisions.length > 1 && (
|
||||
<select
|
||||
value={maxStepIndex ?? ''}
|
||||
onChange={(e) => setMaxStepIndex(e.target.value ? Number(e.target.value) : null)}
|
||||
aria-label="Export through step"
|
||||
className={cn(
|
||||
'rounded-md border border-white/10 bg-black/50 px-3 py-2 text-sm text-white',
|
||||
'focus:border-white/30 focus:outline-none focus:ring-1 focus:ring-white/20'
|
||||
)}
|
||||
>
|
||||
<option value="">All steps</option>
|
||||
{session.decisions.map((_, idx) => (
|
||||
<option key={idx + 1} value={idx + 1}>
|
||||
Through step {idx + 1}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
)}
|
||||
```
|
||||
|
||||
**Step 5: Build to verify**
|
||||
|
||||
Run: `cd /c/Dev/Projects/patherly/frontend && npx tsc --noEmit`
|
||||
|
||||
**Step 6: Full build**
|
||||
|
||||
Run: `cd /c/Dev/Projects/patherly/frontend && npm run build`
|
||||
|
||||
**Step 7: Commit**
|
||||
|
||||
```bash
|
||||
git add frontend/src/pages/SessionDetailPage.tsx
|
||||
git commit -m "feat(frontend): add step cutoff control to export options"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 6: Final Verification
|
||||
|
||||
**Step 1: Full build**
|
||||
|
||||
Run: `cd /c/Dev/Projects/patherly/frontend && npm run build`
|
||||
|
||||
Expected: Build succeeds with no errors.
|
||||
|
||||
**Step 2: Verify git status is clean**
|
||||
|
||||
```bash
|
||||
git status
|
||||
git log --oneline feat/export-phase-a --not main
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Frontend Acceptance Checklist (Manual QA)
|
||||
|
||||
After all tasks are complete, verify these scenarios with the running app:
|
||||
|
||||
1. **Completion with next_steps:** Complete a session with both outcome_notes and next_steps filled in. Verify both appear on SessionDetailPage with line breaks preserved.
|
||||
2. **Completion without next_steps:** Complete a session with only outcome. Verify no empty "Next Steps" section appears on detail page or in exports.
|
||||
3. **Mid-session copy:** While navigating a tree, click "Copy for Ticket". Paste the result — it should show "In progress" duration and steps completed so far. Verify the session is NOT marked as exported.
|
||||
4. **Step cutoff:** On a completed session with 5+ steps, use the "Through step 3" dropdown, then Preview. Verify only steps 1-3 appear. Verify "Copy for Ticket" also respects the cutoff.
|
||||
5. **Double-click protection:** Click "Copy for Ticket" rapidly on TreeNavigationPage. Verify only one API call fires (button should disable during loading).
|
||||
Reference in New Issue
Block a user