* fix(twitter/following): switch from INTERCEPT+autoScroll to COOKIE+cursor pagination
The previous INTERCEPT strategy relied on autoScroll to trigger Twitter's
pagination by scrolling document.body. Twitter's virtual list doesn't grow
document.body.scrollHeight, so scrolls stopped triggering API calls after
the first few pages, capping results at ~50 regardless of limit.
Now uses Strategy.COOKIE with explicit cursor-based GraphQL pagination
(same pattern as twitter/likes), which correctly fetches all pages.
Fixes#1230
* fix(twitter): harden following pagination
---------
Co-authored-by: jackwener <jakevingoo@gmail.com>
* feat(twitter): rewrite lists via GraphQL + add list-tweets
The DOM-scraping / detail-click approach in PR #1053 remained fragile
against X's frequent overview-page rendering changes and slow (N+1 page
loads per list). Rewrite `twitter lists` to call
`ListsManagementPageTimeline` GraphQL directly — one request returns all
owned + subscribed lists with id/name/member_count/subscriber_count/mode.
Also add `twitter list-tweets <listId>` for pulling the tweet stream from
a list, completing the read-side chain (lists → pick an id → list-tweets).
- lists: drop positional `user` arg (GraphQL returns only logged-in
user's lists), add `id` column, change followers to exact integer from
subscriber_count.
- list-tweets: same GraphQL pattern as bookmarks/likes (BEARER + ct0 +
dynamic queryId with static fallback + cursor pagination).
- Delete obsolete lists-parser.js and lists.d.ts.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(twitter): add list-add / list-remove with Save-button commit
Two new commands to toggle list membership. X's list dialog uses a
"click-to-stage, Save-to-commit" pattern — clicking a row only updates
optimistic UI; the actual POST fires when the user clicks the top-right
"Save" button. Pressing ESC or the close-X silently cancels the change.
Implementation:
- Resolve listId → name via ListsManagementPageTimeline GraphQL, so we
match the dialog row by name (dialog rows have no data-testid listId).
- Open profile page → DOM click "…" menu → "Add/remove from Lists".
- Scroll dialog to locate target row (virtualized list).
- page.nativeClick on row — trusted CDP Input.dispatchMouseEvent fires
React's onclick, flips aria-checked (.click() alone does not suffice;
X ignores non-trusted events for list mutations).
- page.nativeClick on the Save button — commits to server.
- Verify by re-fetching ListsManagementPageTimeline and diffing
member_count: success only if N→N±1. No silent successes.
This fixes the pattern where batch `list-add` calls returned success for
every user but committed zero to the server (optimistic UI lied).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix: stabilize twitter list manifest and query ids
* docs: add twitter list command discoverability
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: jackwener <jakevingoo@gmail.com>
Add three new Twitter/X UI-strategy commands:
- `block` / `unblock` — block or unblock a user by username
- `hide-reply` — hide a bot/spam reply on your own tweet thread