The frontend toggle on /projects/{id} Fristen + Termine emitted
`&direct_only=true`, but `handleListEvents` and `handleEventsSummary`
never read the param, so EventListFilter / EventSummaryFilter went out
without DirectOnly and the backend always returned the subtree-aggregated
default (per t-paliad-139). The toggle has been silently dead since the
Fristen/Termine surfaces migrated to /api/events in t-paliad-139.
Backend-only fix, symmetric across endpoints:
- ListFilter (deadlines), AppointmentListFilter, EventListFilter,
EventSummaryFilter all gain DirectOnly bool.
- When ProjectID != nil && DirectOnly, the SQL predicate swaps from
projectDescendantPredicate("p") to a direct `<alias>.project_id = :project_id`
scope on each rail (deadline list, appointment list, deadline+appointment
bucket counts).
- Handlers parse `direct_only` via the existing parseDirectOnly helper.
- Test extends project_filter_descendants_test.go with three DirectOnly=true
assertions (events, deadlines, appointments) — each must collapse to the
one direct seed row.
DirectOnly is a no-op when ProjectID is nil or PersonalOnly is set —
PersonalOnly already nullifies ProjectID.
Verlauf is untouched: it still uses /api/projects/{id}/events, which
already wired direct_only via projects.go:512.