211 Commits

Author SHA1 Message Date
DIYgod dffc815855 refactor(store): drop the comment of the removed unfollow request type
The batched unfollow removed UnfollowRequest but left its doc comment, which now sat on top of
ConfirmedSubscriptionChange's.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 08:24:54 +08:00
DIYgod 7cd1311203 fix(store): count unread entries again after a list is deleted or loses a feed
The server now also removes the timeline rows only that list brought, for its owner and its
followers, but the change log says nothing about unread counts. A synced list deletion and a
feed removed from a list on this device now recount them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 07:47:54 +08:00
DIYgod 4712f0339b fix(store): skip a subscription in a view the app has no list for
The server now refuses views outside FeedViewType and migrates the rows it had, but one such
row, stored or synced, threw in every store update and emptied the app. The store now skips it,
keeps a synced change of view to one, and adds entries only to views it knows.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 07:47:54 +08:00
DIYgod 4a8695c177 fix(store): ask for at most 100 profiles a request
POST /profiles/batch now reads at most 1000 ids. The batcher sent every profile asked for within
100 ms in one request, as many as a long list or timeline needs; it now sends 100 at a time.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 07:47:54 +08:00
DIYgod 2b32696e3a fix: translate the auth server's answer to too many sign-up, link or reset emails
Sign-up, sign-in links, password resets, verification and email change emails now answer 429
TOO_MANY_REQUESTS. Desktop, mobile and the web login pages show it translated, Electron sign-up
shows its refusals at all, and mobile's reset mail from settings no longer claims it was sent.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 07:47:54 +08:00
DIYgod 8a9b49ef98 fix: say that a verified account approves an email change from its current email
The server now sends the approval link to the current address; the new one gets its link after
that. Desktop and mobile say so for verified accounts, and the store no longer shows the new
address before the change is done.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 07:09:44 +08:00
DIYgod 4b321f6655 refactor(store): unfollow selected feeds and lists in one request
DELETE /subscriptions now takes listIdList next to feedIdList and unfollows both together, so a
selection no longer needs a request per list. A failed request puts the whole selection back.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 07:00:12 +08:00
DIYgod 8c34298d2f fix(store): rename a folder by name, so feeds filed under it elsewhere follow
PATCH /categories now takes {from, to, view}. A folder whose feeds are all filed under its name
is renamed that way, which also renames a feed another device moved in after the pull. Feeds an
auto-grouped folder holds by their site have no category on the server, so those are still named.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 06:58:04 +08:00
DIYgod 6640be21dc refactor(store): read lastSyncId from the typed SDK responses
The SDK types lastSyncId on the read, collection, subscription and messaging responses, so
readLastSyncId and its unknown-typed lookups go.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 06:53:36 +08:00
DIYgod 84812f7a78 chore(deps): bump client sdk to 0.3.98
0.3.98 needs @folo-services/exceptions 0.1.33, whose ESM build is back, so the 0.1.31 pin goes.
Only the @folo-services subtree changes in the lockfile; every peer suffix stays as locked.
The new versions are exempt from minimumReleaseAge, and code 16008 gets its translations.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 06:38:33 +08:00
DIYgod 4b83000129 fix(store): start syncing as soon as someone is signed in
A launch before the session check answered, as on a fresh profile, pulled
nothing, and the engine stayed without a cursor until the next focus or the
60 s interval. A category rename then could not pull first and split it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 00:23:09 +08:00
DIYgod a323f2834f fix(store): load the end page a list asked for during a refresh
TanStack folds a next page asked for while a list refetches its first
page into that refetch. A refresh trims a short list to its first page,
so the list asked for its end page meanwhile and got nothing, and the
mobile list never asks again when the same entries come back.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 19:42:49 +08:00
DIYgod a03b036eed fix(mobile): report a slow request as a timeout, once, and wait for the feed lookup
expo/fetch rejects an aborted request with a "fetch failed" error, so a timeout
read as a lost connection and was retried 3 times: 4 minutes for a slow feed. It
now fails once as a timeout with its own message, and looking up a feed by URL
outwaits the server's 70 s, so the server's own error arrives.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 14:30:52 +08:00
DIYgod 87e9a05e0a fix(store): fail a follow the server answered without a feed or list
When two accounts follow the same new feed at once, the server can answer without
either, having followed nothing. The model built from that had no id, so saving it
failed as "Invalid subscription" with an unhandled rejection; it now asks to retry.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 12:50:20 +08:00
DIYgod 3aa5fea048 fix(store): let the recorded owner decide over a session that answered first
The app asks for the session while the local data is still loading, so on a
launch with another account's session that account claimed the data before its
owner was read: no reset, and the queued changes were dropped or sent with it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 12:02:25 +08:00
DIYgod 2f01b0a408 fix(store): stop taking a mark off an unread snapshot that already counts it
A mark acknowledged during the first sync before the unread snapshot was read
was counted by the snapshot and taken off it again until the next pull, up to
a minute. The snapshot's sync id now tells the queue which marks it holds.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 11:58:27 +08:00
DIYgod e9e5f4fb39 fix(store): forget category names a rename on another device left behind
Patches from the change log only ever added categories, so after a rename the
old, empty name stayed in "Move to Category" on both clients. Patches and edits
now drop a name no feed uses, and the mobile menu follows the store.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 11:01:29 +08:00
DIYgod 4736e80402 fix(store): pull other devices' changes before renaming a category
A rename sent only the feeds this device knew to be in the category, so a feed
another device had just moved in kept the old name and the category split. The
rename now pulls first (for at most 2 s) and names the feeds in the category then.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 10:57:49 +08:00
DIYgod 28565d20c2 fix(store): send only the changed fields when editing a subscription
Edits sent the whole subscription from the local copy, so a field changed on
another device since the last pull was written back with its old value. The
PATCH and the local update now carry only the fields that really changed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 10:53:10 +08:00
DIYgod 12442b61c7 fix(store): leave counters of marks acknowledged during the first sync to the change log
A mark replayed from the outbox while the engine bootstrapped its cursor was
counted by its own prediction and again by the first delta, so the unread
count stayed one below the server's. A bootstrap now counts as delivering.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 10:50:52 +08:00
DIYgod 9a4afc6ca3 fix: tell the user when the server rejects a queued change
A rejected star, read mark or mark-all was rolled back without a word. The
queue now reports each rejected batch once, and both apps show a toast with
the server's reason.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 10:45:13 +08:00
DIYgod c06f146aca fix(store): restore unread counts after a rejected mark-all-read
The rollback re-marked the entries unread but left the feed's count at the
optimistic zero, because the recount found the confirmed counts unchanged.
It now derives the displayed counts again once the transaction is dropped.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 10:42:13 +08:00
DIYgod 5a6992ec06 fix(store): send queued changes only with the session of the account that made them
Writes queued while a session was dead went out with the next account's cookie.
Outbox rows record their account; the queue sends them only once the session
endpoint confirms that account, resumes after re-login, and a sign-in to another
account wipes the local data on both apps.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 10:39:29 +08:00
DIYgod 251fc52d8e fix(store): keep queued changes through outages and roll back only rejections
The queue gave up after eight attempts (about two minutes), rolled changes back
and made every later change fail on its first try. Transport errors, timeouts,
408/425/429 and 5xx now retry with a back-off that resets on resume and focus.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 10:17:30 +08:00
DIYgod edc00b122d fix(store): remember resolved feed addresses across launches
A Discover card for a followed RSSHub instance address showed Follow again after a restart,
as the address-to-feed-URL map lived only in memory. Keep the 100 latest entries
in the account's sync metadata table, which sign-out clears.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 04:19:09 +08:00
DIYgod c3e2e8f013 fix(actions): stop overwriting action rules changed elsewhere
A save now sends the version of the rules it started from as expectedUpdatedAt,
so the server answers 409 instead of replacing another device's changes.
Desktop and mobile then offer to load the latest rules, keep editing, or save over them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 04:13:33 +08:00
DIYgod b506654f63 fix(desktop): only check new or changed regex patterns before saving actions
A stored pattern RegExp rejects, such as a legacy (?i)foo, blocked every later save.
Like the server, check only conditions that differ from the rules as loaded,
and accept a leading (?i), which the server stores as (?i:...).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 04:05:38 +08:00
DIYgod 8c9df6e961 fix(store): find a feed by the RSSHub address it was looked up by
A Discover result for an RSSHub instance address such as
http://localhost:1200/test/sort kept offering Follow after the follow, as
the server keeps the feed as rsshub://test/sort. The store now remembers
the URL an address was looked up as, and finding a feed by URL follows it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 03:39:49 +08:00
DIYgod 7463bc3d4f fix(store): follow a feed the server has not stored by its URL
A new feed address was previewed under a random nonce id that mobile sent
as feedId, so following it failed with 422 "Invalid Snowflake ID". The
nonce is no longer sent and the feed is followed by its URL; the feed that
arrives for that URL then replaces the preview.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 03:35:35 +08:00
DIYgod 515bc47ae3 fix(mobile): check profile edits as the server does and explain refusals
Edit Profile sent blank names, values too long for their columns and handles
with spaces, then toasted the server's English text or a bare 500. Fields are
now checked as the server checks them, its update-user codes show translated
messages under their fields, and after a bare 500 a taken handle is looked up.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 01:33:40 +08:00
DIYgod e31d01ba22 fix(store): save a cleared profile handle as no handle
updateProfile sent a cleared or space-only handle as "", which the unique
index on handles refuses with a bare 500 once another user has one, and
the updated server stores a blank handle as none. It now sends and keeps null.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 00:45:25 +08:00
DIYgod 87cbc67e05 fix(store): stop profile saves from clearing the social links
updateProfile sent socialLinks: null unless the caller passed links, so a
mobile avatar change or a name-only save wiped them. It now sends them only
when given, and trims the name and handle as the server saves them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 00:13:04 +08:00
DIYgod a5f01e9f3e fix(store): unfollow every list of a selection the server can handle
Unfollowing feeds and lists together sent one DELETE with feedIdList and
listId; the server only handles feedIdList, so the lists stayed followed
(and of several lists only the first was sent). Feeds now go in one
request and each list in its own; a failed request restores only its own.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 22:57:06 +08:00
DIYgod df73e5e31f test(store): cover a failed unfollow and a delta behind the cursor
The tests for confirmed follows and unfollows did not check that a failed
unfollow confirms nothing, or that a replica answering with an older sync
id leaves the cursor and the confirmed unfollow in place.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 22:34:39 +08:00
DIYgod ce29910ba9 fix(store): keep a confirmed follow or unfollow over older sync rows
Right after unfollowing a feed or list, a refresh could put it back for up
to a minute: /sync/delta lags behind the DELETE and still returned the
follow before it, and an undo vanished the same way. Confirmed follows and
unfollows now keep transaction-queue overlays until the engine applies
their sync id; older delta rows and lagging subscription lists yield.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 20:44:18 +08:00
DIYgod d1386331eb fix(desktop): use the Folo name in exported files and 2FA
Export Database saved follow.db, Export Settings saved
follow-integration-settings-<date>.json, the OPML link asked for
follow.opml, and authenticator apps listed the account under "Follow".
They now say Folo; the 2FA setup passes "Folo" as the TOTP issuer. The
local database keeps its follow.db name so existing data stays.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 16:09:59 +08:00
DIYgod 7f5b016205 fix(desktop): stop requesting feed analytics with no feed ids
Every time the Feeds tab opened, it first sent POST /feeds/analytics
with an empty id list, before the table had measured its rows, and the
server answered 422. The analytics query now waits for ids.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 16:01:23 +08:00
DIYgod 95f5b9db40 fix(store): import exported action rules that have an empty name
The editor saves rules with an empty name and shows "Action N" for them,
but the importer rejected an empty name as missing, so such an export or
clipboard copy could not be imported again. Only reject a missing name.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 12:52:59 +08:00
DIYgod 1076922a87 fix(desktop): keep existing action rules when they fail to load
When GET /actions failed on a fresh device, the Actions page showed the
empty state, and saving a new rule there replaced every rule on the server.
Show a load error with Retry instead, and make saveRules refuse to send
rules before the server's rules were loaded.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 12:38:53 +08:00
DIYgod ebc18454d7 fix(store): let a fresh feed fetch update follower and update counts
Upserting a feed kept the stored subscriber count, updates per week and
latest entry date whenever one was set, in memory and in SQLite, so the
follow screen and search cards kept showing the first count ever seen.
Keep the stored values only when the incoming feed has none, as feeds
from subscriptions carry no analytics.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 08:36:58 +08:00
DIYgod aa5c70b1c6 fix(store): treat an account without action rules as an empty rule set
GET /actions answers without data when the account has no rules, and
fetchRules then left the store alone. Unsaved edits could not be
discarded for such accounts, since there was no confirmed copy to go
back to, and rules deleted on another device stayed in the store.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 06:20:57 +08:00
DIYgod 15d83c367f fix: drop unsaved action edits when the editor closes
Leaving the Actions screen after choosing to discard the changes only
navigated away. The edits stayed in the store marked dirty, so the next
visit showed them again, remote updates from the sync engine were held
back, and the next save sent them to the server.

