Skip to content

feat(about): 关于页技术栈卡片显示真实版本号 - #775

Open
k6G52m4Dz75W wants to merge 22 commits into
codedogQBY:mainfrom
k6G52m4Dz75W:feat/about-tech-stack-versions
Open

feat(about): 关于页技术栈卡片显示真实版本号#775
k6G52m4Dz75W wants to merge 22 commits into
codedogQBY:mainfrom
k6G52m4Dz75W:feat/about-tech-stack-versions

Conversation

@k6G52m4Dz75W

@k6G52m4Dz75W k6G52m4Dz75W commented Sep 11, 2026

Copy link
Copy Markdown
Contributor

动机

关于页的技术栈卡片此前是纯硬编码标签,已经发生漂移:移动端写的是
"Expo SDK 55",而项目实际安装并锁定的是 expo 54.0.33(lockfile、
node_modules、上游 pin 三方一致)。版本号一旦写死,升级时必然忘记同步,
而关于页恰恰是用户排查问题时对照环境的地方。

本 PR 让每张卡片显示真实生效的版本号,并保证来源不可能漂移。

方案

卡片 平台 版本来源 当前值
Tauri 桌面 vite define 从 src-tauri/Cargo.lock 解析 tauri 包版本注入 2.10.2
React 桌面 运行时 React.version 19.1.0
TypeScript 桌面 vite define 从解析到的 typescript/package.json 注入 5.8.3
Expo SDK 移动 expo/package.json 主版本推导(自 SDK 51 起主版本 == SDK 号) 54.0.33
React Native 移动 运行时 Platform.constants.reactNativeVersion 0.81.5
Foliate 双端 vendored 副本无有效版本号(0.0.0),保持不显示
SQLite 移动 expo-sqlite 不暴露引擎版本常量,保持不显示

选型说明:

  • Tauri 不取自 @tauri-apps/api:api 与核心 crate 存在 patch 级偏差
    (2.10.1 vs 2.10.2),且其 exports map 挡住了 package.json 子路径导入;
    Cargo.lock 里的是应用真正构建Against的版本。
  • TypeScript 走注入而非 import:应用源码不应为一条数据 import 仅为
    开发期存在的包,vite.config.ts 通过 createRequire 解析(不受依赖
    提升影响),manifest 不再被打进客户端 bundle。
  • 取不到可靠来源就不显示:缺失版本时卡片优雅退化为纯名称,不猜、
    不兜底假数据。

变更明细

  • 桌面 vite.config.ts:新增 readTauriVersion()(解析 Cargo.lock)与
    readTypeScriptVersion()(createRequire 解析),define 注入
    __TAURI_VERSION__ / __TS_VERSION__,读取失败时注入空串
  • 桌面 AboutSettings.tsx:TECH_STACK 增加可选 version 字段,版本号以
    弱化样式缀在卡片名称旁
  • 移动 AboutScreen.tsxEXPO_SDK_LABEL / REACT_NATIVE_VERSION
    上述来源推导,替换硬编码标签
  • 移动 expo-platform-service.ts:清理注释中残留的硬编码版本数字
    ("Expo SDK 55+"、"expo-file-system v55",后者实际安装 19.0.21),
    改为无版本号写法,杜绝再次漂移

验证

  • 双端 tsc --noEmit 通过(桌面仅存在 1 个与本次改动无关的存量
    auto-metadata.ts 错误,已用 stash 对照确认基线即有)
  • 桌面 vite build 通过,产物中确认内联了 Tauri",version:"2.10.2"
    与 TS 版本串,且 typescript manifest 特征串已从 bundle 中消失
  • 构建版实测:桌面 4 张卡片显示 3 个版本号(Foliate 无版本),安卓
    显示 2 个(Foliate.js / SQLite 无版本),符合设计

备注

  • 本分支基于 feat/webview-version(堆叠提交),关于页版本区块的
    WebView 行来自该分支;建议在其合并后基于最新 main 重读,或两个 PR
    按序合并
  • 已通过 open-code-review 两轮审查(4 条 low 发现:1 条修复、3 条评估
    后记录结论)

