mirror of
https://github.com/Such-Software/hash-wallet.git
synced 2026-10-17 05:41:29 +02:00
The About screen has been showing Cake Wallet's app icon, and the receive QR page — the screen a user screenshots to get paid — has been showing Cake's wordmark. In CI builds only, which is why nobody caught it. compile_graphics.sh compiles res/pictures/*.svg into assets/new-ui/*.svg.vec, and every platform workflow runs it. That makes res/pictures the source and assets/new-ui the output. The August de-cake pass replaced three files under assets/new-ui and left res/pictures untouched, so each CI build faithfully recompiled Cake's artwork straight over the top of ours. Locally it looked right, because locally nobody re-runs the compiler. Three files, all of them branding: hash_squircle_icon.svg, hash-wordmark.svg, hash-wordmark-light.svg. The sources are now the Hash Bags artwork, so the compiler produces what the filenames have claimed since August. This is the third time somebody else's artwork has reached users through this repo, and the second time an audit missed it, so it gets a test rather than another careful look: any SVG present in both res/pictures and assets/new-ui must be byte-identical. Drift means a hand-edit that the next CI build will silently revert. The test also asserts it compared something, because a silent zero is how the previous two got through. AGENTS.md has had the rule the whole time: generated files are never hand-edited, fix the source and regenerate. The trap here is that the generated copies sit beside the sources under identical names, and the ones the Dart code references are the generated ones.
58 lines
2.5 KiB
Dart
58 lines
2.5 KiB
Dart
import 'dart:io';
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
|
|
// compile_graphics.sh compiles res/pictures/*.svg into assets/new-ui/*.svg.vec,
|
|
// and CI runs it on every platform. That makes res/pictures the source and
|
|
// assets/new-ui the output, which is easy to get backwards: the files sit side
|
|
// by side, have identical names, and the ones under assets/ are the ones the
|
|
// Dart code names.
|
|
//
|
|
// Getting it backwards shipped Cake Wallet's artwork twice. The August de-cake
|
|
// pass replaced the copies under assets/new-ui and left res/pictures untouched,
|
|
// so every CI build recompiled Cake's icon over the top: their app icon on the
|
|
// About screen, and their wordmark on the receive QR page, which is the screen
|
|
// a user screenshots to get paid. A local build looked right, because locally
|
|
// nobody re-runs the compiler.
|
|
//
|
|
// So: any SVG present in both places must be byte-identical. If they drift, the
|
|
// one under assets/ is a hand-edit that the next CI build will silently revert.
|
|
void main() {
|
|
test('brand SVGs under assets/new-ui match their res/pictures source', () {
|
|
final assetsDir = Directory('assets/new-ui');
|
|
final sourceDir = Directory('res/pictures');
|
|
expect(assetsDir.existsSync(), isTrue, reason: 'no assets/new-ui — wrong CWD?');
|
|
expect(sourceDir.existsSync(), isTrue, reason: 'no res/pictures — wrong CWD?');
|
|
|
|
final drifted = <String>[];
|
|
var compared = 0;
|
|
for (final f in assetsDir.listSync().whereType<File>()) {
|
|
if (!f.path.endsWith('.svg')) continue;
|
|
final name = f.uri.pathSegments.last;
|
|
final source = File('${sourceDir.path}/$name');
|
|
if (!source.existsSync()) continue; // not compiled from res/pictures
|
|
compared++;
|
|
final a = f.readAsBytesSync();
|
|
final b = source.readAsBytesSync();
|
|
if (a.length != b.length || !_same(a, b)) {
|
|
drifted.add(
|
|
'$name: assets/new-ui is ${a.length}b, res/pictures is ${b.length}b. '
|
|
'Edit res/pictures and re-run ./compile_graphics.sh; the assets copy '
|
|
'is output and CI overwrites it.',
|
|
);
|
|
}
|
|
}
|
|
|
|
// A silent zero would make this test vacuous, which is how the last two
|
|
// branding regressions reached users.
|
|
expect(compared, greaterThan(0), reason: 'compared no SVG pairs at all');
|
|
expect(drifted, isEmpty, reason: 'generated brand assets drifted from source:\n${drifted.join('\n')}');
|
|
});
|
|
}
|
|
|
|
bool _same(List<int> a, List<int> b) {
|
|
for (var i = 0; i < a.length; i++) {
|
|
if (a[i] != b[i]) return false;
|
|
}
|
|
return true;
|
|
}
|