Files
resolutionflow/docs/archive/2026-02-04-session-scratchpad-design.md
Michael Chihlas 89d343d49a chore: archive 11 completed plan documents
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>
2026-02-10 10:51:21 -05:00

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: flex layout
  • Main content: flex-1 min-w-0 (prevents overflow)
  • Sidebar: Fixed 300px open, 48px collapsed
  • Collapse state persisted in localStorage as scratchpad-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 from initialContent, only reset when sessionId changes (via useEffect with [sessionId] dependency)
  • isCollapsed: boolean — sidebar open/closed, persisted in localStorage
  • isSaving: boolean — network request in flight
  • hasUnsavedChanges: booleancontent !== lastSaved
  • lastSaved: string — last successfully saved content
  • showPreview: boolean — toggle between edit and markdown preview

Save Flow

  1. User types → content updates → hasUnsavedChanges = (content !== lastSaved)
  2. After 1000ms of no typing (debounce), if hasUnsavedChanges, fire onSave(content)
  3. Set isSaving = true during request
  4. On success: lastSaved = content, isSaving = false, hasUnsavedChanges = false
  5. On blur: trigger immediate save if hasUnsavedChanges (cancel pending debounce)
  6. 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 when hasUnsavedChanges === 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 content using existing MarkdownContent component
  • 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:

  1. Load session by ID
  2. Verify session.user_id == current_user.id (ownership check)
  3. Set session.scratchpad = data.scratchpad
  4. 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