Step 1: The Concept
Say what a drawing is, before saying anything about tools.
Before we describe a single tool, we describe what a drawing is. Every decision downstream depends on it, and if we get it wrong nothing below can be right.
The root concept
Create src/concepts/Paint.psy:
use "@psy/morphic"
export concept Paint:
description: How the paint program fits together.
overview:
A drawing is one bitmap. There are no layers, no objects and no shapes
that can be selected after the fact — tools write pixels directly into
the bitmap and the pixels are all that is kept.
This means every edit is destructive, on purpose. It is what a paint
program is, as opposed to a vector editor, and it is why undo is a
bitmap snapshot rather than a list of reversible operations.
constraints:
- The bitmap is the only document state. Everything else is UI.
- Tools write pixels; nothing else does.
- The canvas has a fixed size, chosen when the drawing is created.
nonGoals:
- Layers. Adding them changes what a drawing is, so it is a different
program rather than a later feature.
- Vector shapes. Same reason.Then run:
psy checkcheck: no problems found in 2 file(s)What that bought us
Read the second paragraph of overview again. It says destructive editing is a
decision, and gives the reason.
Without it, an agent asked to "improve undo" has every incentive to propose a command-and-reverse model — that is the better design, in general. The concept is what tells it that in this program the bitmap snapshot is correct, and why.
And nonGoals is doing the same job pre-emptively. "Layers" is the single most
likely unrequested feature suggestion for a paint program. One line kills it, with
a reason a person would accept.
A second concept
Here is an idea that is bigger than any one feature and less specific than a domain: what a stroke is. Pencil, Brush and Eraser all use it, and none of them owns it.
Create src/concepts/StrokeModel.psy:
use "@psy/morphic"
export concept StrokeModel:
description: How a drag becomes marks on the bitmap.
overview:
Pointer input arrives as a series of positions, not a continuous line.
A stroke interpolates between consecutive positions and stamps the
active tool's shape along the interpolated path.
Every tool is therefore the same machinery with a different stamp.
Pencil stamps one hard pixel, Brush stamps a soft disc, Eraser stamps
the background colour. A new tool is a new stamp, not a new stroke
implementation.
invariants:
- A stroke is one undo step, from pointer-down to pointer-up
- Interpolation never skips pixels, however fast the pointer moves
boundaries:
- Where the stamp comes from is the tool's business
- Where the pixels land is the canvas's businesspsy checkcheck: no problems found in 3 file(s)StrokeModel is a concept rather than a domain
because you would not assign work to it — you would draw it on a whiteboard. And
it is not a component because no single file implements it;
it is the shape the tools and the canvas share between them.
That third paragraph — a new tool is a new stamp, not a new stroke implementation — is the sentence that will stop an agent writing three separate drawing loops when we add the Brush.
What these generate
Definitions become skills, so they load when they are relevant. Running the build
now would write .claude/skills/paint/SKILL.md:
---
name: paint
description: "Concept: How the paint program fits together."
---
# Paint
## Overview
A drawing is one bitmap. There are no layers, no objects and no shapes that can
be selected after the fact — tools write pixels directly into the bitmap and the
pixels are all that is kept.
This means every edit is destructive, on purpose. It is what a paint program is,
as opposed to a vector editor, and it is why undo is a bitmap snapshot rather
than a list of reversible operations.
## Constraints
- The bitmap is the only document state. Everything else is UI.
- Tools write pixels; nothing else does.
- The canvas has a fixed size, chosen when the drawing is created.
## Non Goals
- Layers. Adding them changes what a drawing is, so it is a different program
rather than a later feature.
- Vector shapes. Same reason.
## 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 things to notice in that output:
constraintsandnonGoalsare not properties Morphic knows about. They became sections because everything you declare does.nonGoalswas title-cased intoNon Goalsby splitting on the case boundary.- The
Authoritysection we never wrote is inherited from every definition's shared base. It is what makes this normative rather than advisory.
And .claude/skills/stroke-model/SKILL.md:
---
name: stroke-model
description: "Concept: How a drag becomes marks on the bitmap."
---
# StrokeModel
## Overview
Pointer input arrives as a series of positions, not a continuous line. A stroke
interpolates between consecutive positions and stamps the active tool's shape
along the interpolated path.
Every tool is therefore the same machinery with a different stamp. Pencil stamps
one hard pixel, Brush stamps a soft disc, Eraser stamps the background colour. A
new tool is a new stamp, not a new stroke implementation.
## Invariants
- A stroke is one undo step, from pointer-down to pointer-up
- Interpolation never skips pixels, however fast the pointer moves
## Boundaries
- Where the stamp comes from is the tool's business
- Where the pixels land is the canvas's business
## 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.Two files, and the shape of the whole program is now written down.
Next: The Domains