Reader features vary per engine (@layer/:has floors, execCommand,
clipboard behavior), so bug reports need the engine name and build, not
just the app version. Add a Web-engine row under the app version in
Settings → About, parsed from the UA per Tauri platform:

- Windows: WebView2 (Edg/ token = real Evergreen build)
- Android: system WebView (Chrome/ token, ; wv) marker)
- macOS/iOS: WebKit via the Version/ token (follows the system release,
  unlike readest's frozen AppleWebKit/605.1.15 parse)
- Linux: WebKitGTK without a version — the UA carries only frozen
  tokens; the real one is the system libwebkit2gtk package
- plain vite dev in a browser: generic Chrome/Edge/Firefox/Safari labels

Labels localized for all 7 locales (settings.webviewEngine).
…sion label

The UA string is reduced (Edg/152.0.0.0 on a 152.0.4191.62 WebView2
runtime), so the UA-parsed version showed zeros after the major.
Fetch fullVersionList via User-Agent Client Hints for Chromium-family
engines (WebView2 -> Microsoft Edge, Android WebView -> Android
WebView, Chrome -> Google Chrome) and fall back to the UA value
elsewhere. Rename the About row label to the plainer
settings.webviewVersion in all locales.
Hovering the version card reveals a copy button (readest's About window
has the same affordance — mobile users can't select the version string
for bug reports). Copies both lines at once:

  ReadAny 1.3.5
  WebView2 152.0.4191.62

Feedback via icon swap + toast (common.copied). Uses the same
navigator.clipboard.writeText as the chat/markdown copy buttons.
Hover-reveal hid the affordance; keep it visible like the neighboring
check-update button.
The webview-version feature was desktop-only; bring it to the Expo app's
About screen:

- Move the UA parser to core (packages/core/src/utils/webview-info.ts)
  so both apps produce identical engine/version labels; the desktop
  wrapper keeps its Tauri check and Client Hints lookup.
- The mobile app has no real UA (App.tsx polyfills "ReactNative"), so
  reader.html now reports its own navigator.userAgent over the RN bridge
  once at startup (type: readany-ua); use-reader-bridge stores it in a
  small zustand store. It is the system WebView's actual UA — the exact
  engine the book content renders in.
- AboutScreen gains a tappable WebView-version row: touch has no hover,
  so the row itself is the affordance — tapping copies both versions
  (ReadAny vX + engine label) via expo-clipboard, with a transient
  check-mark feedback. Before a book is opened it shows the engine name
  without a build.
readest's mobile app can show the engine at launch because its entire
UI runs inside the system WebView; our native UI has no webview until a
book is opened, so the About screen previously fell back to an
engine-only label. Add a 0x0 hidden probe WebView that posts its
navigator.userAgent to the webview-info store on first launch and then
unmounts — the About screen shows the full engine/build immediately,
and the reader bridge keeps it fresh afterwards.
Android system WebViews reduce their UA too (Chrome/138.0.0.0), so the
probe now also asks Client Hints for fullVersionList and stores the real
build alongside the UA; the reader bridge keeps it when its messages
carry no version. About label prefers the full build.
Drop the separate labeled section and its copy hint — show the web
engine build as a second line right under v1.3.6, and make the
two-line block itself tappable to copy both. Matches the desktop
About card's behavior with a flatter mobile layout.
The installed expo is 54.0.33 (SDK 54); the About screen hardcoded
"Expo SDK 55" and drifted. Derive the label from the expo package's
major version (matches the SDK number since SDK 51) so it follows
future upgrades, and fix the platform-service comment.
Bug reports need the engine axis (justify fallbacks, clipboard, and CSS
capability floors all vary per webview build). Add an optional
DeviceInfo.webview to the shared type, fill it on both apps:

- desktop: full build via Client Hints (getWebviewLabel)
- mobile: engine + build from the reader-UA store (probe/bridge)

and render it as a WebView line in the worker's GitHub issue body
(packages/feedback-worker), falling back to "unknown" for old
clients. The field is optional, so payloads from versions without this
change keep submitting fine.
OCR code review follow-ups (all severities):

- core parser: Android UA without the wv marker no longer falls through
  to WebKitGTK; Apple platforms fall back to the OS x_y token when the
  Version/ token is absent (embedded WKWebView UAs often omit it)
- UAProbe: unmount after a 5s timeout or WebView error instead of
  staying mounted all session; guard the injected postMessage; brand
  list cross-referenced with the desktop client-hints lookup
- AboutScreen/AboutSettings: copy feedback timer cleared on unmount;
  copied confirmation renders even when the engine label is empty;
  copy payload format unified across platforms
- feedback worker: device fields sanitized before issue-body render
- webview-info parser: table-driven unit tests (13 cases) added
Device fields are client-supplied and rendered verbatim into issue
Markdown — strip line breaks and Markdown-significant characters
(backticks, brackets, angle brackets, bangs) and cap length so they
cannot inject headings, links, or mentions.
Metro's Windows fallback watcher fatally crashed (ENOENT) on transient
files under packages/app/src-tauri/target while the desktop tauri
dev/build ran, killing the mobile bundler. Exclude src-tauri/target
from the Metro file map via blockList.
The settings/onboarding language handlers dynamically imported
@readany/core/i18n; in dev Metro serves that as an on-demand module
bundle which failed to load on this Windows setup, so the language
switch silently died in the catch. The module is already in the
startup bundle (App.tsx imports it), so import it statically.
Medium:
- feedback worker: sanitize strips @ so device fields cannot inject
  GitHub mentions; also flattens line breaks and caps length
- UAProbe: unmounts after a 5s timeout or WebView error instead of
  staying mounted all session when the injected script never reports
- AboutScreen: copied confirmation renders even when the engine label
  is empty (tap is always acknowledged)
- core parser: Apple platforms fall back to the OS x_y token when the
  Version/ token is absent (embedded WKWebView UAs often omit it)
- build-reader entry: reports the Client Hints full build with the UA
  (the reader page is a secure context, unlike the probe's inline HTML)

Low:
- store: fallback keyed on a missing engine; ?? -> || so an empty
  fullVersion falls back to the UA parse; fallback lookup map instead
  of nested ternaries
- lib/webview-info: brand match loosened to /Microsoft Edge/i (WebView2
  reports a distinct brand); reuses core formatWebviewInfo; drops the
  dead || version
- AboutSettings: type=button on the copy/update buttons; '...' webview
  placeholder; failure surfaces via toast.error; timer cleanup on
  unmount
- AboutScreen: copy payload format unified with desktop (no v prefix);
  timer cleanup on unmount
- core parser: table-driven unit tests (13 UA samples)
The Tauri/React/TypeScript cards now show the versions the app actually
runs on, read from sources that cannot drift:

- Tauri: injected at build time by vite define, parsed from the
  src-tauri/Cargo.lock "tauri" package entry (2.10.2). @tauri-apps/api
  releases in lockstep but can differ by a patch, and its exports map
  blocks package.json imports, so the lockfile is the honest source.
- React: React.version at runtime (19.1.0).
- TypeScript: the resolved typescript/package.json import.

Foliate has no meaningful version (vendored, 0.0.0) so it stays
version-less; missing versions degrade to the plain name.
The About screen hardcoded "Expo SDK 55" while the installed expo is
54.0.33 (the upstream pin is ~54.0.33 too) — the label had drifted.
Derive it from the expo package's major (== SDK number since SDK 51),
and take React Native from Platform.constants.reactNativeVersion
(0.81.5) instead of an unnamed label.

Also drop the hardcoded SDK number from the expo-platform-service doc
comment so it cannot go stale again.
The section comment said "expo-file-system v55" while the installed
expo-file-system is 19.0.21 — same drift class the previous commit
removed from the header comment; this one slipped through.
Application source no longer imports a dev-only package's manifest for
one field. vite.config.ts reads typescript/package.json through
createRequire (so hoisting cannot break the path) and defines
__TS_VERSION__ next to __TAURI_VERSION__ — same source pattern, same
empty-string fallback, and the TypeScript manifest stays out of the
client bundle (OCR review follow-up).
"Expo SDK 54" only tracked the SDK release line; an in-SDK patch bump
(54.0.33 → 54.0.35) was invisible — the version display's whole purpose
is environment reproduction. "Expo 54.0.33" matches the granularity of
the other cards (Tauri 2.10.2, React Native 0.81.5) and loses nothing:
the major IS the SDK number.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant