Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

version: 2
updates:
- package-ecosystem: "bun" # See documentation for possible values
directory: "/" # Location of package manifests
- package-ecosystem: 'bun' # See documentation for possible values
directory: '/' # Location of package manifests
schedule:
interval: "weekly"
interval: 'weekly'
22 changes: 22 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
name: ci

on:
push:
branches: [main]
pull_request:

concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }}
cancel-in-progress: true

jobs:
ci:
name: ci
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
- run: bun install --frozen-lockfile
- run: bun run lint
- run: bun run fmt:check
- run: bunx tsc -b
6 changes: 4 additions & 2 deletions .prettierrc → .oxfmtrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
"semi": false,
"singleQuote": true,
"trailingComma": "all",
"plugins": ["@ianvs/prettier-plugin-sort-imports", "prettier-plugin-tailwindcss"],
"sortTailwindcss": {},
"importOrder": ["<THIRD_PARTY_MODULES>", "", "^~/(.*)$", "", "^[./]"],
"importOrderTypeScriptVersion": "5.2.2"
"importOrderTypeScriptVersion": "5.2.2",
"sortPackageJson": false,
"ignorePatterns": ["public/", "dist/", "bun.lock"]
}
5 changes: 1 addition & 4 deletions .oxlintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,7 @@
"caughtErrorsIgnorePattern": "^_"
}
],
"react/only-export-components": [
"error",
{ "allowConstantExport": true }
]
"react/only-export-components": ["error", { "allowConstantExport": true }]
},
"ignorePatterns": ["dist", "node_modules", "public"]
}
37 changes: 26 additions & 11 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
This file provides guidance to Claude Code (claude.ai/code) when working with code in this
repository.

## Project Overview

Rack Explorer is an interactive 3D web application for visualizing Oxide Rack hardware architecture. Users navigate a hierarchical component tree (rack → sleds → CPUs/disks/etc.) with smooth camera animations, and can take guided tours with step-by-step walkthroughs.
Rack Explorer is an interactive 3D web application for visualizing Oxide Rack hardware
architecture. Users navigate a hierarchical component tree (rack → sleds → CPUs/disks/etc.)
with smooth camera animations, and can take guided tours with step-by-step walkthroughs.

## Commands

Expand All @@ -16,13 +19,15 @@ Rack Explorer is an interactive 3D web application for visualizing Oxide Rack ha

Package manager is **Bun**.

**Do not start the dev server (`bun run dev`) — the user always verifies UI changes themselves.** Run `bunx tsc -b` to verify changes compile.
**Do not start the dev server (`bun run dev`) — the user always verifies UI changes
themselves.** Run `bunx tsc -b` to verify changes compile.

## Architecture

### State Management

Uses `@tldraw/state` reactive atoms (not Redux/Zustand). Core state lives in `src/atoms.ts`:

- `selectedId` / `hoveredId` — current selection and hover
- `navigationMode` — `'free'` (exploration) or `'guided'` (tour mode)
- `activeTourId` / `activeTourStepIndex` — guided tour state
Expand All @@ -33,16 +38,20 @@ Read atoms in React with `useValue()` from `@tldraw/state-react`.
### Component Tree (`src/data/componentTree.ts`)

Central data structure defining the hardware hierarchy. Each `ComponentNode` has:

- ID, label, model path (GLB), children, camera waypoint, specs reference
- Optional `instances: Vec3[]` for repeated components (e.g., 32 compute sleds in a 2×16 grid)
- Optional `instances: Vec3[]` for repeated components (e.g., 32 compute sleds in a 2×16
grid)

**ID convention:** `"component-id"` for single items, `"component-id:0"`, `"component-id:1"` for instances.
**ID convention:** `"component-id"` for single items, `"component-id:0"`, `"component-id:1"`
for instances.

### 3D Rendering (`src/Scene.tsx`)

- React Three Fiber canvas with `CameraControls` for orbit/pan
- `SelectableGLBModel` — renders a single GLB model
- `InstancedGLBModel` — efficient instanced rendering for repeated components (single draw call for ~32 instances)
- `InstancedGLBModel` — efficient instanced rendering for repeated components (single draw
call for ~32 instances)
- GPU tier detection (`@pmndrs/detect-gpu`) adjusts DPR and post-processing
- DRACO-compressed GLB models loaded via `src/loaders.ts`
- Post-processing (selection outlines, AO) lazy-loaded via `React.lazy`
Expand All @@ -58,16 +67,22 @@ Central data structure defining the hardware hierarchy. Each `ComponentNode` has

### Guided Tours (`src/data/guidedTours.ts`)

Data-driven tour definitions with steps containing title, description, selectedId, and optional custom waypoints. In guided mode, direct interaction (clicks, keyboard nav) is disabled — navigation is through tour controls only.
Data-driven tour definitions with steps containing title, description, selectedId, and
optional custom waypoints. In guided mode, direct interaction (clicks, keyboard nav) is
disabled — navigation is through tour controls only.

### UI Layer

- **Tailwind CSS 4** with Oxide Design System (`@oxide/design-system`) for colors, typography, and icons
- **Motion (Framer Motion)** for animations — consistent spring config: `type: 'spring', duration: 0.5, bounce: 0`
- Key UI components: `Outline.tsx` (tree browser), `Specifications.tsx` (specs panel), `GuidedTourPanel.tsx`, `LandingModal.tsx`
- **Tailwind CSS 4** with Oxide Design System (`@oxide/design-system`) for colors,
typography, and icons
- **Motion (Framer Motion)** for animations — consistent spring config:
`type: 'spring', duration: 0.5, bounce: 0`
- Key UI components: `Outline.tsx` (tree browser), `Specifications.tsx` (specs panel),
`GuidedTourPanel.tsx`, `LandingModal.tsx`

