[t-009] Create page_builder.py — build sessions page from Hermes dat #9

Open
opened 2026-07-05 08:30:59 +00:00 by david · 0 comments
Owner

Goal

Implement the page builder that transforms Hermes session data into SessionCard components forming a complete page.

References

Reference

  • DESIGN.md §4 BFF services (page_builder)
  • AGENT.md §6 Common Patterns (Page building pattern)

Implementation Steps

Implementation Steps

1. Create src/services/page_builder.py

Create the page builder:

# src/services/page_builder.py
from datetime import datetime, timezone
from src.models.page import Header, EmptyState, LinkAction
from src.models.components import SessionCard

def _relative_time(dt: datetime) -> str:
    now = datetime.now(timezone.utc)
    diff = now - dt
    seconds = int(diff.total_seconds())
    if seconds < 60:
        return "Just now"
    minutes = seconds // 60
    if minutes < 60:
        return f"{minutes}m ago"
    hours = minutes // 60
    if hours < 24:
        return f"{hours}h ago"
    days = hours // 24
    if days == 1:
        return "Yesterday"
    if days < 7:
        return f"{days}d ago"
    weeks = days // 7
    return f"{weeks}w ago"

def build_sessions_page(sessions: list[dict]) -> dict:
    header = Header(
        title="Conversations",
        subtitle=f"{len(sessions)} total",
        actions=[LinkAction(type="tap", label="New Chat", style="normal", link="keryx://new-chat")],
    )
    items = []
    for session in sessions:
        card = SessionCard(
            id=session.get("id"),
            title=session.get("title"),
            model_badge=session.get("model_name"),
            message_count=session.get("message_count", 0),
            timestamp=_relative_time(datetime.fromtimestamp(session.get("created_at", 0))),
            preview=session.get("preview"),
            actions=[
                LinkAction(type="tap", label="Open", style="normal", link=f"keryx://chat/{session['id']}"),
                LinkAction(type="swipe_left", label="Remove", style="destructive", link=f"keryx://delete/{session['id']}"),
            ],
        )
        items.append(card)
    empty_state = None
    if not items:
        empty_state = EmptyState(
            icon="tray",
            title="No conversations yet",
            subtitle="Start a new chat to begin",
            action=LinkAction(type="tap", label="Start Chat", style="normal", link="keryx://new-chat"),
        )
    return {
        "page": {
            "id": "sessions",
            "header": header,
            "empty_state": empty_state,
            "items": items,
            "footer": None,
        }
    }

Verification Gate

Verification Gate

Run:

cd /home/david/Projects/keryx-bff && uv run python -c "
from src.services.page_builder import build_sessions_page
page = build_sessions_page([])
assert page['page']['id'] == 'sessions'
assert page['page']['header'].title == 'Conversations'
assert page['page']['empty_state'].title == 'No conversations yet'
print('OK')"
# Expected output: OK

And with sample data:

cd /home/david/Projects/keryx-bff && uv run python -c "
from src.services.page_builder import build_sessions_page
sample = [{'id': 'abc123', 'title': 'Test Chat', 'message_count': 5, 'created_at': 0}]
page = build_sessions_page(sample)
assert len(page['page']['items']) == 1
card = page['page']['items'][0]
assert card.id == 'abc123'
assert card.title == 'Test Chat'
print('OK')"
# Expected output: OK

STOP Conditions

STOP Conditions

  • If the empty state is not generated for zero sessions, stop and fix.

Depends on: t-008

## Goal Implement the page builder that transforms Hermes session data into SessionCard components forming a complete page. ## References ## Reference - DESIGN.md §4 BFF services (page_builder) - AGENT.md §6 Common Patterns (Page building pattern) ## Implementation Steps ## Implementation Steps ### 1. Create `src/services/page_builder.py` Create the page builder: ```python # src/services/page_builder.py from datetime import datetime, timezone from src.models.page import Header, EmptyState, LinkAction from src.models.components import SessionCard def _relative_time(dt: datetime) -> str: now = datetime.now(timezone.utc) diff = now - dt seconds = int(diff.total_seconds()) if seconds < 60: return "Just now" minutes = seconds // 60 if minutes < 60: return f"{minutes}m ago" hours = minutes // 60 if hours < 24: return f"{hours}h ago" days = hours // 24 if days == 1: return "Yesterday" if days < 7: return f"{days}d ago" weeks = days // 7 return f"{weeks}w ago" def build_sessions_page(sessions: list[dict]) -> dict: header = Header( title="Conversations", subtitle=f"{len(sessions)} total", actions=[LinkAction(type="tap", label="New Chat", style="normal", link="keryx://new-chat")], ) items = [] for session in sessions: card = SessionCard( id=session.get("id"), title=session.get("title"), model_badge=session.get("model_name"), message_count=session.get("message_count", 0), timestamp=_relative_time(datetime.fromtimestamp(session.get("created_at", 0))), preview=session.get("preview"), actions=[ LinkAction(type="tap", label="Open", style="normal", link=f"keryx://chat/{session['id']}"), LinkAction(type="swipe_left", label="Remove", style="destructive", link=f"keryx://delete/{session['id']}"), ], ) items.append(card) empty_state = None if not items: empty_state = EmptyState( icon="tray", title="No conversations yet", subtitle="Start a new chat to begin", action=LinkAction(type="tap", label="Start Chat", style="normal", link="keryx://new-chat"), ) return { "page": { "id": "sessions", "header": header, "empty_state": empty_state, "items": items, "footer": None, } } ``` ## Verification Gate ## Verification Gate Run: ```bash cd /home/david/Projects/keryx-bff && uv run python -c " from src.services.page_builder import build_sessions_page page = build_sessions_page([]) assert page['page']['id'] == 'sessions' assert page['page']['header'].title == 'Conversations' assert page['page']['empty_state'].title == 'No conversations yet' print('OK')" # Expected output: OK ``` And with sample data: ```bash cd /home/david/Projects/keryx-bff && uv run python -c " from src.services.page_builder import build_sessions_page sample = [{'id': 'abc123', 'title': 'Test Chat', 'message_count': 5, 'created_at': 0}] page = build_sessions_page(sample) assert len(page['page']['items']) == 1 card = page['page']['items'][0] assert card.id == 'abc123' assert card.title == 'Test Chat' print('OK')" # Expected output: OK ``` ## STOP Conditions ## STOP Conditions - If the empty state is not generated for zero sessions, stop and fix. Depends on: t-008
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: david/keryx-bff#9
No description provided.