From 75a7193da6bdc98bdca78d00b1cbd1bcd9364d08 Mon Sep 17 00:00:00 2001 From: jwinterm Date: Fri, 18 Sep 2026 13:09:54 -0400 Subject: [PATCH] fix(reports): say which build produced the error An error report that cannot be tied to a binary tells us a bug exists and nothing about whether we already fixed it. Of the ten reports received since May, five carry the Wownero addresses crash that shipped fixed in 1.0.2, and we could only tell by reading timestamps buried in the traces themselves. Two gaps, both closed here. The body carried "App Version: 1.0.0" without the build number, and 1.0.0 shipped as Play builds 9 and 10 which encrypted under different keys, so the version alone cannot identify the binary that wrote a wallet we are being asked to heal. And the body is the part of this flow we do not control. It reaches us only if the user pastes what we put on their clipboard. On 2026-09-17 somebody typed a sentence into the trace box instead, and the report arrived with no version, no platform and no stack at all. So the app now also puts its version and platform in the query string of the link it opens, where the form reads it rather than the user. Someone who reaches the page without the app simply reports nothing, which is honest and stays empty rather than guessing. --- lib/utils/exception_handler.dart | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/lib/utils/exception_handler.dart b/lib/utils/exception_handler.dart index 5d2c11603..9836e9ddb 100644 --- a/lib/utils/exception_handler.dart +++ b/lib/utils/exception_handler.dart @@ -103,7 +103,17 @@ class ExceptionHandler { // don't bother trimming here. await Clipboard.setData(ClipboardData(text: fullBody)); - final uri = Uri.parse(_reportUrl); + // Carry the build in the URL as well as in the pasted body. A report is + // only useful if we can tell which binary produced it, and the body is + // the one part of this flow we do not control: on 2026-09-17 somebody + // typed a sentence into the trace box instead of pasting, and the report + // arrived with no version, no platform and no stack. The query string + // survives that, because the form reads it rather than the user. + final packageInfo = await PackageInfo.fromPlatform(); + final uri = Uri.parse(_reportUrl).replace(queryParameters: { + 'v': '${packageInfo.version}+${packageInfo.buildNumber}', + 'p': _platformName(), + }); final launched = await launchUrl(uri, mode: LaunchMode.externalApplication); if (!launched) { printV('Could not open report URL: $uri'); @@ -318,6 +328,15 @@ class ExceptionHandler { "support for coin removed, your seedphrase:" ]; + static String _platformName() { + if (Platform.isAndroid) return 'Android'; + if (Platform.isIOS) return 'iOS'; + if (Platform.isLinux) return 'Linux'; + if (Platform.isMacOS) return 'macOS'; + if (Platform.isWindows) return 'Windows'; + return 'unknown'; + } + static Future _addDeviceInfo(File file) async { final packageInfo = await PackageInfo.fromPlatform(); final currentVersion = packageInfo.version; @@ -344,8 +363,12 @@ class ExceptionHandler { deviceInfo["Platform"] = "Windows"; } + // The build number matters as much as the version: 1.0.0 shipped as Play + // builds 9 and 10, which encrypted under different keys, so "1.0.0" alone + // cannot tell us which binary wrote a wallet we are being asked to heal. await file.writeAsString( - "App Version: $currentVersion\nApp Name: $appName\nPackage: $package\n\nDevice Info $deviceInfo\n\n", + "App Version: $currentVersion+${packageInfo.buildNumber}\n" + "App Name: $appName\nPackage: $package\n\nDevice Info $deviceInfo\n\n", mode: FileMode.append, ); }