Skip to content

Latest commit

 

History

History
237 lines (165 loc) · 13.3 KB

SPEC.md

File metadata and controls

237 lines (165 loc) · 13.3 KB

Rackula Technical Overview

Version: 26.5.0 Updated: 2026-06-11 Status: Active


1. Overview

Purpose

Rackula is a lightweight, FOSS, web-based rack layout designer for homelabbers to plan optimal equipment arrangement before physical mounting.

Core Problem

"I have a rack and a pile of gear — I need to figure out the optimal arrangement before I start mounting."

Target User

Homelabbers planning rack layouts. Desktop browser users for creation/editing, mobile for viewing.

Design Principles

  • ADHD-friendly — Minimal decision points, visual clarity
  • Lightweight — Runs entirely in the browser; optional backend for persistence
  • Portable — Layouts saved as self-contained .Rackula.zip archives
  • Multi-rack — Support multiple racks per layout
  • Single-level nesting — Containers hold devices but cannot be nested
  • FOSS — MIT licensed

Links

Resource URL
Production https://count.racku.la/
Dev https://d.racku.la/
Repository https://github.com/RackulaLives/Rackula

2. Deployment Modes

Rackula supports two deployment modes:

Mode Backend Persistence Use Case
Static None File download/upload Cloudflare Workers, static 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.


3. Tech Stack

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)
Testing Vitest + @testing-library/svelte + Playwright
Build Vite

4. Data Model

Reference: For complete schema documentation including all fields, validation rules, and YAML examples, see SCHEMA.md.

Mounting Model

Rackula models rack mounting carrier-first. This is a design principle: it decides where a device can sit and answers the recurring "why can't I place this at U5 1/3?" question with one authoritative rule.

Rails register equipment at whole-U boundaries only, matching the EIA-310 standard. A U is defined by three mounting holes; those holes locate the boundary of a unit, they are not alternative mounting positions. There is no real 1/3U or 1/2U rail position on a physical rack, so Rackula does not offer one. Rail positions are whole-U integers.

Devices smaller than a full U, or narrower than full width, do not bolt to the rails on their own. They mount inside a carrier: a bracket, tray, or shelf that occupies a whole U. The carrier registers to the rails at a whole-U boundary, and the small devices register to the carrier. A half-width pair, for example, is one carrier with two cells side by side. A MikroTik K-79 is a branded 2x2 carrier holding up to four small units in 1U.

The mounting rule:

Device Mounts on
Full-width, integer u_height Rails
Sub-U height, non-integer height, or sub-width A carrier

Carriers are ordinary devices with a face and a whole-U position. A device with u_height below 1, a non-integer u_height, or less than full width cannot be placed on the rails directly; it must be a child of a carrier. This rule is enforced in the schema, in store placement actions, and in drag-and-drop targeting, so a fractional rail position cannot be reintroduced from any path.

A device is less than full width when it has slot_width: 1 (half width) or a measured width_mm. Inside a carrier, a cell accepts a measured device when width_mm is no more than the cell's width_fraction of the rack's clear opening, plus 0.5 mm. Cells of 0.33 and 0.66 count as the exact thirds they round down from. The clear opening is the published one where it exists: 222.25 mm (8.75 in) for 10", 450 mm for 19" (EIA-310 minimum), and 500 mm for 21" (ETSI ETS 300 119-3). 23" has no published opening and is approximated as the nominal width minus 1.25 in. Without width_mm, a half-width device needs a cell of at least 0.5 and a full-width device a cell of 1.0.

A measured device may also carry height_mm, from which its u_height is derived: the smallest multiple of 0.5U that holds it, within 0.5 mm. In a carrier it is drawn at height_mm, on the floor of its cell, so its image keeps the device's proportions. A measured device can be turned 90 degrees onto its side, and back, before or after it is placed; 0 and 90 are the only angles. Turned, its width and height swap, so every cell fit uses the device as it stands. Its generated carrier is reshaped with it: the cell is recut to the turned width and the carrier takes the whole U that holds its tallest child. A turn that does not fit is refused.

