Step 2: The Domains
Split the problem into three areas, and say where the boundaries are.
Three areas of the problem, each one something you could assign work to: the bitmap, the tools that draw into it, and getting drawings on and off disk.
Canvas
src/domains/Canvas.psy:
use "@psy/morphic"
export domain Canvas:
description: The bitmap, and everything that writes to it.
covers:
- The pixel buffer and its fixed dimensions
- Compositing a stamp onto the buffer
- Undo snapshots and restoration
- Presenting the buffer on screen at the current zoom
excludes:
- Deciding what to stamp. That is Tools. The canvas is told what shape
and colour to write, never which tool asked.
invariants:
- The buffer is never resized after a drawing is created
- Nothing outside this domain writes a pixelTools
src/domains/Tools.psy:
use "@psy/morphic"
export domain Tools:
description: What the user draws with, and how one is chosen.
covers:
- The set of available tools and which is active
- Tool options: size, colour, hardness
- Turning pointer input into stamps
excludes:
- Writing pixels. Tools produce stamps; the canvas applies them.
- The colour values themselves. Those belong to ColorPicker, which
Tools reads from.
invariants:
- Exactly one tool is active at all times. There is no null tool.
- Switching tools mid-stroke is impossible; the stroke ends first.Files
src/domains/Files.psy:
use "@psy/morphic"
export domain Files:
description: Creating, saving and opening drawings.
covers:
- Creating a new drawing at a chosen size
- Encoding the buffer as PNG and writing it
- Decoding a PNG into a buffer
- Tracking whether there are unsaved changes
excludes:
- Prompting the user. The dialogs belong to the UI; this domain
answers "is it safe to discard" and does the reading and writing.
invariants:
- Saving never modifies the buffer
- A failed write leaves the previous file intactCheck it:
psy checkcheck: no problems found in 6 file(s)Why excludes is on all three
Every one of those exclusions is a boundary between two domains that obviously touch — and each names where the responsibility actually lands.
Read the Canvas and Tools exclusions together:
Canvas.excludes: Deciding what to stamp. That is Tools.
Tools.excludes: Writing pixels. Tools produce stamps; the canvas applies them.That is one decision, stated from both sides, and it is the decision that makes
StrokeModel implementable. Somebody
adding a tool now cannot reasonably put a putPixel call in it — the boundary is
written down in the two places they would look.
Boundaries stated only from the inside are half a boundary. The near-misses are where mistakes actually happen, because they are the plausible-looking wrong answers.
What they generate
.claude/skills/tools/SKILL.md:
---
name: tools
description: "Domain: What the user draws with, and how one is chosen."
---
# Tools
## Covers
- The set of available tools and which is active
- Tool options: size, colour, hardness
- Turning pointer input into stamps
## Excludes
- Writing pixels. Tools produce stamps; the canvas applies them.
- The colour values themselves. Those belong to ColorPicker, which Tools reads
from.
## Invariants
- Exactly one tool is active at all times. There is no null tool.
- Switching tools mid-stroke is impossible; the stroke ends first.
## Authority
This describes the product as it is meant to work. Where the implementation
disagrees, treat this as correct unless the task is explicitly to change it.Three, not eight
We could have had a Colour domain, a History domain, a UI domain, an Input domain. We do not, because none of them is an area you would assign a week of work to in a program this size — they are concerns inside the three we have.
Colour lives in Tools' covers. History lives in Canvas'. Input is split across
Tools and StrokeModel. When one of them grows enough that somebody could
plausibly own it, promote it then.
Three to seven is the useful range. Eight domains in a program with five features means the domains are really components.
Next: The Features