4. Selection & a detail pane¶
Goal: click a row, see its detail — selection highlighted, all client-side.
A selection signal¶
search: Local[str] = ""
selected_id: Local[int] = 0 # which row's detail is open (0 = none)
filter: Url[str] = "all"
issues: Server[object]
Make each row clickable and mark the selected one:
<li data-id="{{ t.id }}" data-title="{{ t.title }}"
:show="search.lower() in t.title.lower()"
:class="selected: selected_id == t.id">
<button @click="selected_id = t.id">{{ t.title }}</button>
<span class="status {{ t.status }}">{{ t.status }}</span>
</li>
and add the detail pane below the list, still inside the region:
<div class="detail">
{% for t in issues %}
<div data-id="{{ t.id }}" :show="selected_id == t.id">
<h2>{{ t.title }}</h2>
<p>#{{ t.id }} · {{ t.status }}</p>
</div>
{% endfor %}
</div>
Three compiled pieces:
@click="selected_id = t.id"— an event action assigning aLocalfrom the row's projected id. Click; zero network.:class="selected: selected_id == t.id"— toggles one class on a condition; the highlight follows the selection.:show="selected_id == t.id"on each detail block — only the selected issue's detail is visible.
Why == just works¶
t.id comes from a data-* attribute, so it arrives as a string; selected_id is an int signal. The compiler knows this and normalizes numeric comparisons explicitly — both sides are wrapped in Number() before a strict === — so selected_id == t.id behaves the way the Python reads. You never debug "2" !== 2.
The render-all pattern and its ceiling
This detail pane server-renders every issue's detail and shows one — fine here, where a detail is two lines. For rows with heavy payloads (message bodies, documents) this pattern ships everything to be hidden, and the right move is to load the open item's body on demand through a server read. The mail demo does exactly that, and the unified idiom turns the mistake into a compile error.
Restart: click rows, watch the highlight and the pane follow, and confirm in the network tab that none of it touches the server.
Next: Part 5 — commands, where the database changes.