SharedInbox — Improvement Plan (30 tasks) #19
Closed
opened 2026-05-13 19:54:25 +00:00 by guettlibot
·
2 comments
No Branch/Tag Specified
main
issue-563-agentloop-validation
dummy-pr-test
issue-560-fix-firebase-run-url
issue-539-stable-imap-uid
issue-533-shared-email-list
plan-issue-555
drop-nix
plan-issue-484
plan-issue-539
plan-issue-535
plan-issue-474
plan-issue-533
fix-dagger-engineless-precommit
issue-521-fix-deploy-yml-wait-time-api
issue-502-fix-email-id-collision-mailbox
issue-492-eliminate-duplicate-build-runner
issue-494-website-change-detection
issue-491-parallelize-check
issue-478-fix-stalwart-dual-stack-bind
issue-475-allowed-addresses-glob
issue-473-search-result-reorder
issue-453-update-agentloop-defaults
issue-466-structured-search
issue-505-exclude-chaos-monkey-from-regular-ci
issue-509-fix-search-result-sorting
fix-ink-sparkle-remaining-tests
issue-506-fix-search-emails-tests
issue-504-runner-wait-time
issue-488-search-notes
issue-472-changelog-issue-links
issue-501-folder-search-local-sqlite
issue-486-fix-stale-test-shader-mismatch
fix/prevent-settled-search-rerun-473
issue-467-fix-search-stale-results
issue-446-installed-versions-in-changelog
issue-462-fix-pr
issue-448-chaos-monkey-test
issue-436-notes-on-emails
issue-429-unify-mail-display
issue-422-move-to-folder-create-new
issue-414-ensure-not-run-as-root
issue-424-unify-email-list-views
issue-419-trusted-senders-page
issue-425-fix-prs
test-foo
issue-421-bug-report
issue-383-fix-ci
issue-394-fix-deploy-flutter-version
issue-391-fix-ci-double-trigger
issue-376-combined-inbox-v2
issue-376-combined-inbox
issue-384-fix-open-prs
sops-migrate
issue-339-safe-first-on-imap-fetch
issue-340-try-catch-measure-height
issue-342-pin-intl-version
issue-341-guard-threademails-last
issue-335-agentloop-code-test
issue-329-fix
issue-315-fix
issue-320-fix
issue-325-fix
issue-312-fix
issue-311-fix
issue-305-fix
issue-304-fix
issue-299-fix
issue-300-fix
issue-298-fix
issue-296-fix
issue-294-fix
issue-289-fix
issue-288-fix
issue-287-fix
issue-286-fix
issue-277-fix
issue-282-fix
issue-280-fix
issue-272-fix
issue-268-fix
issue-267-fix
issue-266-fix
issue-258-fix
issue-260-fix
issue-257-fix
issue-253-fix
issue-216-fix
issue-251-fix
issue-249-fix
issue-question-fixes
issue-235-fix
issue-236-fix-v2
issue-237-fix
issue-236-fix
issue-228-fix
issue-217-fix
issue-214-fix
issue-213-fix
issue-208-fix
issue-205-fix
issue-204-fix
issue-203-fix
issue-202-fix
issue-129-fix
issue-161-fix
issue-160-fix
issue-201-fix
issue-210-fix
issue-198-fix
issue-200-fix
issue-144-fix
issue-199-fix
fix/playstore-upload-use-requests
issue-193-fix
issue-186-fix
issue-185-fix
issue-192-fix
issue-183-fix
issue-175-fix
issue-172-fix
issue-171-fix
issue-167-fix
issue-136-fix
issue-162-fix
issue-179-fix
issue-155-fix
issue-154-fix
issue-152-fix
issue-151-fix
issue-141-fix
issue-150-fix
issue-164-fix
migrate-to-dagger
task/d1-ci-matrix
task/a4-typeconverter-json
task/u7-onboarding-walkthrough
task/d3-sync-doc
task/a5-layer-boundary-lint
task/t5-golden-tests
task/p5-date-cache
task/s4-link-handling
task/p3-html-parse-isolate
task/u8-mark-all-read
task/u3-recent-searches
task/a3-jmap-injectable-http-client
task/r5-tls-error-handling
fix/playstore-redirect-retry
task/t3-repository-contract-tests
task/p2-email-list-pagination
task/p1-fts5-search
fix/playstore-upload-timeout
task/a1-email-detail-notifier
fix/upgrade-workmanager-0.9
fix/android-core-library-desugaring
task/p4-db-indexes
task/r3-html-error-boundary
task/d2-check-coverage
task/a2-email-tile
task/t4-migration-tests
task/t2-widget-tests
task/t1-email-repo-coverage
task/u6-connection-status
task/u4-push-notifications
task/u2-draft-sync
task/u1-list-unsubscribe
task/s2-hostname-validation
task/r6-reliability-fuzz-tests
task/r4-sync-error-banner
task/r2-force-resync
task/r1-undo-history-persistence
No results found.
Labels
Clear labels
NeedSupervisor
State/InProgress
State/Later
State/Planned
automerge
ci-failure
do-not-merge
loop/code
loop/code-ci-pending
loop/code-done
loop/code-in-process
loop/merge
loop/merge-done
loop/merge-in-process
loop/plan
loop/plan-done
loop/plan-in-process
Issue escalated to a human supervisor; agentloop will skip it until cleared.
Eligible for automatic merge by CI
Issue opened by agentloop to track a failing CI workflow; used for deduplication.
Plan PR — review only, do not merge.
Add to run the built-in "code" prompt; override at prompts/code.md.
Prompt "code" finished; waiting for the PR's CI to pass before advancing.
Prompt "code" finished successfully.
Agent for the "code" prompt is currently running on this issue.
Managed by agentloop
Managed by agentloop
Managed by agentloop
Add to run the built-in "plan" prompt; override at prompts/plan.md.
Prompt "plan" finished successfully.
Agent for the "plan" prompt is currently running on this issue.
No labels
Milestone
No items
No Milestone
Projects
Clear projects
No projects
No Assignees
Notifications
Due Date
No due date set.
Dependencies
No dependencies set.
Reference: guettli/sharedinbox#19
Reference in New Issue
Block a user
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.
30 tasks across 7 perspectives. Priority markers: 🔴 high · 🟡 medium · 🟢 nice-to-have.
Group 1: Performance
P1 🔴 Replace LIKE-based search with FTS5 virtual table
The current
observeEmailsand search queries useLIKE '%query%'which becomes a full-table scan at scale.Create an
email_ftsFTS5 virtual table (subject, preview, fromJson) populated via trigger or sync-time insert.Wire
SearchScreento query the FTS table instead.Files:
lib/data/db/database.dart,lib/data/repositories/email_repository_impl.dart.P2 🔴 Lazy-load email bodies on scroll (pagination)
observeThreadsandobserveEmailsreturn the full list with no limit. As the mailbox grows this streams thousands of rows into memory.Add a page-size parameter (e.g. 50) with "load more" support in
EmailListScreen.The
EmailBodiestable is already separate — never fetch bodies in the list query.Files:
lib/data/repositories/email_repository_impl.dart,lib/ui/screens/email_list_screen.dart.P3 🟡 Defer HTML parsing off the UI thread using an Isolate
flutter_htmlparsing blocks the raster thread for large HTML bodies, causing jank when opening email detail.Move the HTML→Widget tree conversion (or at minimum the
html_utils.dartHTML-to-plain step) into acompute()call.Files:
lib/ui/screens/email_detail_screen.dart,lib/core/utils/html_utils.dart.P4 🟡 Index DB columns used in WHERE/ORDER clauses
emails.receivedAt,emails.accountId,emails.mailboxPath,emails.threadId, andemails.snoozedUntilare queried without indexes.Add explicit
@Indexannotations (or rawCREATE INDEX) in the Drift schema.Files:
lib/data/db/database.dart.P5 🟢 Cache the formatted date strings in EmailListScreen
DateFormat('MMM d').format(...)is called for every email on every rebuild. Compute and cache these in the model layer or inside the list item widget'sbuildmethod using a static cache map.Files:
lib/ui/screens/email_list_screen.dart,lib/core/utils/format_utils.dart.Group 2: Reliability & Resilience
R1 — Done: https://codeberg.org/guettli/sharedinbox/pulls/20
R2 — Done: https://codeberg.org/guettli/sharedinbox/pulls/22
R3 🔴 Wrap HTML renderer in an ErrorWidget boundary
A malformed HTML body can throw inside
flutter_htmland crash the entireEmailDetailScreen.Wrap the
Html(...)widget in aBuilder+try/catchor useErrorWidget.builderlocally so only the body section shows the error, not the whole screen.Files:
lib/ui/screens/email_detail_screen.dart.R4 — Done: https://codeberg.org/guettli/sharedinbox/pulls/23
R5 🟡 Handle TLS certificate changes gracefully
tls_error.dartdetects TLS errors but they bubble up as generic errors in the sync loop.Detect
TlsErrorspecifically in_AccountSyncand show a user-facing dialog offering to re-add the account or trust the new certificate.Files:
lib/data/imap/tls_error.dart,lib/core/sync/account_sync_manager.dart.R6 — Done: https://codeberg.org/guettli/sharedinbox/pulls/24
Group 3: Security
S1 🔴 Optional SQLCipher encryption for the Drift database
Emails cached locally are plaintext. Users on shared or rooted devices are exposed.
Add an opt-in "Encrypt local storage" setting using
drift'sencryptedbackend (sqflite_cipher/sqlcipher_flutter_libs).Store the database key in
flutter_secure_storage(already present).Files:
lib/data/db/database.dart,pubspec.yaml, a new settings toggle.S2 — Done: https://codeberg.org/guettli/sharedinbox/pulls/25
S3 🟡 Enforce certificate pinning for known providers (opt-in)
Auto-discovered accounts for major providers (Gmail, Fastmail, Proton) could be pinned to their known CA hierarchy.
Implement as an opt-in per-account setting; only applies when the account is auto-discovered via
AccountDiscoveryService.Files:
lib/core/services/account_discovery_service.dart,lib/data/imap/imap_client_factory.dart.S4 🟢 Audit and restrict external link handling in HTML emails
flutter_htmlpasses<a href>clicks tourl_launcherwithout a prompt.Before launching, show a confirmation dialog with the destination URL so phishing links are visible.
Files:
lib/ui/screens/email_detail_screen.dart.Group 4: User Experience
U1 — Done: https://codeberg.org/guettli/sharedinbox/pulls/26
U2 🔴 Draft synchronisation with server Drafts folder
DraftRepositoryImplis local-only. Drafts are lost if the app is uninstalled or the user switches devices.On
sendDraft, upload to the IMAPDraftsfolder via APPEND. On startup, fetch any server drafts not present locally.Files:
lib/data/repositories/draft_repository_impl.dart,lib/core/repositories/draft_repository.dart.U3 🟡 Add "Recent searches" history to SearchScreen
The search bar clears on navigation. Store the last 10 search terms in a local DB table and show them as chips below the search field when the field is focused but empty.
Files:
lib/ui/screens/search_screen.dart,lib/data/db/database.dart.U4 🟡 UnifiedPush / platform notification integration
IMAP IDLE runs in the foreground sync loop. When the app is backgrounded, new emails arrive silently.
Implement a
WorkManager/foreground-service approach on Android (orBGAppRefreshTaskon iOS) that wakes the sync loop and posts a local notification on new mail.Files:
lib/main.dart,pubspec.yaml,android/manifest.U5 🟡 Accessible swipe actions on email list items
Delete and Move are hidden behind long-press or detail-screen menus. Add leading/trailing swipe actions on the
EmailListScreentile (archive / delete) matching Material 3 patterns.Files:
lib/ui/screens/email_list_screen.dart.U6 🟡 Show connection status indicator in app bar
Users have no way to know if background sync is running, stalled, or paused.
Add a small icon in the
EmailListScreenapp bar (animated for syncing, static for idle, red for error) driven by aSyncStatusProviderbacked byAccountSyncManager.Files:
lib/ui/screens/email_list_screen.dart,lib/di.dart,lib/core/sync/account_sync_manager.dart.U7 🟢 Onboarding walkthrough for first-time users
The app opens directly to an empty account list with only a
+button. First-time users have no guidance.Add a one-time welcome card or bottom-sheet with the three-step flow: Add account → wait for sync → open inbox.
Files:
lib/ui/screens/account_list_screen.dart.U8 🟢 "Mark all as read" action in mailbox
Power users managing high-volume mailboxes need bulk read marking. Add a "Mark all as read" option in the mailbox overflow menu.
Files:
lib/ui/screens/email_list_screen.dart,lib/core/repositories/email_repository.dart,lib/data/repositories/email_repository_impl.dart.Group 5: Testing
T1 🔴 Raise coverage on EmailRepositoryImpl to ≥90 %
scripts/check_coverage.dartexcludes several impl files.email_repository_impl.dartis the most critical path (sync, send, move, delete) and has the highest risk of regressions.Add unit tests for edge cases: concurrent moves, IMAP UID mismatch, SMTP auth failure.
Files:
test/unit/email_repository_impl_test.dart.T2 🔴 Widget tests for ThreadDetailScreen and SearchScreen
These screens exist but have no entries in
test/widget/. A regression in thread rendering or search result display would go undetected.Add
test/widget/thread_detail_screen_test.dartandtest/widget/search_screen_test.dartfollowing the pattern intest/widget/helpers.dart.Files:
test/widget/(new files).T3 🟡 Contract tests for all Repository interfaces
The interfaces in
core/repositories/have no shared contract test suite. Concrete impls can silently diverge.Add a shared
EmailRepositoryContractabstract test class; run it against bothEmailRepositoryImpland any future mock/fake. Mirror this forMailboxRepositoryandAccountRepository.Files:
test/unit/(new contract test files).T4 🟡 DB migration test for every schema version
test/unit/migration_test.dartexists. Verify it covers every schema version bump (currently v1→v22) by asserting the step count matches the DBschemaVersion.If gaps exist, add migration steps for missing versions.
Files:
test/unit/migration_test.dart,lib/data/db/database.dart.T5 🟢 Snapshot / golden tests for key email list states
The email list has multiple states: loading, empty, normal, selection mode, search active, error banner.
Add golden tests using
matchesGoldenFilefor each state so visual regressions surface in CI.Files:
test/widget/email_list_screen_test.dart.Group 6: Architecture & Code Quality
A1 🔴 Eliminate direct DB access from UI screens
email_detail_screen.dartcallsref.read(emailRepositoryProvider)and awaits futures ininitState— tightly coupling the screen to the repository API.Extract a
EmailDetailNotifier(StateNotifier or AsyncNotifier) that owns the load logic and exposes a singleAsyncValue<(Email, EmailBody)>to the screen.Files:
lib/ui/screens/email_detail_screen.dart,lib/di.dart.A2 🟡 Extract reusable EmailTile widget
The email list item rendering is inlined in
email_list_screen.dartand duplicated inthread_detail_screen.dart.Extract to
lib/ui/widgets/email_tile.dartwith a clear interface; both screens import it.Files:
lib/ui/screens/email_list_screen.dart,lib/ui/screens/thread_detail_screen.dart,lib/ui/widgets/email_tile.dart(new).A3 🟡 Make AccountSyncManager testable without real IMAP connections
AccountSyncManageracceptsImapConnectFnas a dependency but_JmapAccountSyncconstructs its HTTP client internally.Pass an injectable
http.Clientto_JmapAccountSync(already done inEmailRepositoryImpl; mirror the pattern here).Files:
lib/core/sync/account_sync_manager.dart,test/unit/account_sync_manager_test.dart.A4 🟡 Replace raw JSON strings in DB with structured encoding
fromJson,toAddresses,ccJson,referencesare stored as raw JSON strings parsed on every model conversion.Create typed value classes with
fromJson/toJsonincore/models/email.dartand add aTypeConverterin the Drift schema so the DB layer owns the serialisation.Files:
lib/data/db/database.dart,lib/core/models/email.dart,lib/data/repositories/email_repository_impl.dart.A5 🟢 Enforce layer boundaries via lint custom rules or barrel imports
The
ui/layer directly importsdata/concrete classes in several screens (e.g.drifttypes leak through).Add a custom
analysis_options.yamlrule or a CI lint step that flags anyui/import ofdata/(onlycore/interfaces are allowed from UI).Files:
analysis_options.yaml, CI config.Group 7: Developer Experience
D1 🔴 CI matrix for macOS and Windows builds
The CI currently tests Linux and Android. The macOS and Windows targets are "scaffolded" and may have accumulated silent breakage.
Add
flutter build macos --debugandflutter build windows --debugjobs to the CI workflow with the same failure threshold as Linux.Files:
.github/workflows/ci.yml(or Codeberg equivalent).D2 🟡 Add
task check-coveragecommand that fails on regressionscripts/check_coverage.dartruns coverage but there is no Taskfile target that fails the build if coverage drops below the 85 % gate.Wire it to a
task checkstep so a PR that drops coverage is blocked before merge.Files:
Taskfile.yml,scripts/check_coverage.dart.D3 🟢 Document the sync protocol in a SYNC.md architecture doc
DB-SYNC.mdexists but focuses on the DB schema. The IMAP IDLE loop, exponential backoff, pending-change queue, and undo cancel logic are spread across four files with no single reference.Write
SYNC.mdthat describes the full lifecycle of an email action from UI tap to server confirmation.Files:
SYNC.md(new).U2 (Draft sync with IMAP Drafts folder): PR #27 opened — https://codeberg.org/guettli/sharedinbox/pulls/27
U4 (Background sync + local notifications): PR #28 opened — https://codeberg.org/guettli/sharedinbox/pulls/28