18 Commits

Author SHA1 Message Date
DIYgod fac0b0832a fix(ci): keep mobile asset builds on one pnpm layout 2026-09-22 16:42:50 +08:00
DIYgod 2f392594d5 chore(deps): refresh dependencies and verify affected app flows 2026-09-22 16:20:40 +08:00
DIYgod 141567e928 fix(store): pair unread snapshots with the sync id they reflect
A `/reads` recount taken while new-entry hints were still hidden behind a
stale KV head already counted those entries; when the hints arrived with
the next pull their increments were added on top, and the reads that
followed left the hint values behind as phantom unread counts.

The engine now keeps the sync id an unread snapshot reflects
(`unreadSnapshotSyncId`, from the `/reads` answer or `/sync/state` read
just before) and applies the counter part of `N`, `U` and `inbox_entry`
`D` actions only above it. A refresh the user asks for recounts through
the engine, and the list-versus-counter check also fires when a fully
loaded unread-only list shows fewer unread entries than the counter.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-19 10:29:35 +08:00
DIYgod 9be883a63a fix(desktop): accept bodiless responses from the main process and catch lists up on return
The staging desktop build showed "Unable to connect to the server" and a timeline that
never caught up.

- the main process hands API responses over as text, so a `204` such as the analytics
  endpoint's arrived as an empty string; `Response` refuses a body for those statuses,
  the throw looked like a failed connection, and the new prompt reported it
- a desktop window keeps its visibility when the user switches apps, so the sync engine
  never saw a "return"; window focus now counts as one, and duplicate signals within a
  few seconds share one pull
- views that received new entries during background pulls are remembered and their
  lists brought up to date on the next return, even when that pull finds nothing new
- oldest-first lists receive new entries at their end: the empty page that marked the
  end is dropped and the page after the last entry is requested again

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-18 15:50:01 +08:00
DIYgod b8ef823a95 docs(store): describe presence-based fan-out of new-entry hints
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-18 13:40:42 +08:00
DIYgod 1006fb77c8 docs(store): describe KV answered polls and the shorter life of new-entry hints
The server no longer repeats entry ids in `N` actions, answers up-to-date delta polls
from KV and drops `N` rows after three days. The client never read the entry ids, so
only its type and the design document follow.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-17 23:18:00 +08:00
DIYgod b6c50296a1 feat(store): stop requesting the session, actions, settings, wallet and push token on every launch
A launch of the web app made twelve API requests. Half of them fetched data the
sync engine can keep current, or fetched the same thing more than once.

- session: start-up fetches it through the query cache and `useSession` reuses
  it for a minute, instead of three separate requests
- action rules and settings become sync models: they are loaded in full once per
  account, stored locally, and afterwards follow the change log; unsaved rule
  edits and queued setting changes are never replaced
- sync models outside the engine register through `sync/model-registry`, with a
  one-time bootstrap and an apply step; a model whose actions went by unhandled
  is bootstrapped again
- wallet: requested when the profile menu opens, which is the only place that
  uses the answer
- web push: the token is registered again only when it changed or the change
  log shows another browser took over the channel
- the new-user guide remembers accounts that already have settings, so the
  entry list no longer requests `/settings`

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-17 21:32:04 +08:00
DIYgod b09af5f5cd feat(store): rely on the delta feed instead of full snapshots once a sync cursor exists
The sync engine added an incremental channel, but the app kept fetching full
snapshots next to it: both prefetch hooks, a recount after every pull, every
loaded page of the entry lists, and whole-list refetches after unsubscribing or
importing.

- unread counters keep confirmed counts under the local transactions; the change
  log moves them by the flips and new unread rows the server reports, and an
  acknowledged transaction drops its prediction once the engine reached its
  sync id
- usePrefetchSubscription, usePrefetchUnread, pull to refresh, the refresh
  button, OPML import, unsubscribe and the mobile background task go through the
  engine and only fall back to the full requests when it cannot help
- snapshots remain as calibration: unread hourly, subscriptions daily, plus a
  throttled recount when the list on screen disagrees after a delta pull
