Architecture
Monorepo layout, package structure, and design decisions.
Architecture
This document describes how Ziteox Forge is structured and why.
Monorepo layout
ziteox-forge/
├── packages/
│ └── ui/ # @ziteox/ui — shared React components
├── examples/
│ └── playground/ # Vite demo app for local development
├── docs/ # Human + agent documentation (this tree)
├── agents/ # Reserved
├── prompts/ # Reserved
└── standards/ # ReservedTooling:
| Tool | Role |
|---|---|
| pnpm workspaces | Package linking (packages/*, examples/*) |
| Turborepo | Cached builds across packages |
TypeScript (tsc) | Compile @ziteox/ui to dist/ |
Root scripts:
pnpm install # Install all workspace dependencies
pnpm build # Build all packages (turbo)
pnpm dev # Watch mode where configured
pnpm clean # Remove build artifacts@ziteox/ui package structure
packages/ui/src/
├── index.ts # Root public API — re-exports domains only
├── animations/ # Internal motion presets
├── shared/ # Internal hooks and utilities
│ └── hooks/
├── theme/ # Theme domain
├── navigation/ # Navigation domain
└── branding/ # Branding domainExport model
Consumers import from:
| Import path | Contents |
|---|---|
@ziteox/ui | All public symbols |
@ziteox/ui/theme | Theme domain |
@ziteox/ui/navigation | Navigation domain |
@ziteox/ui/branding | Branding domain |
Each domain has index.ts as its barrel. The root index.ts re-exports domain barrels only — never deep files.
package.json exports field maps subpaths to compiled dist/ outputs.
Internal modules
| Path | Purpose | Public? |
|---|---|---|
shared/hooks/use-mounted.ts | Hydration guard for client components | No |
shared/hooks/use-skip-initial-animation.ts | Suppress first-paint motion/transition | No |
shared/hooks/use-prefers-reduced-motion.ts | prefers-reduced-motion media query | No |
animations/springs.ts | Shared motion spring presets | No |
Internal modules may change without semver notice. Do not import them from consuming apps.
Domain-first organization
Components are grouped by product concern (theme, navigation, branding), not by file type.
Rationale:
- Keeps related code colocated as domains grow
- Limits cross-domain coupling
- Makes subpath exports (
@ziteox/ui/navigation) meaningful - Scales to dozens of components without a flat
components/dump
See domain-boundaries.md for ownership rules.
Framework-agnostic design
@ziteox/ui targets React 18+ and avoids framework-specific imports in component source.
| Pattern | Implementation |
|---|---|
| Routing links | Plain <a> (not next/link) |
| Icons | Inline SVG (no lucide-react in package) |
| Styling | Scoped ziteox-* CSS injected per component (no Tailwind in package) |
| Theme persistence | next-themes as peer dependency; wrapper in ThemeProvider |
Apps using Next.js, Vite, or other React frameworks consume the same package. Next.js App Router consumers import client components ("use client") from client boundaries where required.
Controlled components
Interactive components use controlled state at the public API boundary:
<HamburgerButton isOpen={isOpen} onOpenChange={setIsOpen} />Rationale:
- Parent owns state (local state, context, or URL)
- No hidden global providers required for basic usage
- Easier to test and compose
- Menu/nav context can be added at app level when needed
ThemeToggle is an exception at the convenience layer: it reads next-themes internally but still delegates rendering to controlled AnimatedThemeToggler.
Animation strategy
Two tiers — see animation-guidelines.md:
- CSS transitions — simple morphs (e.g.
HamburgerButton) motion(motion/react) — complex SVG/path animation (e.g.AnimatedThemeToggler)
Do not add framer-motion as a separate dependency. Forge standardizes on motion.
Build output
- Input:
packages/ui/src/**/*.tsx|ts - Output:
packages/ui/dist/(ESM +.d.ts) - Module format: ESM (
"type": "module") - JSX:
react-jsxruntime
The playground resolves @ziteox/ui to source via Vite alias during local dev. Production consumers use compiled dist/.
Dependency model
Runtime libraries are peer dependencies on @ziteox/ui:
react,react-domnext-themes(theme domain)motion(animated components)
Apps install peers once. The package does not bundle them.
Adding a new component (checklist)
- Choose domain — see domain-boundaries.md
- Add component file under
packages/ui/src/<domain>/ - Export from domain
index.ts - Re-export from
packages/ui/src/index.ts - Add subpath to
package.jsonexportsif new domain - Add
docs/ui/<component>.md - Add playground example if useful
- Run
pnpm build