The store now restores the confirmed rules when the editor unmounts. On
mobile a swipe back is also disabled while there are unsaved changes, so
leaving goes through the prompt.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 04:43:08 +08:00
DIYgod 120c6b1a2d fix(mobile): remove unchecked feeds when saving a list
Saving the manage-list screen only sent every selected feed to the add
endpoint, so feeds the user unchecked stayed in the list and the local
copy collected duplicate ids. It now adds only the new feeds and removes
the unchecked ones. The selection also started empty when the list was
still loading; it now starts from the list's feeds once they arrive.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 04:39:05 +08:00
DIYgod 88f114844a perf(store): store streamed entry contents once per network chunk
Prefetching the contents of up to 30 entries updated the entry store
once per streamed entry, and every update re-ran the timeline selectors
that filter and sort every entry of the session. Store the entries that
arrive together in one update, and report a failed stream instead of
leaving the rejection unhandled.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 03:50:45 +08:00
DIYgod ebb769a3e9 fix(store): update the mobile subscription list when unread counts change
The mobile subscription list sorted by count and hid read subscriptions
through subscription store selectors that read the unread counts from
the unread store, which never ran them again, so a feed read to zero
stayed visible and the order froze until a subscription changed. The
folder sort also walked every subscription twice per comparison.

Run the selectors again when the counts they use change, and add up
each folder's unread count once per sort from the ids already grouped.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 03:49:19 +08:00
DIYgod bdf991b9ec perf(store): apply new-entry counters of the change log together
Every new-entry action in the change log updated the dirty feed set,
copied and published the whole unread map and wrote the database on
its own. After a night away that is thousands of sequential writes and
store updates, re-rendering every badge and sidebar count each time.
Add up consecutive new-entry actions and apply them in one update,
before any other action so counts set outright still apply in order.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 03:47:28 +08:00
DIYgod e10e28bd24 fix(store): allow for clock skew before skipping a new entries refresh
New entries skipped refreshing a list that was fetched after the newest
entry was logged, but compared the server's log time with this device's
fetch time. With a clock a few minutes fast, a list fetched before the
entries looked newer and never showed them until it went stale. Only
skip lists fetched clearly after the entries.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 03:41:11 +08:00
DIYgod 35ac2c9e62 fix(store): keep other views' subscriptions when refetching one view
Refetching a single view's subscriptions, the fallback when the sync
engine isn't running such as a pull to refresh on mobile, emptied the
set of subscribed ids for every view but refilled it only for that
view, so feeds in other views looked unsubscribed. It also kept the
subscriptions the server had dropped. Reset only the refetched view's
subscriptions.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 03:39:12 +08:00
DIYgod 3081bfffdd fix(store): clear unread counts when a list is unfollowed
Unfollowing a list only cleared the unread counts of feeds unfollowed
directly, so feeds that had been followed only through the list kept
counting toward the app badge until the next hourly recount, and a list
deleted on another device never cleared them at all. Clear the counts of
feeds no longer followed after a local unfollow, and recount after a
synced list delete.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 03:38:33 +08:00
DIYgod 5b3c7943c3 perf(store): drop the summary cleanup that never removed anything
After every summary update the store sorted all summaries in memory to
keep the ten most recent, but it deleted them by language code instead
of entry id, so nothing was ever removed and every update paid for the
sort. Hydration loads every stored summary anyway, and evicting them
would only refetch summaries still shown in the timeline, so remove the
cleanup together with an unused getter that wrote to the store on read.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 03:36:29 +08:00