diff --git a/README.md b/README.md index 9c13186..3d7d8f6 100644 --- a/README.md +++ b/README.md @@ -57,7 +57,24 @@ read the receipts. No browser, no terminal. value); the scoreboard shows observed routing outcomes, not promises. The read-only training card reports the local run as it is. -## Run it +## Install it + +Flywheel ships as a Windows installer: the app and the engine arrive +together, no Python, no PATH setup. Download the latest +`Flywheel-Setup--x64.exe` from the +[releases page](https://github.com/HarperZ9/flywheel-desktop/releases); +every release is built from its version tag by a public pipeline and ships +with a `SHA256SUMS.txt` receipt. Verify the copy you downloaded: + +``` +Get-FileHash Flywheel-Setup-0.2.1-x64.exe -Algorithm SHA256 +``` + +The installer is not code-signed yet, so Windows SmartScreen will warn on +first run; the hash is the substitute, proving the file you hold is the file +the pipeline built. + +## Run it from source Install the engine and start it once: @@ -72,7 +89,9 @@ Then run the app: flutter run -d windows --release ``` -If the engine is offline the app says so and can start it for you. +If the engine is offline the app says so and can start it for you. An +installed app launches its own bundled engine by absolute path; a dev +checkout falls back to `flywheel` on PATH. ## Design diff --git a/lib/version.dart b/lib/version.dart new file mode 100644 index 0000000..c809a41 --- /dev/null +++ b/lib/version.dart @@ -0,0 +1,9 @@ +// version.dart — the app's release identity, surfaced in the UI. +// +// One constant, no plugin. The truth lives in pubspec.yaml; a drift gate +// (test/version_truth_test.dart) fails the suite the moment the two +// disagree, so this can never silently lie about what is running. + +const String appVersion = '0.2.1'; +const String appPublisher = 'ZentropyLabs'; +const String appReleases = 'github.com/HarperZ9/flywheel-desktop/releases'; diff --git a/lib/widgets/appearance_panel.dart b/lib/widgets/appearance_panel.dart index 3236faf..da53361 100644 --- a/lib/widgets/appearance_panel.dart +++ b/lib/widgets/appearance_panel.dart @@ -8,6 +8,7 @@ import 'package:flutter/material.dart'; import '../services/settings.dart'; import '../theme/flywheel_theme.dart'; +import '../version.dart'; import 'fw.dart'; const kTextChoices = [ @@ -121,6 +122,20 @@ class _AppearanceFormState extends State<_AppearanceForm> { ), ], ), + const SizedBox(height: FwLayout.s4), + Divider(height: 1, color: t.hairline), + const SizedBox(height: FwLayout.s3), + // The app's release identity. The version constant is drift-gated + // against pubspec by the test suite, so this line cannot lie. + Text('Flywheel v$appVersion · $appPublisher', + style: fwMono(t, size: 11.5, color: t.inkMuted)), + const SizedBox(height: 3), + SelectableText(appReleases, style: fwMono(t, size: 10.5, color: t.inkFaint)), + const SizedBox(height: 3), + Text( + 'Every release ships with a SHA-256 receipt beside the installer; ' + 'verify your download against it.', + style: TextStyle(fontSize: 11, color: t.inkFaint, height: 1.4)), ], ); } diff --git a/test/version_truth_test.dart b/test/version_truth_test.dart new file mode 100644 index 0000000..67db5ff --- /dev/null +++ b/test/version_truth_test.dart @@ -0,0 +1,19 @@ +// The drift gate for lib/version.dart: the constant the UI shows must be +// the version pubspec ships. The moment a release bump touches one and not +// the other, this fails, so the About surface can never lie. +import 'dart:io'; + +import 'package:flutter_test/flutter_test.dart'; + +import 'package:flywheel_desktop/version.dart'; + +void main() { + test('appVersion matches pubspec.yaml', () { + final line = File('pubspec.yaml') + .readAsLinesSync() + .firstWhere((l) => l.startsWith('version:')); + final pubspec = line.split(':')[1].trim().split('+')[0]; + expect(appVersion, pubspec, + reason: 'bump lib/version.dart together with pubspec.yaml'); + }); +}