[t-008] Create page.py models — Page structure + FeedComponent union #8

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

Goal

Define the Strawberry GraphQL types for pages, headers, footers, and the FeedComponent union.

References

Reference

  • DESIGN.md §4 GraphQL Schema (Page, Header, EmptyState, Footer types)
  • AGENT.md §9 GraphQL Schema Rules

Implementation Steps

Implementation Steps

1. Create src/models/page.py

Define page structure types:

# src/models/page.py
import strawberry
from typing import Optional

@strawberry.enum
class LinkActionStyle:
    NORMAL = "normal"
    DESTRUCTIVE = "destructive"

@strawberry.type
class LinkAction:
    type: str  # tap, swipe_left, swipe_right
    label: str
    style: LinkActionStyle
    link: str  # keryx:// URL

@strawberry.type
class Header:
    title: Optional[str] = None
    subtitle: Optional[str] = None
    actions: list[LinkAction] = strawberry.field(default_factory=list)

@strawberry.type
class EmptyState:
    icon: str
    title: str
    subtitle: Optional[str] = None
    action: Optional[LinkAction] = None

@strawberry.type
class Footer:
    # Will be populated when compose_bar model is created (later task)
    pass

2. Create src/models/components.py — component models (SessionCard first, others added later)

Start with SessionCard and the FeedComponent union:

# src/models/components.py
import strawberry
from typing import Optional
from src.models.page import LinkAction

@strawberry.enum
class MessageRole:
    USER = "user"
    ASSISTANT = "assistant"

@strawberry.type
class SessionCard:
    id: str
    title: Optional[str] = None
    model_badge: Optional[str] = None
    message_count: int
    timestamp: str  # relative time string (e.g. '2m ago')
    preview: Optional[str] = None
    actions: list[LinkAction]

@strawberry.type
class MessageBubble:
    id: str
    role: MessageRole
    text: Optional[str] = None
    streaming: bool
    timestamp: Optional[float] = None

@strawberry.type
class ThinkingBlock:
    id: str
    text: str
    collapsed_by_default: bool = True

@strawberry.type
class ToolCallCard:
    id: str
    tool: str
    command: Optional[str] = None
    status: str  # running, completed, failed
    output: Optional[str] = None
    streaming: bool = False

@strawberry.type
class CodeBlock:
    id: str
    language: Optional[str] = None
    code: str
    streaming: bool = False

@strawberry.type
class Divider:
    id: str
    label: Optional[str] = None

# FeedComponent union — all component types
FeedComponent = strawberry.union(
    "FeedComponent",
    [SessionCard, MessageBubble, ThinkingBlock, ToolCallCard, CodeBlock, Divider],
)

Verification Gate

Verification Gate

Run:

cd /home/david/Projects/keryx-bff && uv run python -c "
from src.models.components import SessionCard, FeedComponent, MessageRole
sc = SessionCard(id='s1', title='Test', message_count=3, timestamp='2m ago', actions=[])
assert sc.title == 'Test'
assert sc.message_count == 3
print('OK')"
# Expected output: OK

And verify the union is defined:

cd /home/david/Projects/keryx-bff && uv run python -c "
from src.models.components import FeedComponent
assert FeedComponent is not None
print('FeedComponent union defined')"
# Expected output: FeedComponent union defined

STOP Conditions

STOP Conditions

  • If the strawberry.union call fails, stop and check Strawberry version compatibility.

Depends on: t-005

## Goal Define the Strawberry GraphQL types for pages, headers, footers, and the FeedComponent union. ## References ## Reference - DESIGN.md §4 GraphQL Schema (Page, Header, EmptyState, Footer types) - AGENT.md §9 GraphQL Schema Rules ## Implementation Steps ## Implementation Steps ### 1. Create `src/models/page.py` Define page structure types: ```python # src/models/page.py import strawberry from typing import Optional @strawberry.enum class LinkActionStyle: NORMAL = "normal" DESTRUCTIVE = "destructive" @strawberry.type class LinkAction: type: str # tap, swipe_left, swipe_right label: str style: LinkActionStyle link: str # keryx:// URL @strawberry.type class Header: title: Optional[str] = None subtitle: Optional[str] = None actions: list[LinkAction] = strawberry.field(default_factory=list) @strawberry.type class EmptyState: icon: str title: str subtitle: Optional[str] = None action: Optional[LinkAction] = None @strawberry.type class Footer: # Will be populated when compose_bar model is created (later task) pass ``` ### 2. Create `src/models/components.py` — component models (SessionCard first, others added later) Start with SessionCard and the FeedComponent union: ```python # src/models/components.py import strawberry from typing import Optional from src.models.page import LinkAction @strawberry.enum class MessageRole: USER = "user" ASSISTANT = "assistant" @strawberry.type class SessionCard: id: str title: Optional[str] = None model_badge: Optional[str] = None message_count: int timestamp: str # relative time string (e.g. '2m ago') preview: Optional[str] = None actions: list[LinkAction] @strawberry.type class MessageBubble: id: str role: MessageRole text: Optional[str] = None streaming: bool timestamp: Optional[float] = None @strawberry.type class ThinkingBlock: id: str text: str collapsed_by_default: bool = True @strawberry.type class ToolCallCard: id: str tool: str command: Optional[str] = None status: str # running, completed, failed output: Optional[str] = None streaming: bool = False @strawberry.type class CodeBlock: id: str language: Optional[str] = None code: str streaming: bool = False @strawberry.type class Divider: id: str label: Optional[str] = None # FeedComponent union — all component types FeedComponent = strawberry.union( "FeedComponent", [SessionCard, MessageBubble, ThinkingBlock, ToolCallCard, CodeBlock, Divider], ) ``` ## Verification Gate ## Verification Gate Run: ```bash cd /home/david/Projects/keryx-bff && uv run python -c " from src.models.components import SessionCard, FeedComponent, MessageRole sc = SessionCard(id='s1', title='Test', message_count=3, timestamp='2m ago', actions=[]) assert sc.title == 'Test' assert sc.message_count == 3 print('OK')" # Expected output: OK ``` And verify the union is defined: ```bash cd /home/david/Projects/keryx-bff && uv run python -c " from src.models.components import FeedComponent assert FeedComponent is not None print('FeedComponent union defined')" # Expected output: FeedComponent union defined ``` ## STOP Conditions ## STOP Conditions - If the strawberry.union call fails, stop and check Strawberry version compatibility. Depends on: t-005
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#8
No description provided.