summaryrefslogtreecommitdiff
path: root/notes/primitives-vs-domains.md
diff options
context:
space:
mode:
authorMaze Winther <mazewinther@gmail.com>2026-05-02 16:38:06 +0200
committerMaze Winther <mazewinther@gmail.com>2026-05-02 16:38:06 +0200
commit8ab3aeba7c99cb634254d5c1f52fab3f5f4811b1 (patch)
treee7913d52cf35223ec95fa7c10e86b1c6b2a44cf4 /notes/primitives-vs-domains.md
parent0b7597b31fa956c7f3d4838923b545ba5e341422 (diff)
downloadopencut-8ab3aeba7c99cb634254d5c1f52fab3f5f4811b1.tar.gz
docs: move NOTES.md file
Diffstat (limited to 'notes/primitives-vs-domains.md')
-rw-r--r--notes/primitives-vs-domains.md57
1 files changed, 57 insertions, 0 deletions
diff --git a/notes/primitives-vs-domains.md b/notes/primitives-vs-domains.md
new file mode 100644
index 00000000..6708246c
--- /dev/null
+++ b/notes/primitives-vs-domains.md
@@ -0,0 +1,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). \ No newline at end of file