---
name: Core types and enums
type: architecture-map-page
slice: 05-core-storage-hotkey-build
last_verified: 2026/05/09
---

# Core types and enums

> **Where you are:** [Architecture map](../README.md) → [Core, Storage, Hotkey, Build](README.md) → Core types and enums

**Plain English summary.** This is the public type vocabulary every other crate in Lumotia builds on top of. Newtype wrappers (`ModelId`, `EngineName`, `Megabytes`) prevent stringly-typed and unit-mistake bugs at compile time. `Segment` and `Transcript` are the structured shape of a transcription result. Everything is `Serialize` / `Deserialize` so it crosses the Tauri IPC boundary unchanged.

## At a glance

- File: `crates/core/src/types.rs` (177 LOC).
- Re-exported from `crates/core/src/lib.rs` at the top level: `AudioSamples`, `DownloadProgress`, `EngineName`, `Megabytes`, `ModelId`, `Segment`, `Transcript`, `TranscriptionOptions`.
- External deps: `serde 1` only.
- Consumers: every workspace crate. The frontend receives these shapes via Tauri-serialised JSON.

## What's in here

### `ModelId` — `crates/core/src/types.rs:5`

Stringly-typed wrapper around a model identifier. Prevents passing raw strings where a model id is expected.

```rust
pub struct ModelId(String);
impl ModelId {
    pub fn new(id: impl Into<String>) -> Self;
    pub fn as_str(&self) -> &str;
}
impl Display for ModelId;
```

Used in `MagnotiaError::ModelNotFound`, `ModelEntry.id`, `DownloadProgress.model_id`, the LLM model registry (slice 4), the speech model registry (this crate, see [`core-model-registry.md`](core-model-registry.md)), and the file-system path API (`AppPaths::speech_model_dir`).

### `EngineName` — `crates/core/src/types.rs:25`

Same shape as `ModelId`, separate type. Prevents an engine name being passed where a model id is expected and vice versa.

```rust
pub struct EngineName(String);
impl EngineName {
    pub fn new(name: impl Into<String>) -> Self;
    pub fn as_str(&self) -> &str;
}
impl Display for EngineName;
```

### `Megabytes` — `crates/core/src/types.rs:45`

Newtype around `u64` with display formatting that switches between MB and GB at the kilobyte boundary. Prevents bytes / kilobytes / megabytes / gigabytes mix-ups in the model registry and the recommendation scoring path.

```rust
pub struct Megabytes(pub u64);
impl Megabytes {
    pub fn from_gb(gb: f64) -> Self;
    pub fn as_gb(&self) -> f64;
}
impl Display for Megabytes;  // "650 MB" or "1.5 GB"
```

`Megabytes` is `Copy`, `PartialOrd`, `PartialEq`, so models can be sorted and compared without ceremony. `saturating_sub` is used at the call site (`recommendation::score_model`) to compute headroom without underflow.

### `AudioSamples` — `crates/core/src/types.rs:69`

Wraps a `Vec<f32>` with sample-rate and channel metadata. The `mono_16khz` constructor is the canonical Whisper / Parakeet shape; constants come from [`core-constants.md`](core-constants.md).

```rust
pub struct AudioSamples { samples: Vec<f32>, sample_rate: u32, channels: u16 }
impl AudioSamples {
    pub fn new(samples: Vec<f32>, sample_rate: u32, channels: u16) -> Self;
    pub fn mono_16khz(samples: Vec<f32>) -> Self;
    pub fn samples(&self) -> &[f32];
    pub fn into_samples(self) -> Vec<f32>;
    pub fn sample_rate(&self) -> u32;
    pub fn channels(&self) -> u16;
    pub fn duration_secs(&self) -> f64;
}
```

Note: `AudioSamples` does **not** derive `Serialize`. It is an internal Rust shape. The wire format for audio is bytes plus a sidecar metadata struct.

### `Segment` — `crates/core/src/types.rs:117`

A single timed chunk of transcribed text. Plain data, public fields.

```rust
pub struct Segment {
    pub start: f64,  // seconds
    pub end: f64,    // seconds
    pub text: String,
}
```

### `Transcript` — `crates/core/src/types.rs:125`

The full transcription result. Encapsulates the segment list and provides text-join via the `text()` accessor. Fields are private; access is via accessors so the joining logic stays here.

```rust
pub struct Transcript { segments: Vec<Segment>, language: String, duration: f64 }
impl Transcript {
    pub fn new(segments: Vec<Segment>, language: String, duration: f64) -> Self;
    pub fn text(&self) -> String;          // Joins segments with " "
    pub fn segments(&self) -> &[Segment];
    pub fn language(&self) -> &str;
    pub fn duration(&self) -> f64;
}
```

### `TranscriptionOptions` — `crates/core/src/types.rs:163`

Options passed to a transcription engine. `Default` is two `None`s.

```rust
pub struct TranscriptionOptions {
    pub language: Option<String>,
    pub initial_prompt: Option<String>,
}
```

### `DownloadProgress` — `crates/core/src/types.rs:170`

Progress envelope emitted from the model downloader to the frontend. `percent: u8` is computed at the source (the downloader rounds).

```rust
pub struct DownloadProgress {
    pub model_id: ModelId,
    pub file_name: String,
    pub bytes_downloaded: u64,
    pub total_bytes: u64,
    pub percent: u8,
}
```

## Engine, SpeedTier, AccuracyTier

These three live alongside the model registry, not in `types.rs`. They are documented under [`core-model-registry.md`](core-model-registry.md). Listed here for completeness because the slice scope mentions them:

- `Engine` — `Whisper` / `Parakeet` / `Moonshine` (`model_registry.rs:7`)
- `SpeedTier` — `Instant` / `Fast` / `Moderate` / `Slow` (`model_registry.rs:14`)
- `AccuracyTier` — `Excellent` / `Great` / `Good` (`model_registry.rs:23`)
- `LanguageSupport` — `EnglishOnly` / `Multilingual(u16)` (`model_registry.rs:31`)

## Data flow / contract

- All types in `types.rs` derive `Serialize` and `Deserialize` except `AudioSamples` (Rust-only).
- The JSON wire shape on the Tauri boundary is what `serde_json` produces from these structs. No bespoke representation.
- `Segment` and `Transcript` are the contract between the transcription engines (slice 3) and the AI-formatting pipeline (slice 4).

## Watch-outs

- **Newtypes are not transitively unique.** `ModelId::new("whisper-tiny-en")` and `EngineName::new("whisper-tiny-en")` will both compile. The discipline is at the call site.
- **`Transcript.text()` joins with `" "`.** No re-punctuation. Smart-paragraph breaks happen later in the AI-formatting pipeline (`SMART_PARAGRAPH_GAP_SECS` from [`core-constants.md`](core-constants.md)).
- **`AudioSamples::duration_secs` returns 0.0 if `sample_rate == 0`.** Defensive; callers should never construct a zero-rate sample bag, but the helper does not panic.

## See also

- [Constants module](core-constants.md)
- [Error type](core-error.md)
- [Model registry (Engine, SpeedTier, AccuracyTier)](core-model-registry.md)
- [Slice 3 transcription engines](../03-audio-transcription/README.md)
- [Slice 4 AI-formatting pipeline](../04-llm-formatting-mcp/README.md)
