)
## Summary
Unifies three independent text message import implementations into a
coherent system with a shared database schema, phone-based participant
model, and dedicated TUI Texts mode.
Supersedes #160 (WhatsApp), #224 (iMessage), #225 (Google Voice) —
squash-merged all three, refactored for consistency, and built a shared
foundation. Original contributor commits preserved.
## Import commands
```
msgvault import-whatsapp <msgstore.db> --phone +1...
msgvault import-imessage [--me +1...]
msgvault import-gvoice <takeout-dir>
```
Deprecated `import --type whatsapp` alias kept for backward
compatibility.
## Shared foundation
- `NormalizePhone` E.164 utility with international format support
(`00`-prefix, trunk `(0)`, whitespace)
- `EnsureParticipantByPhone(phone, displayName, identifierType)` —
cross-source phone-based participant dedup
- `RecomputeConversationStats` — idempotent post-import stats
recomputation
- `LinkMessageLabel` — single-label convenience wrapper
## Importer changes
**iMessage**: Dropped `gmail.API` adapter and synthetic MIME. Reads
`chat.db` directly, writes proper `message_type` (`imessage`/`sms`),
`sender_id`, `conversation_type` (`group_chat`/`direct_chat`),
`message_recipients`, and raw JSON. Handles macOS streamtyped
`attributedBody` format (Ventura+/Sequoia). Group chats detected from
`;+;` GUID prefix with participant-derived titles.
**Google Voice**: Same refactoring pattern. Three `message_type` values
(`google_voice_text`/`call`/`voicemail`), phone-based participants,
labels, raw HTML storage, correct outbound recipients.
**WhatsApp**: Cleaned up to use shared utilities. Skips broken
attachment rows when `--media-dir` not provided.
## Query layer
- `TextEngine` interface (separate from `Engine` to avoid rippling
through remote/API/MCP layers)
- DuckDB and SQLite implementations with conversation-first queries
- Parquet cache extended with `conversation_type`, schema v5
- Email-only filter on existing `Engine` queries (search, stats,
aggregates)
- FTS backfill handles phone-based senders via `sender_id`
## TUI Texts mode
Press `m` to toggle between Email and Texts modes.
- **Conversations view**: sortable by name/count/last message,
content-aware column widths
- **Aggregate views**: Contacts, Contact Names, Sources, Labels, Time
- **Chat timeline**: full message bodies with word wrapping, reverse
sort (`r`), local search (`/`)
- **Navigation**: drill-down with breadcrumbs, consistent keybindings
with Email mode
- Read-only (no deletion staging)
## Schema
- `conversations.conversation_type` column + migration for legacy DBs
- `SQLite MaxOpenConns(4)` for concurrent TUI reads (`:memory:` stays at
1)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Ed Dowding <me@eddowding.com>
Co-authored-by: Ryan Stern <206953196+vanboompow@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Summary
sync-gvoicecommand that imports SMS, MMS, and call records from Google Takeout Voice exportsgmail.APIinterface for plug-and-play integrationNew files
internal/gvoice/client.go- gmail.API implementation over Takeout HTMLinternal/gvoice/parser.go- HTML conversation parserinternal/gvoice/models.go- conversation and message typesinternal/gvoice/parser_test.go- parser testscmd/msgvault/cmd/sync_gvoice.go- CLI commandUsage
msgvault sync-gvoice --takeout-dir ~/path/to/Takeout/VoicePerformance