Skip to content

Latest commit

 

History

History
172 lines (151 loc) · 26.7 KB

File metadata and controls

172 lines (151 loc) · 26.7 KB

TabMail iOS - Project Memory

iOS-specific knowledge. Claude reads this before every task and updates it when discovering something new. For cross-cutting knowledge, see ../PROJECT_MEMORY.md.

Last updated: 2026-08-06


How to use this index

Root ../CLAUDE.md is normative; the full pre-pass iOS routing preamble is preserved.

Current topics

Search the topic text below as subsystem keywords. Each link is mandatory when its row matches the task.

Save Draft adopts the visible AI suggestion synchronously: Topic 049.

Topic / search terms Detail
Key Files read in full
Inbox list = ONE merged read-model (ADR-IOS-055, 2026-07-09 evening) — supersedes the same-day guard/carry-over fixes read in full
Summary recipient_status (cc detection) — 2026-07-04 read in full
IMAP external-deletion blind spot — old server-deleted messages linger forever (audit 2026-07-02) read in full
CLI testing breaks with "Simulator device failed to launch / Launchd job spawn failed" → app is UNSIGNED in DerivedData (2026-06-22) read in full
Test flakiness: UserDefaults(suiteName:) isolation FALLS THROUGH to .standard — isolating a suite's WRITES is necessary but not sufficient (2026-06-22) read in full
Foreground-return UI freeze — main-actor DB ops must not block (2026-06-21) read in full
OAuth / Google Cloud read in full
Architecture Patterns read in full
Email image aspect-ratio distortion — gated post-load correction (2026-06-26) read in full
IMAP Connection Pool (supersedes ADR-IOS-014) read in full
ProviderWorkQueue Cancellation Semantics (2026-06-09) read in full
FTS Tokenizer — NO tokenchars (ADR-024, 2026-06-09, lockstep with Rust crate) read in full
SwiftUI preview convention read in full
Inbox usage-throttle banner (ADR-IOS-044) read in full
Remote Search (SearchView) read in full
FTS↔GRDB id drift (folder baked into the key) — "indexed but unfindable" / wrong-subject class read in full
"Searchable but can't open / no snippet / not in its folder" — GRDB-side stuck headers (NOT FTS orphans), IMAP (under investigation 2026-06-22) read in full
Stale-detection window MUST match the fetch's ordering dimension — IMAP Archive "missing months" data-loss (CONFIRMED ROOT CAUSE + FIXED 2026-06-23) read in full
GRDB Persistence read in full
GRDB ValueObservation — DO NOT put on messageHeader read in full
AI Priority Processing (ADR-IOS-013) read in full
IMAP Message IDs & UID Resolution read in full
iOS Keychain Persistence read in full
Persistent Offline Action Queue (ADR-IOS-003, ADR-IOS-018) read in full
Progressive Backfill & Storage Budget (ADR-IOS-005, ADR-IOS-006) read in full
Backfill diagnostics: backfill.log channel (2026-07-02) read in full
Backfill stall fixes (2026-07-02) — UID-remap re-key, no fast-sync cursor reset, persisted server limit read in full
bodyComplete = FTS-indexed truth; display cache has NO flag (ADR-IOS-050, 2026-07-02) read in full
Backfill / Fast Sync Completion — gate on pendingBodyCount, NEVER a server total read in full
Ever-Rolling FIFO Queues (ADR-IOS-027) read in full
Bounded Memory (CRITICAL) read in full
Optimistic UI Rollback read in full
Gmail Label System read in full
SwiftUI Observable Array Mutation Safety read in full
SwiftUI Layout Gotchas read in full
HTML Email Render Pipeline (AutoSizingHTMLView) — MUST stay idempotent (ADR-IOS-039) read in full
Swift Gotchas read in full
Folder.== must include every UI-visible field read in full
IMAP Folder Role Detection & Dedup (iCloud "Trash" + "Deleted Messages") read in full
Two-Tier Sync (ADR-IOS-009) read in full
syncStartup Budget Discipline (v50/v51 lesson) read in full
Stale Message Detection (syncMessages) read in full
Benign Sync Log Noise — DraftUpsertSkip + Gmail 429 (diagnosed 2026-06-09) read in full
Transient provider HTTP errors must NOT surface as a sync failure — SyncEngine.isTransientError (2026-06-17) read in full
Raw NIO error types must be added to isConnectionError by NSError DOMAIN, not substring (2026-07-04) read in full
ICS Calendar Import — Invisible SFSafariViewController Hack read in full
Attachment Support read in full
Compose AI Suggestion — chat pill edits suggestion in place when bubble is visible read in full
Compose body — caret-aware scroll, NO input gating read in full
Compose Attachments read in full
SwiftMail Types read in full
Outgoing email header encoding — RFC 2047 (mojibake fix, 2026-06-21) read in full
Hybrid FTS5 + Vector Search (Local) read in full
Device Prompt Sync & AI Cache Probe read in full
Cross-Instance Action Tag Sync (ADR-IOS-036, supersedes ADR-IOS-004 "First Compute Wins") read in full
AI Summary & Action Pipeline read in full
Background AI Processing (Tier 3) read in full
Swift 6 BGTask Isolation Pattern (ADR-IOS-020) — CRITICAL read in full
SSE Streaming & Tool Execution Loop read in full
Thread (ThreadGroup) Actions — tag-tap dispatch & grouped undo read in full
Manual Tag Teaching (Long-Press Context Menu) read in full
Tool Registry (Scaffold) read in full
Agent Chat (ADR-IOS-022, ADR-IOS-023) read in full
Screen Keep-Awake (chat pill) read in full
Agent Compose FIFO Queue (ADR-IOS-030) read in full
Outgoing Threading — reply/forward stay in-thread on every provider (ADR-IOS-043, 2026-06-23) read in full
Incoming Thread Detection — ThreadDetection.findRelatedMessages has NO subject-based fallback (by design) read in full
Outbox — Persistent Offline Send Queue (ADR-IOS-019) read in full
Proactive Local Notifications (ADR-IOS-026) read in full
App Icon Badge Routine (NSE counter + main-app recount) read in full
Persistent NSE log file + watchdog partial-result delivery (2026-07-09) read in full
Sync-status subtitle ("Updated N min ago") read in full
Stuck-isLoading rule: async view loads must defer-clear their spinner flag — and .task must NOT hang on the conditional it flips read in full
Cron Reminders (ScheduledItem Architecture) read in full
Banner Flash Prevention (MailNavigationView) read in full
Test Isolation for UserDefaults-Backed State read in full
Delivered-Notification Cleanup read in full
Startup Data Migrations (StartupMigrations.swift) read in full
TextEditor/TextField Caret Jump — Never Save Per Keystroke (2026-07-02) read in full
Migration Splash / iOS Indeterminate Linear ProgressView Pitfall (2026-07-01) read in full
Zero (BYOK) Plan — IAP Surface (ADR-IOS-040) read in full
BYOK Model Picker — Two-Section Query-All (ADR-026, 2026-07-08) read in full
HTML-Document Guard for text/plain Extraction (2026-06-11) read in full
GRDB Database Suspension — 0xdead10cc Defense (ADR-IOS-041, 2026-06-12) read in full
Dictation leaves AVAudioSession active → haptics die process-wide (2026-06-27) read in full
fullScreenCover content must never re-fetch by a rekeyable id → presented-but-empty cover renders SOLID BLACK (2026-07-08) read in full
Inbox gesture actions derive intent from the VISUALIZED snapshot, never a gesture-path DB read (dead-toggle fix, 2026-07-10) read in full
Action queue coalesces gesture intents to latest-per-field (ADR-IOS-057, 2026-07-10) + overlay call-site audit closed read in full
Knowledge Gaps read in full

