diff --git a/backend/internal/web/handlers_events_test.go b/backend/internal/web/handlers_events_test.go
index 511aac5..1aed581 100644
--- a/backend/internal/web/handlers_events_test.go
+++ b/backend/internal/web/handlers_events_test.go
@@ -632,3 +632,133 @@ func TestListUserEventsRangeReturnsOnlyOwnedTablos(t *testing.T) {
t.Fatalf("row tablo color = %#v; want #123abc", row.TabloColor)
}
}
+
+func TestTabloDetailEventsTabFullPageFallback(t *testing.T) {
+ pool, cleanup := setupTestDB(t)
+ defer cleanup()
+
+ ctx := context.Background()
+ q := sqlc.New(pool)
+ store := auth.NewStore(q)
+ router := newEventTestRouter(q, store)
+
+ user := preInsertUser(t, ctx, q, "eventfullpage@example.com", "correct-horse-12")
+ tablo := insertEventTestTablo(t, ctx, q, user, "Events Full Page Tablo")
+ sessionCookie := sessionCookieForUser(t, ctx, store, user)
+
+ req := httptest.NewRequest(http.MethodGet, "/tablos/"+tablo.ID.String()+"/events?month=2026-05", nil)
+ req.AddCookie(sessionCookie)
+ rec := httptest.NewRecorder()
+ router.ServeHTTP(rec, req)
+
+ if rec.Code != http.StatusOK {
+ t.Fatalf("GET full events page status = %d; want 200; body: %.500s", rec.Code, rec.Body.String())
+ }
+ body := rec.Body.String()
+ for _, want := range []string{"Back to tablos", "Overview", "Tasks", "Files", "Events", "May 2026", `id="tab-content"`} {
+ if !strings.Contains(body, want) {
+ t.Errorf("full events page missing %q; body: %.800s", want, body)
+ }
+ }
+}
+
+func TestEventContentIsEscaped(t *testing.T) {
+ pool, cleanup := setupTestDB(t)
+ defer cleanup()
+
+ ctx := context.Background()
+ q := sqlc.New(pool)
+ store := auth.NewStore(q)
+ router := newEventTestRouter(q, store)
+
+ user := preInsertUser(t, ctx, q, "eventescaped@example.com", "correct-horse-12")
+ tablo := insertEventTestTablo(t, ctx, q, user, "Escaped Event Tablo")
+ insertEventTestEvent(t, ctx, q, tablo.ID, "", "2026-05-20", "09:30", "", "", "")
+ sessionCookie := sessionCookieForUser(t, ctx, store, user)
+
+ req := httptest.NewRequest(http.MethodGet, "/tablos/"+tablo.ID.String()+"/events?month=2026-05", nil)
+ req.Header.Set("HX-Request", "true")
+ req.AddCookie(sessionCookie)
+ rec := httptest.NewRecorder()
+ router.ServeHTTP(rec, req)
+
+ if rec.Code != http.StatusOK {
+ t.Fatalf("GET escaped event status = %d; want 200; body: %.500s", rec.Code, rec.Body.String())
+ }
+ body := rec.Body.String()
+ if strings.Contains(body, "") {
+ t.Fatalf("event title rendered as raw script; body: %.800s", body)
+ }
+ if !strings.Contains(body, "<script>alert(1)</script>") {
+ t.Fatalf("escaped event title missing; body: %.800s", body)
+ }
+}
+
+func TestEventWithoutEndTimeDisplaysOnlyStartTime(t *testing.T) {
+ pool, cleanup := setupTestDB(t)
+ defer cleanup()
+
+ ctx := context.Background()
+ q := sqlc.New(pool)
+ store := auth.NewStore(q)
+ router := newEventTestRouter(q, store)
+
+ user := preInsertUser(t, ctx, q, "eventnoend@example.com", "correct-horse-12")
+ tablo := insertEventTestTablo(t, ctx, q, user, "No End Event Tablo")
+ event := insertEventTestEvent(t, ctx, q, tablo.ID, "Open End", "2026-05-20", "09:30", "", "", "")
+ sessionCookie := sessionCookieForUser(t, ctx, store, user)
+
+ req := httptest.NewRequest(http.MethodGet, "/tablos/"+tablo.ID.String()+"/events/"+event.ID.String()+"/edit?month=2026-05", nil)
+ req.Header.Set("HX-Request", "true")
+ req.AddCookie(sessionCookie)
+ rec := httptest.NewRecorder()
+ router.ServeHTTP(rec, req)
+
+ if rec.Code != http.StatusOK {
+ t.Fatalf("GET event edit status = %d; want 200; body: %.500s", rec.Code, rec.Body.String())
+ }
+ body := rec.Body.String()
+ if !strings.Contains(body, `name="start_time" value="09:30"`) {
+ t.Fatalf("start time missing for event without end time; body: %.800s", body)
+ }
+ if strings.Contains(body, "09:30-") || strings.Contains(body, "09:30 –") {
+ t.Fatalf("event without end time rendered an inferred range; body: %.800s", body)
+ }
+}
+
+func TestEventsOrderedByStartTimeThenTitle(t *testing.T) {
+ pool, cleanup := setupTestDB(t)
+ defer cleanup()
+
+ ctx := context.Background()
+ q := sqlc.New(pool)
+ store := auth.NewStore(q)
+ router := newEventTestRouter(q, store)
+
+ user := preInsertUser(t, ctx, q, "eventorder@example.com", "correct-horse-12")
+ tablo := insertEventTestTablo(t, ctx, q, user, "Ordered Events Tablo")
+ insertEventTestEvent(t, ctx, q, tablo.ID, "Beta", "2026-05-20", "10:00", "", "", "")
+ insertEventTestEvent(t, ctx, q, tablo.ID, "Gamma", "2026-05-20", "09:00", "", "", "")
+ insertEventTestEvent(t, ctx, q, tablo.ID, "Alpha", "2026-05-20", "09:00", "", "", "")
+ sessionCookie := sessionCookieForUser(t, ctx, store, user)
+
+ req := httptest.NewRequest(http.MethodGet, "/tablos/"+tablo.ID.String()+"/events?month=2026-05", nil)
+ req.Header.Set("HX-Request", "true")
+ req.AddCookie(sessionCookie)
+ rec := httptest.NewRecorder()
+ router.ServeHTTP(rec, req)
+
+ if rec.Code != http.StatusOK {
+ t.Fatalf("GET ordered events status = %d; want 200; body: %.500s", rec.Code, rec.Body.String())
+ }
+ body := rec.Body.String()
+ alpha := strings.Index(body, "Alpha")
+ gamma := strings.Index(body, "Gamma")
+ beta := strings.Index(body, "Beta")
+ if alpha == -1 || gamma == -1 || beta == -1 {
+ t.Fatalf("ordered event titles missing; Alpha=%d Gamma=%d Beta=%d body: %.800s", alpha, gamma, beta, body)
+ }
+ if !(alpha < gamma && gamma < beta) {
+ t.Fatalf("events not ordered by start time then title; Alpha=%d Gamma=%d Beta=%d body: %.800s", alpha, gamma, beta, body)
+ }
+}