Commit Graph

3 Commits

Author SHA1 Message Date
Simon
e8eac70f4d Write a check into the list instead of rebuilding it
A check asks each saved search in turn and writes back what it found, and
the list was built again from scratch on every one of those writes. So a
background check -- which has no business being visible at all beyond a
new count and a new time -- replayed the whole list's entry animation,
threw away the reader's scroll position, and took the pointer out of
whatever row it was over, several times over.

Rows are now matched by id and told what changed: the count, the name, the
line underneath, nothing else. A row is only built when it is new, gone, or
changes shape, and the entry animation belongs to a row that has arrived
rather than to every row every time. One mid-rename or mid-confirm is left
alone entirely, so a check can no longer type over the reader.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MPZFnNdHbPGDTqQUNiE4ZN
2026-10-20 07:33:27 +00:00
Simon
d5c859a1c7 Count only the part of a page the reader was shown
`perPage` is a request, not a promise: a source whose own pages are 30
long answers a page of 12 with 30. The grid showed the reader the page it
asked for, the mark kept 24 of those ids, and the daily check then counted
every probe id the mark did not contain -- including ids 25 to 30 of the
same page, which were never new and no amount of looking took off the
badge. A saved search on sxyprn sat on "6" through every reload.

So the count now goes only as far down the page as the mark reaches, and
the mark holds enough ids (60) for a page larger than the one asked for.
Order does not come into it, which keeps it right for sources that shift a
listing between visits.

And the whole list can be marked read in one press, for a reader who sees
the badge and decides none of it is for them. That moves each mark on to
what the source lists now rather than zeroing the count, which would come
straight back on the next check.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MPZFnNdHbPGDTqQUNiE4ZN
2026-10-19 20:15:17 +00:00
Simon
bc7c0047b7 Keep a whole search, and say what is new in it
A search here is more than the words in the box: it is the source, the
channel, every filter that channel offers, and the query together. The
bookmark in the search bar keeps all of it, the one in the header opens
the list, and tapping a row puts the whole thing back.

Each saved search remembers the ids of the newest videos it showed the
last time it was opened. Once a day -- at startup, when the tab is looked
at again, and on a slow heartbeat for tabs left open -- the client asks
each one's first page again and counts how many of those videos it has
never shown. That count is the badge, on the row and summed in the
header, and it stops at 9+. Opening a saved search is viewing it, so the
count goes out and the mark moves to what is on screen now.

Nothing new leaves the device: the check is the same listing request the
grid would have made, and everything else is localStorage.

Also: handleSearch's "put this in the box without searching for it" half
is now App.videos.setSearchBox, which is what applying a saved search
needs.

tests/smoke_saved_searches.py covers it end to end against a fixture
listing whose ids the test shifts to mean "new uploads": 46 checks.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MPZFnNdHbPGDTqQUNiE4ZN
2026-10-17 05:59:19 +00:00