Skip to content

feat(notes): import Granola meeting notes from CSV export - #1813

Merged
Chadpiha merged 3 commits into
mainfrom
feat/granola-import
Aug 26, 2026
Merged

feat(notes): import Granola meeting notes from CSV export#1813
Chadpiha merged 3 commits into
mainfrom
feat/granola-import

Conversation

@Chadpiha

Copy link
Copy Markdown
Collaborator

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

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.
@Chadpiha
Chadpiha requested a review from gabrielste1n August 24, 2026 23:29
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.
@Chadpiha
Chadpiha merged commit 97d5291 into main Aug 26, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants