On production the rack drew only name plates. Cutsheet items were named after the product (UDM-Pro, USW-Pro-24-POE (400W)), but /rack matched devices by their exact UniFi name (Dream Machine Pro, 24RJ45-POE). The page was also left-aligned and hard to read in light mode.
What changes
Item → device link. Cutsheet items get a Gandalf device field, with suggestions drawn from known devices. An item keeps its own name and shows the linked device's faceplate, ports and status.
Cable checks, undocumented-link suggestions, ticket context and the device page all resolve the alias.
A cable can name either the item or the device. A cable on USW-Pro-24-POE (400W) port 3 is checked against 24RJ45-POE.
Shelves. An item can sit on a shelf instead of having a rack unit, set with the item form's "On shelf" field. The shelf draws its contents side by side, each with live status and a link.
Renaming a shelf carries over to the items on it.
Deleting a shelf, or changing its kind, is refused while it still holds items.
Multiple racks. Items have a rack (empty means the first rack), and the page shows a tab per rack, also reachable as ?rack=Name. Racks are configured as racks: [{"name", "units"}]. Without that setting there is one rack, named by rack.name and sized by rack.units.
Export.Download SVG / PNG saves the current rack on a white page. Faceplates are copied with their computed styles inlined, so the file stands alone.
Layout and theme
The rack is centred.
Runs of 3 or more empty units fold into a "U26–42 · 17U free" band. A "Show empty units" toggle (remembered per browser) draws them in full.
Every device fills the 19" width.
The UPS 2U Pro shows only its front, so it fits its 2U slot.
The rack keeps its own hardware palette in both themes, with a softer frame in light mode. Before this, plate names were dark blue on dark grey in light mode.
Schema. New cutsheet_items columns rack, shelf and device are added on first use. The migration was checked against a table on the old schema and runs safely twice.
After deploy
Your existing items need their Gandalf device set once:
Item
Gandalf device
UDM-Pro
Dream Machine Pro
USW-Pro-24-POE (400W)
24RJ45-POE
USW-Aggregation
8SFP-AGG
UPS-2U-Pro
UPS 2U Pro
UNVR-Pro has no live device in UniFi's data, so it stays a plate.
Testing
Automated: 355 tests pass (8 new). They cover the alias checks (match, wrong neighbor, documented, runs_for), the rack API (linked, unlinked, shelf contents, rack tabs and selection, 404 for an unknown rack), and the updated rack-shape test.
Local MariaDB:
the column migration
all three shelf rules (not a shelf, rack unit set, shelf on a shelf)
rename carry-over
delete and kind-change blocked while the shelf holds items
Playwright with items renamed to production-style names, which also renames their cables:
4 faceplates drawn
hovering the DAC still matches across the alias
shelf contents shown
rack tabs switch and update the URL
SVG and PNG downloads work
light and dark themes and 390 px all checked, with no JS errors
flake8 and bandit are clean, and ESLint reports 0 errors.
## Why
On production the rack drew only name plates. Cutsheet items were named after the product (`UDM-Pro`, `USW-Pro-24-POE (400W)`), but `/rack` matched devices by their exact UniFi name (`Dream Machine Pro`, `24RJ45-POE`). The page was also left-aligned and hard to read in light mode.
## What changes
**Item → device link.** Cutsheet items get a **Gandalf device** field, with suggestions drawn from known devices. An item keeps its own name and shows the linked device's faceplate, ports and status.
- Cable checks, undocumented-link suggestions, ticket context and the device page all resolve the alias.
- A cable can name either the item or the device. A cable on `USW-Pro-24-POE (400W)` port 3 is checked against `24RJ45-POE`.
**Shelves.** An item can sit on a shelf instead of having a rack unit, set with the item form's "On shelf" field. The shelf draws its contents side by side, each with live status and a link.
- Renaming a shelf carries over to the items on it.
- Deleting a shelf, or changing its kind, is refused while it still holds items.
**Multiple racks.** Items have a rack (empty means the first rack), and the page shows a tab per rack, also reachable as `?rack=Name`. Racks are configured as `racks: [{"name", "units"}]`. Without that setting there is one rack, named by `rack.name` and sized by `rack.units`.
**Export.** **Download SVG / PNG** saves the current rack on a white page. Faceplates are copied with their computed styles inlined, so the file stands alone.
**Layout and theme**
- The rack is centred.
- Runs of 3 or more empty units fold into a "U26–42 · 17U free" band. A "Show empty units" toggle (remembered per browser) draws them in full.
- Every device fills the 19" width.
- The UPS 2U Pro shows only its front, so it fits its 2U slot.
- The rack keeps its own hardware palette in both themes, with a softer frame in light mode. Before this, plate names were dark blue on dark grey in light mode.
**Schema.** New `cutsheet_items` columns `rack`, `shelf` and `device` are added on first use. The migration was checked against a table on the old schema and runs safely twice.
## After deploy
Your existing items need their **Gandalf device** set once:
| Item | Gandalf device |
|---|---|
| UDM-Pro | Dream Machine Pro |
| USW-Pro-24-POE (400W) | 24RJ45-POE |
| USW-Aggregation | 8SFP-AGG |
| UPS-2U-Pro | UPS 2U Pro |
UNVR-Pro has no live device in UniFi's data, so it stays a plate.
## Testing
- **Automated:** 355 tests pass (8 new). They cover the alias checks (match, wrong neighbor, documented, `runs_for`), the rack API (linked, unlinked, shelf contents, rack tabs and selection, 404 for an unknown rack), and the updated rack-shape test.
- **Local MariaDB:**
- the column migration
- all three shelf rules (not a shelf, rack unit set, shelf on a shelf)
- rename carry-over
- delete and kind-change blocked while the shelf holds items
- **Playwright** with items renamed to production-style names, which also renames their cables:
- 4 faceplates drawn
- hovering the DAC still matches across the alias
- shelf contents shown
- rack tabs switch and update the URL
- SVG and PNG downloads work
- light and dark themes and 390 px all checked, with no JS errors
- flake8 and bandit are clean, and ESLint reports 0 errors.
Screenshots are in the comments.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Cutsheet items gain a "Gandalf device" link, so an item named after the
product (UDM-Pro) shows the live device's faceplate, ports and status.
Cable checks, suggestions, tickets and the device page treat an item and
its linked device as one. Without this, product-named items drew as bare
name plates.
Items can sit on a shelf instead of a rack unit; the shelf draws what is
on it with live status. Items have a rack, the page shows a tab per rack
(config "racks"), and a rack can be downloaded as SVG or PNG.
The rack is centred, long runs of empty units fold into one band (with a
toggle), every device fills the 19" width, the UPS shows its front only,
and the rack keeps its own palette so it reads in light mode.
New cutsheet_items columns rack, shelf and device are added on first use.
Deleting a shelf, or making it something else, is refused while it holds
items; renaming it carries over.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Screenshots (local preview, items renamed to match production)
Light mode. The rack is centred, empty units are folded into bands, every device is full width, the UPS shows its front, the shelf shows its contents, and the rack tabs sit on top.
Dark mode.
Phone (390 px).
Download PNG (the export shows every unit).
## Screenshots (local preview, items renamed to match production)
**Light mode.** The rack is centred, empty units are folded into bands, every device is full width, the UPS shows its front, the shelf shows its contents, and the rack tabs sit on top.

