feat: Session Scratchpad sidebar for tree navigation #27

Merged
chihlasm merged 8 commits from feat/session-scratchpad into main 2026-02-04 08:13:51 +00:00
3 changed files with 64 additions and 2 deletions
Showing only changes of commit 7d0000827b - Show all commits

View File

@@ -1,7 +1,8 @@
import uuid
from datetime import datetime, timezone
from typing import Optional, Any
from sqlalchemy import String, DateTime, ForeignKey, Boolean
from sqlalchemy import String, DateTime, ForeignKey, Boolean, Text
import sqlalchemy as sa
from sqlalchemy.orm import Mapped, mapped_column, relationship
from sqlalchemy.dialects.postgresql import UUID, JSONB
from app.core.database import Base
@@ -44,6 +45,9 @@ class Session(Base):
ticket_number: Mapped[Optional[str]] = mapped_column(String(100), nullable=True)
client_name: Mapped[Optional[str]] = mapped_column(String(255), nullable=True)
exported: Mapped[bool] = mapped_column(Boolean, default=False)
scratchpad: Mapped[Optional[str]] = mapped_column(
Text, nullable=True, server_default=sa.text("''")
)
# Relationships
tree: Mapped["Tree"] = relationship("Tree", back_populates="sessions")

View File

@@ -1,7 +1,7 @@
from datetime import datetime
from typing import Optional, Any
from uuid import UUID
from pydantic import BaseModel, Field
from pydantic import BaseModel, Field, validator
class DecisionRecord(BaseModel):
@@ -27,6 +27,7 @@ class SessionUpdate(BaseModel):
custom_steps: Optional[list[dict[str, Any]]] = None
ticket_number: Optional[str] = Field(None, max_length=100)
client_name: Optional[str] = Field(None, max_length=255)
scratchpad: Optional[str] = None
class SessionResponse(BaseModel):
@@ -42,6 +43,11 @@ class SessionResponse(BaseModel):
ticket_number: Optional[str] = None
client_name: Optional[str] = None
exported: bool
scratchpad: str = ""
@validator('scratchpad', pre=True, always=True)
def normalize_scratchpad(cls, v):
return v or ""
class Config:
from_attributes = True
@@ -51,3 +57,7 @@ class SessionExport(BaseModel):
format: str = Field(default="markdown", pattern="^(text|markdown|html)$")
include_timestamps: bool = True
include_tree_info: bool = True
class ScratchpadUpdate(BaseModel):
scratchpad: str

View File

@@ -315,3 +315,51 @@ class TestSessions:
data = response.json()
assert len(data) >= 1
assert all(s["completed_at"] is None for s in data)
@pytest.mark.asyncio
async def test_create_session_has_scratchpad(
self, client: AsyncClient, auth_headers: dict, test_tree: dict
):
"""Test that new sessions include scratchpad field."""
session_data = {
"tree_id": test_tree["id"],
}
response = await client.post(
"/api/v1/sessions",
json=session_data,
headers=auth_headers
)
assert response.status_code == 201
data = response.json()
assert "scratchpad" in data
assert data["scratchpad"] == ""
@pytest.mark.asyncio
async def test_update_scratchpad_via_put(
self, client: AsyncClient, auth_headers: dict, test_tree: dict
):
"""Test updating scratchpad through the existing PUT endpoint."""
# Create session
create_response = await client.post(
"/api/v1/sessions",
json={"tree_id": test_tree["id"]},
headers=auth_headers
)
session_id = create_response.json()["id"]
# Update scratchpad via PUT
update_data = {
"scratchpad": "- Server IP: 192.168.1.50\n- Error: 0x80070005"
}
response = await client.put(
f"/api/v1/sessions/{session_id}",
json=update_data,
headers=auth_headers
)
assert response.status_code == 200
data = response.json()
assert data["scratchpad"] == update_data["scratchpad"]