Offline-first mobile apps with local storage, sync queues, conflict resolution. Use for offline functionality, data sync, connectivity handling, or encountering sync conflicts, queue management, storage limits, network transition errors.
npx skills add https://github.com/secondsky/claude-skills --skill mobile-offline-support
Build offline-first mobile applications with local storage and synchronization.
import AsyncStorage from '@react-native-async-storage/async-storage';
import NetInfo from '@react-native-community/netinfo';
class OfflineManager {
constructor() {
this.syncQueue = [];
this.isOnline = true;
// Maximum items in sync queue before discarding oldest
this.MAX_SYNC_QUEUE_LENGTH = 1000;
NetInfo.addEventListener(state => {
this.isOnline = state.isConnected;
if (this.isOnline) this.processQueue();
});
}
/**
* Fetch data from server.
* TODO: Replace with actual API endpoint implementation.
*/
async fetchFromServer(key) {
try {
// Example implementation - replace with your API
const response = await fetch(`${API_BASE_URL}/data/${key}`);
if (!response.ok) {
throw new Error(`Server returned ${response.status}`);
}
return await response.json();
} catch (error) {
console.error('fetchFromServer failed:', error);
throw new Error(`Failed to fetch ${key}: ${error.message}`);
}
}
/**
* Sync data to server.
* TODO: Replace with actual API endpoint implementation.
*/
async syncToServer(key, data) {
try {
// Example implementation - replace with your API
const response = await fetch(`${API_BASE_URL}/data/${key}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
});
if (!response.ok) {
throw new Error(`Server returned ${response.status}`);
}
return await response.json();
} catch (error) {
console.error('syncToServer failed:', error);
throw new Error(`Failed to sync ${key}: ${error.message}`);
}
}
async getData(key) {
const cached = await AsyncStorage.getItem(key);
if (cached) return JSON.parse(cached);
if (this.isOnline) {
const data = await this.fetchFromServer(key);
await AsyncStorage.setItem(key, JSON.stringify(data));
return data;
}
return null;
}
async saveData(key, data) {
await AsyncStorage.setItem(key, JSON.stringify(data));
if (this.isOnline) {
await this.syncToServer(key, data);
} else {
// Add to queue
this.syncQueue.push({ key, data, timestamp: Date.now() });
// Enforce queue bounds - discard oldest if exceeded
while (this.syncQueue.length > this.MAX_SYNC_QUEUE_LENGTH) {
const discarded = this.syncQueue.shift();
console.warn(`Sync queue full - discarded oldest item: ${discarded.key}`);
}
// Persist trimmed queue
await AsyncStorage.setItem('syncQueue', JSON.stringify(this.syncQueue));
}
}
async processQueue() {
const failedItems = [];
for (const item of this.syncQueue) {
try {
await this.syncToServer(item.key, item.data);
} catch (err) {
console.error('Sync failed:', err);
failedItems.push(item);
}
}
this.syncQueue = failedItems;
if (failedItems.length === 0) {
await AsyncStorage.removeItem('syncQueue');
} else {
await AsyncStorage.setItem('syncQueue', JSON.stringify(failedItems));
}
}
}
function resolveConflict(local, server) {
// Last-write-wins
if (local.updatedAt > server.updatedAt) return local;
return server;
// Or merge changes
// return { ...server, ...local };
}
function OfflineIndicator() {
const [isOnline, setIsOnline] = useState(true);
useEffect(() => {
return NetInfo.addEventListener(state => {
setIsOnline(state.isConnected);
});
}, []);
if (isOnline) return null;
return (
<View style={styles.banner}>
<Text>You're offline. Changes will sync when connected.</Text>
</View>
);
}
See references/native-implementations.md for:
Integration with protocols.io API for managing scientific protocols. This skill should be used when working with protocols.io to search, create, update, or publish protocols; manage protocol steps and materials; handle discussions and comments; organize workspaces; upload and manage files; or integrate protocols.io functionality into workflows. Applicable for protocol discovery, collaborative protocol development, experiment tracking, lab protocol management, and scientific documentation.
Analyzes job descriptions and generates tailored resumes that highlight relevant experience, skills, and achievements to maximize interview chances
Generate Excalidraw diagrams from natural language descriptions. Use when asked to "create a diagram", "make a flowchart", "visualize a process", "draw a system architecture", "create a mind map", or "generate an Excalidraw file". Supports flowcharts, relationship diagrams, mind maps, and system architecture diagrams. Outputs .excalidraw JSON files that can be opened directly in Excalidraw.
Build and distribute Expo development clients locally or via TestFlight
Use when you have a written implementation plan to execute in a separate session with review checkpoints
Data structure for annotated matrices in single-cell analysis. Use when working with .h5ad files or integrating with the scverse ecosystem. This is the data format skill—for analysis workflows use scanpy; for probabilistic models use scvi-tools; for population-scale queries use cellxgene-census.
Benchling R&D platform integration. Access registry (DNA, proteins), inventory, ELN entries, workflows via API, build Benchling Apps, query Data Warehouse, for lab data management automation.
Comprehensive molecular biology toolkit. Use for sequence manipulation, file parsing (FASTA/GenBank/PDB), phylogenetics, and programmatic NCBI/PubMed access (Bio.Entrez). Best for batch processing, custom bioinformatics pipelines, BLAST automation. For quick lookups use gget; for multi-service integration use bioservices.
Take secondsky/mobile-offline-support from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.