feat(notes): import Granola meeting notes from CSV export - #1813
Merged
Conversation
Both import handlers read stale closure state, so a fast double-click could open two file dialogs or trigger a second import run that consumed an empty stash and surfaced a spurious error after a successful import. Guard both with a shared in-flight ref. Also key preview sample titles by index (duplicate titles collided in React) and add the missing userNotes field to the mapHeaders JSDoc.
Participants consumers type entries as { email, displayName } and read
p.email unguarded (SpeakerPicker), so an imported attendee without an
email would crash the speaker picker.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Adds the first competitor-migration path: users switching from Granola can import their meeting notes from Granola's official CSV export (Settings → Profile → Export historical data → Generate CSV) via a new Import from Granola block in Settings → General.
Granola encrypted all of its local data (cache, SQLite DB, auth tokens) as of v6, so the emailed CSV export is the only source available to every Granola user regardless of plan. Its columns are undocumented and have changed shape over time, so the parser is header-driven and tolerant: unknown columns are ignored, transcripts are optional, and per-row problems become warnings rather than aborting the import. The header mapping and heuristics were reconciled against a real August 2026 export and pinned with a sanitized fixture.
Imported notes land in an "Imported" folder (private space) as meeting notes with their original creation dates, transcripts stored as native segments (speaker names locked when the transcript carries Speaker: labels), and deterministic client_note_ids derived from Granola's document ids — so re-running an import is idempotent and duplicates are skipped, never overwritten. Bulk-import side effects run once, not per note: one batched vector reindex of the new notes, one markdown-mirror rebuild (only if enabled), and one batched cloud-sync pass (50/chunk, preserving backdated timestamps) instead of per-note pushes.
Verification
2988 tests, 0 failures under Node 24 (npm test); DB suite additionally executed for real via ELECTRON_RUN_AS_NODE=1 npx electron --test (7/7)
typecheck, lint, i18n:check, prettier all clean
Manual E2E on macOS against a real Granola export: import succeeded, note landed with original date and 19-segment transcript; re-import correctly reported everything as duplicate