# State Management

Mechanica uses Zustand for client-side state. State is split by persistence and shareability.

## Stores

```text
src/store/preferencesStore.ts
src/store/viewerStore.ts
```

## Preferences store

Persisted to localStorage with key:

```text
mechanica-preferences-v1
```

State:

- `theme`
- `favorites`

Actions:

- `setTheme`
- `toggleTheme`
- `toggleFavorite`
- `isFavorite`

This state is private to the current browser and is not encoded in share links.

## Viewer store

Transient runtime state:

- Current machine ID
- Selected part
- Hovered part
- Per-part visibility and opacity
- Playback state
- RPM
- Time scale
- Cycle step index
- Exploded distance
- Display mode
- Cross-section axis and offset
- Annotation visibility
- Camera preset
- Custom camera override

Actions are intentionally generic. They do not reference specific machine IDs beyond initialization.

## URL state boundary

Shareable state is serialized by `src/utils/urlState.ts` and hydrated in `ViewerPage`.

URL state includes:

- Camera preset or vectors
- Exploded distance
- Display mode
- Clipping
- Hidden parts
- Opacity overrides
- Annotation labels

URL state does not include:

- Theme
- Favourites
- Local performance mode
- Internal hover state

## Initialization flow

```text
ViewerPage resolves slug
  └─ gets MachineRegistryEntry
      └─ parses URL query
          └─ initializeMachine(machine.id, partIds, initialPartState)
              └─ applies viewer URL overrides
```

If the route changes to a new machine, machine-specific state resets to safe defaults. If the same route receives updated URL parameters, valid part overrides merge into the existing state.

## Why not one global store?

Keeping preferences and viewer state separate prevents:

- Accidentally persisting temporary part opacity.
- Polluting share links with private settings.
- Coupling catalogue favourites to Canvas rendering.
- Large store subscriptions across unrelated UI regions.

## Selector guidance

Components should subscribe to the smallest state slice they need:

```ts
const rpm = useViewerStore((state) => state.rpm);
```

Avoid subscribing to the whole store in frequently-rendered components.

## Future additions

Planned stores or slices:

- Asset loading progress.
- Performance quality profile.
- Guided-tour runtime.
- Machine-specific option state, e.g. planetary gearbox input/output selection.
- Toast/notification state.

Machine-specific option state should be keyed by machine ID and schema-defined controls, not stored as arbitrary component-local flags.
