Skip to content

4. Selection & a detail pane

Goal: click a row, see its detail — selection highlighted, all client-side.

A selection signal

components/tracker.py (the new line)
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:

components/tracker.html (the rows, updated)
<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:

components/tracker.html (after </ul>)
<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 a Local from 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.