diff --git a/.skill-lock.json b/.skill-lock.json
index 4102749c2..cbbc8fd4d 100644
--- a/.skill-lock.json
+++ b/.skill-lock.json
@@ -153,6 +153,15 @@
"skillFolderHash": "c5ce954ef1fca62dd177cd1fd16e2fc81898458a",
"installedAt": "2026-02-25T00:51:33.360Z",
"updatedAt": "2026-02-25T00:51:33.360Z"
+ },
+ "mobile-offline-support": {
+ "source": "aj-geddes/useful-ai-prompts",
+ "sourceType": "github",
+ "sourceUrl": "https://github.com/aj-geddes/useful-ai-prompts.git",
+ "skillPath": "skills/mobile-offline-support/SKILL.md",
+ "skillFolderHash": "05bb4c1a9463fe22342c09422760c5526d2dac06",
+ "installedAt": "2026-02-25T00:51:58.400Z",
+ "updatedAt": "2026-02-25T00:51:58.400Z"
}
},
"dismissed": {
diff --git a/memory/memories.db-wal b/memory/memories.db-wal
index ff1e848ec..fd67c79fa 100644
Binary files a/memory/memories.db-wal and b/memory/memories.db-wal differ
diff --git a/skills/mobile-offline-support/SKILL.md b/skills/mobile-offline-support/SKILL.md
new file mode 100644
index 000000000..0074c06e8
--- /dev/null
+++ b/skills/mobile-offline-support/SKILL.md
@@ -0,0 +1,466 @@
+---
+name: mobile-offline-support
+description: Implement offline-first mobile apps with local storage, sync strategies, and conflict resolution. Covers AsyncStorage, Realm, SQLite, and background sync patterns.
+---
+
+# Mobile Offline Support
+
+## Overview
+
+Design offline-first mobile applications that provide seamless user experience regardless of connectivity.
+
+## When to Use
+
+- Building apps that work without internet connection
+- Implementing seamless sync when connectivity returns
+- Handling data conflicts between device and server
+- Reducing server load with intelligent caching
+- Improving app responsiveness with local storage
+
+## Instructions
+
+### 1. **React Native Offline Storage**
+
+```javascript
+import AsyncStorage from '@react-native-async-storage/async-storage';
+import NetInfo from '@react-native-community/netinfo';
+
+class StorageManager {
+ static async saveItems(items) {
+ try {
+ await AsyncStorage.setItem(
+ 'items_cache',
+ JSON.stringify({ data: items, timestamp: Date.now() })
+ );
+ } catch (error) {
+ console.error('Failed to save items:', error);
+ }
+ }
+
+ static async getItems() {
+ try {
+ const data = await AsyncStorage.getItem('items_cache');
+ return data ? JSON.parse(data) : null;
+ } catch (error) {
+ console.error('Failed to retrieve items:', error);
+ return null;
+ }
+ }
+
+ static async queueAction(action) {
+ try {
+ const queue = await AsyncStorage.getItem('action_queue');
+ const actions = queue ? JSON.parse(queue) : [];
+ actions.push({ ...action, id: Date.now(), attempts: 0 });
+ await AsyncStorage.setItem('action_queue', JSON.stringify(actions));
+ } catch (error) {
+ console.error('Failed to queue action:', error);
+ }
+ }
+
+ static async getActionQueue() {
+ try {
+ const queue = await AsyncStorage.getItem('action_queue');
+ return queue ? JSON.parse(queue) : [];
+ } catch (error) {
+ return [];
+ }
+ }
+
+ static async removeFromQueue(actionId) {
+ try {
+ const queue = await AsyncStorage.getItem('action_queue');
+ const actions = queue ? JSON.parse(queue) : [];
+ const filtered = actions.filter(a => a.id !== actionId);
+ await AsyncStorage.setItem('action_queue', JSON.stringify(filtered));
+ } catch (error) {
+ console.error('Failed to remove from queue:', error);
+ }
+ }
+}
+
+class OfflineAPIService {
+ async fetchItems() {
+ const isOnline = await this.checkConnectivity();
+
+ if (isOnline) {
+ try {
+ const response = await fetch('https://api.example.com/items');
+ const items = await response.json();
+ await StorageManager.saveItems(items);
+ return items;
+ } catch (error) {
+ const cached = await StorageManager.getItems();
+ return cached?.data || [];
+ }
+ } else {
+ const cached = await StorageManager.getItems();
+ return cached?.data || [];
+ }
+ }
+
+ async createItem(item) {
+ const isOnline = await this.checkConnectivity();
+
+ if (isOnline) {
+ try {
+ const response = await fetch('https://api.example.com/items', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify(item)
+ });
+ const created = await response.json();
+ return { success: true, data: created };
+ } catch (error) {
+ await StorageManager.queueAction({
+ type: 'CREATE_ITEM',
+ payload: item
+ });
+ return { success: false, queued: true };
+ }
+ } else {
+ await StorageManager.queueAction({
+ type: 'CREATE_ITEM',
+ payload: item
+ });
+ return { success: false, queued: true };
+ }
+ }
+
+ async syncQueue() {
+ const queue = await StorageManager.getActionQueue();
+
+ for (const action of queue) {
+ try {
+ await this.executeAction(action);
+ await StorageManager.removeFromQueue(action.id);
+ } catch (error) {
+ action.attempts = (action.attempts || 0) + 1;
+ if (action.attempts > 3) {
+ await StorageManager.removeFromQueue(action.id);
+ }
+ }
+ }
+ }
+
+ private async executeAction(action) {
+ switch (action.type) {
+ case 'CREATE_ITEM':
+ return fetch('https://api.example.com/items', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify(action.payload)
+ });
+ default:
+ return Promise.reject(new Error('Unknown action type'));
+ }
+ }
+
+ async checkConnectivity() {
+ const state = await NetInfo.fetch();
+ return state.isConnected ?? false;
+ }
+}
+
+export function OfflineListScreen() {
+ const [items, setItems] = useState([]);
+ const [isOnline, setIsOnline] = useState(true);
+ const [syncing, setSyncing] = useState(false);
+ const apiService = new OfflineAPIService();
+
+ useFocusEffect(
+ useCallback(() => {
+ loadItems();
+ const unsubscribe = NetInfo.addEventListener(state => {
+ setIsOnline(state.isConnected ?? false);
+ if (state.isConnected) {
+ syncQueue();
+ }
+ });
+
+ return unsubscribe;
+ }, [])
+ );
+
+ const loadItems = async () => {
+ const items = await apiService.fetchItems();
+ setItems(items);
+ };
+
+ const syncQueue = async () => {
+ setSyncing(true);
+ await apiService.syncQueue();
+ await loadItems();
+ setSyncing(false);
+ };
+
+ return (
+
+ {!isOnline && Offline Mode}
+ {syncing && }
+ }
+ keyExtractor={item => item.id}
+ />
+
+ );
+}
+```
+
+### 2. **iOS Core Data Implementation**
+
+```swift
+import CoreData
+
+class PersistenceController {
+ static let shared = PersistenceController()
+
+ let container: NSPersistentContainer
+
+ init(inMemory: Bool = false) {
+ container = NSPersistentContainer(name: "MyApp")
+
+ if inMemory {
+ container.persistentStoreDescriptions.first?.url = URL(fileURLWithPath: "/dev/null")
+ }
+
+ container.loadPersistentStores { _, error in
+ if let error = error as NSError? {
+ print("Core Data load error: \(error)")
+ }
+ }
+
+ container.viewContext.automaticallyMergesChangesFromParent = true
+ }
+
+ func save(_ context: NSManagedObjectContext = PersistenceController.shared.container.viewContext) {
+ if context.hasChanges {
+ do {
+ try context.save()
+ } catch {
+ print("Save error: \(error)")
+ }
+ }
+ }
+}
+
+// Core Data Models
+@NSManaged class ItemEntity: NSManagedObject {
+ @NSManaged var id: String
+ @NSManaged var title: String
+ @NSManaged var description: String?
+ @NSManaged var isSynced: Bool
+}
+
+@NSManaged class ActionQueueEntity: NSManagedObject {
+ @NSManaged var id: UUID
+ @NSManaged var type: String
+ @NSManaged var payload: Data?
+ @NSManaged var createdAt: Date
+}
+
+class OfflineSyncManager: NSObject, ObservableObject {
+ @Published var isOnline = true
+ @Published var isSyncing = false
+
+ private let networkMonitor = NWPathMonitor()
+ private let persistenceController = PersistenceController.shared
+
+ override init() {
+ super.init()
+ setupNetworkMonitoring()
+ }
+
+ private func setupNetworkMonitoring() {
+ networkMonitor.pathUpdateHandler = { [weak self] path in
+ DispatchQueue.main.async {
+ self?.isOnline = path.status == .satisfied
+ if path.status == .satisfied {
+ self?.syncWithServer()
+ }
+ }
+ }
+
+ let queue = DispatchQueue(label: "NetworkMonitor")
+ networkMonitor.start(queue: queue)
+ }
+
+ func saveItem(_ item: Item) {
+ let context = persistenceController.container.viewContext
+ let entity = ItemEntity(context: context)
+ entity.id = item.id
+ entity.title = item.title
+ entity.isSynced = false
+
+ persistenceController.save(context)
+
+ if isOnline {
+ syncItem(item)
+ }
+ }
+
+ func syncWithServer() {
+ isSyncing = true
+ let context = persistenceController.container.viewContext
+ let request: NSFetchRequest = ActionQueueEntity.fetchRequest()
+
+ do {
+ let pendingActions = try context.fetch(request)
+ for action in pendingActions {
+ context.delete(action)
+ }
+ persistenceController.save(context)
+ } catch {
+ print("Sync error: \(error)")
+ }
+
+ isSyncing = false
+ }
+}
+```
+
+### 3. **Android Room Database**
+
+```kotlin
+@Entity(tableName = "items")
+data class ItemEntity(
+ @PrimaryKey val id: String,
+ val title: String,
+ val description: String?,
+ val isSynced: Boolean = false
+)
+
+@Entity(tableName = "action_queue")
+data class ActionQueueEntity(
+ @PrimaryKey val id: Long = System.currentTimeMillis(),
+ val type: String,
+ val payload: String,
+ val createdAt: Long = System.currentTimeMillis()
+)
+
+@Dao
+interface ItemDao {
+ @Insert(onConflict = OnConflictStrategy.REPLACE)
+ suspend fun insertItem(item: ItemEntity)
+
+ @Query("SELECT * FROM items")
+ fun getAllItems(): Flow>
+
+ @Update
+ suspend fun updateItem(item: ItemEntity)
+}
+
+@Dao
+interface ActionQueueDao {
+ @Insert
+ suspend fun insertAction(action: ActionQueueEntity)
+
+ @Query("SELECT * FROM action_queue ORDER BY createdAt ASC")
+ suspend fun getAllActions(): List
+
+ @Delete
+ suspend fun deleteAction(action: ActionQueueEntity)
+}
+
+@Database(entities = [ItemEntity::class, ActionQueueEntity::class], version = 1)
+abstract class AppDatabase : RoomDatabase() {
+ abstract fun itemDao(): ItemDao
+ abstract fun actionQueueDao(): ActionQueueDao
+}
+
+@HiltViewModel
+class OfflineItemsViewModel @Inject constructor(
+ private val itemDao: ItemDao,
+ private val actionQueueDao: ActionQueueDao,
+ private val connectivityManager: ConnectivityManager
+) : ViewModel() {
+ private val _items = MutableStateFlow>(emptyList())
+ val items: StateFlow> = _items.asStateFlow()
+
+ init {
+ viewModelScope.launch {
+ itemDao.getAllItems().collect { entities ->
+ _items.value = entities.map { it.toItem() }
+ }
+ }
+ observeNetworkConnectivity()
+ }
+
+ fun saveItem(item: Item) {
+ viewModelScope.launch {
+ val entity = item.toEntity()
+ itemDao.insertItem(entity)
+
+ if (isNetworkAvailable()) {
+ syncItem(item)
+ } else {
+ actionQueueDao.insertAction(
+ ActionQueueEntity(
+ type = "CREATE_ITEM",
+ payload = Json.encodeToString(item)
+ )
+ )
+ }
+ }
+ }
+
+ private fun observeNetworkConnectivity() {
+ val networkRequest = NetworkRequest.Builder()
+ .addCapability(NET_CAPABILITY_INTERNET)
+ .build()
+
+ connectivityManager.registerNetworkCallback(
+ networkRequest,
+ object : ConnectivityManager.NetworkCallback() {
+ override fun onAvailable(network: Network) {
+ viewModelScope.launch { syncQueue() }
+ }
+ }
+ )
+ }
+
+ private suspend fun syncQueue() {
+ val queue = actionQueueDao.getAllActions()
+ for (action in queue) {
+ try {
+ actionQueueDao.deleteAction(action)
+ } catch (e: Exception) {
+ println("Sync error: ${e.message}")
+ }
+ }
+ }
+
+ private fun isNetworkAvailable(): Boolean {
+ val activeNetwork = connectivityManager.activeNetwork ?: return false
+ val capabilities = connectivityManager.getNetworkCapabilities(activeNetwork) ?: return false
+ return capabilities.hasCapability(NET_CAPABILITY_INTERNET)
+ }
+}
+```
+
+## Best Practices
+
+### ✅ DO
+- Implement robust local storage
+- Use automatic sync when online
+- Provide visual feedback for offline status
+- Queue actions for later sync
+- Handle conflicts gracefully
+- Cache frequently accessed data
+- Implement proper error recovery
+- Test offline scenarios thoroughly
+- Use compression for large data
+- Monitor storage usage
+
+### ❌ DON'T
+- Assume constant connectivity
+- Sync large files frequently
+- Ignore storage limitations
+- Force unnecessary syncing
+- Lose data on offline mode
+- Store sensitive data unencrypted
+- Accumulate infinite queue items
+- Ignore sync failures silently
+- Sync in tight loops
+- Deploy without offline testing