Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
d4b9ee5
feat(settings): show web engine and version on the About page
k6G52m4Dz75W Sep 9, 2026
b080d2e
fix(settings): real WebView build via Client Hints; plain WebView-ver…
k6G52m4Dz75W Sep 9, 2026
ede3470
feat(settings): copy app + webview versions from the About card
k6G52m4Dz75W Sep 9, 2026
8753174
fix(settings): always show the version-card copy button
k6G52m4Dz75W Sep 9, 2026
5fbbe2a
feat(mobile): web engine row on About, synced with the desktop card
k6G52m4Dz75W Sep 9, 2026
55caffa
feat(mobile): probe the webview UA at startup, no book needed
k6G52m4Dz75W Sep 9, 2026
9a29d51
feat(mobile): full WebView build via Client Hints in the probe
k6G52m4Dz75W Sep 9, 2026
9204c33
refactor(mobile): fold webview version into the app-version block
k6G52m4Dz75W Sep 10, 2026
607e0dc
fix(mobile): derive the Expo SDK label instead of a stale hardcoded 55
k6G52m4Dz75W Sep 10, 2026
1250d8b
Revert "fix(mobile): derive the Expo SDK label instead of a stale har…
k6G52m4Dz75W Sep 10, 2026
2eeabca
feat(feedback): include the webview engine/build in device info
k6G52m4Dz75W Sep 10, 2026
725ec97
fix: harden webview version detection and probe lifecycle
k6G52m4Dz75W Sep 10, 2026
6c549ae
fix(worker): sanitize device fields before issue-body render
k6G52m4Dz75W Sep 10, 2026
4123dc8
fix(mobile-dev): stop Metro watching Rust build artifacts
k6G52m4Dz75W Sep 10, 2026
f065920
fix(mobile-dev): static import for changeAndPersistLanguage
k6G52m4Dz75W Sep 10, 2026
fd21600
fix: address OCR code-review findings on webview-version (11 items)
k6G52m4Dz75W Sep 11, 2026
54e4048
chore(mobile-dev): regenerate reader.html (justify engine + fullVersi…
k6G52m4Dz75W Sep 11, 2026
b361cc0
feat(settings): real toolchain versions on the About tech-stack cards
k6G52m4Dz75W Sep 11, 2026
9e17103
feat(mobile): derive the About tech-stack labels from installed versions
k6G52m4Dz75W Sep 11, 2026
04ca453
docs(mobile): drop another stale version number OCR flagged
k6G52m4Dz75W Sep 11, 2026
df0a3d8
refactor(settings): inject the TypeScript version like the Tauri one
k6G52m4Dz75W Sep 11, 2026
7f39c94
fix(mobile): show the full expo package version on the tech-stack card
k6G52m4Dz75W Sep 11, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/app-expo/assets/reader/reader.html

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions packages/app-expo/metro.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ config.resolver.blockList = [
/node_modules\/esbuild\/.*/,
/node_modules\/typescript\/.*/,
/node_modules\/@biomejs\/.*/,
// Rust build artifacts churn constantly during tauri dev/build; Metro's
// Windows fallback watcher crashes (ENOENT) trying to watch them, which
// kills the whole bundler.
/[\\/]src-tauri[\\/]target[\\/].*/,
];

// 4. Add support for TypeScript files
Expand Down
19 changes: 19 additions & 0 deletions packages/app-expo/scripts/build-reader.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,25 @@ const OUTPUT = path.resolve(ASSETS_DIR, "reader.html");
async function buildReader() {
// Create a temporary entry point
const entryContent = `
// Report the WebView's own UA (plus the full build via Client Hints —
// this page is served from the local server, a secure context, so the
// high-entropy value is readable) to RN once at startup — Settings →
// About shows it as the "WebView version" (RN itself has no real UA).
(async () => {
let fullVersion = null;
try {
const uaData = navigator.userAgentData;
if (uaData && typeof uaData.getHighEntropyValues === "function") {
const { fullVersionList } = await uaData.getHighEntropyValues(["fullVersionList"]);
const hit = (fullVersionList || []).find((b) => /Android WebView|Microsoft Edge/i.test(b.brand));
if (hit) fullVersion = hit.version;
}
} catch (e) {}
if (window.ReactNativeWebView) {
window.ReactNativeWebView.postMessage(JSON.stringify({ type: 'readany-ua', ua: navigator.userAgent, fullVersion }));
}
})();

import { makeBook, View } from "${FOLIATE_DIR.replace(/\\/g, "/")}/view.js";
import { Overlayer } from "${FOLIATE_DIR.replace(/\\/g, "/")}/overlayer.js";
import * as CFI from "${FOLIATE_DIR.replace(/\\/g, "/")}/epubcfi.js";
Expand Down
2 changes: 2 additions & 0 deletions packages/app-expo/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import { GestureHandlerRootView } from "react-native-gesture-handler";
import { KeyboardProvider } from "react-native-keyboard-controller";
import { SafeAreaProvider } from "react-native-safe-area-context";

import { UAProbe } from "@/components/common/UAProbe";
import { AnimatedSplash } from "@/components/splash/AnimatedSplash";
import { rnSessionEventSource } from "@/hooks";
import { setStreamingFetch } from "@readany/core/ai/llm-provider";
Expand Down Expand Up @@ -292,6 +293,7 @@ function AppInner() {
</NavigationContainer>
<UpdateDialog />
<FloatingTTSBubble />
<UAProbe />
</SafeAreaProvider>
</KeyboardProvider>
</GestureHandlerRootView>
Expand Down
77 changes: 77 additions & 0 deletions packages/app-expo/src/components/common/UAProbe.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { useWebviewInfoStore } from "@/stores/webview-info-store";
import { useEffect, useState } from "react";
import { View } from "react-native";
import { WebView } from "react-native-webview";

const PROBE_HTML =
"<!DOCTYPE html><html><head><meta name='viewport' content='width=device-width, initial-scale=1'></head><body></body></html>";

// The UA string is reduced (Chrome/138.0.0.0) on modern Chromium WebViews, so
// also ask Client Hints for the fullVersionList of the matching brand — the
// About screen then shows the real build (e.g. 138.0.7204.67) instead of
// x.0.0.0. Keep this brand list in sync with the desktop client-hints lookup
// in packages/app/src/lib/webview-info.ts.
const INJECTED_JS = `(async () => {
let fullVersion = null;
try {
const uaData = navigator.userAgentData;
if (uaData && typeof uaData.getHighEntropyValues === "function") {
const { fullVersionList } = await uaData.getHighEntropyValues(["fullVersionList"]);
const hit = (fullVersionList || []).find((b) => /Android WebView|Microsoft Edge/i.test(b.brand));
if (hit) fullVersion = hit.version;
}
} catch (e) {}
if (window.ReactNativeWebView) {
window.ReactNativeWebView.postMessage(JSON.stringify({ type: "readany-ua", ua: navigator.userAgent, fullVersion }));
}
})(); true;`;

/**
* A 0×0 hidden WebView that reports the system WebView's real
* navigator.userAgent (plus the full build via Client Hints) to the
* webview-info store, then unmounts.
*
* The RN layer has no real UA (App.tsx polyfills "ReactNative"), and our only
* other webview — the reader — mounts per book. This probe makes
* Settings → About show the exact engine/build immediately at app startup,
* before any book is opened. Best-effort by design: if the probe never
* reports (load failure, no bridge), it unmounts after a short timeout and
* the reader bridge fills the store on the first book open instead.
*/
export function UAProbe() {
const ua = useWebviewInfoStore((s) => s.ua);
const [gaveUp, setGaveUp] = useState(false);

useEffect(() => {
if (ua) return;
const timer = setTimeout(() => setGaveUp(true), 5000);
return () => clearTimeout(timer);
}, [ua]);

if (ua || gaveUp) return null;

return (
<View pointerEvents="none" style={{ height: 0, opacity: 0, width: 0 }}>
<WebView
source={{ html: PROBE_HTML }}
injectedJavaScript={INJECTED_JS}
onMessage={(event) => {
try {
const msg = JSON.parse(event.nativeEvent.data) as {
type?: string;
ua?: string;
fullVersion?: string | null;
};
if (msg.type === "readany-ua" && msg.ua) {
useWebviewInfoStore.getState().setUa(msg.ua, msg.fullVersion ?? undefined);
}
} catch {
// ignore probe noise
}
}}
onError={() => setGaveUp(true)}
style={{ height: 0, width: 0 }}
/>
</View>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { DarkModeSvg } from "@/components/DarkModeSvg";
import { type ThemeMode, useTheme } from "@/styles/ThemeContext";
import { useNavigation } from "@react-navigation/native";
import type { NativeStackNavigationProp } from "@react-navigation/native-stack";
import { changeAndPersistLanguage } from "@readany/core/i18n";
import { Coffee, Moon, Sun } from "lucide-react-native";
import { useTranslation } from "react-i18next";
import { Pressable, ScrollView, StyleSheet, Text, View } from "react-native";
Expand Down Expand Up @@ -46,7 +47,6 @@ export function AppearancePage() {

const handleLangChange = async (code: string) => {
try {
const { changeAndPersistLanguage } = await import("@readany/core/i18n");
await changeAndPersistLanguage(code);
} catch (err) {
console.warn("[Settings] Failed to change and persist language:", err);
Expand Down
Loading