## Conventions

- Prettier with 92 print width, import sorting (third-party → local `~/` → relative `./`)
- Strict TypeScript (`tsconfig.app.json`)
- Instance disposal: `InstancedGLBModel` manually disposes Three.js geometries/materials on unmount to prevent VRAM leaks
- Instance disposal: `InstancedGLBModel` manually disposes Three.js geometries/materials on
unmount to prevent VRAM leaks
22 changes: 17 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
# Rack Explorer

An interactive 3D view of the [Oxide Cloud Computer](https://oxide.computer), live at [explorer.oxide.computer](https://explorer.oxide.computer).
An interactive 3D view of the [Oxide Cloud Computer](https://oxide.computer), live at
[explorer.oxide.computer](https://explorer.oxide.computer).

Navigate the rack hierarchy — from the chassis down to sleds, CPUs, DIMMs, and disks — or take a guided tour through how the system fits together.
Navigate the rack hierarchy — from the chassis down to sleds, CPUs, DIMMs, and disks — or
take a guided tour through how the system fits together.

## Development

Expand All @@ -17,7 +19,10 @@ bun run lint # oxlint

## Analytics

Analytics are off by default. The canonical deploy at `explorer.oxide.computer` sets `VITE_ANALYTICS_DOMAIN` at build time, which injects a [Plausible](https://plausible.io) script proxied through the `vercel.json` rewrites. Forks build with the variable unset and ship no analytics.
Analytics are off by default. The canonical deploy at `explorer.oxide.computer` sets
`VITE_ANALYTICS_DOMAIN` at build time, which injects a [Plausible](https://plausible.io)
script proxied through the `vercel.json` rewrites. Forks build with the variable unset and
ship no analytics.

## Stack

Expand All @@ -39,6 +44,13 @@ Analytics are off by default. The canonical deploy at `explorer.oxide.computer`

Source code is licensed under the [Mozilla Public License 2.0](LICENSE).

The 3D models, textures, images, and other binary assets are licensed under [CC BY-NC-ND 4.0](https://creativecommons.org/licenses/by-nc-nd/4.0/). They may not be used for commercial purposes or in derivative works. See [LICENSE-ASSETS](LICENSE-ASSETS) for full terms and covered paths.
The 3D models, textures, images, and other binary assets are licensed under
[CC BY-NC-ND 4.0](https://creativecommons.org/licenses/by-nc-nd/4.0/). They may not be used
for commercial purposes or in derivative works. See [LICENSE-ASSETS](LICENSE-ASSETS) for
full terms and covered paths.

**Trademark notice:** The Oxide name, logo, and hardware designs are trademarks or trade dress of Oxide Computer Company. Nothing in these licenses grants the right to use them in any way that suggests endorsement or affiliation with Oxide Computer Company. Some models depict third-party components; all third-party trademarks remain the property of their respective owners and their inclusion does not imply endorsement by those manufacturers.
**Trademark notice:** The Oxide name, logo, and hardware designs are trademarks or trade
dress of Oxide Computer Company. Nothing in these licenses grants the right to use them in
any way that suggests endorsement or affiliation with Oxide Computer Company. Some models
depict third-party components; all third-party trademarks remain the property of their
respective owners and their inclusion does not imply endorsement by those manufacturers.
45 changes: 43 additions & 2 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,12 @@
"dev": "vite",
"build": "tsc -b && vite build",
"lint": "oxlint",
"fmt": "oxfmt",
"fmt:check": "oxfmt --check",
"preview": "vite preview"
},
"dependencies": {
"@ctrl/tinycolor": "^4.2.0",
"@ianvs/prettier-plugin-sort-imports": "^4.7.1",
"@oxide/design-system": "^6.2.1",
"@pmndrs/detect-gpu": "^6.0.4",
"@react-three/drei": "^10.7.7",
Expand Down Expand Up @@ -39,8 +40,8 @@
"@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^6.0.1",
"baseline-browser-mapping": "^2.10.27",
"oxfmt": "^0.52.0",
"oxlint": "^1.63.0",
"prettier": "^3.8.3",
"typescript": "~6.0.3",
"vite": "^8.0.11"
}
Expand Down
6 changes: 1 addition & 5 deletions src/components/PostProcessing.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -82,11 +82,7 @@ export const PostProcessing = ({
}

return (
<EffectComposer
ref={setSharedComposer}
enableNormalPass={aoEnabled}
autoClear={false}
>
<EffectComposer ref={setSharedComposer} enableNormalPass={aoEnabled} autoClear={false}>
{effects}
</EffectComposer>
)
Expand Down
5 changes: 1 addition & 4 deletions src/components/SelectableGLBModel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,7 @@ export const SelectableGLBModel = memo(function SelectableGLBModel({
// Swap any perforation material on the loaded scene to the shared module-
// level material before sceneInfo clones it (clone shares material refs).
// Idempotent per gltf.scene.
useMemo(
() => rewritePerforations(gltf.scene, textures, gl),
[gltf.scene, textures, gl],
)
useMemo(() => rewritePerforations(gltf.scene, textures, gl), [gltf.scene, textures, gl])

// Re-clone whenever the source GLTF or low-tier setting changes. The clone
// owns any new lambert materials we create during downgrade — they get
Expand Down
Loading
Loading