# Carbon Preferences API

Store and retrieve per-user key-value pairs via the Carbon `hydrogen_preferences` object.
Adrian built this for Hydrogen's theme/UI settings, but it works for any lightweight per-user config (adom-desktop flags, feature toggles, timestamps, etc.).

## Hard limit

**10 MB total per user.** Keep individual values small. Don't dump logs, blobs, or large JSON objects into preferences.

## Data model

`GET /user` returns the authenticated user. The `hydrogen_preferences` field contains three sections:

```typescript
hydrogen_preferences: {
  settings:    Record<string, string | number | boolean>
  keybindings: Record<string, string>
  extensions:  Record<string, { version: string, enabled: boolean }>
}
```

## Reading preferences

Read via `GET /user` and extract the `hydrogen_preferences` field.

### CLI
```bash
# Full user profile (hydrogen_preferences is a top-level field)
adom-cli carbon user get

# Extract just settings with jq
adom-cli carbon user get | jq '.hydrogen_preferences.settings'

# Get a single setting
adom-cli carbon user get | jq -r '.hydrogen_preferences.settings["general.appearance.theme"]'
```

### curl (from container)
```bash
API_KEY=$(cat /var/run/adom/api-key)
curl -s -H "Cookie: session_token=$API_KEY" https://carbon.adom.inc/user \
  | jq '.hydrogen_preferences'
```

### JavaScript (Hydrogen web)
```javascript
const user = await get(`${API_BASE_URL}/user`);
const theme = user.hydrogen_preferences.settings['general.appearance.theme'];
```

### Tauri (Hydrogen Desktop)
```javascript
const { invoke } = await import('@tauri-apps/api/core');
const result = await invoke('auth_proxy', { path: '/user', method: 'GET' });
const settings = result.data.hydrogen_preferences.settings;
```

## Writing settings

### Endpoint
```
PATCH /user/hydrogen/settings
Content-Type: application/json
```

### Request body
```json
{ "key": "dotted.key.name", "value": "<string | number | boolean | null>" }
```

- **key** -- dotted lowercase string matching `^(?:[a-z0-9_]+)(?:\.[a-z0-9_]+)*$`
- **value** -- `string`, `number` (f64), `boolean`, or `null` to delete

One key per request. Returns `{ "status": 200 }` on success.

### CLI
```bash
# Set a boolean
adom-cli carbon user hydrogen-settings '{"key":"desktop.enabled","value":true}'

# Set a string
adom-cli carbon user hydrogen-settings '{"key":"desktop.last_slug","value":"john-myproject-abc123"}'

# Set a number
adom-cli carbon user hydrogen-settings '{"key":"desktop.last_connect_epoch","value":1747612800}'

# Delete a key (set value to null)
adom-cli carbon user hydrogen-settings '{"key":"desktop.enabled","value":null}'
```

### curl (from container)
```bash
API_KEY=$(cat /var/run/adom/api-key)
curl -s -X PATCH https://carbon.adom.inc/user/hydrogen/settings \
  -H "Cookie: session_token=$API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"key":"desktop.enabled","value":true}'
```

### JavaScript (Hydrogen web)
```javascript
await patch(`${API_BASE_URL}/user/hydrogen/settings`, {
  key: 'general.appearance.theme',
  value: 'adom-light'
});
```

### Tauri (Hydrogen Desktop)
```javascript
await invoke('auth_proxy', {
  path: '/user/hydrogen/settings',
  method: 'PATCH',
  body: { key: 'desktop.enabled', value: true }
});
```

## Writing keybindings

### Endpoint
```
PATCH /user/hydrogen/keybindings
Content-Type: application/json
```

### CLI
```bash
adom-cli carbon user hydrogen-keybindings '{"key":"editor.save","value":"Ctrl+S"}'
```

## Key naming conventions

Use dotted namespaces to avoid collisions. Third-party tools (adom-desktop, CLI tools, etc.) MUST use their own namespace prefix so they never clobber keys the Hydrogen frontend uses.

| Namespace | Owner | Examples |
|-----------|-------|---------|
| `general.*` | Hydrogen frontend | `general.appearance.theme`, `general.developer_mode` |
| `schematic.*` | Hydrogen schematic editor | `schematic.nudge_amount.small` |
| `3d.*` | Hydrogen 3D viewer | `3d.control_style` |
| `code.*` | Hydrogen code editor | `code.vim_emulation_mode` |
| `simulator.*` | Hydrogen simulator | `simulator.code_editor_position` |
| `desktop.*` | adom-desktop / HD | `desktop.enabled`, `desktop.last_connect_epoch`, `desktop.last_slug` |

**Rule:** Never write to `general.*`, `schematic.*`, `3d.*`, `code.*`, `simulator.*`, or `extensions.*` -- those belong to the Hydrogen frontend. Pick a unique prefix for your tool (e.g., `desktop.*`, `chipfit.*`, `tsci.*`).

## Known Hydrogen settings (defined in settings tree)

These have schema definitions with types, defaults, and validation:

| Key | Type | Default | Description |
|-----|------|---------|-------------|
| `general.developer_mode` | boolean | false | Developer mode |
| `general.pane_focus_mode` | boolean | true | Only focused pane is active |
| `general.appearance.theme` | string | adom-dark | Theme |
| `general.appearance.user_interface_style` | string | standard | UI density (standard, compact) |
| `schematic.nudge_amount.small` | number | 1 | Arrow key nudge (px) |
| `schematic.nudge_amount.large` | number | 8 | Shift+arrow nudge (px) |
| `schematic.invert_zoom_direction` | boolean | false | Invert scroll zoom |
| `schematic.default_cursor` | string | crosshair | Default cursor |
| `3d.control_style` | string | fusion | 3D controls (fusion, solidworks, blender) |
| `3d.invert_zoom_direction` | boolean | false | Invert 3D scroll zoom |
| `code.appearance.font.family` | string | DM Mono | Code font |
| `code.line_numbers.enabled` | boolean | true | Show line numbers |
| `code.line_numbers.style` | string | absolute | Line number style |
| `code.vim_emulation_mode` | boolean | false | Vim mode |
| `simulator.code_editor_position` | string | right | Code panel position |

Custom keys (like `desktop.*`) are not in this schema -- they are stored and returned as-is with no server-side validation.

## Behavior notes

- Setting a value to `null` deletes the key entirely (not stored as null)
- Setting a value equal to the schema default in the Hydrogen frontend also deletes it (clean storage)
- Keys not in the schema are accepted and stored -- no server-side key validation
- One key per PATCH request; batch by issuing multiple requests
- Auth: same as all Carbon endpoints -- session token cookie or X-Api-Key header
- The full preferences object is returned inline with every `GET /user` call, so there's no separate GET endpoint for just preferences
