mianaz/jargonslayer / docs / power users
Landing 中文 App GitHub
Power Users

Run, Customize, Extend

This page is for operators and developers: running JargonSlayer from source, standing up the local sidecars, the full settings reference, theming and dictionary-pack internals, webhook payloads, and the app's own architecture and test suite. If you just want to use the app, the User Guide covers that.

npm workspace Local Whisper sidecar 17-token themes Remote pack manifests Webhooks IndexedDB

Self-hosting the Web App

The web app is a normal Next.js project. Clone, install, and run:

git clone https://github.com/mianaz/jargonslayer.git
cd jargonslayer
npm install
npm run dev
# open http://localhost:3000

For a production-like local run, build and start instead of using the dev server:

npm run build
npm start

Before committing changes, run the same checks CI runs:

npm run typecheck
npm test
npm run build

Server-side provider keys go in .env.local and are never shipped to the browser:

ANTHROPIC_API_KEY=sk-ant-...

Deploy tiers

The default build is the full local-first build — every feature is available, gated only by what the visitor configures. Setting NEXT_PUBLIC_DEPLOY_TIER=preview switches to the hosted-preview tier: sidecar-dependent features (local Whisper, tab audio through the sidecar, video URL import, subscription-direct) show disabled/greyed with an explanation instead of failing, and zero-key AI use is backed by the preview server's own configured model path — see AI Modes in the User Guide for what that means for privacy.

PWA install

The web app installs as a Progressive Web App off its existing manifest and icons — no separate build. From Chrome/Edge desktop it installs as a windowed app; from Safari on iOS/iPadOS it adds to the home screen.

Local Whisper Sidecar

Local Whisper, tab audio, and sidecar-based recording/URL import on the web app all go through one Python process, sidecar/whisper_server.py. On the desktop app this is provisioned and self-managed by the first-run wizard — nothing below is needed there unless you're pointing it at an external sidecar (see sidecarMode below). For the web app, set it up by hand:

cd sidecar
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
python whisper_server.py --model small

It serves live realtime transcription over ws://127.0.0.1:8765 and a local HTTP job API for recording and video-URL imports, built on faster-whisper, energy-based VAD, optional local audio saving, and pyannote for diarization when configured.

Model choices

