Skip to content

Database

igor-c-assuncao edited this page Jun 20, 2026 · 2 revisions

Database

The app uses SQLite via rusqlite (bundled — no system SQLite dependency). The database file notetaker.db is created in the working directory of the Tauri process.


Schema

meetings table

Column Type Notes
id INTEGER PK AUTOINCREMENT
date TEXT NOT NULL toLocaleString() of recording end time
title TEXT NOT NULL Auto-generated: Meeting <date>
raw_transcript TEXT NOT NULL Plain text from WhisperX
markdown_summary TEXT NOT NULL Markdown/fallback text from the LangGraph workflow
speakers TEXT JSON array of speaker names (nullable)
tags TEXT JSON array of tag strings from LLM (nullable)
structured_summary TEXT Full structured JSON from LangGraph (nullable)
transcript_segments TEXT Versioned transcript evidence JSON (nullable)
schema_version INTEGER Structured transcript/summary schema version, defaults to 1 for old rows

meetings_fts virtual table (FTS5)

CREATE VIRTUAL TABLE meetings_fts
USING fts5(title, raw_transcript, markdown_summary, content='meetings', content_rowid='id');

The content= option links the FTS index to the meetings table without duplicating data. Three triggers (INSERT, UPDATE, DELETE) keep the index in sync automatically.


Migrations

On every startup, lib.rs attempts additive migrations for existing databases:

for sql in &[
    "ALTER TABLE meetings ADD COLUMN speakers TEXT",
    "ALTER TABLE meetings ADD COLUMN tags TEXT",
    "ALTER TABLE meetings ADD COLUMN structured_summary TEXT",
    "ALTER TABLE meetings ADD COLUMN transcript_segments TEXT",
    "ALTER TABLE meetings ADD COLUMN schema_version INTEGER DEFAULT 1",
] {
    if let Err(e) = conn.execute(sql, []) {
        if !e.to_string().contains("duplicate column name") {
            eprintln!("[DB Migration] {}: {}", sql, e);
        }
    }
}

SQLite does not support IF NOT EXISTS on ALTER TABLE. The duplicate column name error is treated as a no-op. Any other error is logged to stderr but does not crash the app.


IPC commands

Command Parameters Returns
save_meeting date, title, raw_transcript, markdown_summary, speakers?, tags?, structured_summary?, transcript_segments?, schema_version? inserted row id
get_meetings — Meeting[] ordered by id DESC
search_meetings query: string Meeting[] via FTS5 MATCH, falls back to get_meetings if query is empty

FTS5 search

search_meetings appends * to the query for prefix search — typing "intel" matches "intelligence". Special characters are stripped before the query is passed to SQLite.

let fts_query = format!("{}*", query.trim().replace('"', ""));

The sidebar search in the UI debounces the input by 300ms before calling search_meetings.


Structured summary JSON shape

{
  "schema_version": 2,
  "metadata": {"title": "Meeting Summary", "date": "2026-06-20", "tags": ["release"]},
  "tldr": "One sentence summary.",
  "participants": [
    {"name": "Alice", "role": "PM", "engagement_level": "high"}
  ],
  "metrics": [
    {"label": "Latency", "value": "68.428s", "trend": "flat"}
  ],
  "key_decisions": [
    {
      "decision": "Ship the Windows sidecar fix",
      "rationale": "The packaged app needs the PyInstaller binary",
      "owner": "Igor",
      "evidence_segment_ids": ["seg_000001"],
      "evidence_quote": "Ship the Windows sidecar fix",
      "confidence": 0.95,
      "inference": false
    }
  ],
  "action_items": [
    {
      "task": "Update the README and wiki",
      "assignee": "Igor",
      "due_date": null,
      "priority": "Medium",
      "status": "open",
      "evidence_segment_ids": ["seg_000002"],
      "evidence_quote": "Update the README and wiki",
      "confidence": 0.9,
      "inference": false
    }
  ],
  "summary_points": ["Important detail from the meeting."],
  "risks": [],
  "open_questions": [],
  "unresolved_topics": []
}

This object is serialized as a JSON string and stored in the structured_summary column. The frontend parses it on load.

If the LLM returns invalid summary JSON, the Python engine stores a transcript-derived fallback summary with summary_generation_warning instead of storing the old hard-failure TL;DR.

Clone this wiki locally