Devlog · Part 2 ·

Specs before code

A product brief, a functional spec with numbered requirements, an open file format, and a record of every big decision. All before writing a single line of the app.

The current best practice for building software with an AI assistant has a name: spec-driven development. The idea is simple. An AI model will happily build whatever you describe, so the quality of what you get depends heavily on how well you describe it. Vague requests produce plausible-looking software that does the wrong thing. A written specification gives both of you something fixed to build against and check against.

So before any code, Seura Photos got a stack of documents, in this order:

  1. A product brief: what it is, who it’s for, and the principles behind it.
  2. A functional spec: what version 1 does, as testable requirements.
  3. An edit format specification: the file format for edits, in its own repository.
  4. User flows and wireframes: the screens and how you move between them.
  5. A technical design: architecture, libraries, testing.
  6. A milestone plan: the order of work from first code to version 1.0.

All of them are public in the seura repository.

The brief: deciding what it isn’t

Claude drafted the brief from a list of questions, and my answers shaped it:

Tags, settled precisely

The tags discussion is a good example of why writing things down matters. I wanted tags like family/kids, shown as folders in a tree. Simple enough, until you ask what happens when you click family. Do you see everything under it?

We settled on exact tags: family, kids, and family/kids are three separate tags, and the slash only affects how they’re displayed. Clicking a tag shows the photos with exactly that tag. I also decided there’s no renaming tags, since renaming would mean rewriting the edit file beside every photo that uses it. Each of those decisions is now a numbered requirement (TAG-1 to TAG-7) that the code will be tested against.

Settings, not steps

My favorite question from this stage was mine, and the answer changed the design. If you raise the contrast, then change the tint, then raise the contrast again, does the order matter? Should the edit file record every step, so undo works forever?

Claude’s answer was to store settings, not steps. The file records the final value of each adjustment, and the app always applies them in a fixed order. Contrast, then tint, then contrast again is just one contrast setting. That keeps files small, makes edits predictable, and means any app reading the format gets the same result.

Undo still matters, though. So each file also keeps a short history of recent changes, capped at the last 50, so you can undo even after closing the app. I preferred plain, readable JSON over compressing it.

A format other apps can use

The edit format got its own repository, seura-photos/edit-format, with a written specification, a JSON Schema, examples, tests for invalid files, and a checker script. It’s licensed so anyone can implement it. Here’s part of one, for a photo that was rotated, cropped, brightened, and tagged:

{
  "format": "seura-edit",
  "formatVersion": "1.0",
  "photo": { "file": "IMG_1234.jpg", "width": 4032, "height": 3024 },
  "operations": [
    { "id": "seura.orient", "version": 1, "params": { "rotate": 90, "flipH": false, "flipV": false } },
    { "id": "seura.crop", "version": 1, "params": { "x": 0.05, "y": 0.1, "width": 0.9, "height": 0.75, "aspect": "4:3" } },
    { "id": "seura.exposure", "version": 1, "params": { "ev": 0.3 } },
    { "id": "seura.contrast", "version": 1, "params": { "amount": 35 } }
  ],
  "tags": ["family/kids", "vacation/2026"],
  "rating": 4
}

It’s designed to grow: plugins can add their own operations, and an app that doesn’t recognize an operation must keep it intact when saving.

Recording decisions

The last habit worth mentioning is decision records: short documents saying what was chosen, why, and what was rejected. There are twelve so far, covering things like Tauri and Rust, storing edits beside photos, and refusing GPL-licensed dependencies. Their real audience is future me, and future AI sessions: anyone about to undo a deliberate choice should first read why it was made.

Next: finding a name.