# Core Domain Model Rules Follow these rules for all changes in `core/`. ## Purpose - `core/` contains pure domain types for the image editor: documents, artboards, layers, assets, ids, and geometry. - Keep it framework-free, runtime-light, and reusable by renderer, commands, persistence, and tests. ## Hard Rules - Do not import React, DOM APIs, UI components, storage, networking, filesystem, command handlers, or renderer code. - Do not add side effects, global state, caches, singletons, or environment-dependent behavior. - Prefer exported TypeScript `type`s. Add runtime code only when it is pure, deterministic, and domain-generic. - Keep domain files small and focused. One concept per file; re-export public types from `core/index.ts`. - Use `import type` / `export type` for type-only dependencies. - Preserve discriminated unions. Every `Layer` variant must have a stable `type` string. - Do not weaken domain types with `any`, broad `string | number` unions, optional fields, or nullable values unless the domain truly allows absence. - IDs are opaque aliases from `id.ts`; do not inline plain `string` ID fields in models. - Do not duplicate geometry shapes outside `geometry.ts`. Use `Vec2D`, `Size`, `Rect`, `Bounds`, `Transform`, `Mat2D`, and `CoordinateSpace`. - Avoid app/workflow concerns in names and fields. Domain models describe image-editing state, not UI state. ## Model Invariants - `ImageDocument` owns `artboards` and shared `assets`. - `Artboard` owns top-level `layers` and has document-space `bounds`. - `LayerGroup.children` owns nested layers; only groups have children. - `ImageLayer.assetId` must reference an `Asset.id` in the same document. - `BaseLayer.opacity` is normalized `0..1`; `visible` and `locked` are explicit booleans. - `Transform` stores position, scale, and rotation only; derived matrices/bounds should not be persisted on models. - `clippingMask.maskLayerId` references another layer by `LayerId`; do not embed mask layer objects. ## Changing Models - Before adding a field, decide whether it is core persisted state or derived/UI state. Derived/UI state does not belong here. - When adding a new domain type, create a focused file and export it from `index.ts`. - When adding a new layer kind, update the union in `layer.ts`, add a discriminant, and document its required relationships. - Keep names stable and serialization-friendly; assume these types may be saved, loaded, diffed, and migrated.