summaryrefslogtreecommitdiff
path: root/notes/primitives-vs-domains.md
blob: 6708246c0ee9be45197ab1e2561a127d64f536ba (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
# Primitives vs domains

The codebase has a recurring smell: **primitive value types defined inside
domain folders**. The clearest current example is `Transform`, which lives in
`apps/web/src/rendering/index.ts`. Rendering happens to consume it — but so do
`@/timeline`, `@/preview`, `@/animation`, `@/text`, and anything else that
positions things on a 2D canvas. It's not "of" rendering; rendering just owns
the file.

## The test

If a type can be described without mentioning clips, tracks, effects, layers,
keyframes, or any other product concept — and it has no behavior beyond shape
— it's a **primitive**. The moment a type needs to know what a clip is, it
has crossed into domain territory.

Primitives have:

- No domain-specific invariants (a 2D position doesn't care that it's a clip's
position; it's just `{ x, y }`).
- No dependencies on other parts of the app — they're leaves.
- Multiple unrelated consumers across domains.
- A name that would make sense in any 2D editor / video tool / graphics lib.

Domains, in contrast, can name things that only make sense given the rest of
the product (`TimelineElement`, `Effect`, `GraphicDefinition`, `MediaAsset`).

## Why it matters

When a primitive lives in a domain folder, every other domain that consumes it
takes a misleading dependency — `@/timeline` ends up importing from
`@/rendering` not because timeline needs rendering, but because that's where
`Transform` happens to sit. The dependency graph lies, and pieces that should
move freely become anchored to the wrong layer.

## The refactor

Move primitives out of domain folders into a primitives location (somewhere
like `apps/web/src/primitives/`, or split by concern — `geometry/`, `time/`,
`color/`, etc.). Whatever the bucket, the rule is "no product concepts, no
behavior, no upward dependencies".

Don't bulk-move. Each move is deliberate — the right destination depends on
what other primitives already exist and what naming convention has emerged.

## Side effects to watch for

Files often end up parked next to misplaced primitives because they had nowhere
better to live. Example: `apps/web/src/rendering/animation-values.ts` exists
only because `Transform` lives next door. Once `Transform` moves to a primitive
location, that file collapses back into `apps/web/src/animation/values.ts`
alongside the other resolve-at-time helpers — there's no remaining reason to
split them.

When moving a primitive, look at what *else* in its current folder only exists
because of that primitive. Those usually want to move too (or merge somewhere
else once the anchor is gone).