--model accepts tiny, base, small, medium, large-v3, large-v3-turbo, and parakeet-tdt-0.6b-v3 (MLX-accelerated, English-priority — the manual-sidecar counterpart to desktop's 英文加速 engine). small is a reasonable daily-use default for this manual path. The desktop wizard's own curated picker is narrower and macOS-specific: small / medium / large-v3 / large-v3-turbo plus an available Parakeet entry, with medium pre-selected.

Speaker diarization

Configure a Hugging Face token (hfToken in Settings, or pasted during desktop's first-run onboarding — see Desktop App in the User Guide) after accepting the usage terms for pyannote's segmentation and speaker-diarization models on Hugging Face. Batch imports diarize the whole recording. Realtime diarization is beta: the sidecar re-runs pyannote on a recent tail window, matches speakers against a stable registry, and pushes label updates back to the UI without blocking transcription.

Moved-directory gotcha

Python virtual environments bake in absolute paths. If you move or rename the repository, the venv breaks silently; rebuild it (rm -rf sidecar/.venv, then repeat the setup above).

sidecarMode (desktop)

The desktop build's sidecarMode setting is managed (default — the app installs, starts, and restarts its own bundled sidecar) or external (point it at a sidecar you started yourself, same as the web build). The web build is always effectively external.

No sidecar README yet. This page and the sidecar's own test files (see Testing) are the current runbook for sidecar operation.

Subscription-direct Agent Sidecar (experimental)

sidecar/agent_server.py is experimental and local-only. It drives your own already-authenticated claude or codex CLI login for detect/define only — translation and summary/reports always go through the normal app routes, in every build.

  • Default loopback host: http://127.0.0.1:8767.
  • Endpoints: GET /agent/health, POST /agent/detect, POST /agent/define.
  • Detect/define calls require a loopback Origin plus a one-time connection code printed on sidecar startup, copied by hand into Settings.
  • Feature gate: NEXT_PUBLIC_ENABLE_SUBSCRIPTION_DIRECT=1 at build time — unset, the whole UI section and call branch don't exist in the built bundle.
  • If the sidecar is unreachable, not logged in, or quota-limited, the app falls back to dictionary detection instead of failing.
Environment warning: if ANTHROPIC_API_KEY is present in the agent sidecar's own environment, Claude calls may prefer that key over your subscription login. The sidecar warns about this but does not unset it — clear it from the sidecar's shell environment if you want subscription login to win.

Settings Reference

One settings object controls transcription, model routing, detection, translation, display, and integrations. As of v0.7.3, LLM provider keys are stored and sent per-provider, never as one shared key; as of v0.7.4, 设置 → 密钥 is the single UI home for every key and token in the table below.

There is no single apiKey field anymore. Each provider preset has its own field — apiKeyAnthropic, apiKeyOpenai, apiKeyDeepseek, apiKeyQwen, apiKeyOpenrouter, apiKeyPoe, apiKeyOllama, apiKeyCustom (plus llmCustomHost for the custom endpoint) — so switching providers can never carry the previous provider's key to the new one's host. A bare apiKey only still exists inside a per-task taskLlm override.

Transcription

FieldWhat it controlsNotes
engineLive capture engine choice.import and browser-whisper are stored on imported sessions but are not selectable live engines.
modeAudio source: mic / tab / system-audio / import / url.Its own control since v0.7.4 — changeable mid-meeting, not just before one starts.
languageBCP-47 language passed to Web Speech.e.g. en-US.
whisperUrlLocal Whisper websocket URL.Default ws://localhost:8765.
whisperModelSidecar model name.Default small — see Whisper Sidecar for the full choice list.
proxyUrlDesktop-only manual proxy override."" (default) follows the system proxy; otherwise http:///https:///socks5:///socks5h://, userinfo allowed for basic auth — for env-var-only or PAC-only networks the OS auto-detection can't see.
partialsWhether the sidecar streams interim (non-final) text.Default on.
preferOnDeviceSpeechPrefer the browser's on-device recognizer over its cloud fallback.Default on.
sidecarModeDesktop only: managed or external.See Whisper Sidecar.
tabAudioCloudProviderWhich BYOK cloud engine tab audio uses.soniox or deepgram.
sonioxKey / deepgramKey / elevenLabsKeyBYOK keys for the three third-party cloud STT engines.Stored under 设置 → 密钥.

AI routing

FieldWhat it controlsNotes
providerPrimary LLM route.anthropic or openai-compat.
baseUrlBase URL for openai-compat providers.e.g. https://api.deepseek.com/v1.
apiKeyAnthropic / apiKeyOpenai / apiKeyDeepseek / apiKeyQwen / apiKeyOpenrouter / apiKeyPoe / apiKeyOllama / apiKeyCustomPer-provider key.Isolated since v0.7.3 — see the callout above.
llmCustomHostBinds apiKeyCustom to whichever base URL the 自定义 preset points at.
taskLlmOptional per-task provider/model overrides for translate, detect, summary.Define always rides detect's config; a domain absent (or enabled:false) inherits the primary route entirely.
agentUrlSubscription-direct agent sidecar base URL.Default http://127.0.0.1:8767 — see Agent Sidecar.

Detection

FieldWhat it controlsNotes
autoDetectLive detection on/off, the master switch.
aiDetectOnly controls the LLM layer.The dictionary floor stays instant and offline regardless of this flag.
minConfidenceThreshold applied when merging detections.
enabledPacksWhich built-in/installed dictionary packs are active.null = all packs enabled; core is always on regardless.
detectIdiomMaxWords / detectIdiomMaxCharsLength ceiling above which an idiom/slang span is dropped as an over-selected sentence.Default 12 words / 90 chars.
packAutoUpdateInstalled remote packs auto-check for a newer version.Once a day.

Translation

FieldWhat it controlsNotes
bilingualTranscriptLive segment-by-segment translation on/off.Feeds explainLanguage.
explainLanguageTarget language for explanations/translation.zh (default) or en.
translateEngineWhich translation engine runs."system" (on-device, default) | "deepl" | "youdao" | "llm".
deeplKeyBYOK DeepL key.
youdaoAppKey / youdaoAppSecretBYOK 有道 (Youdao) 应用ID/应用密钥.Native-app only (desktop/iOS) — Youdao's API has no cross-origin support for the web app.

Display

FieldWhat it controlsNotes
uiModeSettings tier.simple or advanced.
themeId / customThemesActive theme id and user-authored theme definitions.See Theming.
uiFont / monoFontInterface and monospace font pickers.See Theming.
overlayGlassFrosted-glass overlay toggle.Off by default.
fontSize / transcriptScale / transcriptLeadingGlobal, transcript-only, and line-spacing type-size controls.
bitCostumeBit's wardrobe pin."auto" (follow theme, default), "none", or a specific costume id.

Integrations

FieldWhat it controlsNotes
hfToken / realtimeDiarizeDiarization token and the realtime-beta toggle.See Whisper Sidecar.
ankiConnect{ enabled, deckName, port } — AnkiConnect sync target.Port defaults to 8765, independently configurable since it collides with whisperUrl's own default port. See Automation.
usageTrackingLocal, on-device usage ledger (STT seconds, LLM calls/tokens per day+provider).No telemetry — nothing here ever leaves the browser/desktop app.
autoExport / webhookUrl / exportFrontmatterFolder auto-export, webhook URL ("" = off), and Markdown frontmatter.See Automation for payloads and file naming.

Model Routing

Per-task models

Advanced settings can point detection, translation, and reports at different providers or models via taskLlm (see Settings Reference above). The normal split is a fast/cheap model for live detection and a stronger model for post-meeting reports; define always rides detect's configuration.

Provider presets

设置 → AI 检测 ships eight presets:

  • Anthropic 官方 — api.anthropic.com, native provider.
  • OpenAI — api.openai.com/v1.
  • DeepSeek — api.deepseek.com.
  • 通义千问 — dashscope.aliyuncs.com/compatible-mode/v1.
  • OpenRouter — openrouter.ai/api/v1, also reachable via one-click OAuth.
  • Poe 订阅 — api.poe.com/v1, a Poe subscription driving any OpenAI-compatible client.
  • Ollama 本地 — localhost:11434/v1, fully local inference.
  • 自定义 — any other OpenAI-compatible endpoint.

Every preset besides Anthropic maps to the shared openai-compat provider type with a different base URL. Each preset's key is stored and sent in isolation — a v0.7.3 fix so switching providers never sends the previous provider's key to the new one's host.

BYOK key handling paths

PathWhere the key livesNotes
Browser-storedLocal browser storageEntered under 设置 → 密钥; every path below starts here.
Self-hosted web, relayRelayed in memory through your own server's API routesNever persisted server-side.
Hosted preview, BYOKBrowser-direct to the providerNever touches the project server; the server rejects key-carrying relay requests outright.
Environment keys.env.local server variable, e.g. ANTHROPIC_API_KEYThe key never enters the browser at all.
Desktop, OAuthmacOS Keychain, via RFC 8252 loopback OAuthThe system browser opens for OpenRouter login instead of an in-app popup, same flow as first-run onboarding.

proxyUrl (desktop only) is a manual escape hatch for reaching a BYOK endpoint when the OS-level auto-detection can't see the route — an env-var-only proxy setup, or a PAC-only network the app can't execute a script against.

Theming

Every theme — built-in or custom — is a flat token-to-hex map plus a scheme flag. Every value must match strict hex, #RGB or #RRGGBBrgb(), hsl(), named colors, and CSS variables are all rejected outright — and values are injected as CSS custom properties rather than raw strings. Every theme is checked against WCAG AA contrast. scheme (dark or light) is a separate structural field, not an 18th token — it drives native form-control/scrollbar chrome and the in-UI icon swap.

The 17 tokens

ink, panel, panel2, panel3, edge, edge2, fg, mut, mut2, lab-red, lab-orange, lab-yellow, lab-green, lab-purple, lab-cyan, act, warn-soft.

Editor behavior

  • The theme grid's 新建 tile creates a theme from scratch or duplicates any theme, built-in or custom.
  • A live preview applies as you edit each token and reverts to the saved theme if you cancel.
  • Contrast hints flag tokens that fall below the AA/UI-contrast bars as you go — they warn, they never block saving.
  • Export to a JSON file, or import by pasting or picking a file; an imported theme is re-validated and its id is re-minted on import, so an imported file can never shadow or overwrite a built-in.
  • Deleting a theme asks for confirmation; deleting the active theme falls back to the terminal default.

Example custom theme

{
  "id": "custom-1737421200000",
  "label": "深海蓝调",
  "scheme": "dark",
  "tokens": {
    "ink": "#050b14",
    "panel": "#0d1620",
    "panel2": "#14202c",
    "panel3": "#1b2c3a",
    "edge": "#25384a",
    "edge2": "#37516a",
    "fg": "#e8f1fb",
    "mut": "#93a9bd",
    "mut2": "#5c7186",
    "lab-red": "#ff6b6b",
    "lab-orange": "#ffa552",
    "lab-yellow": "#f4d35e",
    "lab-green": "#4ade80",
    "lab-purple": "#b895ff",
    "lab-cyan": "#41d1e0",
    "act": "#2fd0e8",
    "warn-soft": "#ffcf7a"
  }
}
Fonts are deliberately excluded from the theme schema and from exported theme files — a shared theme JSON can never change which fonts your app loads. Fonts are two separate settings, 界面字体 (UI font) and 等宽字体 (monospace), each with zero-download system presets (UI: default / serif / rounded; monospace: bundled JetBrains Mono default / your system monospace) or a custom system font name you type in. uiFont/monoFont accept "default", a preset id, or "custom:<family>"; a custom name is sanitized before use.

Dictionary Pack Authoring

Remote packs are JSON manifests, validated and normalized into an in-memory registry, then shown in Settings alongside the built-in packs. Publish one at a GitHub raw URL or a jsDelivr URL and install it by pasting that URL, or share the file directly for file import.

Manifest format

{
  "id": "biotech-terms",
  "name": "Biotech Terms",
  "version": "1.2.0",
  "terms": [{
    "term": "IND",
    "type": "acronym",
    "gloss_en": "Investigational New Drug application",
    "gloss_zh": "美国 FDA 新药临床试验申请"
  }],
  "expressions": [{
    "expression": "go/no-go",
    "category": "phrase",
    "meaning": "a decision point to proceed or stop a program",
    "chinese_explanation": "继续或终止(项目决策点)",
    "plain_english": "decide whether to continue or stop",
    "tone": "neutral, common in R&D governance"
  }]
}

id, name, and version are required at the manifest root; terms and expressions are both optional arrays. Acronyms are just terms entries with "type": "acronym" — there is no separate acronym shape.

Validation behavior

  • Malformed entries are dropped individually with a warning; the whole pack only fails to install when the manifest itself is unusable.
  • An entry-level pack field is ignored and overwritten with the manifest's own id — an entry can't spoof another pack's id.
  • Changing version is enough to trigger an update; installed packs auto-check for a newer version once a day (packAutoUpdate).

Official catalog

Settings has a one-click browse-and-install catalog — six specialized packs (medicine, biopharma, AI/ML, law, finance, engineering; 4,300+ terms total) published at jargonslayer-dicts — alongside file or GitHub-URL import for any third-party pack.

Precedence

personal glossary > installed packs > built-ins. Personal glossary entries shadow every dictionary match. An installed pack's own definition taking precedence over a built-in one (rather than the built-in silently winning) shipped in v0.7.2.

Filter before importing large acronym sources. A global acronym dump is candidate data, not a ready pack — filter by domain and quality, and publish the result as an optional pack, before promoting any entries into the built-ins.

Automation and Integrations

Auto-export folder

Settings can save every session to a folder as Markdown plus JSON on each save, named YYYY-MM-DD-HHmm-jargonslayer.md and a matching .json. This uses the File System Access API, so it's Chromium-oriented (Chrome/Edge) — built for Obsidian vaults and agent workflows, not a general cross-browser feature.

Webhook

Setting a webhook URL POSTs a meeting.saved payload after each session save:

{
  "schemaVersion": 1,
  "event": "meeting.saved",
  "exportedAt": 1735689600000,
  "session": { /* full MeetingSession */ }
}

The same URL also receives background-task lifecycle events — task.started, task.done, task.error — envelope-identical apart from carrying a task object (id, kind, label, stage, progress, status, error, sessionId, timestamps) instead of a full session; these mirror the desktop 后台任务 task registry (model downloads, diarization install, import jobs). Every call has an 8-second timeout and no retry — a slow or down receiver just misses that event, it doesn't block or requeue.

Receivers should return 200 fast and do the real work asynchronously:

app.post("/jargonslayer-webhook", (req, res) => {
  res.sendStatus(200);                   // ack immediately, before the 8s timeout
  queue.add("process-event", req.body);  // do the real work off-request
});

AnkiConnect

Enabling ankiConnect fires an addNotes call to a local AnkiConnect instance on every session save, reusing the same flashcard projection as manual Anki TSV export, with dedup so re-saving a session doesn't duplicate cards. Its default port 8765 collides with the Whisper sidecar's own websocket default, so it's independently configurable rather than hardcoded — check ankiConnect.port if both run on the same machine.

Obsidian

Markdown exports can include YAML frontmatter (exportFrontmatter) for Obsidian/Dataview — combine it with folder auto-export to build a vault that updates itself after every meeting.

Backup and restore

A full backup exports sessions, glossary, and settings together. The export dialog's 不包含 API Key option is checked by default, stripping every API key, the Hugging Face token, the subscription-direct agent token, the webhook URL, and per-task API keys from the embedded settings before download; uncheck it to keep them in the file. Restoring a backup previews its contents before writing anything, re-sanitizes those same fields regardless of how the backup was exported, and clears subscription-direct connection state so a restored backup can't silently reconnect to an old local agent sidecar.

Architecture

The repository is an npm workspace: apps/web (Next.js 15, TypeScript, Tailwind, zustand, IndexedDB, server route proxies for LLM calls) plus packages/core (the pure-TS shared core — dictionary, types), apps/desktop (the Tauri shell), and apps/extension (the Chrome MV3 side panel). The zustand store in apps/web is the shared bus for transcript segments, interim text, cards, terms, summary output, settings, and sessions.

apps/web (browser client)
  transcription engines
    demo | Web Speech | local Whisper | tab audio | appaudio (desktop)
  zustand store
    segments | cards | terms | summaries | settings | sessions
  detection
    dictionary instant floor
    LLM scheduler and dedupe
  UI
    transcript | cards | history | summary | review

Server routes
  /api/detect
  /api/define
  /api/translate
  /api/summarize

Sidecars (Python)
  whisper_server.py
  agent_server.py

apps/desktop (Tauri v2, macOS)
  Rust core: provisions an isolated Python via a pinned uv,
    manages the sidecar process, CoreAudio process-tap pipeline
  jargonslayer-audiocap: signed Swift helper (CoreAudio process taps)

apps/extension (Chrome MV3)
  side panel: Web Speech capture + built-in dictionary + on-device Translator API
  own IndexedDB database + chrome.storage.local, independent of apps/web's storage

packages/core
  shared types + built-in dictionary, consumed by web and extension
  • apps/web/src/lib/types.ts (re-exporting @jargonslayer/core) owns cross-module contracts.
  • apps/web/src/lib/store.ts is the app bus; STT and detection communicate through it rather than importing each other.
  • apps/web/src/lib/detect/dictionary.ts is the synchronous instant floor.
  • apps/web/src/lib/detect/scheduler.ts batches LLM detection and falls back to dictionary mode.
  • apps/web/src/lib/detect/dedupe.ts merges repeats and upgrades dictionary hits with contextual LLM explanations.
  • apps/web/src/app/api/summarize/route.ts orchestrates summary, chunked translation, and missed-item gap fill.
  • apps/desktop/src-tauri/src/audiocap.rs supervises the Swift capture helper and resamples its output into the existing transcription pipeline.

Data and Storage

All app data is IndexedDB, accessed through idb-keyval — not localStorage. Legacy meetlingo:* keys are copy-migrated forward on startup and left in place for rollback safety.

SurfaceStorage keyNotes
Settingsjargonslayer:settingsLocal configuration.
Sessionsjargonslayer:sessions:index + jargonslayer:session:<id>Index array plus one full MeetingSession record per key.
Glossaryjargonslayer:glossaryPersonal entries and mastery state.
Auto-save folderjargonslayer:export-dirBrowser file-handle; Chromium-oriented.
Remote packsjargonslayer:remote-packsInstalled pack source URLs and validated manifests.

Schema version 1 covers session JSON, Markdown frontmatter, webhook payloads, full backup, and remote dictionary packs.

The only true localStorage keys in the app are a theme FOUC (flash-of-unstyled-content) mirror, js-display, read synchronously before IndexedDB resolves so the very first paint already has the right theme/font-size; and an update-check ETag cache, js-update-etag-cache, that makes repeat GitHub-release checks cheap.

This table is the web/desktop app. The Chrome extension (Lite) keeps its own, separate storage — a dedicated IndexedDB database for session history plus chrome.storage.local for saved lookups — never synced or shared with the web app's storage.

Behavior Specs

These are the behaviors most worth knowing when debugging a setup or extending the tool.

Detection Spec

  • Every finalized segment is scanned synchronously by the dictionary when detection is enabled.
  • The LLM layer batches fresh text and uses the previous context tail only for disambiguation.
  • LLM results can upgrade dictionary cards in place; they do not retract dictionary hits.
  • Repeated expressions are merged by normalized key, with count and last-seen metadata.
  • Personal glossary entries shadow built-in dictionary matches and are never overwritten by the AI.
  • All-caps acronyms use stricter matching to avoid false positives; ordinary mixed-case terms are matched more flexibly.

Translation Spec

  • Live bilingual transcript translates finalized segments in small batches keyed by segment id.
  • Toggling bilingual transcript on mid-meeting backfills the most recent untranslated finalized segments.
  • If a segment is manually edited, its stale translation is invalidated and re-queued while the meeting is live.
  • No-key pauses are self-healing: once a key is added, new translation work can resume without restarting the meeting.
  • Repeated 429s pause and eventually skip the oldest stuck batch so newer segments are not starved forever.
  • Post-meeting report translation is separate: it chunks the full transcript by segment index and repairs missing indices where possible.

Import Spec

Import PathProcessingLimits / Notes
Transcript paste / .txt / .srt / .vttParse, build synthetic session, run detection, optional translation, save.Timestamped cues are normalized to the import time; plain text gets synthetic spacing.
Browser audio importDecode with browser AudioContext, resample to 16 kHz mono, run browser Whisper worker.45-minute duration cap; 200 MB compressed audio cap.
Browser video importExtract audio with ffmpeg.wasm, then reuse the browser audio path.Video extraction has its own size gate; first run may download browser-side model assets.
Sidecar recording importLocal job API handles transcription and optional diarization.Requires local sidecar; progress display is UI-local and may disappear on refresh while the job continues.
Video URL importLocal sidecar uses yt-dlp, then transcribes through the same job pipeline.Local tier only; user is responsible for rights and platform terms.

Failure and Recovery Spec

  • No API key: detection falls back to dictionary; live translation pauses; report generation asks for a key.
  • Rate limits: detection retries once with jitter for 429; translation pauses and avoids starving newer work indefinitely.
  • Sidecar unreachable: local Whisper, sidecar import, URL import, diarization, and subscription-direct cannot run; browser-local import still works for supported files.
  • Meeting changed: async results carry meetingGen; stale results from an old meeting are dropped.
  • Background tab: browsers throttle timers, so detection also flushes on segment arrival and visibility changes.
  • Post-stop late results: detection and translation can land after stop; the store schedules a save top-up so history is not missing tail results.

Glossary and Learning Spec

  • Personal glossary entries are the durable learning home today; meeting sessions stay immutable archives.
  • Entries can be manual, AI-defined from selected text, or collected from a meeting's cards.
  • Each entry stores headword, variants, explanation, example, original context, user note, source, timestamps, and review state.
  • Expression entries may store category, meaning, plain-English rewrite, and tone. Term entries may store term type and English gloss.
  • Review state includes mastered, review count, and last reviewed time — a true learn-set and due-review loop (SM-2-lite scheduling) since v0.3.0.

Async Delegation Design

The product should feel like one calm foreground process with specialized background workers. The foreground owns listening, transcript editing, user focus, and saved session state. Expensive work runs as asynchronous delegates and reports back when results are ready.

Conceptual, not the literal schema: this section describes the coverage model live detection/translation already follow informally. The shipped, user-visible 后台任务 / background task center tracks a simpler, coarser set of whole-job tasks (imports, model downloads, diarization install) with its own in-memory registry — it does not implement this per-segment ledger or the translate | detect | search | diarize kinds below.

Foreground

Append finalized transcript segments, keep focus stable, let the user edit/review, and render the best available state without blocking on AI or search.

Delegates

Run translation, LLM detection, realtime search, diarization, and post-meeting report work in independent queues with priorities and retry policy.

Ledger

Track per-segment coverage: dictionary done, LLM detection pending/done/failed, translation pending/done/skipped, search pending/done/stale.

{
  "id": "job_x",
  "kind": "translate | detect | search | diarize",
  "meetingGen": 12,
  "segmentIds": ["seg_1", "seg_2"],
  "textHash": "...",
  "priority": 2,
  "status": "queued | running | done | failed | stale",
  "attempts": 0
}

Results should apply only when the meeting generation still matches, target segments still exist, and the source text hash is still current. A result should patch the relevant field; it should never replace the foreground transcript or steal the user's focus.

Why this matters: translation and detection can fail, pause, or complete late. A job ledger makes those states visible, so text is never silently lost; it is processed, pending, stale, skipped with a reason, or retryable.

Realtime search fits this model naturally: a card can request background search using the current expression plus recent context, keep showing the local explanation immediately, then attach references or disambiguation notes when the search delegate returns. Search should be triggered by ambiguity, user request, or explicit pack/card action, not every card by default; results should attach to the card as references or context notes, not replace the local explanation; search jobs should stay lower priority than live transcription, dictionary scan, and visible-card detection; and privacy settings should clearly distinguish local dictionary, provider AI, and web search calls.

Testing

Web App

npm run typecheck
npm test
npm run build

Sidecar

sidecar/.venv/bin/python sidecar/test_agent_json.py
sidecar/.venv/bin/python sidecar/test_agent_postfilter.py
sidecar/.venv/bin/python sidecar/test_agent_server.py
sidecar/.venv/bin/python sidecar/test_download.py
sidecar/.venv/bin/python sidecar/test_ingest_url.py
sidecar/.venv/bin/python sidecar/test_lazy_load.py
sidecar/.venv/bin/python sidecar/test_model_registry.py
sidecar/.venv/bin/python sidecar/test_parakeet_backend.py
sidecar/.venv/bin/python sidecar/test_realtime_diar.py
sidecar/.venv/bin/python sidecar/test_whisper_protocol.py

Sidecar tests are plain-assert scripts and assume dependencies from sidecar/requirements.txt. The local Whisper and agent sidecars are independent processes, so test the one you changed.

Desktop (Rust + Swift)

cd apps/desktop/src-tauri && cargo test
cd apps/desktop/src-tauri/audiocap-helper && swift test

The Rust core (process supervision, resampling, the CoreAudio framing protocol) and the signed Swift capture helper each have their own suite; CI has no live CoreAudio session, so the audio-capture tests are designed around fakes/seams rather than a real tap.

Extension

npm test -w apps/extension