Bring the workout app and status web UI up to latest stable
Some checks are pending
dependency freshness / dependencies on newest stable (push) Waiting to run
md-naming / markdown naming convention (push) Waiting to run
pre-commit / pre-commit (push) Waiting to run
Tests / test (push) Waiting to run
Tests / lock-popup-safety (push) Waiting to run
release-apk / release (APK) (push) Waiting to run

Pre-existing, and surfaced only because the pre-push CI mirror runs the
freshness gate over --all-files. Twelve findings across three ecosystems,
none of them related to the free-day work they were blocking.

Flutter: permission_handler 13.0.2, vibration 3.2.1, and
very_good_analysis ^11.0.0. The major analyzer bump newly flags 10
async_return_with_no_await sites across the Firebase, storage and sync
services; each is now `return await` rather than suppressed. Without the
await the future escapes the enclosing async function, so a failure
surfaces at the caller with a stack trace that no longer names the code
that made the call -- which is precisely the wrong behaviour in a sync
service whose errors are meant to be caught and retried locally.

npm: vitest and @vitest/coverage-v8 4.1.11 -> 5.0.0 (the pair has to move
together), plus eslint, typescript-eslint, globals, the @types packages
and user-event. The major vitest bump needed no test changes.

flutter analyze clean, 411 workout-app tests pass. vitest 52 tests pass
on 5.0.0, eslint clean, tsc --build clean.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Krzysztof Rudnicki 2026-09-07 15:46:07 +02:00
parent d6e8c0973f
commit fbf09fdfef
9 changed files with 330 additions and 420 deletions

View File

