Restructure step 3: your server, Invite, version gate, AppGraph
- One server per app run (loopback, no password); Invite attaches an all-interfaces listener to the same server and Stop sharing detaches it (guests told with 4016); members, media and Up next never reset. No solo mode: you're home on your own server unless in a friend's. - Navigation: none / yours / friend; a join passes a version gate on /health first (clear "they/you need to update", update offer). - Invite as a value (host, port, name, password flag, version, fp reserved); addresses filtered (loopback, link-local, 198.18/15, bridges) and ranked; Sharing chip, Copy invite, Stop sharing. - Alone looks like a plain player (alone replaces solo). - AppGraph builds and disposes systems; widgets use AppScope; SessionState deleted; a test caps the remaining singletons. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
629be0d9ed
commit
4d00b7455e
86 files changed
+2993
-2071
No files matched your search
@@ -0,0 +1,47 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
/// The app's own `.instance` singletons and how often `lib` reaches for
|
||||
/// each. Systems get their dependencies through the composition root
|
||||
/// (`AppGraph`, widgets through `AppScope`); this list may only shrink.
|
||||
/// Lower a number when a use goes; never raise one or add a class.
|
||||
const baseline = {
|
||||
'AppSettings': 55,
|
||||
'JellyfinAccounts': 10,
|
||||
'UpdateController': 8,
|
||||
'Profile': 8,
|
||||
'FolderStore': 6,
|
||||
'AppYtDlp': 4,
|
||||
'RecentLinks': 3,
|
||||
};
|
||||
|
||||
void main() {
|
||||
test('no new .instance singletons, and no more uses of the old ones', () {
|
||||
final files = Directory('lib')
|
||||
.listSync(recursive: true)
|
||||
.whereType<File>()
|
||||
.where((f) => f.path.endsWith('.dart'))
|
||||
.toList();
|
||||
final sources = [for (final f in files) f.readAsStringSync()];
|
||||
// The app's own classes (framework singletons like
|
||||
// `WidgetsBinding.instance` are not counted).
|
||||
final classes = {
|
||||
for (final s in sources)
|
||||
for (final m in RegExp(r'^(?:abstract |final |base )*class (\w+)', multiLine: true).allMatches(s)) m.group(1)!,
|
||||
};
|
||||
final counts = <String, int>{};
|
||||
for (final s in sources) {
|
||||
for (final m in RegExp(r'\b([A-Z]\w*)\.instance\b').allMatches(s)) {
|
||||
final name = m.group(1)!;
|
||||
if (classes.contains(name)) counts[name] = (counts[name] ?? 0) + 1;
|
||||
}
|
||||
}
|
||||
final grown = {
|
||||
for (final e in counts.entries)
|
||||
if (e.value > (baseline[e.key] ?? 0)) e.key: '${e.value} (baseline ${baseline[e.key] ?? 'none'})',
|
||||
};
|
||||
expect(grown, isEmpty,
|
||||
reason: 'pass the system in through AppGraph / AppScope instead of a new `.instance` use or singleton');
|
||||
});
|
||||
}
|
||||
Reference in new issue
Block a user