Commit Graph

7115 Commits

Author SHA1 Message Date
DIYgod f162f27c51 release(desktop): release v1.15.0 desktop@1.15.0 2026-10-02 20:40:12 +08:00
DIYgod 967a6f5e38 docs(desktop): prepare release inputs
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 20:39:56 +08:00
DIYgod 3795ad2b47 docs(skills): take desktop and mobile releases through store submission
The release skills stopped once the release PR was open. They now carry a
release to the stores: wait for the PR's CI builds, verify those packages and
every change in the release, merge the PR, watch the tag, store and OTA
workflows, approve the SignPath request for the Windows installer, publish the
GitHub release, check the OTA worker, and submit the Mac App Store, Microsoft
Store, App Store and Google Play builds for review.

The flow lives once in .agents/skills, with references for verifying the
macOS, Android and iOS builds, the store consoles, credentials and the fixed
store release notes, plus a script that checks the Authenticode signature of
the Windows installer. The Claude Code skills in .claude/skills are thin
adapters that map it onto Claude's tools.

Fixes carried over from the old flow: run the bump from inside the app
directory and answer nbump's confirmation prompt, keep the entry HTML
renderer out of OTA releases, and ignore version-only bump changes when
checking an OTA release for native changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 19:50:25 +08:00
DIYgod 559b4b8423 ci: run dev's required checks on mobile-main pushes
The sync PR that merges a release branch back into dev is opened with
GITHUB_TOKEN, so it starts no pull_request workflows. Its auto-merge waits
for dev's required checks, "Format, Lint and Typecheck (lts/*)" and "Build
web and SSR server (lts/*)". Pushes to main run both on the merge commit, so
the desktop sync PR merges by itself, but pushes to mobile-main ran neither,
and mobile sync PRs sat until someone merged them by hand (#5095 waited eight
days).

Run both workflows on mobile-main pushes too, without cancelling runs in
progress there, and guard the triggers with a test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 19:50:25 +08:00
DIYgod 2131cec389 chore(store-assets): add the store screenshot and listing pipeline
Store copy for 12 listing locales, plus the tooling that captures the apps
with a demo account and renders the App Store, Google Play and Microsoft
Store screenshots and the README banner. Raw captures and rendered images
stay out of the repository (store-assets/.gitignore).

store-assets is a private workspace package, so playwright, sharp, pathe and
the other script dependencies are declared instead of resolved through
hoisting, and turbo typechecks it. The lockfile only gains its importer.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 14:34:42 +08:00
DIYgod 505f6d52f1 docs(readme): refresh the README with the AI reader story and new screenshots
- Lead with a banner of the Mac and iPhone apps and the new tagline
- Describe Folo as an open-source AI RSS reader, matching the store listings
- Replace the four old feature images with the new store posters, a row of
  phone screenshots and an "And More" list; drop the outdated sections
- Fix image alt texts and point the stars badge at RSSNext/Folo
- The images are GitHub attachments hosted on DIYgodLab/debug#1, not files
  in this repository

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 14:34:20 +08:00
DIYgod a993acb754 fix(mobile): make streaming TTS start, stop and hand over reliably
Long articles often failed to start: the stream had to produce decodable
audio within a fixed 10 s, while tts.folo.is takes several seconds to send
the first bytes of a long text. The timeout didn't stop the WebView, which
kept downloading and later played audio the app no longer tracked, and
playEntryTts fell back to downloading the whole file, which could start
playing over a newer stream minutes later.

- The start timeout is a 15 s stall timer that WebView progress events
  (response headers, then chunks) restart, and it stops the WebView.
- Bridge events carry the request id, so events of a replaced or stopped
  request are ignored, and a play command always starts over instead of
  toggling the entry that is playing.
- Each WebView playback is isolated, so work left over from a replaced one
  can't stop or report on the one that replaced it.
- A superseded or stopped request never falls back and its fallback
  download is aborted. The player's stop button cancels a loading request.
- Segments start after the audio already queued (desktop got this in
  4ab06f42b), a paused stream stays paused while more audio decodes, and
  the last decode of a stream is no longer dropped.
- TTS and native playback no longer overlap: a starting stream pauses the
  native player, and native playback (a podcast, the lock screen) stops
  TTS unless that TTS request is the newer one.
- A stream whose download stalls after it started reports an error once
  its queued audio ran out, and a WebView whose content process the system
  killed is recreated instead of swallowing every later request.
- Cached TTS files are pruned once the player is set up and before each
  fallback download, keeping the file the player still holds.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 18:19:26 +08:00
DIYgod 0f12dc2bfb fix(mobile): keep forwarding the caller's abort while a fetch body downloads
trackFetch removed its abort listener as soon as the headers arrived, so
aborting the caller's signal could no longer cancel a body that was still
downloading. Every fetch in the app goes through it (the global fetch, the
API client and the TTS service), so all of them ignored aborts after the
headers, unlike a plain expo fetch. The listener now stays until the
response is finalized.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 18:19:10 +08:00
DIYgod 7d4b23bb4a fix(mobile): show the player on pushed screens with the JS tab bar
On iPad, Android and iOS before 26 the player bar lives in the JS tab bar,
which screens pushed onto the root stack cover. Starting TTS from an
article or a folder timeline gave no feedback and no way to pause or stop
it until returning to a tab root.

Render a floating player bar through the root portal while a pushed screen
is on top. It hides behind modals and the lightbox, which the root portal
host is drawn above on Android, and the article and feed screens keep
their content clear of it. iPhone on iOS 26+ keeps the native tab bar and
its glass player bar unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 16:55:43 +08:00
DIYgod 9949ddeabb fix(desktop): give each autocomplete input its own accessible name
The shared Autocomplete always set aria-label "Select Category", which
was wrong for the feed search in the list settings. Callers now pass a
label that matches their purpose. The list search reuses
lists.select_feeds, whose zh-CN copy drops a stray period and zh-TW
copy uses the Taiwan word for "current".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 16:52:12 +08:00
DIYgod 5cddfa4edd fix(discover): show validation and search errors
- The OPML import form rendered no FormMessage, so a file over 500KB or
  with another extension failed silently on Import.
- UnifiedDiscoverForm checked RSSHub routes inside the search mutation
  and never rendered the error. The check now lives in the form schema,
  so the message shows under the input and Preview stays disabled. RSS
  and RSSHub previews open from onSubmit, which drops an RSS check that
  could never fail and the "No feeds found" line shown after a preview.
- Failed searches on the discover page and in the sidebar discover
  dialog now show a toast.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 16:51:58 +08:00
DIYgod 9e51f06c69 refactor(desktop): remove dead integration, discover and AI settings code
Nothing imports CustomIntegrationValidator, DiscoverForm (replaced by
UnifiedDiscoverForm), TimelinePromptSection (taken off the AI settings
page in 691307925) or AiOnboardingModal. URLSchemeHandler.getExamples,
the CustomIntegrationManager validate helpers, formatFileSize and
withDisplayStateHandler have no callers. Remove them along with the
locale keys only they used.

The aiTimelinePrompt setting stays because the server still reads it
when it scores the AI timeline.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 16:51:16 +08:00
DIYgod 9603533ed4 fix(i18n): translate hard-coded UI strings and complete every locale
Move the hard-coded English left in the desktop renderer, the Electron
main process, the mobile app and the shared packages to i18next, with
en, zh-CN, zh-TW, ja and fr-FR translations. SSR share pages are
unchanged.

- Fill every namespace in every language, delete keys no code reads
  (server error codes stay) and update translations left behind by
  later English changes.
- Stop HTML-escaping interpolated values in the desktop renderer.
  React already escapes text, so names with & or quotes showed entities.
- Render error boundary fallbacks as components so they can use hooks.
- Localize zod validation messages with the app language.
- Throw ClipboardError from the clipboard helpers and show translated
  toasts. formatEstimatedMins takes a formatter so callers localize the
  units.
- Use one glossary term per concept in ja, fr-FR and zh-TW.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 16:50:23 +08:00
DIYgod a7216df5d4 fix(i18n): translate the remaining zh-TW strings
Add every zh-TW key missing from settings and mobile, which fell back to
Simplified Chinese, and translate the values still in English: wallet
transaction labels, social link placeholders, and English words inside
translated strings (Proxy, Fetch, Authenticator, issue, token, App).
Brand names, acronyms and terms that zh-CN also keeps in English stay
as they are.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 14:03:45 +08:00
DIYgod da7b60ab15 fix(desktop): bundle the zh-CN and zh-TW AI locales in Electron
Electron never loads locales at runtime: every language is bundled in
default-resource.electron.ts. The ai namespace of zh-CN and zh-TW still
pointed at the English placeholder added before those translations
existed, so the AI settings and chat panel stayed in English.

- Import the real ai locale files, and test the Electron map against
  locales/ for every supported language and namespace.
- Skip the localStorage locale cache in Electron and start i18next in the
  user's language. Only builds from before June 2025 wrote that cache and
  nothing refreshes it, so it could only shadow the bundled resources.
- Translate the zh-TW plan settings titles.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 13:20:36 +08:00
DIYgod 0eb2fd46fa fix(settings): sync every setting again except the device-local ones
Until 1.6.0 the settings sync sent every appearance, general and AI setting
except the device-local keys each domain lists: interface language, voice,
launch at startup, anonymous data, fonts and the like. 93e4a3b48 read those
lists as the keys to send and swapped omit for pick, on desktop and mobile,
so since then only the device-local keys synced across devices and nothing
else did, the action language and the AI settings included. Its e2e test
checked that the interface language syncs.

The lists name the device-local keys again and are renamed to say so. The
appearance domain also leaves the spotlight keys in its tab to the spotlight
domain, which still syncs only those. actionLanguage and personalizePrompt,
which b0f555266 added to the lists, are out of them again, so they sync.

The server kept what the clients sent before 1.6.0, so once per account
each device first queues the settings it changed from their defaults, before
it takes any from the server. While a local change to a tab waits,
applyRemoteSettings now leaves that tab alone, as the change log already
did; settings still at their defaults take the account's values afterwards.

The e2e test now syncs the action language.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 11:17:23 +08:00
DIYgod b0f5552661 fix(settings): sync the action language and AI personalize prompt
The server adds the general actionLanguage and the AI personalizePrompt
settings to the AI chat system prompt, but neither was in the server sync
whitelist, so both stayed on the device and AI chat ignored them: a French
action language still got English answers. Desktop now syncs both, and
mobile syncs actionLanguage, the only one of the two it has.

A value set before this change reaches the server the next time it is
changed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 10:20:49 +08:00
DIYgod b7df9ad01e fix(i18n): stop title-casing non-English titles and labels
Entry titles and settings labels went through title-case unconditionally,
but it applies English capitalization rules, so with a French UI and French
feeds titles rendered as "Pourquoi Votre Café Est Une Arme De Défense" and
settings labels as "Afficher Le Nombre De Non Lus". CJK titles were also
touched ("iOS 27 beta" became "IOS 27 Beta").

titleCaseIfEnglish in @follow/utils only applies title case when the
language is English (en, en-US, en_GB, or franc's eng). On desktop, entry
titles use the entry language and translated titles the translation
language, falling back to the UI language when unknown; settings labels
and section titles use the UI language. Mobile grouped list cells use the
UI language too. AI chat tool names keep title case since they are English
identifiers.

Mobile no longer imports title-case directly, so the dependency moves to
@follow/utils.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 09:46:44 +08:00
DIYgod 0f35b68dea chore(mobile): tag the RNFB disableSPM line in the committed Podfile
The line was added by hand without the markers that the
@react-native-firebase/app config plugin writes, so every
`expo prebuild --no-clean` inserted a second, tagged copy of it. Wrap it
in the plugin's generated markers so prebuild leaves the committed
Podfile unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 17:09:50 +08:00
DIYgod 1bd65fbae7 fix(mobile): keep the RNFB Podfile block inside post_install
The follow-rnfb-ios-compat block was inserted a fixed 7 lines after
`post_install do |installer|`. In the Expo SDK 57 template that hook is
only the react_native_post_install call and its `end`, so a regenerated
Podfile got the block after the `end`, referenced `installer` outside
the hook and failed `pod install`.

Anchor on the `end` that closes post_install instead, so the block is
always the last thing in the hook and runs after
react_native_post_install. mergeContents now accepts an anchor function
for this. Podfiles that already contain the tagged block are still
updated in place.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 17:05:28 +08:00
DIYgod 2312fe4965 fix(mobile): stop hidden tabs from clearing the tab bar background
All tab screens stay mounted in release builds, and each
SafeNavigationScrollView wrote the shared tab bar background opacity on
layout, content size changes and scroll. A hidden tab whose content fits the
screen, such as Settings, reported that it was scrolled to the end and made
the React Native tab bar transparent over the visible tab, so its icons were
drawn straight over the content. This affects the tab bar used on iPad and on
systems before iOS 26.

Only the focused tab screen now updates the background. useTabScreenIsFocused
returns false outside a tab screen instead of throwing, which replaces
useInTabScreen.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 16:54:38 +08:00
DIYgod 181327be31 fix(mobile): keep the native tab bar visible with the iOS 27 SDK
When the app is linked against the iOS 27 SDK, UITabBarController hosts its
tab bar in a new container where setting isTabBarHidden to false no longer
clears tabBar.isHidden. CustomTabbarController hid the bar unconditionally
and only unhid it through isTabBarHidden, so the Liquid Glass tab bar never
appeared on iOS 27. Builds linked against the iOS 26 SDK still use the old
container and were not affected.

Only hide the native bar where React Native renders its own tab bar
(iPad and systems before iOS 26).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 16:54:36 +08:00
DIYgod de7ac25ca9 fix(mobile): adopt the UIScene life cycle for iOS 27 SDK builds
Apps linked against the iOS 27 SDK (Xcode 27) stop at launch on iOS 27 in
_UIApplicationEvaluateRuntimeIssueForNoSceneLifecycleAdoption unless they use
the scene-based life cycle. Enable expo-build-properties'
ios.enableSceneSupport (Expo 57.0.23+) and apply the same changes to the
committed native project, so a prebuild reproduces it:

- AppDelegate conforms to ExpoReactNativeFactoryProvider and no longer
  creates the window or starts React Native.
- Info.plist declares a scene manifest that points at Expo's
  EXExpoAppSceneDelegate, which creates the window and starts React Native.

Builds made with Xcode 26 keep working with the scene life cycle.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 16:54:20 +08:00
DIYgod 3a10c5a16d fix(mobile): return failed social sign-ins to the app
When the OAuth callback failed, the auth session followed Better Auth's
default error page, which production redirects to the folo.is homepage,
so the login sheet never reached folo:// and the app kept waiting with
no session. On real iPhones this happened to every Google sign-in that
had to go through the Google login form.

Run the Expo authorization proxy session from the app with
errorCallbackURL set to the app scheme: a rejected callback now closes
the sheet and shows a toast, and a denied consent screen counts as a
cancellation. The flow is otherwise the Expo plugin's: same proxy URL
and cookie storage.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 12:44:10 +08:00
DIYgod 59fda3cee7 fix(shared): point the dev environment at the folo.is domains
api.dev.follow.is no longer resolves to a working API, so the dev env
profile (the default in mobile development builds) could not load
anything. Use the current api.dev.folo.is and dev.folo.is hosts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 00:04:33 +08:00
DIYgod b3ef755e10 fix(ota): stream archive mirroring and skip mirrored releases
Every cron sync since 2026-09-23 exceeded the Worker memory limit. The
GitHub release list changes whenever download counts do, so each run
downloaded and fully decompressed every OTA archive on the first page
(a 58 MB desktop archive plus five mobile ones) into memory again.

- Skip releases whose stored record already matches, and only rewrite
  the record when just the metadata changed; latest pointers are still
  reconciled.
- Stream the archive through the zstd decompressor and tar extractor
  with backpressure and upload each verified file as it is extracted,
  so memory stays bounded by the largest referenced file. The download
  is cancelled as soon as extraction fails.
- Sync each release independently so one broken release no longer
  blocks the others; failures still fail the run and keep the stored
  ETag so the next run retries.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 00:04:33 +08:00
DIYgod f0945110e1 chore(desktop): declare the Markdown strikethrough dependency
The Markdown export imports mdast-util-gfm-strikethrough directly; declare it instead of relying on the hoisted copy from remark-gfm.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 00:04:33 +08:00
DIYgod 469412a1d9 fix(desktop): update push-receiver to stop a parser stack overflow
push-receiver 4.3.1 parsed MCS messages recursively and could crash the main process with "Maximum call stack size exceeded" (#4457); 4.4.0 parses them in a loop.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 00:04:33 +08:00
DIYgod ce7210f064 fix(components): unwrap the CommonJS marquee export for Vite 8
Shared pages built with Vite 8 received the whole react-fast-marquee module object for its default import and crashed with React error #130 when a title marquee rendered.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 00:04:33 +08:00
DIYgod 9cd769f2f3 fix(mobile): retry Android entry images through the image proxy
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 00:04:33 +08:00
DIYgod ec0e193f77 fix(desktop): keep the bundle Dock icon in packaged builds
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 00:04:33 +08:00
DIYgod cd2074c724 fix(ssr): render shared list entries with their own feeds
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 00:04:33 +08:00
DIYgod e33fc2d16b fix(ota): serve the newest published Android APK
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 00:04:33 +08:00
DIYgod a9e6b72642 fix(i18n): correct the zh-TW hide read subscriptions label
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 00:04:33 +08:00
DIYgod 07ea633f5e fix(desktop): keep the media preview header clear of the Windows title bar
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 00:04:33 +08:00
DIYgod 655e3051b1 fix(desktop): keep strikethrough when exporting entries to Markdown
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 00:04:32 +08:00
DIYgod 86b21acbf4 fix(desktop): stop saving toolbar order on every drag over
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 00:04:32 +08:00
DIYgod 681de464cb fix(store): include every list in the All view
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 00:04:32 +08:00
DIYgod 356ac09a7d fix(mobile): show custom subscription titles for entries
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 00:04:32 +08:00
DIYgod 167a6a1519 fix(desktop): show custom subscription titles for social posts
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 00:04:32 +08:00
DIYgod d5ffb82f4c fix(desktop): start in the tray when launched at login
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 00:04:32 +08:00
DIYgod 506137f22a fix(desktop): link the upgrade toast to the desktop release tag
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 00:04:32 +08:00
DIYgod dabd55f3a6 chore(github): point issue template support links to Discord
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 00:04:32 +08:00
DIYgod 034f5bc558 fix(desktop): open issue forms for in-app feedback
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 00:04:32 +08:00
Sangeeth Thilakarathna 489019f62e docs: add regex help for action conditions (#5036)
Co-authored-by: sanmaxdev <sanmaxdev@users.noreply.github.com>
2026-09-26 22:22:44 +08:00
dependabot[bot] 3087c362d4 build(deps): bump actions/setup-java from 5 to 6 (#5076)
Bumps [actions/setup-java](https://github.com/actions/setup-java) from 5 to 6.
- [Release notes](https://github.com/actions/setup-java/releases)
- [Commits](https://github.com/actions/setup-java/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/setup-java
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-26 21:38:19 +08:00
DIYgod 0336138fc1 Merge pull request #5095 from RSSNext/mobile-main
chore(sync): merge mobile-main into dev
2026-09-26 21:04:52 +08:00
dependabot[bot] 1bb24aaa9c build(deps): bump signpath/github-action-submit-signing-request (#5089)
Bumps [signpath/github-action-submit-signing-request](https://github.com/signpath/github-action-submit-signing-request) from 2.2 to 3.0.
- [Release notes](https://github.com/signpath/github-action-submit-signing-request/releases)
- [Commits](https://github.com/signpath/github-action-submit-signing-request/compare/v2.2...v3.0)

---
updated-dependencies:
- dependency-name: signpath/github-action-submit-signing-request
  dependency-version: '3.0'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-26 20:56:11 +08:00
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