Move completed design/implementation docs from docs/plans/ to docs/archive/ to keep the plans folder focused on active and future work. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
9.1 KiB
Session Scratchpad Design
Date: February 4, 2026 Source: Feature Ideas Brainstorm - Idea 6 Priority: Must-have (per Michael) Category: Context capture
Overview
A collapsible right sidebar during active sessions for capturing ambient data — IP addresses, error codes, server names, usernames — anything that doesn't fit a specific decision node's notes field.
Core principle: Engineers already accumulate scraps of data during troubleshooting (from ipconfig, Event Viewer, phone conversations) that live on sticky notes or in their head. This gives it a home and includes it automatically in the export.
Data model: Single freeform Text column on the sessions table with markdown formatting support. Not JSONB — we're starting simple with a plain text field that the engineer writes in freely.
Save behavior: Auto-save with 1000ms debounce. No manual save button.
Layout: Collapsible right sidebar in TreeNavigationPage, 300px when open, 48px toggle strip when collapsed.
Section 1: Data Model
Database
New scratchpad column on sessions table:
# SQLAlchemy model (session.py)
scratchpad: Mapped[Optional[str]] = mapped_column(Text, nullable=True, server_default=sa.text("''"))
Migration
# alembic revision -m "add scratchpad to sessions"
def upgrade():
op.add_column('sessions', sa.Column('scratchpad', sa.Text(), nullable=True, server_default=sa.text("''")))
# Backfill existing rows
op.execute("UPDATE sessions SET scratchpad = '' WHERE scratchpad IS NULL")
def downgrade():
op.drop_column('sessions', 'scratchpad')
server_default=sa.text("''")ensures new rows get""at the database level- Backfill normalizes existing rows so no code path sees
NULL
Schemas
SessionUpdate — add field:
scratchpad: Optional[str] = None
SessionResponse — add field with validator:
scratchpad: str = ""
@validator('scratchpad', pre=True, always=True)
def normalize_scratchpad(cls, v):
return v or ""
New schema — ScratchpadUpdate:
class ScratchpadUpdate(BaseModel):
scratchpad: str
Section 2: Frontend Component Design
Layout Restructuring
Current TreeNavigationPage is a single-column container mx-auto div. Restructure to:
┌─────────────────────────────────────┬──────────────┐
│ Existing tree navigation content │ Scratchpad │
│ (header, breadcrumb, node card, │ sidebar │
│ notes, back button, shortcuts) │ (300px) │
│ │ │
└─────────────────────────────────────┴──────────────┘
- Outer wrapper:
flexlayout - Main content:
flex-1 min-w-0(prevents overflow) - Sidebar: Fixed 300px open, 48px collapsed
- Collapse state persisted in
localStorageasscratchpad-collapsed
ScratchpadSidebar Component
File: frontend/src/components/session/ScratchpadSidebar.tsx
Props:
interface ScratchpadSidebarProps {
sessionId: string
initialContent: string
onSave: (content: string) => Promise<void>
}
Internal state:
content: string— textarea value, initialized frominitialContent, only reset whensessionIdchanges (viauseEffectwith[sessionId]dependency)isCollapsed: boolean— sidebar open/closed, persisted in localStorageisSaving: boolean— network request in flighthasUnsavedChanges: boolean—content !== lastSavedlastSaved: string— last successfully saved contentshowPreview: boolean— toggle between edit and markdown preview
Save Flow
- User types →
contentupdates →hasUnsavedChanges = (content !== lastSaved) - After 1000ms of no typing (debounce), if
hasUnsavedChanges, fireonSave(content) - Set
isSaving = trueduring request - On success:
lastSaved = content,isSaving = false,hasUnsavedChanges = false - On blur: trigger immediate save if
hasUnsavedChanges(cancel pending debounce) - On error: keep
hasUnsavedChanges = true, show error indicator
Save Indicator (bottom of sidebar)
| State | Display |
|---|---|
| Idle, no changes | Nothing shown |
| Typing, unsaved | "Unsaved changes" (muted text) |
| Saving | "Saving..." with subtle spinner |
| Just saved | "Saved" with checkmark (green, fades after 2s) |
beforeunload Warning
- Register
window.addEventListener('beforeunload', handler)only whenhasUnsavedChanges === true - Handler calls
e.preventDefault()to trigger browser's "unsaved changes" dialog - Clean up listener on unmount
Markdown Preview
- Small toggle link below textarea: "Preview" / "Edit"
- Preview renders current
contentusing existingMarkdownContentcomponent - No new dependencies required
Dimensions & Animation
- Open width: 300px
- Collapsed width: 48px (notepad icon button)
- Transition:
width 200ms ease - Toggle icon: Notepad/pencil icon from Lucide
Section 3: Backend & API
New Endpoint
PATCH /api/v1/sessions/{id}/scratchpad
Request body:
{ "scratchpad": "- Server IP: 192.168.1.50\n- Error: 0x80070005" }
Response: SessionResponse (full session object)
Handler logic:
- Load session by ID
- Verify
session.user_id == current_user.id(ownership check) - Set
session.scratchpad = data.scratchpad - Commit and return updated session
This is intentionally separate from the existing PUT endpoint. Auto-saving the scratchpad every second should not send the full session payload (path_taken, decisions, custom_steps) — that risks overwriting concurrent changes from decision tracking in the same session.
Frontend API Client
// sessions.ts
updateScratchpad: (id: string, content: string) =>
api.patch<Session>(`/api/v1/sessions/${id}/scratchpad`, { scratchpad: content })
Router Registration
Add to backend/app/api/router.py — the endpoint lives in the existing sessions.py endpoints file.
Section 4: Export Integration
Add a "Evidence / Reference" section to all three export generators, inserted between the header block and "Troubleshooting Steps". Only rendered when scratchpad.strip() is non-empty.
Markdown Export
## Evidence / Reference
- Server IP: 192.168.1.50
- Error code: 0x80070005
- Affected user: jsmith@contoso.com
---
## Troubleshooting Steps
Scratchpad content rendered as-is (user writes markdown, it appears as markdown).
Text Export
EVIDENCE / REFERENCE
--------------------
- Server IP: 192.168.1.50
- Error code: 0x80070005
- Affected user: jsmith@contoso.com
TROUBLESHOOTING STEPS
---------------------
HTML Export
<h2>Evidence / Reference</h2>
<div class="scratchpad" style="white-space: pre-wrap; background: #f9f9f9; padding: 12px; border-radius: 4px; margin-bottom: 20px;">
- Server IP: 192.168.1.50
- Error code: 0x80070005
</div>
<h2>Troubleshooting Steps</h2>
Start with pre-wrap for HTML export. Future enhancement: render scratchpad markdown to HTML.
Files Changed
| File | Action | Description |
|---|---|---|
backend/app/models/session.py |
Modify | Add scratchpad field |
backend/app/schemas/session.py |
Modify | Add to SessionUpdate, SessionResponse + new ScratchpadUpdate |
backend/app/api/endpoints/sessions.py |
Modify | Add PATCH endpoint + scratchpad in all 3 export generators |
backend/app/api/router.py |
Modify | Register new PATCH route |
backend/alembic/versions/xxx_add_scratchpad.py |
Create | Migration with backfill |
frontend/src/components/session/ScratchpadSidebar.tsx |
Create | New sidebar component |
frontend/src/pages/TreeNavigationPage.tsx |
Modify | Add flex layout + render sidebar |
frontend/src/api/sessions.ts |
Modify | Add updateScratchpad method |
frontend/src/types/session.ts |
Modify | Add scratchpad to Session type |
Design Decisions
| Decision | Choice | Rationale |
|---|---|---|
| Storage type | Text column (not JSONB) |
Freeform text is simpler; structured entries are a future enhancement |
| Save mechanism | Auto-save with 1000ms debounce | Reduces friction — engineer never thinks about saving |
| API endpoint | Dedicated PATCH (not reuse PUT) | Prevents scratchpad auto-saves from overwriting concurrent decision updates |
| Conflict detection | None (last-write-wins) | Sessions are single-user; multi-user requires broader solution |
| Markdown rendering | Preview toggle (not live) | Keeps the editing experience simple; preview is optional |
| Collapse persistence | localStorage | Survives page refreshes, per-browser preference |
Pairs With (Future)
- Command Output Capture (Idea 3): Structured output at nodes + freeform notes in scratchpad = complete evidence
- Share Progress (Idea 2): Scratchpad content included in shared summary
Design finalized during brainstorming session, February 4, 2026