Dive & Operations Log
Digital logbook for tracking dives, equipment maintenance, and operations.
Live Demo
The Problem
In the Navy, on yachts, and in the oil fields, I filled out paper logbooks daily. Every dive, every equipment check, every maintenance task — logged by hand. These records are critical for safety audits, insurance, and operational continuity. But paper logs get lost, damaged, or become unreadable. Finding a specific entry from six months ago means flipping through hundreds of pages.
Digital alternatives exist, but they're often bloated enterprise software designed for large organizations, not individual operators or small crews. I wanted to build something that captures the simplicity of a paper logbook with the power of digital search and organization.
The Connection
Twenty years of writing in logbooks gave me strong opinions about what matters:
- Speed of entry — You're logging between tasks, often in challenging conditions. Every extra click costs time and attention.
- Offline-first — Yachts at sea, rigs in the Gulf, remote dive sites — connectivity is a luxury, not a given.
- Structured but flexible — Some entries need specific fields (dive depth, equipment serial numbers), others need freeform notes.
- Immutable audit trail — Regulatory bodies care about log integrity. Entries should be timestamped and append-only.
The Approach
I chose React for this experiment to demonstrate proficiency with the most widely-used frontend framework. The architecture decisions reflect the real constraints:
Offline-First with Sync Using IndexedDB for local storage with a sync queue for when connectivity returns. All operations work offline; sync is opportunistic.
Form-First Design The UI is optimized for rapid data entry. Tab navigation, smart defaults, and templates for common entry types.
Type-Safe Throughout TypeScript interfaces define every entity. The same types are shared between frontend and API.
Technical Deep Dive
The core challenge was handling offline sync without data loss or conflicts. Here's the approach:
Each entry gets a client-generated UUID and timestamp. When online, entries sync to the server in order. Conflicts are rare because entries are append-only — you don't edit, you add amendments.
The sync queue handles network failures gracefully, retrying with exponential backoff. A background service worker manages the queue even when the app isn't open.
// Sync queue manager
interface SyncQueueItem {
id: string;
timestamp: number;
operation: 'create' | 'update';
entity: 'log_entry' | 'equipment' | 'dive';
payload: unknown;
retryCount: number;
}
async function processSyncQueue() {
const queue = await db.syncQueue.toArray();
for (const item of queue) {
try {
await syncToServer(item);
await db.syncQueue.delete(item.id);
} catch (error) {
if (item.retryCount < MAX_RETRIES) {
await db.syncQueue.update(item.id, {
retryCount: item.retryCount + 1
});
}
}
}
}Offline sync queue with retry logic
What I Learned
What worked well:
What I'd change:
Trade-offs accepted: