WorkRunner Documentation

Cloud Sync & Offline

Build offline-first Constructs that automatically sync data to the cloud.

How It Works

Constructs are offline-first by default. Data is stored locally in IndexedDB and optionally synced to the cloud when the user is online.

1

Local First

All writes go to IndexedDB immediately

2

Queue Changes

Offline changes are queued for sync

3

Auto Sync

Syncs automatically when online

Enabling Cloud Sync

Enable sync by passing the sync option to useConstructData:

import { useConstructData } from "@useworkapp/construct-sdk";

// Enable cloud sync
const [data, setData, { status }] = useConstructData(
  "my-data",
  defaultValue,
  { sync: true }  // This enables cloud sync
);

// Sync is debounced by default (1 second)
// Customize the debounce time:
const [data, setData] = useConstructData(
  "my-data",
  defaultValue,
  { sync: true, syncDebounce: 2000 }  // 2 seconds
);

Note: Cloud sync requires the user to be signed in with a Netrunner or higher subscription plan.

Handling Sync Status

The hook returns a status field that indicates the current sync state:

const [data, setData, { status, error }] = useConstructData("items", [], { sync: true });

// Status values:
// 'local'   - Data saved locally only (sync disabled or user not signed in)
// 'syncing' - Currently uploading changes to cloud
// 'synced'  - All changes saved to cloud
// 'offline' - User is offline, changes queued for later
// 'error'   - Sync failed (check error for details)

function SyncIndicator({ status, error }: { status: string; error: Error | null }) {
  const indicators = {
    local: { icon: "💾", text: "Saved locally", color: "text-gray-500" },
    syncing: { icon: "🔄", text: "Syncing...", color: "text-blue-500" },
    synced: { icon: "☁️", text: "Synced", color: "text-green-500" },
    offline: { icon: "📴", text: "Offline", color: "text-yellow-500" },
    error: { icon: "⚠️", text: error?.message || "Sync error", color: "text-red-500" },
  };

  const { icon, text, color } = indicators[status] || indicators.local;

  return (
    <span className={`text-sm ${color}`}>
      {icon} {text}
    </span>
  );
}

Manual Sync Control

Sometimes you want to trigger a sync manually, like before the user leaves:

const [data, setData, { sync, status }] = useConstructData(
  "important-data",
  defaultValue,
  { sync: true }
);

// Manual sync button
<Button
  onClick={() => sync()}
  disabled={status === "syncing"}
>
  {status === "syncing" ? "Syncing..." : "Sync Now"}
</Button>

// Sync before leaving
useEffect(() => {
  const handleBeforeUnload = () => {
    if (status !== "synced") {
      sync();
    }
  };
  window.addEventListener("beforeunload", handleBeforeUnload);
  return () => window.removeEventListener("beforeunload", handleBeforeUnload);
}, [status, sync]);

Offline-First Patterns

Design your UI to work seamlessly offline:

import { useOnlineStatus, useConstructData } from "@useworkapp/construct-sdk";

export default function MyConstruct() {
  const isOnline = useOnlineStatus();
  const [items, setItems, { status }] = useConstructData("items", [], { sync: true });

  return (
    <div>
      {/* Offline banner */}
      {!isOnline && (
        <div className="bg-yellow-100 border-b border-yellow-200 p-2 text-sm text-yellow-800">
          You're offline. Changes will sync when you reconnect.
        </div>
      )}

      {/* Pending changes indicator */}
      {status === "offline" && (
        <div className="text-sm text-muted-foreground">
          {items.length} changes waiting to sync
        </div>
      )}

      {/* Your app content - works the same online or offline */}
      <ItemList items={items} onUpdate={setItems} />
    </div>
  );
}

Background Sync

For standalone PWAs, enable background sync to sync changes even when the app is closed:

import { registerBackgroundSync } from "@useworkapp/construct-sdk";

// Register background sync (call once on app init)
registerBackgroundSync();

// The browser will automatically sync when:
// - The device regains network connectivity
// - The PWA is in the background
// - A Service Worker sync event fires

// Request immediate sync
import { requestImmediateSync } from "@useworkapp/construct-sdk";

// Trigger sync right now (if online)
await requestImmediateSync();

Browser Support: Background Sync is supported in Chrome, Edge, and other Chromium-based browsers. Safari and Firefox have limited support.

Conflict Resolution

When the same data is modified on multiple devices, conflicts can occur. The SDK uses version numbers to detect and resolve conflicts:

// Version is automatically tracked
const [data, setData, { version }] = useConstructData("items", []);

// Current conflict resolution strategy: Last Write Wins
// The most recent change (by timestamp) is kept

// Future: Custom conflict resolution
// const [data, setData] = useConstructData("items", [], {
//   sync: true,
//   onConflict: (local, remote) => {
//     // Return the merged result
//     return mergeData(local, remote);
//   },
// });

Sync Quotas & Limits

PlanCloud StorageSync Frequency
Ghost (Free)No cloud sync
Netrunner100 MBReal-time
Architect1 GBReal-time + Priority

Best Practices

Always show sync status

Users should know if their data is saved locally, syncing, or synced.

Design for offline first

Your Construct should work perfectly offline. Sync is a bonus, not a requirement.

Handle errors gracefully

Sync can fail. Show a clear message and offer a retry option.

Keep data lean

Don't sync large blobs or files. Keep synced data under a few MB for best performance.

Cloud Sync & Offline | Runner Documentation | Work