.cursor/skills/offline-first-patterns/SKILL.md
--- name: offline-first-patterns description: Enforces offline-first architecture patterns: GetStorage/Drift database, local-first data sources, sync strategies, offline indicator. Use when implementing features, designing data flow, or ensuring offline functionality. --- # Offline-First Patterns ## Core Principle **All features must work offline.** Online features are enhancements, not requirements. ## Architecture Pattern ### Local-First Data Flow ``` User Action → Local Database → UI Upd
npx skillsauth add avra-cadavra/avrai .cursor/skills/offline-first-patternsInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
All features must work offline. Online features are enhancements, not requirements.
User Action → Local Database → UI Update
↓ (background)
Sync to Remote (when online)
NOT:
User Action → Remote API → UI Update (❌ Requires internet)
/// Local data source (GetStorage for key-value, Drift for relational)
class SpotsLocalDataSource {
final GetStorage _box = GetStorage('spots');
Future<List<Spot>> getSpots() async {
// Read from local storage
final keys = _box.getKeys<String>();
final spots = <Spot>[];
for (final key in keys) {
final data = _box.read<Map<String, dynamic>>(key);
if (data != null) spots.add(Spot.fromJson(data));
}
return spots;
}
Future<void> saveSpot(Spot spot) async {
// Save to local storage
await _box.write(spot.id, spot.toJson());
}
}
/// Remote data source (optional sync)
class SpotsRemoteDataSource {
Future<List<Spot>> getSpots() async {
// Fetch from remote API (only when online)
// Returns empty list if offline
}
Future<void> syncSpots(List<Spot> spots) async {
// Sync to remote (background, when online)
}
}
/// Repository: Local-first, remote sync
class SpotsRepositoryImpl implements SpotsRepository {
final SpotsLocalDataSource _localDataSource;
final SpotsRemoteDataSource? _remoteDataSource;
@override
Future<List<Spot>> getSpots() async {
// Always read from local first (works offline)
final localSpots = await _localDataSource.getSpots();
// Sync from remote in background (if online)
if (_remoteDataSource != null) {
_syncInBackground();
}
return localSpots;
}
void _syncInBackground() async {
try {
final remoteSpots = await _remoteDataSource!.getSpots();
// Update local database with remote data
for (final spot in remoteSpots) {
await _localDataSource.saveSpot(spot);
}
} catch (e) {
// Silently fail - offline mode
}
}
}
/// Initialize GetStorage (offline-first storage)
/// Call in main.dart before DI initialization
Future<void> initializeGetStorageBoxes() async {
await GetStorage.init('spots');
await GetStorage.init('users');
await GetStorage.init('chat_messages');
// ... other boxes
}
/// Storage service using GetStorage (offline-first)
class SpotsStorageService {
final GetStorage _box = GetStorage('spots');
Future<void> saveSpot(Spot spot) async {
// Save to local storage (always works)
await _box.write(spot.id, spot.toJson());
// Queue for remote sync (if online)
await _queueForSync(spot);
}
Future<Spot?> getSpot(String id) async {
// Read from local storage (always works)
final data = _box.read<Map<String, dynamic>>(id);
if (data == null) return null;
return Spot.fromJson(data);
}
}
For complex relational data (users, lists, spots with queries):
/// Drift database for relational data
final db = GetIt.I<AppDatabase>();
// Insert
await db.upsertSpot(SpotsCompanion.insert(...));
// Query
final spots = await db.getAllSpots();
// Watch for changes (reactive)
db.watchSpots().listen((spots) => updateUI(spots));
Show user when offline:
/// Offline indicator widget
class OfflineIndicator extends StatelessWidget {
@override
Widget build(BuildContext context) {
return StreamBuilder<ConnectivityResult>(
stream: Connectivity().onConnectivityChanged,
builder: (context, snapshot) {
final isOnline = snapshot.data != ConnectivityResult.none;
if (!isOnline) {
return Container(
color: AppColors.warning,
child: Text(
'Offline mode - changes will sync when online',
style: TextStyle(color: AppColors.white),
),
);
}
return SizedBox.shrink();
},
);
}
}
/// Background sync service
class SyncService {
final Database _database;
final RemoteDataSource _remoteDataSource;
Future<void> syncInBackground() async {
// Check connectivity
final connectivity = await Connectivity().checkConnectivity();
if (connectivity == ConnectivityResult.none) {
return; // No sync if offline
}
// Sync pending changes
await _syncPendingChanges();
// Pull latest from remote
await _pullLatestData();
}
Future<void> _syncPendingChanges() async {
final pendingChanges = await _getPendingChanges();
for (final change in pendingChanges) {
try {
await _remoteDataSource.syncChange(change);
await _markSynced(change.id);
} catch (e) {
// Keep in queue for retry
}
}
}
}
lib/core/services/storage_service.dart - Storage service pattern (GetStorage)lib/data/database/app_database.dart - Drift database for relational datalib/data/datasources/local/ - Local data sources (Drift/GetStorage)test/helpers/test_storage_helper.dart - Test storage initializationdevelopment
--- name: world-model-development description: Guides world model development patterns: state/action encoders, ONNX inference, feature extraction pipeline, latency budgets. Use when implementing world model components, state encoders, action encoders, feature extractors, or ONNX models. Core skill for Phases 3-6. --- # World Model Development Patterns ## Core Principle All world model components follow LeCun's autonomous machine intelligence framework. State observations flow through a percep
tools
Implements base workflow controller patterns for multi-step processes. Use when creating complex workflows that require orchestration of multiple steps with error handling and rollback.
testing
--- name: widget-test-patterns description: Guides widget test patterns: BLoC testing, user interactions, state changes, material app setup. Use when writing widget tests, testing UI components, or validating widget behavior. --- # Widget Test Patterns ## Core Pattern Widget tests verify UI behavior: user interactions, state changes, and visual display. ## Basic Widget Test Setup ```dart testWidgets('widget displays correctly', (WidgetTester tester) async { // Arrange: Create widget awa
testing
--- name: test-template-generation description: Generates test templates: unit, widget, integration, service tests following project patterns. Use when creating new tests or ensuring tests follow project standards. --- # Test Template Generation ## Available Templates Test templates are located in `test/templates/`: - `unit_test_template.dart` - `widget_test_template.dart` - `integration_test_template.dart` - `service_test_template.dart` ## Unit Test Template ```dart /// SPOTS Component Uni