Restores Unicode-aware case-insensitive substring search (approach A from the #227 spike). Paging (#223) moved search to a SQL LIKE scan whose case-folding is ASCII-only, so non-ASCII terms stopped matching case-insensitively.
⚠️Schema migration (v18→v19) — review requested, not auto-merged.
Change
Per-field casefold columns on messages: senderFold, senderEmailFold, subjectFold, snippetFold, each = lowercase(source) (Kotlin's lowercase() is Unicode-aware).
Why per-field, not one concatenated column: the searchable fields are maintained by partialUPDATEs that don't carry all four (updateBody fills the snippet post-insert; updateHeaderContent refreshes headers), so a single column couldn't stay in sync. Each fold depends only on its own source, kept current via thin DAO default-method wrappers — so all 5 call sites are unchanged.
Search matches the fold columns with a pattern built from the lowercased query.
Migration v18→v19 (additive columns + lower() backfill; the backfill is ASCII-only, so existing non-ASCII rows re-fold on their next write/sync).
Tests
MigrationTest.migrate18To19_addsAndBackfillsCasefoldSearchColumns — validates the columns + backfill against the exported v19 schema (runs on-device in CI).
MailRepositoryImplTest — asserts the query is casefolded ("ÄPFEL" → "%äpfel%"); the existing LIKE-escape test still holds.
Preflight green: build, unit tests, androidTest compile, lint, ktlint, detekt.
Restores **Unicode-aware case-insensitive substring search** (approach A from the #227 spike). Paging (#223) moved search to a SQL `LIKE` scan whose case-folding is ASCII-only, so non-ASCII terms stopped matching case-insensitively.
> ⚠️ **Schema migration (v18→v19) — review requested, not auto-merged.**
## Change
- **Per-field casefold columns** on `messages`: `senderFold`, `senderEmailFold`, `subjectFold`, `snippetFold`, each `= lowercase(source)` (Kotlin's `lowercase()` is Unicode-aware).
- **Why per-field, not one concatenated column:** the searchable fields are maintained by *partial* `UPDATE`s that don't carry all four (`updateBody` fills the snippet post-insert; `updateHeaderContent` refreshes headers), so a single column couldn't stay in sync. Each fold depends only on its own source, kept current via thin **DAO default-method wrappers** — so all 5 call sites are unchanged.
- Search matches the fold columns with a pattern built from the **lowercased** query.
- Migration **v18→v19** (additive columns + `lower()` backfill; the backfill is ASCII-only, so existing non-ASCII rows re-fold on their next write/sync).
## Tests
- `MigrationTest.migrate18To19_addsAndBackfillsCasefoldSearchColumns` — validates the columns + backfill against the exported v19 schema (runs on-device in CI).
- `MailRepositoryImplTest` — asserts the query is casefolded (`"ÄPFEL"` → `"%äpfel%"`); the existing LIKE-escape test still holds.
- Preflight green: build, unit tests, androidTest compile, lint, ktlint, detekt.
Closes #232
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Restores Unicode-aware case-insensitive substring search (approach A from the #227 spike). Paging (#223) moved search to a SQL
LIKEscan whose case-folding is ASCII-only, so non-ASCII terms stopped matching case-insensitively.Change
messages:senderFold,senderEmailFold,subjectFold,snippetFold, each= lowercase(source)(Kotlin'slowercase()is Unicode-aware).UPDATEs that don't carry all four (updateBodyfills the snippet post-insert;updateHeaderContentrefreshes headers), so a single column couldn't stay in sync. Each fold depends only on its own source, kept current via thin DAO default-method wrappers — so all 5 call sites are unchanged.lower()backfill; the backfill is ASCII-only, so existing non-ASCII rows re-fold on their next write/sync).Tests
MigrationTest.migrate18To19_addsAndBackfillsCasefoldSearchColumns— validates the columns + backfill against the exported v19 schema (runs on-device in CI).MailRepositoryImplTest— asserts the query is casefolded ("ÄPFEL"→"%äpfel%"); the existing LIKE-escape test still holds.Closes #232
🤖 Generated with Claude Code