-
Notifications
You must be signed in to change notification settings - Fork 0
Sync v1.2.0 #3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Sync v1.2.0 #3
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,72 @@ | ||
| [config] | ||
| # https://openrouter.ai/openrouter/free, reached through LiteLLM's openrouter/ | ||
| # prefix, so the id doubles up: openrouter/ + openrouter/free. | ||
| # | ||
| # This is a router over whatever is free right now, not a fixed model, which is | ||
| # the point. Pinned free ids rotate out: the two this file was first written | ||
| # against (qwen-2.5-coder-32b, deepseek-chat-v3) were already gone by the time | ||
| # it landed. The router keeps resolving without anyone editing this line. | ||
| model = "openrouter/openrouter/free" | ||
| # The router alias isn't in PR-Agent's built-in MAX_TOKENS table, so without | ||
| # this it refuses to call the model at all ("not defined in MAX_TOKENS and no | ||
| # custom_model_max_tokens is set"). 32000 matches this repo's PR sizes with | ||
| # room to spare; found by actually running a review and reading the failure, | ||
| # not from the docs. | ||
| custom_model_max_tokens = 32000 | ||
| # PR-Agent's own default fallback is gpt-5.6-terra over a direct OpenAI call, | ||
| # which always fails here (no OPENAI_KEY secret, so it authenticates with a | ||
| # literal "dummy_key"). Fall back to the same OpenRouter router instead, since | ||
| # a second call may land on a different backing model than the first. | ||
| fallback_models = ["openrouter/openrouter/free"] | ||
|
|
||
| enable_auto_review = true | ||
| enable_auto_describe = false | ||
| # Off deliberately: free models are rate limited to roughly 20 requests a minute, | ||
| # and code suggestions are the request-hungry tool. Ask for them per PR with | ||
| # /improve when a change actually wants them. | ||
| enable_auto_improve = false | ||
|
|
||
| ignore_bot_prs = true | ||
| require_ready_for_review = true | ||
|
|
||
| [pr_reviewer] | ||
| # The repo's own rules, so the review flags what a human reviewer here would. | ||
| extra_instructions = """ | ||
| This is a Flutter app (Dart) for an FRC team's pit inventory and scouting. | ||
| When reviewing: | ||
| - No emojis anywhere: code, comments, docs, commit messages, UI strings. | ||
| Material Icons glyphs are the way to draw an affordance. | ||
| - No AI co-author trailers in commits. | ||
| - A top-level dart:ffi import reachable from main.dart breaks the wasm build, | ||
| which CI gates. Platform-specific code belongs behind a conditional import, | ||
| and the right condition for FFI is dart.library.ffi, not dart.library.io. | ||
| - Colors, radii and fonts come from PitPalette and app_theme.dart per | ||
| DESIGN.md: 8px corners, 12px for sheets and dialogs, depth as one tonal step | ||
| plus a 1px Outline border rather than a shadow, violet accent for action and | ||
| selection only, a status hue always paired with a label. Flag hardcoded hex | ||
| colors, radii and font families. | ||
| - A paragraph-long comment justifying a workaround means the code is wrong. | ||
| """ | ||
|
|
||
| [github_action_config] | ||
| auto_review = true | ||
| auto_describe = false | ||
| auto_improve = false | ||
|
|
||
| [ignore] | ||
| glob = [ | ||
| 'pubspec.lock', | ||
| 'package-lock.json', | ||
| 'pnpm-lock.yaml', | ||
| 'yarn.lock', | ||
| '**/*.g.dart', | ||
| '**/*.freezed.dart', | ||
| '**/generated_plugin_registrant.*', | ||
| '**/GeneratedPluginRegistrant.*', | ||
| 'build/**', | ||
| 'ios/Pods/**', | ||
| 'macos/Pods/**', | ||
| '**/*.min.js', | ||
| '**/*.svg', | ||
| '**/*.lock', | ||
| ] |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -3,6 +3,7 @@ import 'dart:convert'; | |||||||||||||||||||||||||||||||||||||||||||
| import 'dart:io'; | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| import 'package:firestore_client/firestore_client.dart' as fc; | ||||||||||||||||||||||||||||||||||||||||||||
| import 'package:http/http.dart' as http; | ||||||||||||||||||||||||||||||||||||||||||||
| import 'package:shared_preferences/shared_preferences.dart'; | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| import 'spectrum_auth_service.dart'; | ||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -29,6 +30,8 @@ class DesktopAuthService implements SpectrumAuthService { | |||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| static const String _prefsKey = 'desktop_auth_session_v1'; | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| Future<void> Function(String uid)? onSessionEnded; | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| final String clientId; | ||||||||||||||||||||||||||||||||||||||||||||
| final String firebaseApiKey; | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -38,6 +41,14 @@ class DesktopAuthService implements SpectrumAuthService { | |||||||||||||||||||||||||||||||||||||||||||
| final Future<SharedPreferences> Function() _prefsLoader; | ||||||||||||||||||||||||||||||||||||||||||||
| late final Future<fc.GoogleTokens> Function() _signInFlow; | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| StreamSubscription<fc.FirebaseUser?>? _authStateSub; | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| Future<void>? _listenerSetup; | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| bool _endingSession = false; | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| Future<void>? _teardown; | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| final StreamController<SpectrumAuthSnapshot> _controller = | ||||||||||||||||||||||||||||||||||||||||||||
| StreamController<SpectrumAuthSnapshot>.broadcast(); | ||||||||||||||||||||||||||||||||||||||||||||
| SpectrumAuthSnapshot _snapshot = const SpectrumAuthSnapshot( | ||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -54,18 +65,59 @@ class DesktopAuthService implements SpectrumAuthService { | |||||||||||||||||||||||||||||||||||||||||||
| SpectrumUser? get currentUser => _snapshot.user; | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| @override | ||||||||||||||||||||||||||||||||||||||||||||
| Future<String?> idToken() => _session.getIdToken(); | ||||||||||||||||||||||||||||||||||||||||||||
| Future<String?> idToken() async { | ||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||
| return await _session.getIdToken(); | ||||||||||||||||||||||||||||||||||||||||||||
| } on SocketException { | ||||||||||||||||||||||||||||||||||||||||||||
| return null; | ||||||||||||||||||||||||||||||||||||||||||||
| } on TimeoutException { | ||||||||||||||||||||||||||||||||||||||||||||
| return null; | ||||||||||||||||||||||||||||||||||||||||||||
| } on http.ClientException { | ||||||||||||||||||||||||||||||||||||||||||||
| return null; | ||||||||||||||||||||||||||||||||||||||||||||
| } on HttpException { | ||||||||||||||||||||||||||||||||||||||||||||
| return null; | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| Future<void> _ensureAuthStateListener() { | ||||||||||||||||||||||||||||||||||||||||||||
| final previous = _listenerSetup; | ||||||||||||||||||||||||||||||||||||||||||||
| final next = () async { | ||||||||||||||||||||||||||||||||||||||||||||
| await previous; | ||||||||||||||||||||||||||||||||||||||||||||
| await _authStateSub?.cancel(); | ||||||||||||||||||||||||||||||||||||||||||||
| _authStateSub = _session.authStateChanges.listen((user) { | ||||||||||||||||||||||||||||||||||||||||||||
| if (user == null) unawaited(_handleSessionRevoked()); | ||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||
| }(); | ||||||||||||||||||||||||||||||||||||||||||||
| _listenerSetup = next; | ||||||||||||||||||||||||||||||||||||||||||||
| return next; | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| @override | ||||||||||||||||||||||||||||||||||||||||||||
| Future<void> initialize() async { | ||||||||||||||||||||||||||||||||||||||||||||
| SharedPreferences? prefs; | ||||||||||||||||||||||||||||||||||||||||||||
| await _ensureAuthStateListener(); | ||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||
| prefs = await _prefsLoader(); | ||||||||||||||||||||||||||||||||||||||||||||
| final prefs = await _prefsLoader(); | ||||||||||||||||||||||||||||||||||||||||||||
| final stored = prefs.getString(_prefsKey); | ||||||||||||||||||||||||||||||||||||||||||||
| if (stored == null) return; | ||||||||||||||||||||||||||||||||||||||||||||
| final user = await _session.restore( | ||||||||||||||||||||||||||||||||||||||||||||
| (jsonDecode(stored) as Map).cast<String, dynamic>(), | ||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||
| final Map<String, dynamic> payload; | ||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||
| payload = (jsonDecode(stored) as Map).cast<String, dynamic>(); | ||||||||||||||||||||||||||||||||||||||||||||
| } catch (_) { | ||||||||||||||||||||||||||||||||||||||||||||
| await prefs.remove(_prefsKey); | ||||||||||||||||||||||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| final uid = payload['uid']; | ||||||||||||||||||||||||||||||||||||||||||||
| final refreshToken = payload['refreshToken']; | ||||||||||||||||||||||||||||||||||||||||||||
| if (uid is! String || | ||||||||||||||||||||||||||||||||||||||||||||
| uid.isEmpty || | ||||||||||||||||||||||||||||||||||||||||||||
| refreshToken is! String || | ||||||||||||||||||||||||||||||||||||||||||||
| refreshToken.isEmpty) { | ||||||||||||||||||||||||||||||||||||||||||||
| await prefs.remove(_prefsKey); | ||||||||||||||||||||||||||||||||||||||||||||
| if (uid is String && uid.isNotEmpty) await _endSession(uid); | ||||||||||||||||||||||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
| final user = await _session.restore(payload); | ||||||||||||||||||||||||||||||||||||||||||||
| if (user != null) { | ||||||||||||||||||||||||||||||||||||||||||||
| _emit( | ||||||||||||||||||||||||||||||||||||||||||||
| SpectrumAuthSnapshot( | ||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -75,16 +127,17 @@ class DesktopAuthService implements SpectrumAuthService { | |||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||||||||||||
| await prefs.remove(_prefsKey); | ||||||||||||||||||||||||||||||||||||||||||||
| final revokedUid = payload['uid']; | ||||||||||||||||||||||||||||||||||||||||||||
| if (revokedUid is String && revokedUid.isNotEmpty) { | ||||||||||||||||||||||||||||||||||||||||||||
| await _endSession(revokedUid); | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
| } catch (_) { | ||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||
| await prefs?.remove(_prefsKey); | ||||||||||||||||||||||||||||||||||||||||||||
| } catch (_) {} | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
| } catch (_) {} | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| @override | ||||||||||||||||||||||||||||||||||||||||||||
| Future<void> signIn() async { | ||||||||||||||||||||||||||||||||||||||||||||
| await _teardown; | ||||||||||||||||||||||||||||||||||||||||||||
| _emit(const SpectrumAuthSnapshot(state: SpectrumAuthState.signingIn)); | ||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||
| final tokens = await _signInFlow(); | ||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -110,16 +163,53 @@ class DesktopAuthService implements SpectrumAuthService { | |||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| @override | ||||||||||||||||||||||||||||||||||||||||||||
| Future<void> signOut() async { | ||||||||||||||||||||||||||||||||||||||||||||
| _endingSession = true; | ||||||||||||||||||||||||||||||||||||||||||||
| final departingUid = currentUser?.uid; | ||||||||||||||||||||||||||||||||||||||||||||
| await _session.signOut(); | ||||||||||||||||||||||||||||||||||||||||||||
| await _runTeardown(departingUid); | ||||||||||||||||||||||||||||||||||||||||||||
| _emit(const SpectrumAuthSnapshot(state: SpectrumAuthState.signedOut)); | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
164
to
+171
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
Line 166 sets Two consequences follow:
🐛 Proposed fix `@override`
Future<void> signOut() async {
_endingSession = true;
final departingUid = currentUser?.uid;
- await _session.signOut();
+ try {
+ await _session.signOut();
+ } catch (_) {
+ // The local session still has to be torn down when the revoke call
+ // cannot reach the network.
+ }
await _runTeardown(departingUid);
_emit(const SpectrumAuthSnapshot(state: SpectrumAuthState.signedOut));
}Add a test that makes 📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| Future<void> _handleSessionRevoked() async { | ||||||||||||||||||||||||||||||||||||||||||||
| if (_endingSession) return; | ||||||||||||||||||||||||||||||||||||||||||||
| if (_snapshot.state != SpectrumAuthState.signedIn) return; | ||||||||||||||||||||||||||||||||||||||||||||
| final departingUid = currentUser?.uid; | ||||||||||||||||||||||||||||||||||||||||||||
| _emit(const SpectrumAuthSnapshot(state: SpectrumAuthState.signedOut)); | ||||||||||||||||||||||||||||||||||||||||||||
| await _runTeardown(departingUid); | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| Future<void> _runTeardown(String? uid) { | ||||||||||||||||||||||||||||||||||||||||||||
| _endingSession = true; | ||||||||||||||||||||||||||||||||||||||||||||
| final done = () async { | ||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||
| await _forgetStoredSession(); | ||||||||||||||||||||||||||||||||||||||||||||
| if (uid != null) await _endSession(uid); | ||||||||||||||||||||||||||||||||||||||||||||
| } finally { | ||||||||||||||||||||||||||||||||||||||||||||
| _endingSession = false; | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
| }(); | ||||||||||||||||||||||||||||||||||||||||||||
| _teardown = done; | ||||||||||||||||||||||||||||||||||||||||||||
| return done; | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| Future<void> _forgetStoredSession() async { | ||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||
| final prefs = await _prefsLoader(); | ||||||||||||||||||||||||||||||||||||||||||||
| await prefs.remove(_prefsKey); | ||||||||||||||||||||||||||||||||||||||||||||
| } catch (_) {} | ||||||||||||||||||||||||||||||||||||||||||||
| _emit(const SpectrumAuthSnapshot(state: SpectrumAuthState.signedOut)); | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| Future<void> _endSession(String uid) async { | ||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||
| await onSessionEnded?.call(uid); | ||||||||||||||||||||||||||||||||||||||||||||
| } catch (_) {} | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| @override | ||||||||||||||||||||||||||||||||||||||||||||
| Future<void> dispose() async { | ||||||||||||||||||||||||||||||||||||||||||||
| await _listenerSetup; | ||||||||||||||||||||||||||||||||||||||||||||
| await _authStateSub?.cancel(); | ||||||||||||||||||||||||||||||||||||||||||||
| _authStateSub = null; | ||||||||||||||||||||||||||||||||||||||||||||
| await _controller.close(); | ||||||||||||||||||||||||||||||||||||||||||||
| _session.close(); | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| import 'dart:io'; | ||
|
|
||
| import 'package:firestore_client/firestore_client.dart' as fc; | ||
| import 'package:path_provider/path_provider.dart'; | ||
|
|
||
| import 'user_scoped_firestore_cache.dart'; | ||
|
|
||
| Future<fc.FirestoreCache?> createDesktopFirestoreCache( | ||
| String? Function() currentUid, | ||
| ) async { | ||
| final root = await _cacheRoot(); | ||
| if (root == null) return null; | ||
| return UserScopedFirestoreCache(root: root, currentUid: currentUid); | ||
| } | ||
|
|
||
| Future<void> clearDesktopFirestoreCacheFor(String uid) async { | ||
| final root = await _cacheRoot(); | ||
| if (root == null) return; | ||
| await UserScopedFirestoreCache( | ||
| root: root, | ||
| currentUid: () => uid, | ||
| ).clearForUid(uid); | ||
| } | ||
|
|
||
| Future<Directory?> _cacheRoot() async { | ||
| try { | ||
| final support = await getApplicationSupportDirectory(); | ||
| return Directory('${support.path}${Platform.pathSeparator}firestore_cache'); | ||
| } on Exception { | ||
| return null; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| import 'package:firestore_client/firestore_client.dart' as fc; | ||
|
|
||
| Future<fc.FirestoreCache?> createDesktopFirestoreCache( | ||
| String? Function() currentUid, | ||
| ) async => null; | ||
|
|
||
| Future<void> clearDesktopFirestoreCacheFor(String uid) async {} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift
Publish integrity metadata for unsigned artifacts.
Lines 13, 28, and 32-36 tell users to install unsigned files or bypass SmartScreen and Gatekeeper. The installation flow provides no checksum or other artifact-integrity check.
Publish a release-generated SHA-256 manifest and add a verification step before asking users to override platform warnings.
Also applies to: 28-28, 32-36
🤖 Prompt for AI Agents