Version: 0.9.0 Updated: 2026-03-10 Status: Active
Rackula is a lightweight, FOSS, web-based rack layout designer for homelabbers to plan optimal equipment arrangement before physical mounting.
"I have a rack and a pile of gear — I need to figure out the optimal arrangement before I start mounting."
Homelabbers planning rack layouts. Desktop browser users for creation/editing, mobile for viewing.
- ADHD-friendly — Minimal decision points, visual clarity
- Lightweight — Runs entirely in the browser; optional backend for persistence
- Portable — Layouts saved as self-contained
.Rackula.ziparchives - Multi-rack — Support multiple racks per layout
- Single-level nesting — Containers hold devices but cannot be nested
- FOSS — MIT licensed
| Resource | URL |
|---|---|
| Production | https://count.racku.la/ |
| Dev | https://d.racku.la/ |
| Repository | https://github.com/RackulaLives/Rackula |
Rackula supports two deployment modes:
| Mode | Backend | Persistence | Use Case |
|---|---|---|---|
| Static | None | File download/upload | GitHub Pages, simple hosts |
| Persist | Hono/Bun | API server (save/load) | Self-hosted Docker |
In static mode, Rackula is a pure client-side SPA with no network dependencies. In persist mode, an optional API server provides layout storage, authentication, and multi-device sync.
See CLAUDE.md for deployment environments and CI/CD details.
| Component | Technology |
|---|---|
| Framework | Svelte 5 (runes: $state, $derived, $effect) |
| Language | TypeScript (strict mode) |
| Rendering | SVG |
| Pan/Zoom | panzoom |
| Persistence | File download/upload (.Rackula.zip) |
| API Server | Hono on Bun (optional, for persist mode) |
| Data Format | YAML (js-yaml) |
| Validation | Zod |
| Styling | CSS custom properties (design tokens) |
| Analytics | Umami (self-hosted, privacy-focused) |
| Testing | Vitest + @testing-library/svelte + Playwright |
| Build | Vite |
Reference: For complete schema documentation including all fields, validation rules, and YAML examples, see SCHEMA.md.
| Constraint | Value |
|---|---|
| Min device height | 0.5U |
| Max device height | 42U |
| Min rack height | 1U |
| Max rack height | 100U |
| Allowed rack widths | 10", 19" |
| Max racks per layout | No limit |
| Max container nesting | 1 level |
| Max image size | 5MB |
| Supported image formats | PNG, JPEG, WebP |
Two devices collide if all conditions are true:
- Their U ranges overlap (
positiontoposition + u_height - 1) - Their faces collide (see rules below)
- Their slot positions overlap (left/right/full)
Face Collision Rules (Face-Authoritative Model):
The face property is the single source of truth for collision detection:
| Face A | Face B | Collision? |
|---|---|---|
| both | any | YES |
| front | front | YES |
| rear | rear | YES |
| front | rear | NO |
Key Principle: If a device's face is explicitly set to front or rear, it only blocks that face — regardless of the device type's is_full_depth property.
Role of is_full_depth:
- Determines the DEFAULT face when placing a device
- Full-depth devices (
is_full_depth: trueor not specified) default toface: "both" - Half-depth devices (
is_full_depth: false) default toface: "front" - Users can override face via EditPanel; the override takes precedence for collision detection
Interaction Consistency:
Both drag-and-drop and keyboard movement use face-aware validation:
| Operation | Validation Parameters |
|---|---|
| Drag-and-drop | Target face from faceFilter prop |
| Keyboard (↑/↓) | Face from placedDevice.face |
Collision detection is hierarchical:
- Rack-level: Container devices collide with other rack-level devices using normal rules. Child devices (those with
container_id) are invisible to rack-level collision. - Container-level: Children only collide with siblings in the same container. Child position is relative (0 = bottom of container). Children must fit within the container's
u_height.
| Device A | Device B | Collision Check |
|---|---|---|
| Rack-level | Rack-level | Normal U/face/slot rules |
| Rack-level | Child | Never collide |
| Child (container X) | Child (container X) | Check within container |
| Child (container X) | Child (container Y) | Never collide |
Children inherit their parent container's face for rendering purposes.
Extension: .Rackula.zip
my-rack.Rackula.zip
└── my-rack/
├── my-rack.yaml # Layout data
└── assets/
├── device-types/ # Device type default images
│ └── [device-slug]/
│ ├── front.webp
│ └── rear.webp
└── placements/ # Placement override images (optional)
└── [placement-id]/
├── front.webp
└── rear.webp
device-types/— Images uploaded when creating device types (shared by all instances)placements/— Per-placement image overrides (keyed byPlacedDevice.id)- Bundled images are not stored in archives (loaded from app assets)
| Document | Purpose |
|---|---|
| ARCHITECTURE.md | Codebase overview and entry points |
| SCHEMA.md | Complete data schema reference |
| BRAND.md | Design system and brand colours |
| CLAUDE.md | Development workflow and commands |
| CHANGELOG.md | Version history |