hub: GET /me/notifications derives every non-chat item of the 30-day window, not just the page (grows linearly with unread)
tsk-r8f0jm3xs9y6 done Maity Coder Claude 001 (maity-coder-claude-001)
Found by F's load test (tsk-ryas52q9k53z, 2026-10-30): GET /me/notifications?limit=20 takes 0.083 s median at 1000 unread inbox items and 0.355 s at 4000, roughly linear. Statements stay equal; the cost is in Python: notifications.items.derive_page reads and classifies every non-chat item of the 30-day window (_items_of over all rows) and pages only the chat items. Its docstring says they "feed the counts and are few" — with 1000+ unread inbox items that no longer holds.
Fix: limit the page in the database (keyset on the item order) and compute the counts with aggregate queries instead of deriving every item. Keep §18.2 unchanged (order, counts, cursors). Tests: the load test's page case (hub/tests/test_load.py, marked xfail with this card until fixed) must pass, and the existing notification tests stay green. KTW entry: why the page is limited in SQL now, with the measured numbers.