--- name: Storage transcripts CRUD type: architecture-map-page slice: 05-core-storage-hotkey-build last_verified: 2026/05/09 --- # Storage transcripts CRUD > **Where you are:** [Architecture map](../README.md) → [Core, Storage, Hotkey, Build](README.md) → Storage transcripts CRUD **Plain English summary.** All the read/write operations against the `transcripts` table. Inserts validate the profile FK at the application layer in addition to the database constraint so the error message is friendly. Updates split between content updates (`update_transcript`) and metadata updates (`update_transcript_meta`). ## At a glance - Source: `crates/storage/src/database.rs:80-289`. - Public surface: `insert_transcript`, `get_transcript`, `list_transcripts`, `list_transcripts_paged`, `count_transcripts`, `update_transcript`, `update_transcript_meta`, `delete_transcript`, `search_transcripts`. - Public types: `InsertTranscriptParams<'a>` (input), `TranscriptRow` (output). - Consumers: slice 2 transcription command (the post-inference write), the history command (reads), the export plumbing, the MCP server. ## Public types ### `InsertTranscriptParams<'a>` — `crates/storage/src/database.rs:61` Borrowed-string parameter struct. 16 fields; one-to-one with the v1+v8+v14 column set: ```rust pub struct InsertTranscriptParams<'a> { pub id: &'a str, pub text: &'a str, pub source: &'a str, pub profile_id: &'a str, pub title: Option<&'a str>, pub audio_path: Option<&'a str>, pub duration: f64, pub engine: Option<&'a str>, pub model_id: Option<&'a str>, pub inference_ms: Option, pub sample_rate: Option, pub audio_channels: Option, pub format_mode: Option<&'a str>, pub remove_fillers: bool, pub british_english: bool, pub anti_hallucination: bool, } ``` `segments_json`, `manual_tags`, `template`, `language`, `starred`, `llm_tags` are not on the insert struct — they default at the column level (`''` or `0`) and are populated later via `update_transcript_meta`. ### `TranscriptRow` — `crates/storage/src/database.rs:793` ```rust pub struct TranscriptRow { pub id: String, pub text: String, pub source: String, pub profile_id: String, pub title: Option, pub audio_path: Option, pub duration: f64, pub engine: Option, pub model_id: Option, pub inference_ms: Option, pub sample_rate: Option, pub audio_channels: Option, pub format_mode: Option, pub remove_fillers: bool, pub british_english: bool, pub anti_hallucination: bool, pub created_at: String, pub starred: bool, pub manual_tags: String, pub template: String, pub language: String, pub segments_json: String, pub llm_tags: String, // v14 } ``` ## Functions ### `insert_transcript(pool, ¶ms) -> Result<()>` — `crates/storage/src/database.rs:80` 1. **Pre-flight FK check.** `profile_exists(pool, params.profile_id)` (private, `database.rs:1094`) runs `SELECT 1 FROM profiles WHERE id = ?`. If the profile is not present, returns a typed `lumotia_storage::Error::InvalidReference { entity: Entity::Profile, reason: "unknown profile id '...'" }`. Without this, sqlite would raise `FOREIGN KEY constraint failed` which the frontend cannot easily disambiguate. 2. Single `INSERT INTO transcripts (...) VALUES (...)` with all 16 fields. ### `get_transcript(pool, id) -> Result>` — `crates/storage/src/database.rs:117` Single-row select. Returns `Ok(None)` for missing id (not `Err`). ### `list_transcripts(pool, limit) -> Result>` — `crates/storage/src/database.rs:129` Calls `list_transcripts_paged(pool, limit, 0)`. ### `list_transcripts_paged(pool, limit, offset) -> Result>` — `crates/storage/src/database.rs:135` `SELECT ... FROM transcripts ORDER BY created_at DESC LIMIT ? OFFSET ?`. The History page in slice 1 paginates through this. ### `count_transcripts(pool) -> Result` — `crates/storage/src/database.rs:153` `SELECT COUNT(*) FROM transcripts`. Used by the History page to render `(N total)` and to decide whether to render the empty state. ### `update_transcript(pool, id, ...) -> Result<()>` — `crates/storage/src/database.rs:168` Updates the **content** fields: `text`, `title`, `language`, `segments_json`. Triggers `transcripts_au` so the FTS index is refreshed. ### `update_transcript_meta(pool, id, ...) -> Result<()>` — `crates/storage/src/database.rs:221` Updates the **metadata** fields: `starred`, `manual_tags`, `template`, plus `llm_tags` (post-v14). Separate from `update_transcript` because the user-driven star / manual-tag actions are common and we do not want to rebuild FTS index for a starring change. The `transcripts_au` trigger does fire either way (it watches `AFTER UPDATE ON transcripts` with no column predicate), so worth verifying that single-column updates do not bloat the FTS table over time. ### `delete_transcript(pool, id) -> Result<()>` — `crates/storage/src/database.rs:257` `DELETE FROM transcripts WHERE id = ?`. The cascade on `segments.transcript_id` removes child rows. The `transcripts_ad` trigger removes the row from the FTS index. ### `search_transcripts(pool, query, limit) -> Result>` — `crates/storage/src/database.rs:270` Full-text search via FTS5. Detailed under [`storage-fts5-search.md`](storage-fts5-search.md). ## Data flow / contract - All reads return `TranscriptRow` shaped from the same private helper `transcript_row_from(&SqliteRow)` at `database.rs:856`. - All writes propagate to FTS via the schema-level triggers; no application-level FTS handling is needed. - The `profile_id` is required at insert time; there is no path to an orphaned transcript post-v9. ## Watch-outs - **`update_transcript_meta` does not bump `created_at`.** Correct behaviour: starring a transcript shouldn't change its sort position. - **Stamp columns (`engine`, `model_id`, `inference_ms`, `sample_rate`, `audio_channels`, `format_mode`) are written once at insert and never updated.** They are provenance, not state. - **`remove_fillers`, `british_english`, `anti_hallucination` are also stamped once at insert.** They record what setting was active when the transcript was produced, not the user's current preference. - **No bulk insert.** Each transcript is one round-trip. A future bulk-import path would batch `BEGIN; INSERT...x; COMMIT` for throughput. ## See also - [Schema and migrations](storage-schema-and-migrations.md) - [FTS5 search](storage-fts5-search.md) - [Storage overview](storage-overview.md) - [Slice 2 transcription command](../02-tauri-runtime/README.md) — the immediate caller of `insert_transcript`.