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):
| Field | Type | Description |
|---|---|---|
id | UUID string | Unique item identifier, generated at capture time. |
type | string | One of the ClipType values: text, link, color, image, file. |
date | date | Last capture/promotion timestamp (standard Swift Codable date encoding); this drives retention. |
createdAt | date | First creation timestamp. date is refreshed when identical content is captured again and is the timestamp used by retention. |
plainText | string · optional | Plain-text content for text, link and color items. |
rtfFile | string · optional | Filename in blobs/ holding the RTF payload, when the copied text had formatting. |
imageFile | string · optional | Filename in blobs/ holding the image payload for image items. |
fileURLs | string[] | File URLs for file items (files copied in Finder). Empty for other types. |
sourceAppName | string · optional | Human-readable name of the app the content was copied from. |
sourceBundleID | string · optional | Bundle identifier of the source app (also used by the exclusion list). |
pinned | boolean | Keeps the item at the top and protects it from time-based retention. Defaults to false. |
contentHash | string | Lowercase hex SHA-256 of the content — the deduplication key. |
boardIDs | UUID 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. |
reminderDate | date · optional | Scheduled local reminder. While present, it protects the item from retention until the user clears it. |
modifiedAt | date | Last user-visible state change. Cross-device conflict resolution uses the newest value; older archives default it to date. |
deletedAt | date · optional | When 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:
| Field | Type | Description |
|---|---|---|
id | UUID string | Stable Board identifier. |
name | string | User-visible Board name. |
kind | string | loved, saved or custom. |
createdAt | date | Creation 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
| Value | Display name (PL) | SF Symbol |
|---|---|---|
text | Tekst | doc.text |
link | Link | link |
color | Kolor | paintpalette |
image | Obraz | photo |
file | Pliki | folder |
Text-type detection
Copied text is classified by ClipItem.detectTextType(_:) using these rules, in order (input is trimmed of whitespace/newlines first):
- Color — matches
^#([0-9A-Fa-f]{3}|[0-9A-Fa-f]{6}|[0-9A-Fa-f]{8})$, i.e.#RGB,#RRGGBBor#RRGGBBAA. - Link — contains no spaces or newlines, parses as a URL with an
httporhttpsscheme and a non-empty host. - 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:
pinned == false;boardIDsis empty; andreminderDate == nil.
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.