Commit Graph

7054 Commits

Author SHA1 Message Date
DIYgod 90f107fd1c release(desktop): release v1.14.0 desktop@1.14.0 2026-09-18 18:40:29 +08:00
DIYgod 259c283b06 test(mobile): center the sign-out row before tapping it in the auth flow
The settings list grew, so the last row now stops underneath the floating tab
bar when Maestro scrolls until it is visible, and the tap landed on the tab bar
instead of the sign-out row.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-18 18:39:23 +08:00
DIYgod bedc3bc6d0 fix(mobile): raise pod deployment targets to the app floor for Xcode 27
Xcode 27 refuses the iOS 9.0 and 12.0 deployment targets that older pods still
declare on their resource bundle targets, and its Swift compiler rejects
react-native-ios-context-menu, which uses UIAction.subtitle behind an iOS 15
availability check. The app requires iOS 16.4, so the Podfile now raises every
pod target to at least ios.deploymentTarget in post_install; the config plugin
generates the block and the checked-in Podfile carries it.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-18 18:22:56 +08:00
DIYgod c297c6edd7 docs(desktop): prepare release inputs
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-18 18:15:30 +08:00
DIYgod d8835acd02 fix(desktop): route the remaining main-process requests through Chromium and keep the CLI token out of logs
After the API bridge moved to `net.fetch`, several main-process requests still used
Node's `fetch`, which ignores the app's proxy settings: the update manifest and policy,
the main-process API client, file downloads, text-to-speech, page fetching for the
reader, and the Eagle, qBittorrent and custom integration requests. They now go through
`net.fetch` as well. The readability package takes a `fetch` implementation for that;
downloads hand it to ky. Requests that manage cookies by hand pass
`credentials: "omit"`, so Chromium does not keep them too.

A failed CLI login sync used to be logged with the whole command line, token included.
The command is now described with the token redacted, and the command's output is
scrubbed the same way.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-18 17:40:26 +08:00
DIYgod d0f255f782 ci(landing): declare the vinext output as the turbo artifact
The landing build writes to dist/, but turbo still listed the old Next.js output
directories. On a cache hit nothing was restored and the deploy could not find
dist/client.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-18 16:58:47 +08:00
DIYgod ca0c85b20d fix(desktop): send API requests through Chromium's network stack so they follow the proxy
The main process sent the renderer's API requests with Node's `fetch`, which knows
nothing about the app's proxy settings: with only a system proxy configured, every API
request went out directly while images and pages went through the proxy. On networks
that need the proxy the API was unreachable.

`net.fetch` uses Chromium's network stack and therefore the session's proxy: the system
proxy, PAC files and SOCKS alike. Cookies stay attached by hand from the managed auth
cookies, so Chromium's own cookie handling is switched off for these requests. Verified
in a real main process: the proxy is resolved and used, a manual `Cookie` header is sent,
`Set-Cookie` is exposed through `getSetCookie()`, and `credentials: "omit"` leaves the
cookie store untouched.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-18 16:47:43 +08:00
DIYgod c75533cbd2 fix: report the API as unreachable only when a probe confirms it
One request failing without a response proved little: an endpoint may be slow or
briefly broken while the API itself is fine, and the prompt appeared on every such
hiccup.

- a failed request now only schedules a probe, a cheap `/status/configs` request two
  seconds later, repeated once; the API counts as unreachable when every probe fails
  and no other request got an answer in the meantime
- while unreachable the probe repeats every fifteen seconds so the prompt goes as soon
  as the API answers; any ordinary answer lifts it as well
- the desktop probe travels the same path as its requests, through the main process;
  the mobile probe uses the app's fetch
- without a probe, three failures in a row are needed instead of one

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-18 16:18:30 +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 97e92a8ac2 feat: show a prompt when the API cannot be reached
Requests that fail without any response (DNS failure, refused or reset
connection, offline device, timeout) previously failed silently. Track
them through the SDK interceptors and surface a single prompt per outage:
a persistent toast on desktop that is dismissed once the API answers
again, and a transient toast on mobile.

The connection state lives in an atom because the first requests can fail
before the toaster is mounted.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-17 21:08:05 +08:00
DIYgod f69d7fe1d1 fix(store): keep the local user unless the session check returns 401
whoami() told connection failures apart from a missing session by looking
for "Failed to fetch" in the error message. Electron's IPC fetch, React
Native's fetch and SDK timeouts use different messages, so an unreachable
API cleared the local user, and on mobile redirected to the login screen.

