Repository navigation
release(mobile): Release v0.5.11 - #5102
Merged
Merged
Conversation
release(desktop): Release v1.14.0
chore(sync): merge main into dev
Signing out (and the other runtime reloads) crashed the app with SIGSEGV on the expo.modules.fetch.RequestQueue thread. Expo's native fetch keeps a closure that owns the JS promise until the response reaches a terminal state; reloading while a request was pending destroyed that closure against the torn-down runtime. Sign-out always had requests pending: clearing the cookie refetches the session queries and popping to the root loads the guest timeline. Expo also installs its fetch as the global `fetch`, so every request in the app goes through it. `network-activity.ts` wraps the global fetch (from the polyfill entry) and the explicit `expo/fetch` imports, tracks each request until the native response reports `readyForJSFinalization`, and can abort them all. `reloadApp` cancels the queries, aborts the in-flight requests, waits for the network to go idle (three seconds at most) and only then reloads. Every `reloadAppAsync` call site goes through it. Verified with the Maestro iOS auth journey against production: register, sign out and log in pass with no crash report. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> (cherry picked from commit a662c08)
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>
The Thumb's `before:` pseudo-element was meant to enlarge its hit target, but its negative offsets plus `-translate-x-full -translate-y-full` place the box 44px to the left of the scrollbar and half a thumb height above it. Tailwind 3 dropped the unitless `min-w-[44]`, so the box was inert; Tailwind 4 compiles it to `min-width: 44px`, turning it into an invisible strip in the scrollbar layer that steals hover and swallows clicks on the right edge of timeline entries (and the right edge of wide views). Radix already handles pointer down on the whole track, so drop the pseudo-element instead of repositioning it. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
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](SignPath/github-action-submit-signing-request@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>
chore(sync): merge mobile-main into dev
Bumps [actions/setup-java](https://github.com/actions/setup-java) from 5 to 6. - [Release notes](https://github.com/actions/setup-java/releases) - [Commits](actions/setup-java@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>
Co-authored-by: sanmaxdev <sanmaxdev@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e bar Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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. 93e4a3b 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 b0f5552 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>
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>
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>
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>
…code Nothing imports CustomIntegrationValidator, DiscoverForm (replaced by UnifiedDiscoverForm), TimelinePromptSection (taken off the AI settings page in 6913079) 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>
- 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>
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>
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>
…wnloads 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>
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 4ab06f4), 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>
…eenshots - 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>
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>
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>
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>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
v0.5.11