From 7824cddd71ec5252991708b05daf7e235fe90b58 Mon Sep 17 00:00:00 2001 From: Michael Chihlas Date: Wed, 4 Feb 2026 02:49:11 -0500 Subject: [PATCH] feat: include scratchpad in session export (markdown, text, HTML) Co-Authored-By: Claude Opus 4.5 --- backend/app/api/endpoints/sessions.py | 24 +++++ backend/tests/test_sessions.py | 142 ++++++++++++++++++++++++++ 2 files changed, 166 insertions(+) diff --git a/backend/app/api/endpoints/sessions.py b/backend/app/api/endpoints/sessions.py index 86286b6c..922563b3 100644 --- a/backend/app/api/endpoints/sessions.py +++ b/backend/app/api/endpoints/sessions.py @@ -273,6 +273,16 @@ def _generate_markdown_export(session: Session, options: SessionExport) -> str: lines.append("---") lines.append("") + # Scratchpad / Evidence section + scratchpad = getattr(session, 'scratchpad', '') or '' + if scratchpad.strip(): + lines.append("## Evidence / Reference") + lines.append("") + lines.append(scratchpad) + lines.append("") + lines.append("---") + lines.append("") + lines.append("## Troubleshooting Steps") lines.append("") @@ -311,6 +321,14 @@ def _generate_text_export(session: Session, options: SessionExport) -> str: lines.append(f"Completed: {session.completed_at.strftime('%Y-%m-%d %H:%M')}") lines.append("") + # Scratchpad / Evidence section + scratchpad = getattr(session, 'scratchpad', '') or '' + if scratchpad.strip(): + lines.append("EVIDENCE / REFERENCE") + lines.append("-" * 20) + lines.append(scratchpad) + lines.append("") + lines.append("TROUBLESHOOTING STEPS") lines.append("-" * 20) @@ -360,6 +378,12 @@ def _generate_html_export(session: Session, options: SessionExport) -> str: html.append(f'

Completed: {session.completed_at.strftime("%Y-%m-%d %H:%M")}

') html.append('') + # Scratchpad / Evidence section + scratchpad = getattr(session, 'scratchpad', '') or '' + if scratchpad.strip(): + html.append('

Evidence / Reference

') + html.append(f'
{scratchpad}
') + html.append('

Troubleshooting Steps

') for i, decision in enumerate(session.decisions, 1): diff --git a/backend/tests/test_sessions.py b/backend/tests/test_sessions.py index d7994de4..3ad858bc 100644 --- a/backend/tests/test_sessions.py +++ b/backend/tests/test_sessions.py @@ -461,3 +461,145 @@ class TestSessions: assert response.status_code == 200 assert response.json()["scratchpad"] == "post-resolution notes" + + @pytest.mark.asyncio + async def test_export_includes_scratchpad_markdown( + self, client: AsyncClient, auth_headers: dict, test_tree: dict + ): + """Test that markdown export includes scratchpad content.""" + # Create session with scratchpad + create_response = await client.post( + "/api/v1/sessions", + json={"tree_id": test_tree["id"], "ticket_number": "SP-001"}, + headers=auth_headers + ) + session_id = create_response.json()["id"] + + # Add scratchpad content + await client.patch( + f"/api/v1/sessions/{session_id}/scratchpad", + json={"scratchpad": "- Server IP: 192.168.1.50\n- Error: 0x80070005"}, + headers=auth_headers + ) + + # Export as markdown + response = await client.post( + f"/api/v1/sessions/{session_id}/export", + json={"format": "markdown", "include_tree_info": True}, + headers=auth_headers + ) + + assert response.status_code == 200 + content = response.text + assert "## Evidence / Reference" in content + assert "192.168.1.50" in content + assert "0x80070005" in content + + @pytest.mark.asyncio + async def test_export_includes_scratchpad_text( + self, client: AsyncClient, auth_headers: dict, test_tree: dict + ): + """Test that text export includes scratchpad content.""" + create_response = await client.post( + "/api/v1/sessions", + json={"tree_id": test_tree["id"]}, + headers=auth_headers + ) + session_id = create_response.json()["id"] + + await client.patch( + f"/api/v1/sessions/{session_id}/scratchpad", + json={"scratchpad": "Error code: 12345"}, + headers=auth_headers + ) + + response = await client.post( + f"/api/v1/sessions/{session_id}/export", + json={"format": "text"}, + headers=auth_headers + ) + + assert response.status_code == 200 + content = response.text + assert "EVIDENCE / REFERENCE" in content + assert "Error code: 12345" in content + + @pytest.mark.asyncio + async def test_export_includes_scratchpad_html( + self, client: AsyncClient, auth_headers: dict, test_tree: dict + ): + """Test that HTML export includes scratchpad content.""" + create_response = await client.post( + "/api/v1/sessions", + json={"tree_id": test_tree["id"]}, + headers=auth_headers + ) + session_id = create_response.json()["id"] + + await client.patch( + f"/api/v1/sessions/{session_id}/scratchpad", + json={"scratchpad": "DNS server: 10.0.0.5"}, + headers=auth_headers + ) + + response = await client.post( + f"/api/v1/sessions/{session_id}/export", + json={"format": "html"}, + headers=auth_headers + ) + + assert response.status_code == 200 + content = response.text + assert "Evidence / Reference" in content + assert "DNS server: 10.0.0.5" in content + + @pytest.mark.asyncio + async def test_export_excludes_empty_scratchpad( + self, client: AsyncClient, auth_headers: dict, test_tree: dict + ): + """Test that export omits scratchpad section when empty.""" + create_response = await client.post( + "/api/v1/sessions", + json={"tree_id": test_tree["id"]}, + headers=auth_headers + ) + session_id = create_response.json()["id"] + + # Export without setting scratchpad + response = await client.post( + f"/api/v1/sessions/{session_id}/export", + json={"format": "markdown"}, + headers=auth_headers + ) + + assert response.status_code == 200 + content = response.text + assert "Evidence / Reference" not in content + + @pytest.mark.asyncio + async def test_export_excludes_whitespace_only_scratchpad( + self, client: AsyncClient, auth_headers: dict, test_tree: dict + ): + """Test that export omits scratchpad section when only whitespace.""" + create_response = await client.post( + "/api/v1/sessions", + json={"tree_id": test_tree["id"]}, + headers=auth_headers + ) + session_id = create_response.json()["id"] + + await client.patch( + f"/api/v1/sessions/{session_id}/scratchpad", + json={"scratchpad": " \n \n "}, + headers=auth_headers + ) + + response = await client.post( + f"/api/v1/sessions/{session_id}/export", + json={"format": "markdown"}, + headers=auth_headers + ) + + assert response.status_code == 200 + content = response.text + assert "Evidence / Reference" not in content