Most moodboard software stores a board's layout somewhere you can't get at
directly — a row in a database, a blob behind a sync service, a format
that has to be exported before any other tool can look at it. Version
control never gets a foothold there, because there's nothing readable to
version. A .coii-canvas doesn't have that problem: it's pretty-printed
JSON, sitting in a folder you already control, and every plate's position,
every note, every tag reference is a line in a text file the moment you
open it in anything that reads text. That's the whole reason the steps
below work at all — they're describing how to point an ordinary version
control tool at a file that was never hidden from it in the first place.
This isn't a feature the app adds. It's a consequence of the canvas format being a plain file rather than a database row, and it's worth treating deliberately rather than as an afterthought once a board has already gone through several rounds of changes nobody can reconstruct.
It's also worth being clear about what "version control" means here versus what it might mean for the images themselves. This page is about the canvas — the arrangement, the notes, which plates are on the board and where — not about tracking edits to the photographs and clips a board references. Building a moodboard from folders you already have covers how those images get onto a board in the first place; this page picks up once a board exists and starts changing over time.
Step 1: Put the canvas somewhere a repository can see it
A canvas file can live anywhere inside the folder you've registered as a
workspace, so the first real decision is where — a dedicated Boards
folder is a reasonable default, especially if you plan to track just the
canvases and not every image in the same repository.
~/Projects/Atlas- Concept art
- Client refs
- Boards
- coii-thumbnailswritten by Coii Ref
- coii-proxieswritten by Coii Ref
- coii.dbwritten by Coii Ref
None of the steps below require the workspace itself to know anything about version control, and that independence runs both directions: deleting the repository entirely, at any point, leaves the workspace exactly as functional as it was before — the canvas still opens, the board still renders, and nothing about how the app reads the file changes. Version control here is purely additive, a history layered on top of a file that was already going to exist regardless of whether anyone chose to track it.
Step 2: Initialize a repository and make the first commit
From inside that folder, git init and a first commit of the canvas file
is the entire setup. Nothing about the app needs to know a repository
exists alongside it — the workspace watcher reads whatever's on disk
regardless of whether it's also tracked by git, so version control and the
app's own indexing run independently of each other without conflicting.
Step 3: Decide what belongs in the diff and what doesn't
A workspace folder holds more than the canvas: a database, thumbnails and
canvas-resolution proxies all live beside your files. None of those are
meant to be read by a person, and none of them diff usefully — a thumbnail
that changed is a different binary blob, not a readable change. Excluding
coii.db, coii-thumbnails and coii-proxies from the repository, the
same way build artifacts are usually excluded from any other project, keeps
the history focused on the one file that's actually worth tracking changes
to.
Whether the source images themselves belong in the same repository is a separate call, and a reasonable one to answer "no" to — a repository full of full-resolution photographs grows large fast, and the images are usually already backed up by whatever already backs up the rest of the folder. Tracking the canvas on its own, with the images left to that other backup, covers the thing that actually benefits from a diff-based history without duplicating a job something else already does.
Step 4: Read a diff the way you would for any text file
This is the part that makes the whole exercise worth doing. A diff between two commits of a canvas shows exactly which plate entries were added, removed, repositioned or renamed, because each plate is its own block in the JSON rather than a pixel in a flattened image. A change that moved three plates and added a note reads as three moved blocks and one new line — legible in an ordinary diff view, the same way a change to a paragraph of prose would be.
That readability is the entire argument for doing this over just saving
board-final-v2-actually-final.coii-canvas next to the original and hoping
the filenames stay meaningful. A version history answers "what changed
between Tuesday and today" with an actual diff instead of a naming
convention that degrades the moment two people are both saving copies.
Step 5: Roll back to an earlier version when a change needs undoing
Because the app re-reads the canvas file rather than keeping its own internal undo history, checking out an earlier commit and reopening the file is enough to see the board exactly as it stood at that point — there's no separate "restore" step inside the app itself, since from its perspective the file on disk simply changed, the same way it would if you'd edited it by hand.
That progression from round-by-round commits to a tagged history is worth building as a habit rather than a one-off exercise applied to a single troublesome board — the second and third boards under this treatment take less thought than the first, once committing after every meaningful change becomes as automatic as saving the file itself.
Step 6: Branch a board when two versions need to exist at once
A client-facing variant of a board and an internal working version are a natural fit for a branch each, the same as any other file that needs to diverge and possibly merge back later. Because plates are addressable blocks rather than pixels, a JSON-aware merge tool can often reconcile two branches that touched different plates without a person resolving every line by hand — though a merge that touched the same plate on both sides still needs a human decision, the same as any other conflicting edit.
What doesn't version well, and why that's fine
A canvas versions cleanly because it's small, textual and changes in discrete, nameable ways — a plate moved, a note added. The images it references don't share those properties: a full-resolution photograph is large, binary and, once captured, essentially unchanging, which is exactly the profile of a file that gains nothing from being tracked by a tool built around line-by-line text diffs. Putting thousands of unchanging images into the same repository as a frequently-edited canvas mixes a file that benefits enormously from version control with thousands that would benefit from it not at all, and grows the repository by orders of magnitude for no real gain. Leaving the images to whatever already backs up the folder, and reserving the repository for the one file where a diff is actually legible, is the version of this that scales rather than the version that technically tracks everything.
Step 7: Tag a commit for each round a client actually saw
A running history of every small nudge is useful, but the moments that matter most later are the ones a client actually reviewed — "this is what they approved," specifically, as opposed to every intermediate save along the way. Tagging the commit at the end of each review round, the way a piece of software gets tagged at a release, turns "what did round two look like" from a scroll through history into a single lookup.
A worked example: what three rounds of client notes actually looks like
Round one is the first commit — a dozen plates, a rough arrangement, tagged
round-1. The client asks for two images swapped and the whole thing
nudged tighter; that's a second commit, and the diff between it and
round-1 shows exactly those two plate entries changing and nothing
else — no ambiguity about whether anything unrelated moved, because
nothing else is in the diff. Tag that one round-2.
Round three drops four plates entirely and adds six new ones after the
client changes direction on the location. The diff here is larger, but
still exactly as legible: six new blocks, four removed, the rest
untouched. Anyone looking back at this project in six months — to reuse the
approach on a similar job, or to settle a dispute about who approved
what — can check out round-1, round-2 or the current state and see
precisely what each one contained, rather than relying on anyone's memory
of which version was which.
That's the entire value of the exercise laid out concretely: not a backup, not a collaboration feature, just an accurate record of a board's own history that a diff tool already knows how to read.
Finding the boards worth bringing under version control
Not every canvas needs this treatment — a scratch board thrown together for
one afternoon's work doesn't need a commit history any more than a sketch
does. What usually does are the boards that get revisited: ongoing client
work, a project that spans months, anything that's already been renamed
-v2 once.
A tag applied to a project's folder once a canvas exists inside it makes this kind of sweep straightforward later, rather than having to remember by project which boards are the ones actually worth versioning.
Common mistakes worth avoiding
Committing the entire workspace folder wholesale is the most common one —
it works, technically, but buries the one file worth diffing under
thumbnails and a database that change on every re-index and add nothing
readable to the history. A quieter mistake is treating the repository as a
backup rather than a history: version control answers "what changed and
when," not "is this safe if the drive dies," and the two problems need
different tools even though they can live in the same folder without
conflict. And waiting until a board has already gone through a dozen
unversioned edits before starting — the first commit is cheapest the day a
canvas is created, not after months of manual -final filenames have
already lost the story a real history would have kept.
What to do as the number of boards grows
One repository per project, rather than one for the entire workspace, keeps
history focused and keeps a merge conflict on one client's boards from ever
touching another's. That maps naturally onto organizing references by
project in the first
place — a project that already has its own folder gets its own repository
inside it for free, rather than needing a separate scheme invented just for
version control. For someone managing boards across many projects at once,
a consistent Boards folder name inside each project root — the same
convention step one sets up — makes it straightforward to script
initializing a repository as part of starting a new project, rather than
deciding folder-by-folder whether this one gets tracked.
None of this requires anything from the app beyond writing the canvas as plain, readable JSON in the first place — the version control itself is entirely tools you likely already have installed, applied to a file that's already what a moodboard is rather than anything the app had to add for this to work. Sharing a canvas without a cloud account covers the companion problem of getting a board to someone else once it's in the state you want, and backing up a reference library properly covers the wider folder this all sits inside. What a sidecar file is and an infinite canvas for reference images are worth reading if the plain-file argument above is the part that actually matters to you. Or try it against a board you're already revising — thirty days, every feature, no card and no account, is enough to put a real canvas under version control and watch the first few diffs actually make sense.