- new entries fetch only the first page of the lists on screen and merge it in
  front of the loaded pages; a refresh trims the infinite query to one page
- mobile refreshes subscriptions through the delta feed after an OPML import

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-17 19:53:09 +08:00
DIYgod 0e59bd2ddd feat(store): settle transactions by sync id and sync lists and inboxes
Follow up on the sync engine:

- Transaction kinds report the `lastSyncId` their mutation returned. The
  queue releases an acknowledged transaction's overlays as soon as the sync
  engine has applied that id, instead of always waiting out the 30 s window,
  which stays as the upper bound for servers without sync ids.
- The engine applies the new `list`, `inbox` and `inbox_entry` actions and
  inbox `N` hints, so list edits, inbox changes and new mail made elsewhere
  show up without a full refetch.
- While the engine is active the unread mismatch heuristic stands down and
  the desktop blanket invalidation skips the queries the delta feed already
  keeps fresh. Both remain as the fallback for servers without `/sync`.

Also fix the Android build: `setup-android` installs the obsolete `tools`
package by default, which the SDK repository no longer serves.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-17 18:16:25 +08:00
DIYgod 9b890144e8 chore(deps): upgrade client sdk to 0.3.96 and use its sync module
Bump `@follow-app/client-sdk` to 0.3.96 (and `@folo-services/drizzle` to
0.1.48) and hand `followApi.sync` to the store instead of requesting the
`/sync` endpoints through `followClient.request`.

The new SDK no longer declares the frozen wallet and RSS3 writes. Keep the
existing screens and their runtime behaviour unchanged through a small
compatibility module: legacy withdraw fields and the `IS_RSS3_TESTNET`
flag get local types, and the removed `wallets.post()` is requested at the
same path with `followClient.request`. Drop an over-wide tuple annotation
in the mobile plan screen now that `PaymentFeature` keys are precise.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-17 14:36:00 +08:00
DIYgod 247a9294ea feat(store): add an incremental sync engine driven by server sync ids
Add `sync/sync-engine.ts`, which keeps the last applied sync id in a new
`sync_meta` SQLite table (migration 0039), bootstraps from `/sync/state`
plus the existing snapshot endpoints, and then pages through `/sync/delta`
on launch, foreground, reconnect, a 60 s interval and after the transaction
queue receives an acknowledgement. Subscription, collection and timeline
actions are applied to the stores and SQLite, pending local marks keep
precedence through the transaction queue overlays, and unread counts are
refreshed once per pull. A 404 from the sync endpoints keeps today's full
refetch behaviour for the session.

Both apps expose the endpoints through `followClient.request` until the
client SDK release with the `sync` module lands.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-17 13:53:47 +08:00
DIYgod b606f0c432 feat(store): add a persisted transaction queue with rebase
Introduce a Linear-style transaction queue for user mutations. Read marks,
unread marks, mark-all and star/unstar are applied optimistically, written to
a new `sync_transactions` outbox table, batched and sent in order, retried
while offline and rolled back only when the server rejects them. Pending and
just-acknowledged transactions expose overlays so server snapshots are rebased
on top of the local intent, which replaces the 30 s read-protection window.

Also add the sync engine design spec that covers the server change log and
delta endpoints planned for the next phase.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-16 23:13:42 +08:00
DIYgod 1158281f59 chore(deps): update frontend dependencies (#5038)
* chore(deps): update frontend dependencies

* fix(ci): format OTA verification commands
2026-07-10 22:36:12 +08:00
DIYgod 33c8bb98b6 feat(ota): unify desktop updates with ota service 2026-04-12 15:39:13 +08:00
DIYgod af7f2e755a docs(spec): add desktop ota unification design 2026-04-12 15:36:34 +08:00
DIYgod dc0d5c2cda docs(ota): add rollout and rollback runbook 2026-04-10 22:24:37 +08:00
DIYgod 4170a858fb docs(ota): add implementation plan and release scoping 2026-04-10 18:47:44 +08:00
DIYgod 59eb6aae00 docs(ota): add OTA service design spec 2026-04-10 18:06:34 +08:00