**Dark mode.**

**Phone (390 px).**

**Download PNG** (the export shows every unit).

jared
merged commit cd3d2f05ee into main2026-11-10 22:29:39 -05:00
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Why
On production the rack drew only name plates. Cutsheet items were named after the product (
UDM-Pro,USW-Pro-24-POE (400W)), but/rackmatched devices by their exact UniFi name (Dream Machine Pro,24RJ45-POE). The page was also left-aligned and hard to read in light mode.What changes
Item → device link. Cutsheet items get a Gandalf device field, with suggestions drawn from known devices. An item keeps its own name and shows the linked device's faceplate, ports and status.
USW-Pro-24-POE (400W)port 3 is checked against24RJ45-POE.Shelves. An item can sit on a shelf instead of having a rack unit, set with the item form's "On shelf" field. The shelf draws its contents side by side, each with live status and a link.
Multiple racks. Items have a rack (empty means the first rack), and the page shows a tab per rack, also reachable as
?rack=Name. Racks are configured asracks: [{"name", "units"}]. Without that setting there is one rack, named byrack.nameand sized byrack.units.Export. Download SVG / PNG saves the current rack on a white page. Faceplates are copied with their computed styles inlined, so the file stands alone.
Layout and theme
Schema. New
cutsheet_itemscolumnsrack,shelfanddeviceare added on first use. The migration was checked against a table on the old schema and runs safely twice.After deploy
Your existing items need their Gandalf device set once:
UNVR-Pro has no live device in UniFi's data, so it stays a plate.
Testing
runs_for), the rack API (linked, unlinked, shelf contents, rack tabs and selection, 404 for an unknown rack), and the updated rack-shape test.Screenshots are in the comments.
🤖 Generated with Claude Code
Screenshots (local preview, items renamed to match production)
Light mode. The rack is centred, empty units are folded into bands, every device is full width, the UPS shows its front, the shelf shows its contents, and the rack tabs sit on top.
Dark mode.
Phone (390 px).
Download PNG (the export shows every unit).