# 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: ```python # SQLAlchemy model (session.py) scratchpad: Mapped[Optional[str]] = mapped_column(Text, nullable=True, server_default=sa.text("''")) ``` ### Migration ```python # 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: ```python scratchpad: Optional[str] = None ``` **SessionResponse** — add field with validator: ```python scratchpad: str = "" @validator('scratchpad', pre=True, always=True) def normalize_scratchpad(cls, v): return v or "" ``` **New schema — ScratchpadUpdate:** ```python 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:** ```typescript interface ScratchpadSidebarProps { sessionId: string initialContent: string onSave: (content: string) => Promise } ``` **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: boolean` — `content !== 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:** ```json { "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 ```typescript // sessions.ts updateScratchpad: (id: string, content: string) => api.patch(`/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 ```markdown ## 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 ```html

Evidence / Reference

- Server IP: 192.168.1.50 - Error code: 0x80070005

Troubleshooting Steps

``` 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*