States

Loading and placeholder card components for the card stack viewport. Empty-state rendering is provided by cjm-fasthtml-app-core’s render_empty_state (V8 anatomy composition helper) — card-stack consumers wire that helper into their viewport’s empty callback rather than card-stack shipping a local empty-state default.

render_placeholder_card

Rendered in viewport slots that fall outside the items list (e.g., before the first item or after the last). The placeholder card fills the geometric slot so the focus position stays centered and the visible-card-count indicator remains accurate.

The label text ("Beginning" / "End") is hidden by default via visibility.invisible — this preserves the placeholder’s height contract (text dimensions still drive layout) while removing the visual text noise that competes with content cards. Set show_label=True to restore the visible label for debugging or alternate consumer contexts. Padding matches non-placeholder content cards (p(3)) so the placeholder’s vertical footprint is consistent with single-line content cards — this keeps the slot-count indicator faithful.


render_placeholder_card


def render_placeholder_card(
    placeholder_type:Literal, # Which edge of the list
    show_label:bool=False, # Render the "Beginning"/"End" label visibly. Default hides via visibility.invisible (preserves height contract).
)->Any: # Placeholder card component

Render a placeholder card for viewport edges (fills geometric slot so focus position stays centered).

# Test render_placeholder_card
from fasthtml.common import to_xml

# Default (show_label=False): label text is in the DOM but hidden via
# visibility.invisible — preserves height contract while removing visible
# "Beginning"/"End" text noise from the card stack viewport.
start_card = render_placeholder_card("start")
html = to_xml(start_card)
assert 'data-placeholder-type="start"' in html
assert 'placeholder-card' in html
# Label text still in DOM (height contract preserved)
assert 'Beginning' in html
# Visibility class applied (text invisible)
assert 'invisible' in html

# End variant — same shape, different text content.
end_card = render_placeholder_card("end")
end_html = to_xml(end_card)
assert 'data-placeholder-type="end"' in end_html
assert 'End' in end_html
assert 'invisible' in end_html

# show_label=True restores visible label (no `invisible` class).
labeled = render_placeholder_card("start", show_label=True)
labeled_html = to_xml(labeled)
assert 'Beginning' in labeled_html
assert 'invisible' not in labeled_html, (
    "show_label=True must produce a P tag without visibility.invisible. "
    f"Got: {labeled_html!r}"
)

# Padding standardized to p(3) — matches non-placeholder content cards
# so the placeholder's vertical footprint is consistent.
assert 'p-3' in html
# Regression guard: the historical p(4) padding must not return.
assert 'p-4' not in html, (
    "Placeholder card body padding must be p(3) (matching non-placeholder cards), "
    "not p(4). A regression to p(4) would distort the slot-count indicator at "
    "every card-stack consumer site."
)

print("render_placeholder_card tests passed!")
render_placeholder_card tests passed!

render_loading_state

Displayed while the card stack is initializing (e.g., fetching items from a service).


render_loading_state


def render_loading_state(
    ids:CardStackHtmlIds, # HTML IDs for this card stack instance
    message:str='Loading...', # Loading message text
)->Any: # Loading component

Render loading state with spinner and message.

# Test render_loading_state
ids = CardStackHtmlIds(prefix="cs0")
loading_el = render_loading_state(ids)
html = to_xml(loading_el)
assert 'id="cs0-loading"' in html
assert "Loading..." in html

# Test custom message
loading_el = render_loading_state(ids, message="Initializing segments...")
html = to_xml(loading_el)
assert "Initializing segments..." in html
print("render_loading_state tests passed!")
render_loading_state tests passed!