Because a measured device's fit depends on the rack, a rack width change or a move to another rack recuts each generated carrier so its cells keep their devices' measured widths. The change is refused when a measured child would no longer fit a shipped carrier's cell, or a generated carrier's cells and gaps would no longer fit the new opening. A measured device gets a carrier generated around one cell cut to its own size, whatever its height: the carrier takes the whole U that holds the cell, so only a width past the rack's clear opening leaves it placeable only in an existing shelf or carrier with a wide enough cell.

Rationale: fractional rail offsets modelled a physical fiction and scattered paired half-width gear into impossible positions. Carrier-first keeps the data faithful to how equipment actually mounts, and gives sub-U devices a real parent rather than a floating coordinate.

Legacy layouts and share links that used fractional rail positions are adapted on load: positions snap to the nearest whole U, and sub-U or paired half-width placements are wrapped in an automatically synthesized carrier. The adaptation runs once on the read path. Older files open with their gear intact, now mounted in carriers the user did not place by hand.

Constraints

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

Interface Data

Device types can define interfaces[] port templates (see SCHEMA.md for the InterfaceTemplate shape). Interface data is optional; most starter and brand-pack devices are visual-only today. Brand packs (src/lib/data/brandPacks/) do not yet carry interface data; that population is tracked as deferred follow-up work (#260).

Starter library devices (src/lib/data/starterLibrary.ts) that ship with interface data:

Device Slug Interfaces
Switch (24-Port) 24-port-switch 24x 1000base-t
Switch (48-Port) 48-port-switch 48x 1000base-t
Half Switch (8-Port) 1u-half-switch 8x 1000base-t
Fiber Patch Panel 1u-fiber-patch-panel 12x 1000base-x-sfp
Patch Panel (24-Port) 24-port-patch-panel 24x 1000base-t
Patch Panel (48-Port) 48-port-patch-panel 48x 1000base-t
Half Patch Panel 1u-half-patch-panel 12x 1000base-t
Amplifier (1U) 1u-amplifier xlr-3 inputs, speakon outputs, rs-232 control
Amplifier (2U) 2u-amplifier xlr-3 inputs, speakon outputs, rs-232 and management control
Audio Processor 1u-audio-processor xlr-3 in, trs-1-4 out, aes3 out, dual dante, rs-232 control
AV Receiver (1U) 1u-av-receiver hdmi in/out, rca aux in, rs-232 control
AV Receiver (2U) 2u-av-receiver hdmi in/out, rca aux in, management and rs-232 control
Power Amplifier (3U) 3u-power-amplifier xlr-3 inputs, speakon outputs, dual dante, management control
Streaming Encoder 1u-streaming-encoder hdmi and sdi-bnc in, xlr-3 audio in, 1000base-t network
Video Switcher 1u-video-switcher hdmi/displayport/vga in, hdmi out, rs-232 and management control

Collision Detection

Two rail-level devices collide if both conditions are true:

  1. Their U ranges overlap (position to position + u_height - 1)
  2. Their faces collide (see rules below)

Rail-level devices are all whole-U and full-width (see Mounting Model above), so there is no left/right slot dimension to compare at the rails. Sub-U gear lives inside carriers, where collision is checked per cell (see Container-Aware Collision below).

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: true or not specified) default to face: "both"
  • Half-depth devices (is_full_depth: false) default to face: "front"
  • Users can override face via EditPanel; the override takes precedence for collision detection

Interface Position Constraints:

Half-depth device types (is_full_depth: false) have a single physical face. Their interface templates cannot have mixed position values — all explicitly positioned interfaces must use the same face (front or rear). Unpositioned interfaces default to front. This constraint is enforced by DeviceTypeSchema.

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

Container-Aware Collision

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.


5. File Format

Archive Structure

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 by PlacedDevice.id)
  • Bundled images are not stored in archives (loaded from app assets)

See Also

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