# ReelPile — the manual
> A macOS app that keeps video references on a board that plays. Paste a link and the clip lands on the canvas, stays where you put it, and plays from your own disk.
Source: https://reelpile.com/manual — this file is the same text, in one piece.
ReelPile runs on macOS on Apple Silicon and costs $30 once.
---
# ReelPile — what it actually does
Each feature is written as: **what it does** → **how you reach it** → **what it
does not do**. The last part is not modesty. It is there so nothing in this manual
promises you something the app cannot deliver.
---
## Contents
1. [What ReelPile is](#1)
2. [Getting material in](#2)
3. [The card](#3)
4. [The canvas](#4)
5. [Playback](#5)
6. [Boards and nested folders](#6)
7. [Search](#7)
8. [Music recognition](#8)
9. [The full-screen player](#9)
10. [Text notes](#10)
11. [Getting a file back out](#11)
12. [Sending a board to a client](#12)
13. [Where your data lives](#13)
14. [Instagram sign-in](#14)
15. [Settings](#15)
16. [Updates](#16)
17. [Platform, price, licences](#17)
18. [What ReelPile does not do](#18)
---
## 1. What ReelPile is
A native macOS desktop app. Three layers inside: a Tauri (Rust) shell, a local Node
server bound to **127.0.0.1 only** (port 5181 by default; if it is taken the app
picks a free one and remembers it), and a React interface rendered in that window as
a page on localhost.
The idea: you paste a link to a clip, the app downloads the file to your own disk,
pulls the metadata, identifies the music, and drops a card onto an endless canvas.
Cards play on the board itself. Everything stays with you.
**There is no account, no sign-up, no subscription and no trial.** A licence key,
entered once on first launch, is the only thing between installing ReelPile and
using it. It is verified on your own machine and never contacts a server, so it
works offline and cannot be switched off from outside.
### First launch
A welcome screen, **Welcome to ReelPile**, reading *"Start a new board, or bring
across a library you already have"*, with **Start fresh** and **Import library** (the second
copies boards and video files from another install; nothing is moved or deleted).
The app also seeds one board called **Examples** with four short clips — *Harbour at
dusk*, *Stairwell, top down*, *Pour, macro* and *Ridge line, wide*. They ship inside
the installer as files, not as links, so the board **plays offline the first time it
is opened**, before anything has been pasted and before the machine has been asked
for a network.
All four were rendered from scratch for this purpose and belong to ReelPile outright:
there is no other rights holder and no licence to observe. That is deliberate. The
demo board used to be four YouTube links fetched on first boot, which was the one
place the app reached for YouTube before the user had pasted anything at all — and
stock footage was considered and rejected, because those licences permit reuse but
are silent on bundling a static copy inside a paid installer. Footage rendered here
has nothing to be silent about.
Each of the four carries an invented author handle and invented view, like and
comment counts,
so a fresh board looks like a working one rather than four bare rectangles. That is
honest only because the footage underneath is ours: no real creator is misattributed
and no real engagement is faked on anybody's behalf.
---
## 2. Getting material in
**Four ways in, all feeding one pipeline:**
- The toolbar field, *Paste a TikTok, Instagram or Vimeo link…*, then
Enter or **+ Add**. Several links separated by whitespace are accepted at once.
- The **File** button (also *Upload file* in the `⋯` menu) picks video or image
files from disk, multiple at a time.
- **Drag** a file or a link straight onto the board.
- **⌘V** anywhere on the board pastes an image from the clipboard, or a link.
(Not while your cursor is in a text field.)
### What happens after you press Add
In this exact order:
1. The platform is detected from the hostname.
2. **A YouTube link stops here.** It gets an embed URL and nothing else: no metadata
call, no download, no ffmpeg. Its title, author and thumbnail come from YouTube's
public oEmbed endpoint instead, and the card plays through YouTube's own player.
Everything from step 3 on describes the other platforms.
3. For Vimeo an embed URL is built, as a fallback if the download fails.
4. **yt-dlp** fetches the metadata: title, author/channel, thumbnail, views, likes,
comments, duration, upload date, and the description (kept to 2000 characters).
4. **yt-dlp downloads the video.** The format request **prefers 720p and below**,
and that is what nearly everything resolves to — it keeps libraries manageable and
ReelPile is not built to pull a 4K master. It is a preference rather than a ceiling:
the request is a cascade of eight fallbacks, and the last few carry no height limit
at all, so a clip offered *only* above 720p arrives at whatever it is offered at
rather than failing. A card with no file is a worse outcome than a large one.
5. **ffmpeg checks** that the file really has a video track. If only audio arrived
(the classic Instagram-without-a-login failure) the file is deleted and the card
records an honest error.
6. **ffmpeg normalises**: anything that is not already H.264 + AAC is re-encoded
(`libx264 -preset veryfast -crf 23`, audio always AAC 160k, `+faststart`). The
reason is concrete: the embedded WebKit view silently drops audio from VP9-in-MP4.
7. If the video path fails, **gallery-dl** is tried instead; it handles photo posts
and carousels, which yt-dlp cannot.
8. **The poster is fetched, or failing that, made.** First choice is the platform's
own thumbnail, downloaded and stored locally — it is the frame the platform chose,
which is usually the frame the author chose. Only if that fetch fails does ffmpeg
grab one itself, and it seeks to the **middle** of the clip; 0.5 s is the fallback
for a clip whose duration is unknown or shorter than a second. An early fixed frame
too often lands in a fade from black.
9. **The music is identified** (section 8).
10. For photo posts, `gallery-dl -j` runs once more to recover author and caption.
**This takes time, and there is no progress bar.** The request is synchronous: it
returns only once the download, transcode, poster and Shazam lookup have all
finished. On a long or slow clip that is seconds, sometimes minutes. A card does not
appear instantly, and nothing here pretends otherwise.
### A file the player cannot open is converted in the background
The steps above describe a link. A file brought in from your own disk takes a shorter
path, with one branch worth knowing about.
ReelPile plays video inside the system's own WebKit view, and that view accepts a
narrow set of formats. Bring in a `.mkv`, an `.avi`, a ProRes `.mov` — the kind of
thing that comes off a camera or out of an edit — and the card appears immediately,
sits where you dropped it, and says **Converting…** while ffmpeg re-encodes it to
H.264 and AAC in the background. Open it full-screen during that and the player says
so in words: *"Converting this file so it can play here. It stays on the board — this
takes a minute for a big clip."*
Three things about that are worth stating plainly:
- **The card is on the board the whole time.** Nothing is queued out of sight and
nothing has to be watched. You can carry on arranging around it.
- **Your original is not touched until the new file exists.** A conversion that fails
leaves the file it was given exactly as it was, which is what lets `⟳ Re-sync`
offer a second attempt rather than an apology.
- **The converted file takes a new name, not just new contents.** This is not a
detail: `/media` is served as static files, the browser picks its playback engine
from the file extension, and it rejects `video/x-matroska` and `video/x-msvideo`
whatever the bytes inside turn out to be. A conversion that kept the old name would
succeed and still not play.
### A sound file gets a card of its own
Drop in an audio file and it becomes a card that knows it is audio, rather than a
video card with nothing to show. There is no frame to use as a poster and none to
show on hover, so the card presents itself as sound and plays as sound.
The one consequence worth knowing: **⬇ Download video does not appear on an audio
card**, because there is no video to hand you. **♪ Download music does**, and on
these cards it needs no checking — a sound file is its own music.
### Which platforms actually work
Five are recognised by name: YouTube, Vimeo, TikTok, Instagram, and a direct file
URL (`.mp4/.webm/.mov/.m4v`). Everything else is labelled `Link`.
**But the platform label is cosmetic.** The download path does not branch on it:
every URL goes into the same yt-dlp → gallery-dl chain. So **anything yt-dlp
supports will import**, it just shows up as a generic link. And a link from a
"supported" domain that yt-dlp cannot handle fails like any other.
So the honest way to read it is: those five by name, and most other video links.
### Per-platform reality
- **YouTube.** Never downloaded. The bundled yt-dlp is built from source with the
YouTube extractor removed, so the app is not merely declining to fetch YouTube —
it has no means to. A YouTube card is a poster on the board and plays in YouTube's
own player when you open it, which is why it needs a connection and will not play
offline. Title, author and thumbnail come from YouTube's public oEmbed endpoint.
- **Vimeo.** Has an embed too, used as a fallback: a failed download is not recorded
as an error, the card quietly plays through the iframe instead.
- **Instagram.** What the app is really built around. Anonymous access is rate-
limited and serves video split into DASH pieces, hence the "is there actually a
video track?" guard. With a sign-in it is reliable. Photo posts and carousels go
through gallery-dl only.
- **TikTok.** No special handling; plain yt-dlp.
### When something fails
Errors are classified into four kinds: `rate-limit` (asked to slow down, resolves
itself), `login` (a sign-in is needed), `unavailable` (deleted or private; these are
abandoned, never retried), and `failed`. The card is still created, with a badge.
### The background queue
A single-slot background queue quietly re-downloads whatever did not arrive. It:
- runs **only while you are not working**, paused during your own jobs and for five
seconds after your last gesture;
- waits a **random 30–90 seconds** between attempts (a steady rhythm is itself a
robot signature);
- gives each card **three attempts**, except rate-limits, which do not count;
- **stops completely** on a `login` error until a fresh cookie file appears;
- backs off **15 minutes, then 60** on a rate-limit.
Separately, **Update old cards** in the `⋯` menu is a manual sweep over older cards
with no caption, filling in caption, author and title.
---
## 3. The card
Top to bottom:
- **Video or image.** A poster until the video is ready. `Video not downloaded` if
the file is not on this machine — or `Downloading…` while the background queue is
fetching this particular card. A `⚠` badge with a plain-language reason when
something went wrong: the post was removed or made private, it needs a sign-in, the
platform is asking us to slow down, or the fetch simply failed. Four different
reasons, one badge.
- **`Converting…`** instead of a poster while a file you brought in is being
re-encoded into something the player can open (section 2).
- **An audio card looks different on purpose.** No poster, no video, no hover: a `♪`
mark, the name and the duration. There is no frame to show, so it does not pretend
to have one.
- **Platform tag** in the corner.
- **Frame counter** (`1/8`) for carousels.
- **A row of buttons** on hover.
- **The title**.
- **`♪ Artist — Track`**, clickable, opens the track in your browser.
- **`@author`**, also a button: clicking it puts the author into the search box.
- **Stats:** `▶ views`, `♥ likes`, `💬 comments`, `⏱ duration`, abbreviated
(12.3K, 1.4M). Only what the source actually reported. Your own uploads and some
posts arrive with none.
- **A resize handle** in the bottom-right corner.
### The seven buttons
| Button | Tooltip | What it does |
|---|---|---|
| `⬇` | Download video | saves the video file |
| `♪` | Download music | pulls the audio out as an mp3 |
| `⬇` | Download image | saves the image (photo posts only) |
| `⧉` | Copy link | copies the original URL |
| `⟳` | Re-sync | fetches the clip again, or repairs it |
| `⇄` | Move to board | moves it to another board |
| `×` | Delete | deletes the card |
**The first three appear only when there is something to hand you, and the rule is
stricter than it sounds.** A **YouTube card never shows either download button** — not
"when there is nothing to save", but always, because ReelPile never has that file and
never will. A **silent clip shows no ♪ Download music**, even though its file is right
there. An **audio card** shows `♪` but no `⬇ Download video`, because there is no
video to give. So a card carries anywhere from one button to three.
**`⟳ Re-sync` does one of two things, depending on what is wrong.** On a card that
came from a link it fetches the clip again from where it came — except a YouTube card,
where there was never anything to fetch, so it simply re-checks whether the link still
embeds. On a file you brought
in yourself there is no remote to fetch from, so it repairs instead: a conversion that
failed is attempted once more, on the untouched original. Either way the button
reports what it actually did rather than claiming a success it did not have.
It also refuses to spin for nothing. A working silent clip counts, technically, as a
file that "needs converting" — remuxing a silent file leaves it silent — so a broad
rule would have re-run ffmpeg on it at every press, forever. `⟳` acts only when the
card is genuinely complaining.
---
## 4. The canvas
**Dragging.** Grab a card, put it anywhere. If you drop it more than halfway onto
another card, the app spirals it out to the nearest clear spot by itself, so cards
never end up stacked.
**Resizing.** The bottom-right corner. Minimum 60 px. A new card arrives 300 px
wide, so the board keeps one consistent scale.
**Panning.** Drag empty canvas. Pinch works on a trackpad.
**Zoom.** A small bar: `+`, the percentage, `−`, **fit**, **reset**. Range
**5 %–250 %**, steps of ×1.2. `fit` frames the whole board in the window; `reset`
goes to exactly 100 %.
**Every board remembers its own view.** Pan and zoom are stored per board, so
switching boards returns you where you were.
**Selecting several.** Drag a rubber band across empty canvas, or Shift-click. A bar
appears: `N selected`, **Move to ▾**, **Clear**. Drag one and the whole set moves.
`Esc` clears, `Delete`/`Backspace` deletes.
### Arrange
**▦ Arrange** re-lays the whole board.
What it actually does: cards are packed left to right, wrapping to a new row once
the row would exceed 1600 units, with a 24-unit gap, and each row's height is set by
its own tallest card. **It never resizes anything**: a manual resize is a deliberate
act by the user, and the code respects that.
The consequence: **these are rows, not a grid.** The right edge comes out ragged and
rows differ in height.
### Undo (⌘Z), and what it does not cover
**⌘Z only undoes text notes and drawings.** The undo stack holds shapes, not card
positions. Moving a card is not undoable; you move it back by hand. The stack holds
60 steps.
---
## 5. Playback
A **`Playing videos`** dropdown in the toolbar (repeated in the `⋯` menu):
**Off · Hover · 4 · 8 · 16 · All**.
**The default is `Hover`.** Out of the box **nothing autoplays**: the board is a wall
of posters and only the card under your cursor plays. There is a short delay before
it starts, so cards do not flare up while you sweep the mouse across. Hover does not
exist on touch screens, so that mode does nothing there.
**`All` means 16, not all.** Literally: the setting resolves to 16. Playing dozens of
videos at once saturates the machine's video decoders and freezes most of them. So
the **16 cards nearest the centre of the view** play, and the rest show a poster.
The numeric options (4/8/16) are exact.
Two more conditions:
- a card only plays if it is at least **40 px wide on screen**, so zoomed far out
slivers do not start;
- the playing set is recomputed about **150 ms after you stop panning or zooming**,
so nothing thrashes mid-gesture.
Opening the full-screen player **pauses the whole board** and resumes it on close,
so the player and the board do not fight over decoders.
**Sound on the board, and only where it makes sense.** In **Hover** mode — the
default — the card under your cursor plays **with sound**. Move off it and it goes
quiet again. Every other playing card stays muted, and the numeric modes (4, 8, 16,
All) are silent throughout: a dozen clips playing at once with their audio on is
noise, not a board.
**What it does not do:** a card on the board is a looping video **with no controls**.
You cannot scrub, pause or seek on the card — that is what the full-screen player is
for.
---
## 6. Boards and nested folders
A rail on the left.
- **Create:** the `New board` field at the bottom, then Enter or **+**. The new board
is selected immediately, and the canvas goes empty for a moment, which is expected.
- **Rename:** double-click the name.
- **Emoji and colour:** click the board's icon for an emoji field (up to 4 characters)
and six colour swatches.
- **Reorder:** drag rows up and down.
- **Nest:** drop one board **onto** another and it moves inside. The parent expands
automatically. Cycles are prevented; depth is unlimited.
- **Un-nest:** while dragging a nested board, an **`↑ Move to top level`** zone
appears.
- **Expand/collapse:** the `▾`/`▸` caret. Remembered.
- **A count** next to each name.
- **Delete:** `×`, confirming *"Delete this board and all its references?"*. If the
board is published and the link cannot be taken down, **the delete is refused**
rather than leaving an orphaned live link.
- The rail collapses with `«` and can be dragged wider. Both are remembered.
The app reopens on the board you had open last.
---
## 7. Search
Two identical fields, one in the rail and one in the toolbar, both *Search title,
author, track…*. Filtering is instant, as you type.
**It searches exactly four fields:** the clip's title, the author, the **track title
and the artist name**. Substring, case-insensitive.
**It does not search:** captions or descriptions, URLs, board names, or the text of
your notes.
Search works **within the current board**, not across the whole library. Results
sort newest first.
Clicking `@author` on a card puts that author into the search, a fast way to gather
everything by one person.
⚠️ **Gotcha:** publishing a board **clears the search box**, because every card has
to be measured before it is sent and filtered cards are not rendered. The filter does
not come back.
---
## 8. Music recognition
**This is real Shazam.** Not a lookalike, not a guess from the title.
After the download, ffmpeg cuts the **first 20 seconds**, mixes it to mono 16 kHz
WAV, and hands that to a bundled tool called `recognise`: a PyInstaller-frozen build
of the **shazamio** library, which queries Shazam's own service. It returns the track
title, the artist and a link to the track. All three land on the card, and **the
title and artist are searchable**.
Worth knowing:
- **It needs the internet.** Offline, there is no result.
- **No API key** is involved. This is an unofficial client, not a Shazam SDK; if
those endpoints ever close, the feature stops, and only an app update can fix it.
- **It runs once per card that finds something.** A card that already has a track is
not re-identified on a re-download. A card that never resolved one — no music in the
clip, or a lookup that failed — is asked again on every re-download, because "no
track" and "never asked" look identical from the outside.
- **Images and carousels never get music.**
- **Failure is silent.** A clip with no music, a network failure and a broken
recogniser build all look identical: the `♪` line simply is not there. No error is
shown. That is deliberate, but it means a broken recogniser goes unnoticed.
- It updates **with the app**, never at runtime — and so does everything else in
`bin/`. Nothing here reaches out and replaces itself. yt-dlp used to: it ran its own
`-U` at start-up and once a day, and that was removed deliberately, because the
yt-dlp ReelPile ships is built **without** the YouTube extractor and an upstream
self-update would have quietly put it back within a day.
---
## 9. The full-screen player
Clicking a card opens it.
- A locally downloaded video plays immediately **with full controls**, autostarted
and looping.
- A YouTube card always plays through YouTube's official player, and a Vimeo card
does too when there is no local file.
- Image carousels page with `‹ ›`, with **←/→**, and a `3 / 8` counter.
- No file on this machine → *"Video not downloaded on this machine"* and a
**Download now** button that jumps this card to the front of the background queue.
- Nothing playable → a plain-language reason and **Open original ↗**, which opens the
post in your system browser.
- Metadata below: title, `@author` (linked to the original), the track line, and the
**full post caption.** The caption exists only here, never on the card.
- **Being converted** → *"Converting this file so it can play here. It stays on the
board — this takes a minute for a big clip."*
- **An audio card** opens to plain audio controls, started, with no picture.
- Actions: **⇄ Move**, **⟳ Re-sync**, **⬇ Video**, **♪ Music**, **⬇ Image**,
**⧉ Copy**, **× Delete** — the three download buttons on the same terms as on the
card (section 3), so a YouTube card shows none of them and a silent clip shows no
**♪ Music**.
- **Esc** closes; so does clicking the backdrop.
---
## 10. Text notes
A tool dock at the bottom left holds **exactly two** tools: **Select / move** and
**T (Text)**. Plus **Undo (⌘Z)** and **Clear all drawings** (which asks first).
Pick `T`, click the canvas, type, press Enter. With a note selected, a contextual bar
appears: **font**, **size** (`−`/`+`) and **colour** (a palette plus a custom
picker). Notes can be moved and can be selected together with cards (rubber band or
Shift-click) and then the whole mixed group drags as one.
**On drawing.** Four more tools exist in the data model (pen, arrow, rectangle,
eraser) but they are **removed from the palette** and you cannot draw with them.
Existing drawings still render and are not damaged; only authoring was removed.
⚠️ Also: publishing a board **drops drawings entirely**, while text notes do travel.
See section 12.
---
## 11. Getting a file back out
Up to three buttons on the card, repeated in the player. Which of them you get
depends on what the card actually holds — see section 3: a YouTube card offers none of
them, a silent clip offers no music, an audio card offers no video.
**⬇ Download video.** Hands over **the exact file on disk**, unmodified. No transcode
at save time. The filename is the clip title, stripped to letters, digits, Cyrillic,
spaces and hyphens, and **capped at 60 characters**.
⚠️ It is always named `.mp4`, even in the rare case where the container is really
`.mov` or `.mkv` (possible when a clip arrived via gallery-dl).
**♪ Download music.** The first time, ffmpeg extracts the audio to **192 kbps MP3**;
after that it is served from cache. ⚠️ That mp3 **stays in the media folder forever**
and is never cleaned up; it inflates the library and rides along in every full
backup.
**⧉ Copy link.** Puts the original URL on the clipboard.
**⬇ Download image.** Photo posts, saves the first image.
### Where the file lands
Every one of these buttons is an ordinary link pointing at ReelPile's own local
server. **The app never puts up a save panel of its own**, so the file is written
wherever your Mac already saves downloads.
### What is not there
**There is no drag into a video editor.** Not Premiere, not Resolve, not Finder. No
drag-out of any kind is implemented. A file leaves the app **only** through the
download button.
**There is no PNG export of the board.** It existed and was removed.
---
## 12. Sending a board to a client
The share button in the toolbar, or `Share…` in the `⋯` menu.
### What happens
The app builds **one self-contained HTML page** from the board and uploads it, with
the cover images, to a Cloudflare Worker. The link looks like:
```
https:///b/<22 random characters>/
```
Twenty-two random characters is the whole of the secrecy: the address is not
guessable, and it is not listed anywhere. Anyone you give it to can open it.
The link lives on `share.reelpile.com`. The long tail after `/b/` is not an id
that could be shorter and prettier — it is the secret itself, 128 random bits,
and it is the only thing between a private board and anybody who guesses. That
is why the link looks the way it does.
### Exactly what travels, and what does not
**Uploaded:** the board's name and emoji; per card, an id, position and size, the
**poster image (JPEG)**, author, caption, views, likes, duration, `{track, artist}`,
the embed URL, a link to the original, a platform label, and two flags (unavailable /
no link). Plus text notes (text, position, size, colour).
**Never uploaded:** **video files, ever**; audio; the full original URL
(only a stripped version); any path on your disk; the filename of your own uploads;
comments; dates; board ids; error text; drawings.
The module states the rule itself: *"A shared board carries POSTERS ONLY — no video
path, no local file path, nothing that reveals this machine."*
This is verified three ways: the uploader only ever writes `p/.jpg` and
`index.html`; the Worker rejects any other path shape with a 400; and playback on the
client's side runs through **the platform's own iframe**, not your server.
### What the client sees
They open the link in any browser. On a desktop it is the board with your layout
preserved, pannable and zoomable; on a phone it collapses into a single column
(ordered at publish time by rows top to bottom, left to right within a row, so a
note above a group reads as its heading).
Clicking a card opens the platform's player, a link out to the original, a **heart**,
and close. No account, nothing to sign up for.
### Hearts and review: two limits worth knowing
Your review link is **the same link with `?liked=1`** on the end. Open it and picked
cards are outlined in red, the rest dim, and the header reads **"Client marked 7 of
24"**.
**Limit one: one link, one client.** The picks live in a single record that is
overwritten wholesale: last write wins. Send one link to two people and the second
erases the first's choices. **Publish separately for each client.**
**Limit two: `?liked=1` is not a secret.** Anyone holding the client link can append
it and see the picks, and technically overwrite them. The only protection is that
the link itself is unguessable.
Also: **the picks never come back into the app.** You read them in a browser. There
is no "show only the picked ones" filter and no export.
### Managing a published board
- **Update** re-uploads the current state. **The link does not change.**
- **Unpublish** asks *"Unpublish this board? The link you sent will stop working."* It
removes everything, including the client's picks. Publishing again afterwards mints
a **new link**; the old one is dead for good.
- **No expiry.** The link lives until you take it down.
- One board, one publication. Limit: 20 new publications per IP per hour.
- The page is marked `noindex,nofollow`, though the host serves no `robots.txt`.
- The panel shows *"Last published . The client sees the board as it was
then."*
### Things to say plainly rather than gloss over
- **No password.** The link is the only key. Anyone it is forwarded to can open it.
- **Your own uploaded mp4s are dead weight on a shared board.** They have no platform
link, so the client sees a still frame they can never play, badged `no link to
open`. This will be the most common support question.
- **Playback goes through Instagram/YouTube**, so the platform can see your client
opening it.
- **Posters are cached on the client's side for a year.** After an Update, someone
who already opened the board may keep seeing the old thumbnail for a long time.
- Publishing is not instant: each Instagram post is probed to see whether it is still
alive (6 in parallel, 5 s timeout).
- **Publications live only on this machine.** The share id and the write key are
stored in `db.json`, so they travel in **every** backup — the plain `⬇ Backup`
download and each silent auto-snapshot, not only the full one. Treat any backup of a
library with a published board as carrying a live credential: the write key is what
lets its holder overwrite or take down a link a client is looking at. On a fresh
machine without restoring that backup, you cannot update or unpublish your own live
link.
---
## 13. Where your data lives
```
~/Library/Application Support/app.reelpile.desktop/
data/ db.json, ig-cookies.txt, backups/
media/ videos, posters, images, extracted mp3s
bin/ yt-dlp, ffmpeg, gallery-dl, recognise
logs/ server.log, shell.log
```
The path is shown in Settings under **Data**. ⚠️ It is plain text: **there is no
"reveal in Finder" button and no way to change the folder** from inside the app. The
only Finder-opening action is `Open logs`.
**The format is plain JSON.** A single `db.json`. No SQLite. Writes are **atomic**
(temp file, then rename), so a crash mid-save cannot corrupt the library.
Media files are named by the card's UUID: `.mp4`, poster `.jpg`, carousel
`.1.jpg`, extracted audio `.mp3`.
### Auto-backup, as it really is
- A snapshot is taken **on save, at most once every 5 minutes**, plus **one at every
app launch**.
- The **last 60** are kept. (The cap is shared between the two kinds, so in practice
fewer than 60 of either.)
- They live in `data/backups/`, in two shapes: `db-2026-07-24T10-32-27.json` for the
every-five-minutes kind and `db-start-2026-07-24T10-32-27.json` for the one taken at
launch.
- A snapshot is a copy of **`db.json`**: boards, cards, positions, notes. **Neither
videos nor the Instagram cookie are in it** — but if you have ever published a board,
it does carry that board's **write key**, the credential that can overwrite or
unpublish the link your client is looking at. The same is true of the `⬇ Backup`
download, which serves `db.json` whole. It is not a secret-free file.
- In Settings this is one row, `Auto-backup`, reading `on · N snapshots kept`.
**There is no off switch.** "on" is hard-coded.
**How you actually use a snapshot matters.** Automatically, they save you in exactly
one situation: if `db.json` is missing or unreadable at start-up, the newest snapshot
is loaded silently. **There is no interface for picking a snapshot by hand.** The
`↩ Restore` button only accepts a file you exported yourself with `⬇ Backup`. To use
an auto-snapshot you have to open the folder in Finder, and no button in the app
opens that folder.
### The four backup buttons
| Button | What it does |
|---|---|
| `⬇ Backup` | downloads `db.json`: boards and cards, no video |
| `↩ Restore` | loads such a JSON back |
| `⬇ Full backup (with videos & login)` | a `.tar` of the library: database + all media + **the Instagram cookie** |
| `↩ Restore full backup…` | native file picker, unpacks it back |
The note underneath, verbatim: *"A full backup carries your videos and your Instagram
login — keep it private. Restoring replaces your current board. Use ReelPile on one
machine at a time."*
Restoring is destructive and asks first; a copy of the current state is genuinely
saved before it is replaced.
---
## 14. Instagram sign-in
Settings → Account → **Log in to Instagram**.
**Instagram's own login page** opens in a separate 520×720 window. You sign in there.
The app checks that window's cookies once a second for up to five minutes and, the
moment a session appears, writes them to `data/ig-cookies.txt`: **atomically, mode
0600**, then closes the window.
The code's own comment: *"we never see, store, or transmit the password"*, and that
is accurate: it reads cookies from a finished window and never touches the password
field.
The interface says: *"Opens Instagram's login page in a window — sign in there (a
secondary account is safest). No file needed."* The secondary-account advice is sound;
keep it.
- Once connected the row reads `Instagram: connected ✓ — log in again`.
- If the session goes stale, a banner appears: **"Instagram needs you to sign in
again."** with a **Reconnect Instagram** button. It clears itself as soon as the
cookie file is refreshed; no restart needed.
- **Disconnect Instagram** deletes the file.
- The cookie **is included in a full backup**, hence the "keep it private" warning.
Cookies are **not** imported from your system browser. This window is the only path.
---
## 15. Settings
Opened **only** by the `⚙` button in the toolbar. No keyboard shortcut, no menu-bar
item. Closed with `×` or by clicking the backdrop.
Understand the shape of it: **there are no ordinary preferences in Settings.** It is
status readouts and action buttons. Theme and playback live in the toolbar instead.
**Engines.** Four status rows: `ytdlp`, `ffmpeg`, `gallerydl`, `recognise` (version /
`ok` / `missing`), showing `Checking…` for the first few seconds. **There is no update
button here for any of them**, and the note says why in four words: *"Tools update with
the app."* There used to be one, for yt-dlp; it was removed on purpose (section 8).
**Stability.** **Restart engine** and **Open logs**.
**App.** The app version; a **check for updates** button; a **Videos** row with the
background queue's status (`Downloading videos — N left` / `All videos downloaded` /
`Paused — log in to Instagram…` / `Paused — Instagram asked us to slow down;
resuming in N min`); **Port**; **Data** (the path).
**Library.** Auto-backup and the four backup buttons (section 13).
**Account.** Instagram (section 14).
**Sync.** Cloud sync **does not exist in shipped builds**. Every `/api/sync/*`
endpoint answers 404 `sync is disabled`, and there is nothing to reach in the
interface.
---
## 16. Updates
Two independent layers.
### Layer 1: the hot update (interface and local server)
Replaces the built interface and the Node server. Not the shell, not the bundled
tools.
- Checks 20 seconds after start, then **every 5 minutes**.
- Applies **completely silently**, and only when you are not mid-gesture; the page
simply reloads. No banner, no version number, no consent.
- Packages are signature-verified against a public key baked into the app; without
that key the channel is inert.
- **Cannot be disabled, and there is no manual check button.**
- If an update fails to start you get *"Update N didn't start — reverted to the
previous version."*, and Settings offers **Revert to the bundled version** (a
one-way door: that version is blocklisted for good).
### Layer 2: the native updater (the whole app)
Replaces the entire signed `.app`.
- Checks on three triggers: at launch; when the window regains focus (at most once
every 15 minutes); and on a timer **every 6 hours**. The third exists because on
macOS closing a window does not end the process, so a user could sit on an old
version for a week without ever seeing a banner.
- Manual check: Settings → App.
- A banner appears bottom-right, **currently all in Russian**: version available,
**Update** / **Later**, then a download percentage, then a restart notice.
- **The restart is automatic** once the download completes; you are not asked twice.
- **Later** is not remembered: the next check offers the same version again.
- **Errors are completely silent.** A failed check or download shows nothing at all.
- Release notes are downloaded and then never displayed.
- **Cannot be disabled.**
---
## 17. Platform, price, licences
**macOS 10.15 (Catalina) and later, on Apple Silicon.** That is what the update feed
publishes, and it is the only build there is. There is no Windows and no Linux
version.
**Signed and notarised**, and enforced rather than promised: the release script runs
a Gatekeeper assessment, checks that a notarisation ticket is *stapled* to the
bundle, and refuses to publish if either fails. The bundled tools are separately
signed with a hardened runtime.
**Price: $30 once, and every update after.** Updates are free and never gated.
### Licences
Bundled inside the app:
| Tool | Licence |
|---|---|
| ffmpeg | **GPL-2.0-or-later** |
| gallery-dl | **GPLv2** |
| yt-dlp | Unlicense (public domain) |
| shazamio | MIT |
**ffmpeg and gallery-dl are GPL.** If you have a copy of ReelPile you are entitled to
the corresponding source of those two components. Write to **support@reelpile.com**
and it will be sent to you. The exact versions and their upstream addresses are
recorded in the build's `binaries.lock.json`.
---
## 18. What ReelPile does not do
An honest list, so nothing here is a surprise after you buy.
1. **No drag into an editor.** Not Premiere, not Resolve, not Finder. Files leave
only through the download button.
2. **No PNG export of the board.** Removed.
3. **No drawing.** Pen, arrows and rectangles are hidden from the palette. Text notes
only.
4. **The whole board does not play.** The ceiling is 16 cards, and by default nothing
plays except the card under the cursor.
5. **No scrubbing on a card.** No controls on the board. Sound there is limited on
purpose: the hovered card plays with it, every other playing card is muted.
6. **720p is what you get, almost always.** The download format asks for it first
and falls back through looser options, the last of which have no height limit — so
a clip offered only above 720p arrives large rather than not at all.
7. **Moving a card is not undoable.** ⌘Z covers notes and drawings only.
8. **Arrange makes rows, not a grid.** Ragged right edge; sizes are never changed.
9. **Auto-snapshots are not browsable.** 60 exist; no interface reaches them.
10. **The data folder cannot be changed or revealed** from the app.
11. **No sync between machines.** The code exists but is switched off in every build.
12. **No account, sign-up or trial.** A licence key, entered once, is the only
thing asked of you.
13. **Not fully offline.** Importing needs the network (yt-dlp) and music recognition
needs the network (Shazam). Offline, you can browse what you already have.
14. **No password on a published board.** Only an unguessable link.
15. **One shared link cannot serve two clients.** The second overwrites the first's
picks.
16. **Client picks do not come back into the app.** You read them in a browser.
17. **Update failures are never shown.**
18. **Search does not cover captions, URLs or notes.** Only title, author, track and
artist.
19. **Windows is not a shipping product** (unsigned, no updates).
20. **There is no About box and no credits screen.** The GPL notices themselves are
surfaced: Settings → Licences → **Third-party notices** opens `NOTICES.txt`, which
ships inside the app and carries the full third-party licence list and the written
offer of source required by GPL §3(b).