@ -59,7 +59,7 @@ SecureCredentialStore credentialStore() {
read: (key) async {
final file = _desktopCredentialFile();
if (!file.existsSync()) return null;
return file.readAsString();
return await file.readAsString();
},
write: (key, value) async {
final file = _desktopCredentialFile();
@ -86,7 +86,7 @@ SecureCredentialStore credentialStore() {
Future<FirebaseAccount?> loadAccount() async {
// Linux desktop reads the shared PC credential instead of the keystore,
// so a fresh desktop install is connected the moment it starts.
if (Platform.isLinux) return _accountFromDesktopConfig();
if (Platform.isLinux) return await _accountFromDesktopConfig();
try {
return FirebaseAccount.tryParse(
await _secure.read(key: kFirebaseAccountKey),
@ -149,9 +149,9 @@ Future<FirebaseRestClient?> openFirebase() async {
// marker beside it. Treating the marker as the source of truth is what
// made a phone with a live session sync over GitHub and 401 forever --
// the credential was in the keystore the whole time, unused.
return _clientFromStoredSession();
return await _clientFromStoredSession();
}
return firebaseClientFor(
return await firebaseClientFor(
config: kProject.configFor(account.email),
store: credentialStore(),
// A Google-provisioned account stores an empty password.

View File

@ -75,7 +75,7 @@ Future<String?> googleIdToken({
Future<String?> Function()? signInFn,
String serverClientId = kServerClientId,
}) async {
if (signInFn != null) return signInFn();
if (signInFn != null) return await signInFn();
if (serverClientId.isEmpty) {
// A build without --dart-define=GOOGLE_SERVER_CLIENT_ID would otherwise
// fail inside the plugin with a far less obvious message.

View File

@ -207,7 +207,7 @@ class StorageService {
Future<List<Map<String, dynamic>>> getWorkoutHistory({
int limit = 60,
}) async {
return _db.rawQuery(
return await _db.rawQuery(
'SELECT date, workout_type, duration_seconds, succeeded, json '
'FROM workout_history ORDER BY date DESC LIMIT ?',
[limit],

View File

@ -102,7 +102,7 @@ extension StorageServiceSchema on StorageService {
final dir = await getApplicationSupportDirectory();
return dir.path;
}
return getDatabasesPath();
return await getDatabasesPath();
}
// coverage:ignore-end

View File

@ -119,7 +119,7 @@ class WorkoutSyncService {
log('WorkoutSyncService.push skipped: $reason', level: 900);
return const PushResult(pushed: false, reason: reason);
}
return _session(settings.token).run(
return await _session(settings.token).run(
operation: 'push',
successReason: 'pushed',
failurePrefix: 'push failed',
@ -165,7 +165,7 @@ class WorkoutSyncService {
}
// No addition: whatever this device already has, unchanged. The point is
// the round trip, not the payload.
return _session(settings.token).run(
return await _session(settings.token).run(
operation: 'syncNow',
successReason: 'synced',
failurePrefix: 'sync failed',
@ -183,7 +183,7 @@ class WorkoutSyncService {
log('WorkoutSyncService.pushManual skipped: $reason', level: 900);
return const PushResult(pushed: false, reason: reason);
}
return _session(settings.token).run(
return await _session(settings.token).run(
operation: 'pushManual',
successReason: 'pushed',
failurePrefix: 'push failed',

View File

@ -557,18 +557,18 @@ packages:
dependency: "direct main"
description:
name: permission_handler
sha256: e7317eb2eb611d1bf0386b6b974be9c50449f975f19a90a7b8ea013550302b30
sha256: e6bcecadfe40df6b888141f23cbaf6ba6c3da49fb33f6ff887d060dd48544fbd
url: "https://pub.dev"
source: hosted
version: "13.0.1"
version: "13.0.2"
permission_handler_android:
dependency: transitive
description:
name: permission_handler_android
sha256: d7676c6fcf2f0b92537ec41476a6ead45a00b0d8bbb852395a6f9f33f49d6242
sha256: b1ce660f4d0dcaffdf2605a19544c129fb5adda4e6fd039992ae356c4e437039
url: "https://pub.dev"
source: hosted
version: "14.0.0"
version: "14.1.0"
permission_handler_apple:
dependency: transitive
description:
@ -915,18 +915,18 @@ packages:
dependency: "direct dev"
description:
name: very_good_analysis
sha256: "481af67ab5877af20325251dc215a4ebac7666a1c8cf09198ffd457bc612b33d"
sha256: "0bd5eb8d6be76aa0d71e70cbdae6cac8f784f761170568e3c2214a6948c7f6d3"
url: "https://pub.dev"
source: hosted
version: "10.3.0"
version: "11.0.0"
vibration:
dependency: "direct main"
description:
name: vibration
sha256: c6c25eb7acadfd231925a60004736443cbf63f1eb5740d6dd83a1bc09cf00f6d
sha256: "3424f7856b971a2ce94d22be3a3abfb5b9b5984562fdedd621f4e67574be7254"
url: "https://pub.dev"
source: hosted
version: "3.2.0"
version: "3.2.1"
vibration_platform_interface:
dependency: transitive
description:
@ -984,5 +984,5 @@ packages:
source: hosted
version: "3.1.3"
sdks:
dart: ">=3.12.2 <4.0.0"
dart: ">=3.13.0 <4.0.0"
flutter: ">=3.44.0"

View File

@ -24,7 +24,7 @@ dependencies:
http: 1.6.0
path: 1.9.1
path_provider: 2.1.6
permission_handler: 13.0.1
permission_handler: 13.0.2
shared_preferences: 2.5.5
sqflite: 2.4.3
# Linux desktop has no sqflite plugin; the FFI factory drives the same
@ -37,7 +37,7 @@ dependencies:
path: sync_settings_ui
url_launcher: 6.3.2
uuid: 4.6.0
vibration: 3.2.0
vibration: 3.2.1
dev_dependencies:
audioplayers_platform_interface: 7.2.0
@ -47,7 +47,7 @@ dev_dependencies:
flutter_lints: ^6.0.0
flutter_test:
sdk: flutter
very_good_analysis: ^10.3.0
very_good_analysis: ^11.0.0
flutter:
uses-material-design: true

688
web/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -22,20 +22,20 @@
"@testing-library/dom": "10.4.1",
"@testing-library/jest-dom": "7.0.1",
"@testing-library/react": "16.3.3",
"@testing-library/user-event": "14.6.6",
"@types/node": "26.4.0",
"@testing-library/user-event": "14.6.7",
"@types/node": "26.4.1",
"@types/react": "19.2.18",
"@types/react-dom": "19.2.5",
"@types/react-dom": "19.2.7",
"@vitejs/plugin-react": "6.1.1",
"@vitest/coverage-v8": "4.1.11",
"eslint": "10.9.1",
"@vitest/coverage-v8": "5.0.0",
"eslint": "10.10.0",
"eslint-plugin-react-hooks": "7.1.1",
"eslint-plugin-react-refresh": "0.5.5",
"globals": "17.11.0",
"eslint-plugin-react-refresh": "0.5.6",
"globals": "17.12.0",
"jsdom": "30.0.1",
"typescript": "6.0.3",
"typescript-eslint": "8.68.0",
"typescript-eslint": "8.69.0",
"vite": "8.2.2",
"vitest": "4.1.11"
"vitest": "5.0.0"
}
}