Remember a caller

A how-to guide: keep something for one caller across the pages they visit — here, a bookmark on each page they mark, listed back on a page of their own.

from collections.abc import Sequence

from sextile import (
    MenuItem, Page, PageAddress, PageRequest, PageRouter, Sextile, Shortcut,
    StateKey, menu_page, notice_page,
)

BOOKMARKS = StateKey[list[PageAddress]]("bookmarks")

_ITEMS = {1: "Weather", 2: "News", 3: "Sport"}

router = PageRouter()


@router.page("1{n:int}", name="item", title="Page")
async def item(request: PageRequest, n: int) -> Page | None:
    name = _ITEMS.get(n)
    if name is None:
        return None
    keep = request.app.address_for("keep", n=n)
    return notice_page(
        request, name, title=name,
        shortcuts=[Shortcut(key="B", destination=keep, label="bookmark")],
    )


@router.page("2{n:int}", name="keep")
async def keep(request: PageRequest, n: int) -> Page:
    marked = request.session.get(BOOKMARKS) or []
    here = request.app.address_for("item", n=n)
    if here not in marked:
        request.session[BOOKMARKS] = [*marked, here]
    return notice_page(request, "Bookmarked.", shortcuts=[Shortcut("9", request.app.address_for("bookmarks"), label="bookmarks")])


@router.page("9", name="bookmarks", title="Your bookmarks", keywords=("MARKS",))
async def bookmarks(request: PageRequest) -> Page:
    marked: Sequence[PageAddress] = request.session.get(BOOKMARKS) or []
    items = [MenuItem(_ITEMS[int(address.digits[1:])], "", address) for address in marked]
    return menu_page(request, items=items, empty=["No bookmarks yet.", "", "Press B on a page to keep it."])


app = Sextile(name="Bookmarks", pages=[*router])
 Weather                             11a
  
Weather                                 
                                        
                                        
                                        
                                        
                                        
                                        
                                        
                                        
                                        
                                        
                                        
                                        
                                        
                                        
                                        
                                        
                                        
                                        
                                        
  
 B bookmark, 0 index                    

Shortcut(key="B", destination=...) offers B on every frame of the item page, leading to the keep page for that item. keep’s handler appends the item’s address to request.session[BOOKMARKS] — the caller’s own store, read with .get(BOOKMARKS) or [] for the first visit — so the next page sees the mark.

Pressing B on two pages and then keying *9# lists them:

 YOUR BOOKMARKS                       9a
  
 1  Weather                             
                                        
 2  News                                
                                        
                                        
                                        
                                        
                                        
                                        
                                        
                                        
                                        
                                        
                                        
                                        
                                        
                                        
                                        
                                        
                                        
  
 1-2 select, 0 index                    

Why the bookmarks are the caller’s and not the service’s: request.session is one caller’s own store, request.state the shared one the lifespan opened. The session is the connection, so the bookmarks last exactly as long as the line is up and are gone when the caller rings off — nothing is kept between calls, because a viewdata terminal keeps nothing but the frame on screen. That is the Viewdata model, not a limitation of the framework; Why Sextile says why the state lives at the server and dies with the line. To keep something past ring-off, write it to a service database from request.state instead — the readership log in The visits log is the pattern.