Only a 401 (or an empty session) now clears the user. Every other failure
is rethrown so callers keep the cached user.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-17 21:08:01 +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 b71dba08d4 feat(desktop): add a settings entry for unauthenticated users
Show a preferences button next to the login button when logged out, so
guests can open the settings modal without signing in. The modal already
supports guest mode and gates account-bound tabs behind login.

Also fix KbdCombined splitting the comma key in shortcuts like "$mod+,"
as if it were a separator between multiple shortcuts.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-17 19:29:15 +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 93b83f95eb feat(mobile): add reading settings, manual theme and immersive header
- add a Reading settings screen (content font, line height, text color,
  inline style, code theme) reachable from Appearance and from the entry
  "more" menu
- add a System / Light / Dark theme setting applied through
  Appearance.setColorScheme so the reader WebView follows it as well
- optionally slide the entry header away while scrolling down and keep a
  blurred status bar strip so the clock stays readable
- forward reader typography to the html renderer through a new
  setReaderStyle bridge call

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-17 12:50:51 +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
Komh 44f0e5df06 fix(tray): Linux tray icon duplicates and dead menus on minimize-to-tray toggle (#5086)
On Linux, `Tray.destroy()` does not remove the icon from StatusNotifierItem
hosts (waybar, KDE Plasma), so toggling "Minimize to tray" off and on stacked a
dead icon every time. Keep the single Tray instance for the app's lifetime
instead; the window close handler reads `getTrayConfig()` live, so a tray icon
that outlives a disabled setting is inert.

The icon cannot be removed from a running process, so `setTrayConfig` reports
when a restart is needed and the renderer offers one via a new `app.relaunch`
IPC. That relaunch runs from `$APPIMAGE` where set (AppImage's `execPath` points
into a mount that is gone by then), drops `--start-in-tray` so it never comes
back hidden, and uses `app.quit()` so `before-quit` still persists the window
bounds and flushes cookies.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-12 18:10:14 +08:00
DIYgod 5476270687 fix(mobile): repair player and search layouts 2026-09-12 17:48:57 +08:00
DIYgod 8b097abc52 fix(reader): show missing cover images above entry content 2026-09-09 13:13:09 +08:00
DIYgod 41d009a87e fix(desktop): reset masonry layout when item order changes 2026-09-06 11:07:04 +08:00
DIYgod dbab1121fa fix(mobile): add subscription legal links 2026-09-03 18:54:32 +08:00
DIYgod 465b997e89 fix(mobile): upgrade audio player to avoid background service crashes 2026-09-02 16:18:34 +08:00
DIYgod b835efa0c4 Merge pull request #5083 from RSSNext/mobile-main
chore(sync): merge mobile-main into dev
2026-09-02 13:28:40 +08:00
DIYgod c6f21c0a48 release(mobile): Release v0.5.9 (#5081)
* fix(ci): scope release detection to target branch (#5065)

* fix(desktop): allow custom URL schemes

* fix(mobile): fix settings header safe area and contrast

* feat(desktop): build a Linux .deb alongside the AppImage (#5078)

Adds @electron-forge/maker-deb so `electron-forge make` on Linux emits a
.deb next to the existing AppImage.

- forge.config: MakerDeb scoped to linux; `bin` pinned to packagerConfig.name
  so the staging build ("Folo Staging") also resolves; folo:// and follow://
  registered as x-scheme-handler MIME types; Network;News; categories.
- postMake: .deb is renamed to the Folo-<version>-linux-<arch>.deb convention
  but kept out of latest-linux.yml (the electron-updater feed is AppImage-only).
- CI: install fakeroot/dpkg on the Linux leg; upload the .deb as its own
  `linux-x64-deb` artifact; add *.deb to the attestation and release globs.
- pnpm-lock.yaml regenerated with the pinned pnpm@10.17.0. The large churn is
  peer-key reserialization from a newer pnpm than last wrote the lockfile;
  the only functional additions are maker-deb and its electron-installer-debian
  subtree.


Claude-Session: https://claude.ai/code/session_01KeQJNfE1FfJnLMH9psG1jS

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>

* docs(readme): add Flatpak install method via FlatPark (#5079)

* fix(mobile): preserve timeline position across switches

* fix(desktop): restore inbox secret copying

* feat(timeline): support oldest-first unread sorting

* docs(desktop): prepare release inputs

* docs(desktop): reset release inputs

* docs(mobile): prepare release metadata

* release(mobile): release v0.5.9

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Komh <mail@guojing.io>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
mobile/v0.5.9
2026-09-02 11:13:15 +08:00
github-actions[bot] 9a2239d90e Merge pull request #5082 from RSSNext/main
chore(sync): merge main into dev
2026-09-02 03:08:59 +00:00
DIYgod fff09a4e25 Merge pull request #5080 from RSSNext/release/desktop/1.13.0
release(desktop): Release v1.13.0
desktop-build/v124 desktop/v1.13.0
2026-09-02 11:03:46 +08:00
DIYgod 22a9bcd4e4 release(mobile): release v0.5.9 2026-09-02 10:00:44 +08:00
DIYgod d7a3c1e6d9 docs(mobile): prepare release metadata 2026-09-02 09:59:53 +08:00
DIYgod aa72ec2db9 docs(desktop): reset release inputs 2026-09-02 09:59:43 +08:00
DIYgod 96d087fdbc release(desktop): release v1.13.0 desktop@1.13.0 2026-09-02 09:56:53 +08:00
DIYgod 8d52b91d3e docs(desktop): prepare release inputs 2026-09-02 09:55:07 +08:00
DIYgod 9787707ca3 feat(timeline): support oldest-first unread sorting 2026-09-01 20:26:14 +08:00
DIYgod f82a5dad81 fix(desktop): restore inbox secret copying 2026-09-01 20:11:40 +08:00
DIYgod a2ad2a8922 fix(mobile): preserve timeline position across switches 2026-09-01 18:28:26 +08:00
Komh 3b4c85b51d docs(readme): add Flatpak install method via FlatPark (#5079) 2026-09-01 15:47:25 +08:00
Komh 02efd07710 feat(desktop): build a Linux .deb alongside the AppImage (#5078)
Adds @electron-forge/maker-deb so `electron-forge make` on Linux emits a
.deb next to the existing AppImage.

- forge.config: MakerDeb scoped to linux; `bin` pinned to packagerConfig.name
  so the staging build ("Folo Staging") also resolves; folo:// and follow://
  registered as x-scheme-handler MIME types; Network;News; categories.
- postMake: .deb is renamed to the Folo-<version>-linux-<arch>.deb convention
  but kept out of latest-linux.yml (the electron-updater feed is AppImage-only).
- CI: install fakeroot/dpkg on the Linux leg; upload the .deb as its own
  `linux-x64-deb` artifact; add *.deb to the attestation and release globs.
- pnpm-lock.yaml regenerated with the pinned pnpm@10.17.0. The large churn is
  peer-key reserialization from a newer pnpm than last wrote the lockfile;
  the only functional additions are maker-deb and its electron-installer-debian
  subtree.


Claude-Session: https://claude.ai/code/session_01KeQJNfE1FfJnLMH9psG1jS

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-01 15:23:21 +08:00
DIYgod 78f6bd1b74 fix(mobile): fix settings header safe area and contrast 2026-08-26 11:07:51 +08:00
DIYgod 7c220c69a8 fix(desktop): allow custom URL schemes 2026-08-15 14:51:34 +08:00
DIYgod 3846c90b67 fix(ci): scope release detection to target branch (#5065) 2026-08-01 23:14:33 +08:00
github-actions[bot] 3bc8336562 Merge pull request #5068 from RSSNext/mobile-main
chore(sync): merge mobile-main into dev
2026-08-01 10:37:48 +00:00
DIYgod a580e0a337 Merge pull request #5067 from RSSNext/release/mobile/0.5.8
release(mobile): Release v0.5.8
mobile/v0.5.8
2026-08-01 18:28:33 +08:00
DIYgod f6777ee559 release(mobile): release v0.5.8 2026-08-01 17:45:07 +08:00
DIYgod 6a8c07c000 docs(mobile): prepare release metadata 2026-08-01 17:44:47 +08:00
DIYgod e291c3d1da fix(mobile): comply with Android media permissions policy 2026-08-01 17:41:46 +08:00
DIYgod efafb15d7c Merge pull request #5064 from RSSNext/mobile-main
chore(sync): merge mobile-main into dev
2026-07-31 19:28:49 +08:00