Architecture
Understand the GenomeJS compiler, monorepo boundaries, resolution pipeline, adapters, builds, tests, and runtime constraints.
GenomeJS is organized as a small TypeScript monorepo.
The framework-neutral Core package owns token compilation and runtime behavior. React, Vue, and Svelte packages adapt Core subscriptions into each framework's reactive model.
Application configuration
↓
@genomejs/core
↓
Resolved traits and CSS output
↓
React / Vue / Svelte adaptersRepository layout
genome/
├── packages/
│ ├── core/
│ │ └── src/
│ ├── react/
│ │ └── src/
│ ├── vue/
│ │ └── src/
│ └── svelte/
│ └── src/
├── .github/
│ └── workflows/
│ └── ci.yml
├── package.json
├── package-lock.json
├── tsconfig.json
├── tsconfig.base.json
├── vitest.config.ts
└── vitest.setup.tsThe root package is private and manages:
packages/*as npm workspaces.
Package dependency direction
@genomejs/core
↑
├── @genomejs/react
├── @genomejs/vue
└── @genomejs/svelteCore does not depend on the framework adapters.
This keeps the compiler usable in:
- Vanilla TypeScript
- Browser applications
- Server environments
- React
- Vue
- Svelte
- Custom integrations
Core configuration
A Genome begins with:
interface GenomeConfig {
primitives: Record<string, Primitive>;
tokens: Record<string, TokenDefinition>;
}Example:
const genome = new Genome({
primitives: {
baseSpacing: 16,
},
tokens: {
spacing: (dna, context) => {
const scale = typeof context.scale === "number" ? context.scale : 1;
return `${Number(dna.baseSpacing) * scale}px`;
},
},
});Compiler pipeline
Construction performs:
Copy configuration
↓
Discover dependencies
↓
Validate references
↓
Sort derived tokens
↓
Resolve values
↓
Express CSS propertiesDependency discovery
Derived token functions receive a recording DNA proxy during the tracking pass.
spacing: (dna) => Number(dna.baseSpacing) * 2;Reading:
dna.baseSpacing;records this relationship:
baseSpacing → spacingDependencies do not need a separate declaration.
Tracking context
During dependency tracking, runtime context is represented by an inert proxy that returns:
undefined;Dependency compilation tracks DNA reads, not context property reads.
Token relationships should therefore be determined by stable DNA access patterns.
Missing-reference validation
Known graph values are:
Primitive names
Function-valued token namesWhen a token reads an unknown DNA name, construction throws:
UnresolvedTokenError;Token "surface" references undefined token(s): missingSurfaceResolution ordering
GenomeJS uses topological ordering to resolve derived values after their dependencies.
Conceptually:
baseSpacing
↓
controlSpacing
↓
sectionSpacingThe resolver executes:
controlSpacing
sectionSpacingin that order.
Circular dependencies
If the graph cannot be fully ordered, GenomeJS traces a cycle and throws:
CircularDependencyError;Example:
first → second → firstGraph failures occur early during construction rather than producing partially resolved output.
Value resolution
Resolution starts with a new DNA record containing the primitives:
const dna = {
...primitives,
};Each derived function runs in compiled order:
dna[key] = tokenFunction(dna, context);The final DNA record is stored internally.
Individual values are exposed through:
genome.getTrait(name);The complete DNA snapshot is not currently public.
Runtime context
Genome context starts as:
{
}mutate() performs a shallow merge:
this.context = {
...this.context,
...patch,
};Then it runs resolution again.
Context patch
↓
Shallow merge
↓
Resolve all derived tokens
↓
Express changed CSS
↓
Notify subscribersCSS expression
When a target exists, every resolved DNA entry maps to a CSS custom property.
buttonColorbecomes:
--g-button-colorGenomeJS compares the new string value with the last expressed value.
It calls:
target.style.setProperty(cssVariable, value);only when the value changed.
This avoids repeated identical CSS writes.
No-target operation
The Core constructor accepts:
HTMLElement | null;In a browser, the default target is:
document.documentElement;Outside a browser, the default is:
null;When no target exists:
- Compilation works
- Resolution works
- Mutation works
- Trait reading works
- Subscriptions work
- CSS expression is skipped
This is the basis of Core's server-rendering safety.
Subscriptions
Subscribers are stored in a set:
Set<() => void>;After resolution and CSS expression, GenomeJS invokes every listener.
const unsubscribe = genome.subscribe(listener);The returned cleanup removes the listener.
GenomeJS does not currently catch subscriber exceptions.
Scopes
scope() creates a new Genome using:
- The parent's primitive definitions
- The parent's token definitions
- Another
HTMLElement - The parent's current context
- Optional context overrides
const child = parent.scope(element, {
density: "compact",
});The child becomes independent after creation.
Future parent mutations do not automatically propagate.
React adapter
The React package uses:
useSyncExternalStore();It provides:
- A Genome subscription function
- A trait snapshot function
- The same snapshot for server rendering
Genome notification
↓
React reads getTrait()
↓
Component rerendersThe package supports React 18 and newer.
Vue adapter
The Vue package:
- Creates a ref from
getTrait() - Subscribes after mounting
- Updates the ref after Genome changes
- Unsubscribes during unmounting
Genome notification
↓
ref.value changes
↓
Vue updates dependentsSvelte adapter
The Svelte package uses:
$state
$effectThe current value is exposed through:
trait.value;The effect creates and cleans up the Genome subscription.
The package ships rune-aware source that must be processed by a Svelte 5-aware consumer.
TypeScript project references
The root TypeScript project references:
packages/core
packages/react
packages/vue
packages/svelteThis enables repository-wide project checking:
npx tsc -b --prettyPackage builds
Core, React, and Vue produce package output with TypeScript-aware bundling.
Their published packages include:
- ESM
- CommonJS
- Type declarations
- Source maps
Svelte uses Svelte package tooling and publishes a Svelte-specific entry.
Tests
Vitest runs in a jsdom environment.
Included patterns:
packages/*/src/**/*.test.ts
packages/*/src/**/*.test.tsxThe test configuration includes Svelte and Svelte Testing Library plugins.
Test coverage includes:
- Core dependency resolution
- Graph errors
- Runtime mutation
- CSS write deduplication
- Browser bindings
- React updates
- React SSR
- Vue updates
- Svelte updates
Continuous integration
CI runs on:
push to main
pull requests targeting mainCurrent steps:
npm ci
npx tsc -b --pretty
npm run build --workspaces --if-present
npx vitest runThe workflow uses:
Node.js 20
UbuntuPublic package exports
Core
Genome
CircularDependencyError
UnresolvedTokenError
contrastRatio
lockContrast
fluidScale
bindMediaQueries
bindContainerSizeTypes:
Primitive
RuntimeContext
DNAReader
TokenFn
TokenDefinition
GenomeConfig
MutatorReact
useGenomeTraitVue
useGenomeTraitSvelte
genomeTraitCurrent architectural limitations
Static entries under tokens
The public TokenDefinition type allows:
Primitive | TokenFn;But the compiler currently treats only function-valued token entries as derived graph nodes.
Static values should be placed in:
primitives;until the type and runtime behavior are aligned.
Private compiled graph
Dependency order and dependency sets are internal.
The public API does not currently expose the compiled graph.
No complete snapshot getter
Individual traits are readable through:
getTrait();The complete DNA record is private.
No context getter
Runtime context is private.
Applications should track important application state independently rather than depending on a context snapshot API that does not exist.
No destroy()
Genome does not currently expose a destruction method.
Consumers must clean up:
- Subscriptions
- Media-query bindings
- Resize observers
- External synchronization
Scope independence
Scopes copy parent context at creation.
They do not maintain a live parent-child inheritance relationship.
Broad trait typing
Trait names are accepted as strings and values return:
string | number;Per-token key and value inference is not currently generated.
Design principles
GenomeJS architecture prioritizes:
- Framework independence
- Explicit runtime context
- Pure token relationships
- Early graph validation
- Normal CSS custom properties
- Small framework adapters
- Synchronous predictable mutation
- Browser-safe Core construction
- Honest package boundaries