feat: add event detail slide-over, fix toasts, and checkbox selection

Three major features implemented:

1. Event Detail Slide-Over Panel
   - Click timeline events to view details in slide-over
   - New /events/{event_id} route and event_detail.py template
   - Type-specific payload rendering for all event types

2. Toast System Refactor
   - Switch from custom addEventListener to FastHTML's add_toast()
   - Replace HX-Trigger headers with session-based toasts
   - Add event links in toast messages
   - Replace addEventListener with hx_on_* in templates

3. Checkbox Selection for Animal Subsets
   - New animal_select.py component with checkbox list
   - New /api/compute-hash and /api/selection-preview endpoints
   - Add subset_mode support to SelectionContext validation
   - Update 5 forms: outcome, move, tag-add, tag-end, attrs
   - Users can select specific animals from filtered results

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-01 19:10:57 +00:00
parent 25a91c3322
commit 3937d675ba
19 changed files with 1420 additions and 360 deletions

View File

@@ -198,7 +198,7 @@ class TestMoveAnimalSuccess:
location_strip2_id,
ducks_at_strip1,
):
"""Successful move returns HX-Trigger with toast."""
"""Successful move returns session cookie with toast."""
ts_utc = int(time.time() * 1000)
filter_str = 'location:"Strip 1"'
filter_ast = parse_filter(filter_str)
@@ -219,8 +219,16 @@ class TestMoveAnimalSuccess:
)
assert resp.status_code == 200
assert "HX-Trigger" in resp.headers
assert "showToast" in resp.headers["HX-Trigger"]
assert "set-cookie" in resp.headers
session_cookie = resp.headers["set-cookie"]
assert "session_=" in session_cookie
# Base64 decode contains toast message
import base64
cookie_value = session_cookie.split("session_=")[1].split(";")[0]
base64_data = cookie_value.split(".")[0]
decoded = base64.b64decode(base64_data).decode()
assert "Moved 5 animals to Strip 2" in decoded
def test_move_success_resets_form(
self,