7544 Commits

Author SHA1 Message Date
DIYgod 3985df6c81 fix(mobile): search the submitted text in discover
The submit handler searched the text from the last render, so text
pasted or typed in the same frame as the search key was ignored and
nothing was searched. Read the text from the submit event.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 08:30:44 +08:00
DIYgod 64be590570 fix(mobile): pluralize discover search counts
The result count, follower count and entries per week always used the
plural strings, so a single match read "Found 1 results" and
"1 followers". Pass the count to the base keys and let i18next pick the
form.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 08:30:42 +08:00
DIYgod 3a0f5f5b20 fix(mobile): show YouTube embeds as a thumbnail that opens the video
The article renderer is loaded from a local file, so an embedded YouTube
player gets no referrer and only shows "Video player configuration
error 153". Render YouTube video embeds as their thumbnail linking to
the watch page, which opens in the in-app browser or the YouTube app.
Other iframes and playlists keep rendering as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 08:24:53 +08:00
DIYgod 6e612963cb fix(mobile): read device type, onboarding root and player atoms written during mount
jotai 3 subscribes after the mount commit, so a value written in between
is missed until the next write. The device type arrives from an async
init while the first screens mount, the onboarding check reads routes
that screens push from mount effects, and every mounted player control
writes the pause flag and playback rate in its own mount effect, so a
sibling mounting in the same commit kept stale values. Read them with
useAtomValueRawSync.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 08:19:12 +08:00
DIYgod 6c2b46693e feat(mobile): prompt to update from the store when a newer binary is out
The OTA provider fetched the store policy but never used it. Fetch it
once per launch, from the env profile's OTA URL, and show an alert when
the App Store or Google Play has a newer version. Later puts that
version off, a prompt shows at most once per launch, and a blocked
version asks every time the app returns to the foreground. APKs from
outside Google Play are not prompted, the alert waits until the app is
in front, and while it is open the review prompt stays away.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 08:12:40 +08:00
DIYgod 2688ed9730 fix(ota): skip iOS store update prompts where Folo is off the App Store
The store version comes from the US storefront, so devices in China
mainland, where Folo was pulled from the App Store, would be asked to
install every new release from a page that says the app is unavailable.
Use the request country as a stand-in for the storefront and answer
none for iOS there.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 08:06:14 +08:00
DIYgod 3dc41a4c82 fix(mobile): ask before leaving dirty list, follow and RSSHub forms
The list, list follow and RSSHub sheets turned the dismiss gesture off
while dirty, so a swipe silently did nothing, and every one of these
forms, follow feed included, closed from the header without a word and
dropped the edits. They now set preventNativeDismiss and
promptBeforeLeave, so both paths ask first. The RSSHub header read
isDirty from a memoized child through form.formState, which never
updated; it now subscribes with useFormState. The header prompt's Exit
is destructive like the swipe prompt's.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 08:02:47 +08:00
DIYgod 1af0141d37 fix(mobile): show an error with retry when trending feeds fail to load
A failed trending request left an empty section on Discover and the
onboarding interests step with no way to try again.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 07:55:50 +08:00
DIYgod b76b8823e0 fix(mobile): let onboarding sections fill the width on phones
The readable-width wrapper only gets a style on tablets, so on phones it
shrank to its content inside the centered scroll view and the w-full
preference cards collapsed to a narrow column with text wrapping per
letter. Give the wrapper full width and keep its children centered.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 07:53:18 +08:00
DIYgod 2a52b320bb fix(mobile): ignore the empty objects the native app config writes for e2e overrides
The iOS app config ships e2eEnvProfile and e2eLanguage as {} instead of
null, so the bundle env fallback never applied and isE2EEnabled() was
true in store builds. Only accept non-empty strings and drop the unused
isE2EEnabled helper.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 07:49:26 +08:00
DIYgod 291599da72 fix(desktop): restore a maximized window when it is first shown
The restore waited for the window's show event, but on macOS the main
window shown with show() after the renderer is ready never emits it, so
the window always came back unmaximized. WindowManager.showMainWindow now
shows the main window and maximizes it the first time when it was
maximized at quit, and every place that shows the main window uses it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 07:29:42 +08:00
DIYgod 22391dac7e fix(mobile): read tab and screen option atoms written during mount
Tab screens register themselves, and screens set their options, from
effects in the same commit that mounts the components reading those
atoms. With jotai 3, useAtomValue keeps the value from before its
subscription. The JS tab bar only showed its tabs because an unrelated
re-render wrote the list again, and options a screen sets on mount were
dropped until they changed. These readers now use useAtomValueRawSync.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 07:25:01 +08:00
DIYgod 36e6a2aff9 fix(mobile): show a close button on the plans when they open as a sheet
The plan screen's header button forced canDismiss to false, so when the
plans were presented as their own sheet, as settings still does for the
legacy trial role, the header had no button at all. It now closes the
sheet when there is no screen to go back to. The header button prop
types also list the canDismiss and modal props the header passes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 07:15:39 +08:00
DIYgod b441d2c57f fix(mobile): enable the submit button once a new list has a title
The list sheet's header read isValid from form.formState inside a
memoized component that no form change re-renders. Editing worked
because the title was valid from the start, but creating a list kept
the submit button disabled after a title was typed, so no list could be
created. The header now subscribes with useFormState.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 07:13:26 +08:00
DIYgod a268f790bc fix(mobile): render screen headers written while the screen mounts
Screens like the create-list sheet put their header into the screen's
slot atom from a layout effect, in the same commit that mounts the
component rendering the slot. Since jotai 3, useAtomValue keeps the
value from before its subscription, so the header stayed empty: no
title, no close and no submit button until something re-rendered the
screen. The slot is now read with useAtomValueRawSync.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 07:13:23 +08:00
DIYgod 76891ad17f fix(mobile): label the add and back icon buttons in more headers
The create-list and add-spotlight-rule buttons and the back button of
the discover category page were icon-only without an accessible name,
so screen readers skipped or could not describe them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 07:07:14 +08:00
DIYgod f2bc9177c9 fix(mobile): let free users open the lists, actions and feeds settings
With payments enabled, tapping Lists, Actions or Feeds in settings sent
Free users to the plan screen instead, although the Free plan includes a
list and an action rule, as that plan screen itself says, and desktop
opens these pages for everyone. The server enforces the limits and the
app already shows the upgrade prompt when one is hit. Only the legacy
trial role is still sent to the plans.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 07:05:53 +08:00
DIYgod 713f63a211 fix(mobile): apply the banner status bar only while its screen is on top
The settings tab stays focused while another screen is pushed over it,
so the light status bar for the banner also applied to screens such as
Data Control, leaving white text on a light header. The banner status
bar now also requires that no screen is pushed or presented over its
own.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 07:02:03 +08:00
DIYgod 7d1fc7b494 fix(mobile): keep the status bar readable on the profile banner on Android
The profile opens full screen on Android, with the status bar over the
user banner, and it had the same dark-on-dark text as the settings tab.
The banner-aware status bar is now a shared component that the profile
uses on Android; iOS shows the profile in a sheet below the status bar.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 06:57:34 +08:00
DIYgod cf4ae71cf2 fix(mobile): keep the status bar and Edit button readable on the settings banner
On Android the Edit button's blur fallback is an opaque system
background, so its white label disappeared on a dark banner; it now
uses a translucent fill there. The status bar also kept dark text over a
dark banner on both platforms. While the banner is behind it, the status
bar now follows the banner colors, and returns to the default once the
header fades in or another tab shows.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 06:55:39 +08:00
DIYgod f18ab15713 fix(mobile): show Followed on a feed found by its typed URL
Searching for a feed URL shows a card built from the URL, without a feed
id. Its follow state looked up the subscription by that missing id, so
the card still offered Follow after the feed was followed. It now finds
the feed by URL as well.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 06:50:45 +08:00
DIYgod 7610e7ff6e fix(mobile): show the onboarding to new users again
The backup of the onboarding-finished flag used the SecureStore key
"isOnboardingFinished:persistent". expo-secure-store only accepts
alphanumerics, ".", "-" and "_" in keys and throws otherwise, so
hasFinishedOnboarding rejected for every user who had not finished it
yet, and the onboarding never opened. The key now uses a dot, and a
failing backup read or write no longer blocks the onboarding.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 06:46:45 +08:00
DIYgod f49660abb5 fix(mobile): tell a failed timeline load apart from an empty timeline
When the first page failed and nothing was stored for the view yet, the
timeline said "No results found.", as if the view had no entries. It
now shows why loading failed with a retry button, and pull to refresh
still works.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 06:31:32 +08:00
DIYgod 49a1cb14bb fix(mobile): name settings switches and radio rows for screen readers
Switches in grouped settings cells had no accessible name, so TalkBack
and VoiceOver only announced "switch" without saying which setting it
was. They now take the label of their cell. Radio rows, used for the
choices in the action rule editor, report their role and selection.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 06:25:45 +08:00
DIYgod 7070a02973 fix(mobile): turn the badge setting back off when notifications are denied
Enabling Show as Badge asks for the notification permission. When the
user denied it, the switch stayed on although no badge could show. It
now switches back off. The badge helper only checks the permission
now, as the setting is the one place that asks for it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 06:23:52 +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 9ef26e9bc4 fix(mobile): expose the selection of feeds when managing a list
The feed rows on the manage list screen only showed a check mark, so
screen readers could not tell which feeds were in the list. They are now
checkboxes with their checked state.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 06:18:54 +08:00
DIYgod 4bc0dd0ce4 fix(mobile): explain failed requests that never reached the server
When the network or the API was down, error views and toasts showed the
raw native reason from expo/fetch, such as "fetch failed:
java.net.ConnectException: Failed to connect to …". Those errors now
read "Unable to connect to the server. Please check your network
connection.", the message the app already uses for an unreachable API.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 06:15:41 +08:00
DIYgod 027e7bd4e6 fix(mobile): label the icon-only header buttons for screen readers
The back/close button on every screen, the check mark submit button and
the profile share button had no accessible name or role, so TalkBack
and VoiceOver announced them as unlabeled.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 06:09:32 +08:00
DIYgod 4427630b30 fix(mobile): close the open dialog with the Android back button
The back button went past an open dialog and navigated the screen under
it, leaving the dialog on top of a different screen. It now closes the
topmost dialog first, like its cancel button.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 06:07:51 +08:00
DIYgod a9c0ff916c fix(mobile): show toasts and dialogs above pushed screens on Android
The RootSiblingParent lived inside App, which renders as the content of
the root screen. On Android every pushed or modal screen covers the root
screen, so toasts, dialogs and loaders opened from those screens drew
underneath them, e.g. the Link Copied toast in an entry and the mark all
as read dialog in a feed. iOS was unaffected because it uses native
toasts and FullWindowOverlay. The sibling host now wraps the screen
stack.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 06:07:47 +08:00
DIYgod 942b2b1037 test(desktop): mock the audio source teardown in the entry TTS tests
playEntryTts now registers a teardown with AudioPlayer so another audio
source stops the TTS. The test's AudioPlayer mock lacked the setter, so
two tests failed. The mock has it now and the first test checks the
teardown is registered.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 05:16:40 +08:00
DIYgod 15b072e9fa fix(desktop): report the UI render time once the app is ready
The effect that tracks uiRenderInit and logs "App is ready" also ran on
the first render, while the skeleton was still showing, so every launch
sent the event twice and the first time was too early.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 05:07:39 +08:00
DIYgod f168291feb chore(desktop): remove the unused app-ready callback queue
Nothing called waitAppReady, so the queue App drained when the app was
ready was always empty. Its delay handling was also inverted.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 05:06:48 +08:00
DIYgod f1780e88a9 fix(desktop): show one error when copying a link from an entry fails
copyToClipboard already shows its own error toast before it throws, and
the link context menu showed a second one on top.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 05:06:11 +08:00
DIYgod bdf9a70a44 fix(desktop): start find in page with the previous search, not the clipboard
Cmd+F filled the find field with the clipboard text and selected it, so
anything copied before, like a password, showed up on screen and was one
Enter away from being searched. Like browser find bars, the field now
starts with the previous search.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 05:05:13 +08:00
DIYgod 82ac4372af fix(desktop): prefill the quick add panel only with a copied feed link
Cmd+N filled the URL field with whatever text was on the clipboard, so a
copied password or private message showed up on screen. It now takes
the clipboard only when it holds a single http, https or rsshub URL.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 05:03:49 +08:00
DIYgod 9e9300a890 fix(desktop): render paragraphs that hold block media as divs
A paragraph with a lone image or a video rendered the block media's
<div>s inside a <p>, which React reports as invalid nesting and browsers
may restructure. Such paragraphs now render as a <div>, keeping the
paragraph renderer's other behavior.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 05:01:33 +08:00
DIYgod 792e4750a6 fix(desktop): follow the audio source in the timeline play buttons
The play state selectors in the list and All views compared the player
source with the src of the first render, because the selector hook keeps
its callback until the deps change and none were passed. When an entry's
audio attachment arrived later, the button kept showing the wrong state.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 04:58:26 +08:00
DIYgod 46dc8a2f66 fix(mobile): keep the onboarding open for users who just signed up
The sign-in screen pops to the root 300 ms after the session arrives.
When the new-user check answered within that window, the onboarding was
presented and then closed by that pop, and it was not shown again for
the rest of the session. It now waits until no screen is open over the
tabs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 04:57:05 +08:00
DIYgod 565c94fa75 fix(mobile): open the browser once when linking a social account
linkSocial returns a redirect, so the auth client's Expo plugin already
runs the browser session through the authorization proxy before the
call resolves. The account screen then opened the raw authorization URL
a second time. The plugin also had no callback URL to return to the
app with. The screen now passes the app callback and only refreshes the
account data afterwards.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 04:55:47 +08:00
DIYgod 499f962013 fix(mobile): allow deleting webhooks and rewrite rules of an action
The webhook and rewrite rule editors could only add entries. An empty
webhook added by mistake could not be removed and made the server reject
the whole save. Webhooks can now be swiped to delete like conditions,
and each rewrite rule has a delete button.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 04:53:36 +08:00
DIYgod fd60a73bfd chore(desktop): remove a duplicate of the profile modal constants
user-profile-modal.constants.ts is an identical copy of
user-profile-modal/constants.ts and nothing imports it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 04:52:18 +08:00
DIYgod 9859ad9286 fix(mobile): open profile social links and websites without a scheme
Profiles store the account name for each social platform, but the
profile header passed it to Linking.openURL as is, so tapping a social
icon failed. A website saved without https:// failed the same way, and
both left an unhandled rejection. The header now builds the profile URL
for each platform as desktop does and adds a scheme when it is missing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 04:52:16 +08:00
DIYgod 84f12641a4 fix(mobile): make the reset database button on the migration error work
The button imported deleteAsync from the expo-file-system root, which
since SDK 54 only exports stubs of the legacy API that throw. A user
stuck on the migration error screen could not reset the database. It
now uses the legacy module like the other database deletes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 04:51:06 +08:00
DIYgod 19c001f46c fix(mobile): report a failed list delete
Deleting a list removes it optimistically and restores it when the
request fails, but the error was neither caught nor shown. The sheet
stayed open with the list back in place and nothing explained why.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 04:50:25 +08:00
DIYgod e570ea5d47 fix(mobile): show discover search errors with a retry
A failed feed or list search rendered an empty page with no message,
and submitting the same keyword again did not search again because the
query key did not change. Failed searches now show the error with a
retry button, and submitting the same keyword refetches.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 04:50:02 +08:00
DIYgod a04b1d9755 fix(mobile): close the full screen player when playback ends
When TTS finished or failed while the full screen player was open, the
player rendered nothing but its transparent modal stayed in place. The
app underneath was visible but did not take touches, and iOS had no
gesture to leave. The screen now dismisses itself once nothing is
playing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 04:49:15 +08:00
DIYgod 51d4968936 fix(mobile): stop the reader spinner when an entry has no content
An entry without content switches to readability automatically, and the
spinner stayed until readability content arrived. When the entry had no
URL, the request failed (offline or a blocked site) or it returned
nothing, the spinner never went away, and the failed request was an
unhandled rejection.

The screen now tracks the readability request, and once nothing is
pending a missing body shows a message with an Open in Browser button.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 04:48:12 +08:00
DIYgod eeaa32d73a fix(mobile): open url-only follow links by their feed URL
follow://add?url=... without a type opened the follow sheet for a feed
with no id, and feed?url=... ignored the URL, so both kept spinning.
Links without an id now look the feed up by URL as the url flow does,
and list links need an id.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 04:46:42 +08:00