Historical and superseded memory

These preserve source history; read them when a current topic, ADR, plan, or shipped-release comparison points to the older design.

Status Topic / search terms Detail
Historical Original project-memory preamble read in full

Forward-ported topics absent from the shipped source

Pre-v3-line topics absent from v1.6.38:PROJECT_MEMORY.md; bodies byte-for-byte, provenance in ported-manifest.tsv.

Status Topic / search terms Detail
Historical HISTORICAL — Intention journal + fold-at-drain (ADR-IOS-058; superseded by ADR-IOS-060) read in full
Current Intention queue V2 — authoritative current direction (ADR-IOS-060, 2026-07-13) read in full
Current ⚠️ FakeIMAPServer's wrongMessageViolations oracle is BLIND when target and bystander share an RFC 822 Message-ID — C3 goes vacuous; pin on the UID STORE/EXPUNGE log read in full

Post-v1.6.38 topics — routed detail, no byte-identical v1.6.38 twin

Authored after v1.6.38, so deliberately not rows in manifest.tsv; provenance, source line ranges and per-fragment sha256 in amendments-manifest.tsv.

Status Topic / search terms Detail
Historical Compaction drift list — a post-v1.6.38 amendment can differ from its routed twin; check the twin BEFORE editing read in full
Current v3 provider-id action-queue forward-port — ✅ SHIPPED v1.7.0; v3 DELETED, main IS that line; R0/R3 RETIRED; ⛔ v2final never shipped read in full
Current T1.3 — a NEW gesture fails CLOSED on an unknown UIDVALIDITY epoch: newGestureRefusedForUnknownEpoch is a silent no-op (IOS-EPOCH-001, C3) read in full
Historical T4.S6 — ⛔ SUPERSEDED v3 intermediate, never implement from it: v69/v72 draft epoch stamps; RETRACTED mailbox-wide EXPUNGE read in full
Current IMAP external-deletion blind spot — server-deleted messages linger; FIXED by ADR-IOS-051 Ph1+2: SyncEngineDeletionReconcile, handleVanishedUIDs read in full
Current Persistent NSE log + watchdog partial delivery: NSELogStore/nse.log, PartialSignalHolder; idle-timer-vs-SSE; OneShotFlag.hasFired() read in full
Current Two-instant wake handoff — "elapsed" means DO IT NOW: AccountManager.wakeUpDelay, holdUntil, IOS-OUTBOX-005, UInt64(negative) traps read in full
Current isDeletedOnServer — FOUR materialisation paths plus a FIFTH PRESENTATION path (SearchView.searchAccount), IOS-IMAP-001 read in full
Current ⚠️ SIX irreversible wire operations at 967e5b3c5, not one — deletion family + CalDAVProvider.splitSeries cap PUT; membership = no per-item recovery, NOT the verb read in full
Current 🚨 await dbPool.read is NOT a short suspension — the ASYNC overload first awaits NSEDataBridge.mergeIfStagingPending() (7.6 s cold boot) read in full
Current 🚨 a latch that AUTHORISES a transition must be HELD across the write — reading isDrainingOutbox then awaiting proves nothing (IOS-OUTBOX-006) read in full
Current 🚨 a bare print is NOT production observability on iOS — stdout is DISCARDED on device; use BackgroundSyncLogger.logError; a RESIDUAL RECORD is an absolute read in full
Current 🚨 a filter applied AFTER a query's LIMIT narrows the page instead of selecting it — InboxListReader.gather (IOS-SCROLL-002, IOS-BACKFILL-001) read in full
Current 🚨 a staging key that names an ADDRESS must re-prove identity before reusing payload — nse_processed_message PK holds a UID (IOS-NSE-005, C3) read in full
Current 🚨 a Swift String comparison does NOT reproduce SQLite BINARY collation — use utf8.lexicographicallyPrecedes; InboxOrdering, IOS-SCROLL-002 read in full
Current 🚨 THE SAME STAGING KEY HAS FOUR WRITERS, AND "SAFE" POINTS THE OPPOSITE WAY FOR THREE (IOS-NSE-006, stagedIdentityPositivelyDiffers) read in full
Current 🚨 AN ENUM WITH NO SILENT CASE DOES NOT PREVENT A SILENT PATHResultTapOutcome's "cannot reintroduce silence" comment was FALSE (case …: break) read in full
Current 🚨 THE ADDRESS PROBLEM HAS TWO ADDRESS SPACES — Graph's /move response IS the COPYUID; ExchangeProvider.moveMessage discarded the new id (IOS-GRAPH-002); the retirement handoff now also re-addresses the QUEUE and follows the ROW — readdressQueuedOperations, accountScopedIdAccountIds (account-scoped ≠ immutable), markQueued, liveOperation, Outlook drain lanes, IOS-GRAPH-005 / ADR-IOS-081 read in full
Current 🚨 uidValidityResetPendingAt STAYS ARMED ON PURPOSE — never demand proof of transience; crawlWalkWriteAllowed was the LAST consumer writing under it (16ecafd93) read in full
Current 🚨 BOTH UIDVALIDITY RE-DRIVE OWNERS ITERATED syncableFolders (fullSync + imapDeltaSync) — an armed CUSTOM NON-FAVOURITE folder had NO re-drive: 145,754 rows read in full
Current KNOWN_ISSUES.md APPEND PATH — RESOLVED 2026-08-12 — strip-before-compare KNOWN-ISSUES-AMENDMENT-BEGIN/-END + KnownIssues/Amendments/; IOS-IMAP-015 read in full
Current 🚨 ATTACHMENT FILENAMES REJECTED, NOT REDUCEDAttachmentFilename.isSafeFileComponent; 255 NFD UTF-16 unit path-component cap (NOT bytes, NOT Characters) read in full
Current SwiftMail PR #208 MOVE post-completion contract — only the COPYUID-bearing form is non-retryable (retain COPYUID, reconcile both folders); moveFailedAfterPossiblePartialCompletion — any tagged NO with no COPYUID, e.g. raw-channel NO No mailbox selected — is a retryable refusal since #115 read in full
Current 🚨 "TRIAL ENDED" IS DERIVED, NEVER A NEW /whoami FLAGhas_subscription:false + the trial KEY present; .active REQUIRES plan_tier == "Trial"; AccountInfo.trialState(now:), AISubscriptionGate.trialHasEnded; #55 read in full
Current 🚨 POST-LOGIN ROUTING WAITS FOR AN AUTHORITATIVE /whoami (issue #56) — PendingPlanNavigationLatch / pending_plan_navigation, AISubscriptionGate.lastAuthoritativeApplyAt, signInGeneration/applyIfCurrentEpoch read in full
Current 🚨 AFFORDANCE LIFETIME COMES FROM THE DURABLE HOLD, NEVER FROM WHEN THE UI APPEARED — undo-send anchor drift = presentation latency Δ; same-name-different-instant queuedAt (#76) read in full
Current 🚨 ONE LOG FILE PER PROCESS (#83) — AppLogStore/tabmail.log, NSELogStore/nse.log read in full
Current 🚨 A DURABLE WRITE TO A MIRRORED IDENTITY COLUMN MUST REFRESH THE NSE MIRRORSnse.accountMap/nse.imapAccounts are the extension's ONLY resolver; addIMAPAccount/addICloudAccount refreshed neither, so handleIMAPReconnect early-returned; mirrorAccountIdentity(); IOS-NSE-008 read in full
Current 🚨 ORDINARY SIGN-OUT RELEASES THE DEVICE PUSH REGISTRATION AND ENDS THE AUTH SESSIONTabMailAuthService.signOut(): unregisterDeviceForSignOut() FIRST, then GoTrue logout?scope=local, BOTH legs always; guard getSession()?.userId == subject; push#42; SignOutHandshakeTests read in full
Current Background credential refresh shares grant ownership — ProviderCredentialStore, CredentialRefreshGate, TabMailSessionRefresh; NSE cancellation, rotation, and foreground repair (#129) read in full
Current SwiftMail upstream sync, exact app/NSE revision, #215/#216/#219 replacements, NIO IMAP #849 release dependency, parked #103/#74 and migration numbering on resume; 2026-09-09 fixes — R1 quadratic RFC 2231 continuations + C2 blank/windows-1252 charset filename loss are upstream #230 regressions fixed on fork branch fix/rfc2231-extended-parameters (upstream PR), C1 encoded display names (#226 contract) decoded app-side in EmlParsing.parse via RFC5322Parse.decodeRFC2047 read in full