Data Format

Everything SyncPocket App remembers is stored locally in a versioned JSON archive containing items and Board definitions, plus a blob folder for binary payloads. This page is the reference for that format.

Stability: the format below reflects the current source of truth (Packages/MemoryKit). Until a 1.0 release it may evolve; the app migrates old data automatically (it already migrates the legacy ClipStack format).

On-disk layout

~/Library/Application Support/MemoryApp/
├── items.json          # versioned HistoryEnvelope (items + Boards)
├── items.backup.json   # last valid atomic snapshot
└── blobs/              # binary payloads referenced from items.json
    ├── <name>.rtf    # rich-text payloads
    └── <name>.png    # image payloads

HistoryEnvelope

items.json is a versioned HistoryEnvelope. Version 2 stores savedAt plus one atomic archive with two arrays: items and boards. Saving both together prevents an item's Board membership from being persisted without the matching Board definition.

ClipItem schema

Each element of archive.items is a ClipItem — a platform-neutral Codable Swift struct (Foundation + CryptoKit only, shared by macOS and iOS):

FieldTypeDescription
idUUID stringUnique item identifier, generated at capture time.
typestringOne of the ClipType values: text, link, color, image, file.
datedateLast capture/promotion timestamp (standard Swift Codable date encoding); this drives retention.
createdAtdateFirst creation timestamp. date is refreshed when identical content is captured again and is the timestamp used by retention.
plainTextstring · optionalPlain-text content for text, link and color items.
rtfFilestring · optionalFilename in blobs/ holding the RTF payload, when the copied text had formatting.
imageFilestring · optionalFilename in blobs/ holding the image payload for image items.
fileURLsstring[]File URLs for file items (files copied in Finder). Empty for other types.
sourceAppNamestring · optionalHuman-readable name of the app the content was copied from.
sourceBundleIDstring · optionalBundle identifier of the source app (also used by the exclusion list).
pinnedbooleanKeeps the item at the top and protects it from time-based retention. Defaults to false.
contentHashstringLowercase hex SHA-256 of the content — the deduplication key.
boardIDsUUID string[]IDs of every membership-based Board containing the item. Loved and Saved use fixed IDs; Pinned is derived from the separate pinned flag. Duplicates are removed while order is preserved.
reminderDatedate · optionalScheduled local reminder. While present, it protects the item from retention until the user clears it.
modifiedAtdateLast user-visible state change. Cross-device conflict resolution uses the newest value; older archives default it to date.
deletedAtdate · optionalWhen present, the item is in Deleted. It is permanently removed 30 days after this timestamp unless restored first.

Example

{
  "version": 2,
  "savedAt": 804108100.0,
  "archive": {
    "items": [{
      "id": "5B1E9C2A-8F1D-4A5E-9C77-2A1B3C4D5E6F",
      "type": "color",
      "date": 804108000.0,
      "createdAt": 804108000.0,
      "modifiedAt": 804108000.0,
      "plainText": "#FF6B6B",
      "fileURLs": [],
      "sourceAppName": "Figma",
      "sourceBundleID": "com.figma.Desktop",
      "pinned": false,
      "boardIDs": ["F0000000-0000-4000-8000-000000000001"],
      "reminderDate": 804799200.0,
      "contentHash": "a1b2c3…64 hex chars…d4e5f6"
    }],
    "boards": [{
      "id": "F0000000-0000-4000-8000-000000000001",
      "name": "Loved",
      "kind": "loved",
      "createdAt": -978307200.0
    }]
  }
}

MemoryBoard schema

Each element of archive.boards is a MemoryBoard:

FieldTypeDescription
idUUID stringStable Board identifier.
namestringUser-visible Board name.
kindstringloved, saved or custom.
createdAtdateCreation timestamp used for stable ordering.

Pinned, Loved and Saved use fixed Board IDs (F000…000, F000…001 and F000…002) and are restored automatically if an older archive does not contain them. Pinned membership is derived from pinned; isLoved and isSaved are computed from boardIDs.

ClipType

ValueDisplay name (PL)SF Symbol
textTekstdoc.text
linkLinklink
colorKolorpaintpalette
imageObrazphoto
filePlikifolder

Text-type detection

Copied text is classified by ClipItem.detectTextType(_:) using these rules, in order (input is trimmed of whitespace/newlines first):

  1. Color — matches ^#([0-9A-Fa-f]{3}|[0-9A-Fa-f]{6}|[0-9A-Fa-f]{8})$, i.e. #RGB, #RRGGBB or #RRGGBBAA.
  2. Link — contains no spaces or newlines, parses as a URL with an http or https scheme and a non-empty host.
  3. Text — everything else.

Content hashing & deduplication

Every item carries contentHash — the SHA-256 digest of its content, encoded as lowercase hex (for text: the UTF-8 bytes of the string):

Hashing.hash(of: "https://syncpocket.app")
// "…64 lowercase hex characters…"

When new clipboard content arrives, the store looks up the hash: if an item with the same contentHash already exists, no duplicate is created — the existing item is moved to the top and its date is refreshed. Signed iCloud builds use CloudKitSyncEngine; isolated UI-test builds use NoopSyncEngine. A production release still requires the signed container and two-device conflict verification.

Time-based retention

RetentionPolicy offers days30 (default), days60, days90, days120 and forever. The selected preference is stored locally outside the archive. An item is expired only when its refreshed date reaches the cutoff and it has no protection:

A reminder remains protective after its scheduled time until the user clears it, so opening a delivered notification cannot race with foreground cleanup.

Migration

On load, a legacy raw [ClipItem] array and version-1 envelopes are migrated to version 2. Missing item fields receive backward-compatible defaults, Pinned, Loved and Saved are restored, and data from the earlier ClipStack directory is moved automatically.