msg.Collect()) ელფოსტის სინქრონიზაციის დროს. დაინერგა ნამდვილი io.Reader სტრიმინგი, რომელიც პირდაპირ წერს ნედლ IMAP ნაკადებს ლოკალურ .eml დროებით ფაილში. ეს უზრუნველყოფს O(1)მეხსიერების მოხმარებას თითოეული საქაღალდისთვის, ელფოსტისა და დანართების ზომის მიუხედავად.MinConns=5 მკაფიო კონფიგურაციით და MaxConns ლიმიტების შეზღუდვით (ნაგულისხმევად 100), რათა თავიდან იქნას აცილებული კავშირის გაწყვეტის ტაიმაუტები მაღალი დატვირთვის ქვეშ.SyncWorker-ში. მრავალი შეტყობინების UID დაჯგუფდა ერთ IMAP FETCH ბრძანებაში ლატენტურობის შესამცირებლად და დაინერგა შეზღუდული errgroup (მაქს. 4 პარალელური CPU პარსერი თითო ვორკერზე), რათა დაჩქარდეს MIME პარსინგი და თავიდან იქნას აცილებული CPU ბლოკირების გრძელი ციკლები.syncPool-ში (5 კავშირი) poolctx.WithSync(ctx)-ის გამოყენებით, რაც იძლევა გარანტიას, რომ მძიმე სინქრონიზაციის სამუშაოები არასოდეს ამოწურავს მთავარ API კავშირების პულს (20 კავშირი).panic: pattern "/api/health" conflicts გაშვებისას ServeMux-ში დუბლირებული მარშრუტების რეგისტრაციების ამოღებით.ResetAccountSync ობოლი ფიზიკური ფაილების (.eml და დანართები) დისკიდან სათანადოდ წასაშლელად და არა მხოლოდ მონაცემთა ბაზის გასასუფთავებლად, რაც ხელს უშლის საცავის უსასრულო ზრდას.PRAGMA busy_timeout=30000; SQLite კავშირებზე, რათა შემცირდეს database is locked (SQLITE_BUSY) შეცდომები მძიმე წაკითხვის/ჩაწერის კონკურენციის დროს Mono ვერსიაში.Peek: true-ის ჩართვით IMAP FetchOptions-ის სხეულის სექციის სპეციფიკატორში.align, valign, bgcolor) და სხეულის ფონების რენდერინგი HTML სანიტაიზერის პოლიტიკის რეფაქტორინგით და <body> ტეგის სუროგატულ <div>-ად შენარჩუნებით.new-email ივენთებით, რაც ხელს უშლის საწყის IMAP სინქრონიზაციებს ბექენდის დასპამვაში და გლობალური IP სიჩქარის შეზღუდვის დაბლოკვაში./api/users, /api/groups).SyncAllFolders მკაფიოდ არის გამოძახებული runSyncCycle-ის დროს, რაც აგვარებს პრობლემას, როდესაც ახლად დაკავშირებულმა ან გადატვირთულმა აქაუნთებმა ვერ შეძლეს ისტორიული ელფოსტის ჩამოტვირთვა ხელით ჩარევამდე.*server* .gitignore-დან go vet-ის და დისტანციური ბილდების განსაბლოკად, რომლებიც ვერ ხერხდებოდა internal/async/server.go-ს არარსებობის გამო.OFFSET და ჩადგმული ORპირობები ელფოსტის პაგინაციისთვის. დაინერგა მკაცრი ტუპლური შედარება (is_pinned, date_sent, id) < ($1, $2, $3) O(1) მოთხოვნის ლატენტურობისთვის ნებისმიერ სიღრმეზე, და დაემატა გამოყოფილი დაფარვის ინდექსი idx_emails_pagination schema.sql-ში.(folder_id, is_read, is_muted) მიმდევრობითი სკანირების აღმოსაფხვრელად, როდესაც ფონური ვორკერი ხელახლა ითვლის საქაღალდის წაუკითხავ რაოდენობას ყოველ 30 წამში.idx_emails_folder_isread ინდექსი schema_mono.sql-ს, რათა აღმოიფხვრას სრული ცხრილის სკანირება და მონაცემთა ბაზის გაყინვის სცენარები RefreshUnreadCounts ფონური ამოცანების დროს.IMAP Sync — New Emails Incorrectly Marked as Read
IMAP სერვერი (მაგ. Gmail) ავტომატურად მონიშნავდა ახალ შემოსულ წერილებს როგორც \Seenსერვერის მხარეს, რადგან ჩვენი fetcher ითხოვდა ელფოსტის სხეულს Peek პარამეტრის გარეშე, რის გამოც ისინი UI-ში მყისიერად ჩანდა როგორც "წაკითხული".
Fix (worker.go):
Peek: true FetchItemBodySection-ს სინქრონიზაციის ვორკერში, რათა IMAP-ის fetch-ებმა არ შეცვალოს \Seen დროშა.Disk Storage Bloat — Uncollected Files on Resync
"Reset Account Sync"-ის გამოძახება შლიდა მონაცემთა ბაზის რიგებს ელფოსტისა და დანართებისთვის, მაგრამ ტოვებდა ფიზიკურ .html და დანართების ფაილებს ობლად დისკზე, რაც იწვევდა საცავის უსასრულო გაბერვას.
Fix (storage.go for Postgres & SQLite):
ResetAccountSync-ში. ბექენდი ახლა მკაფიოდ აგზავნის მოთხოვნას და იტერაციას უკეთებს ყველა body_path და დანართის path სტრიქონს და იძახებს os.Remove()-ს მონაცემთა ბაზის რიგების წაშლამდე.Database Performance — 5-10 Second Latency & Timeout Fixes
ელფოსტის თემის გახსნა ან ინბოქსის სიის რენდერინგი განიცდიდა მასიურ შეფერხებებს (10 წმ-მდე) დაფარვის ინდექსების არარსებობის და რიგის არაოპტიმალური ძიების გამო.
Fix (schema.sql, storage.go):
idx_emails_thread, idx_emails_unread_fast და idx_emails_folder_isread, რათა დაჩქარდეს მასიური COUNT(*) და თემის მოთხოვნები.idx_sync_queue_fetch_active, რათა უპირატესობა მიენიჭოს account_id-ს როგორც პირველად პრეფიქსს, რითაც სინქრონიზაციის ვორკერი გადარჩება ცხრილების სკანირებისგან.HTML Emails — Layout and Formatting Stripped
ელფოსტამ, რომელიც ფორმატირებული იყო მოძველებული HTML ცხრილებით (align, valign, bgcolor) და სხეულის ფონებით, დაკარგა მთელი სტრუქტურული განლაგება და ფერები, რადგან bluemondayაგრესიულად შლიდა <body> ტეგს და აუქმებდა პრეზენტაციის ატრიბუტებს.
Fix (email_handlers.go, email_normalize.go):
align, valign და bgcolor ატრიბუტები გლობალურად table, tr, td და th ტეგებში bluemondayპოლიტიკაში.normalizeEmailHTML, რათა დინამიურად გადაერქვას ზედა დონის <body> ტეგი <div id="rms-mail-body-surrogate">-ად სანიტარიზაციამდე, რითაც გვერდს უვლის bluemonday-ის სხეულის ამოღების ქცევას, ამასთან უსაფრთხოდ ინარჩუნებს სხეულის ყველა inline სტილს და ფონს.SQLite_BUSY — Database Locked During Sync (Mono)
მძიმე ფონური ამოცანები, კონკრეტულად IMAP სინქრონიზაციები და საქაღალდის წაუკითხავი რაოდენობა, იწვევდა SQLite-ის მიერ database is locked (5) (SQLITE_BUSY) შეცდომის დაფიქსირებას და ბლოკავდა API-ს, Webhook poller-ს და სხვა ვორკერებს.
Fix (schema_mono.sql, storage.go):
idx_emails_folder_isreadschema_mono.sql-ს, რამაც მყისიერად მოაგვარა 10წმ+ ცხრილის სკანირება RefreshUnreadCountsფონური ამოცანის დროს.PRAGMA busy_timeout=30000; LibSQL დრაივერის ინიციალიზაციაზე, რაც ავალებს SQLite-ს დააყენოს რიგში ერთდროული ჩაწერის ბლოკირებები მყისიერად ჩავარდნის ნაცვლად.Empty Groups Visible in UI
ფრონტენდის გვერდითა პანელი აჩვენებდა პროექტის ჯგუფებს მაშინაც კი, როდესაც ისინი არ შეიცავდნენ აქაუნთებს (მაგ. აქაუნთების ჯგუფიდან ამოღების შემდეგ).
Fix (email-sidebar.tsx & models/email.go):
accounts_count ProjectGroup ბექენდის მოდელს.accounts_count == 0.429 Rate Limit Storm on Initial Sync
როდესაც ხდება ახალ აქაუნთში შესვლა, ბექენდის საწყისი IMAP სინქრონიზაცია ასხივებდა SSE new-email ივენთს თითოეული ჩამოტვირთული ელფოსტისთვის. ფრონტენდის useEmails.ts მყისიერად რეაგირებდა თითოეულ ივენთზე მოთხოვნების ინვალიდაციით, რაც იწვევდა ათასობით პარალელურ API მოთხოვნას, რომლებიც აღწევდა ბექენდის InMemoryRateLimiter ლიმიტს (300 მოთხოვნა/წუთში) და ბლოკავდა მომხმარებლის IP-ს გლობალურად ყველა /api/* ენდპოინტზე.
Fix (useEmails.ts):
setTimeout debounce-ში.Initial Historical Sync Stall
ახლად დაკავშირებული IMAP აქაუნთები (ან აქაუნთები, რომლებიც ხელით გადაიტვირთა) ეყრდნობოდა მხოლოდ IMAP IDLE push მექანიზმს ან პერიოდულ სამუშაოებს ისტორიული ელფოსტის ჩამოსატვირთად. ისინი პროაქტიულად არ ასრულებდნენ საქაღალდის სრულ სკანირებას.
Fix (worker.go):
SyncAllFolders(ctx, c, acc) გამოძახება runSyncCycle()-ში, ასე რომ ორივე, Mono და Unified ვერსიები საიმედოდ ასრულებენ ყველა საქაღალდის სრულ ზემოდან-ქვემოთ სინქრონიზაციას მათი ძირითადი სინქრონიზაციის ციკლის განმავლობაში.Missing Build Dependency (.gitignore)
go vet და დისტანციური CI ბილდები იშლებოდა "undefined" reference შეცდომებით ფონური სამუშაოს სერვერისთვის.
Fix (.gitignore):
*server* .gitignore გამონაკლისებიდან, რათა უზრუნველყოფილი იყოს internal/async/server.go-ს სათანადოდ დაქომითება რეპოზიტორიში.Frontend Infinite 404 Retry Loops (Mono Edition)
Mono (ან whitelabel) ვერსიაში ბექენდი განზრახ არ ამაგრებს /api/users და /api/groups ენდპოინტებს. React Query-მ ფრონტენდში მიიღო 404 Not Found ამ ენდპოინტებისთვის და შევიდა აგრესიულ exponential backoff გამეორების ციკლში, რაც ქმნის ზედმეტ ქსელურ ხმაურს და ხელს უწყობს სიჩქარის შეზღუდვას.
Fix (useEmailQueries.ts):
enabled gating შემოწმებები typeof window !== "undefined" && localStorage.getItem("geomail_edition") !== "mono" && !window.location.host.startsWith("wm.") ყველა ადმინისტრაციულ მოთხოვნაზე, ასე რომ ისინი უბრალოდ ტოვებენ მოთხოვნას standalone გარემოში გაშვებისას.MIME Parsing — Gmail IMAP Body-Section Ordering & Boundary Corruption
Gmail IMAP აბრუნებს BODY[HEADER] და BODY[TEXT] ნებისმიერი თანმიმდევრობით. ძველი კოდი იღებდა მხოლოდ პირველ სექციას (break ერთი იტერაციის შემდეგ), გადასცემდა ან ცარიელ ჰედერებს ან ცარიელ ტექსტს enmime.ReadEnvelope-ში — რაც იწვევდა malformed MIME header initial line-ს თითოეულ ელფოსტაზე, რომელიც სინქრონიზებული იყო სამომხმარებლო რიგის მეშვეობით.
Fix (fetcher.go — ProcessMessage + ProcessMessageToFolder):
Specifier-ით და გადალაგებულია: HEADER ყოველთვის პირველი, შემდეგ \r\n გამყოფი, შემდეგ TEXT. ქმნის სრულ RFC822 შეტყობინებას.repairMIMEBoundaries() რეგექსი ასწორებს Gmail-ის გამოტოვებულ CRLF-ს MIME საზღვრებსა და მომდევნო ჰედერს შორის (--abc123Content-Type: → --abc123\r\nContent-Type:). ემთხვევა hex, ალფანუმერულ და _/+= საზღვრებს.XOAUTH2 — Infinite Authentication Loop (CheckWorker)
CheckWorker.runSession() ქმნიდა დროებით SyncWorker-ს თითოეული მცდელობისთვის. როდესაც refreshToken() წარმატებით სრულდებოდა, ის ანახლებდა მხოლოდ დროებითი ვორკერის ლოკალური აქაუნთის ასლს — CheckWorker-ის Account რჩებოდა ძველი. თითოეული გამეორება ქმნიდა ახალ SyncWorker-ს ძველი ასლიდან → უსასრულო AUTHENTICATIONFAILED ციკლი.
Fix (checker.go, worker.go, manager.go):
"token refreshed, need reconnect"შეცდომის შემდეგ.Manager.LockTokenRefresh(accountID) — per-account mutex ახდენს OAuth ტოკენის განახლების სერიალიზაციას. მხოლოდ ერთი goroutine იძახებს Google-ს; სხვები ელოდებიან და აგრძელებენ ახალი ტოკენებით.refreshToken() SyncWorker-ში იღებს per-account ბლოკირებას განახლებამდე.ON CONFLICT — Missing Unique Constraints (SQLSTATE 42P10)
EnqueueUIDs (ON CONFLICT (account_id, folder_name, uid)) და SaveEmailToFolder (ON CONFLICT (msg_id, account_id, folder_id)) მოითხოვს უნიკალურ ინდექსებს, რომლებიც შეიძლება არ არსებობდეს წარმოების მონაცემთა ბაზებში ხელახალი სინქრონიზაციის/რეკონსტრუქციის შემდეგ.
Fix (sync_queue.go, storage.go):
EnqueueUIDs იჭერს 42P10-ს → გადადის INSERT ... WHERE NOT EXISTS-ზე.SaveEmail / SaveEmailToFolder იჭერს 42P10-ს → გადადის check-then-insert-or-update-ზე.emails_msg_id_account_folder_key უნიკალური ინდექსი (3 სვეტიანი).GetFolders — 3-Minute Freeze on Folder List
კორელირებული COUNT(*) ქვემოთხოვნა ჩადგმული smart-category NOT IN-ით სრულდებოდა საქაღალდის თითოეული რიგისთვის. 50K წერილი × 10 საქაღალდე = წუთები.
Fix (storage.go, schema.sql, queue_manager.go):
folders.unread_count INT DEFAULT 0 სვეტი.GetFolders ახლა კითხულობს COALESCE(f.unread_count, 0)-ს პირდაპირ (მილიწამზე ნაკლები).RefreshUnreadCounts() ეშვება ყოველ 30 წამში — ერთი ჯგუფური UPDATE ყველა საქაღალდისთვის, და არა თითო საქაღალდისთვის.idx_emails_folder_unread სწრაფი დათვლისთვის.GetEmails — Correlated NOT IN on Every Row (Smart Categories)
e.msg_id NOT IN (SELECT e2.msg_id ... WHERE e2.account_id = e.account_id ...) — კორელირებული ქვემოთხოვნა სრულდებოდა ელფოსტის თითოეული რიგისთვის.
Fix (storage.go, schema.sql):
emails.smart_category BOOLEAN DEFAULT false სვეტი.RefreshUnreadCounts მონიშნავს Promotions/Social/Updates წერილებს ფონურ რეჟიმში.GetEmails ფილტრავს მარტივი ლოგიკით: e.smart_category = false კორელირებული NOT IN-ის ნაცვლად.idx_emails_smart_cat სწრაფი გაფილტვრისთვის.SQLITE_BUSY — Database Locked Without Backoff (Mono Edition)
Webhook poller და რიგის მენეჯერი აწყდებოდა database is locked-ს და იმეორებდა ფიქსირებულ ინტერვალებში, რაც იწვევდა CPU-ს გადატვირთვას.
Fix (webhook_queue_mono.go, queue_manager.go):
SQLITE_BUSY / database is locked შეცდომებზე.GetStatsByAgent — N+1 Query Pattern (SQLite)
ცალკეული SELECT COUNT(*) თითოეული აგენტის რიგისთვის მთავარ მოთხოვნაში აგრეგაციის ნაცვლად.
Fix (sqlite/storage.go):
SUM(CASE WHEN e.is_read = 0 THEN 1 ELSE 0 END) GROUP BY-ში — ერთი მოთხოვნა.misc_handlers.go:491-493). OAuth შეცდომის პასუხები აღარ შეიცავს სრულ HTTP სხეულს (შეიძლება შეიცავდეს ტოკენებს). ამოღებულია DEBUG username ლოგი authenticate()-ში.metadata.google.internal) ყველა ვერსიაში, Mono-ს ჩათვლით.StopAll() ახლა ელოდება ვორკერის goroutine-ებს sync.WaitGroup-ის მეშვეობით (15 წმ ტაიმაუტი) მყისიერად დაბრუნების ნაცვლად.statement_timeout = 30s-ით (გადაწერადია PG_STATEMENT_TIMEOUT env-ის მეშვეობით). ხელს უშლის გაჭედილ მოთხოვნებს ვორკერების განუსაზღვრელი ვადით დაბლოკვაში.GetEmails აღარ ყლაპავს json.Marshal შეცდომებს — აბრუნებს 500-ს ცარიელი 200-ის ნაცვლად.HandleLicense და სხვა ჰენდლერები ახლა აყენებენ Content-Type: application/json-ს.build-and-push-test.sh + docker-compose-u-test.yml / docker-compose-m-test.ymlიზოლირებული staging დიპლოიმენტებისთვის :test იმიჯის ტეგებით.SYNC_MAX_WORKERS, PG_SYNC_MAX_CONNS გარემოს ცვლადები.GET /api/health აბრუნებს {"status":"ok"} Docker-ის ჯანმრთელობის შემოწმებისთვის. GET /metrics ავლენს 16 Prometheus მთვლელს (rms_mail_emails_synced_total, http_requests_total და ა.შ.) api.MetricsHandler-ის მეშვეობით.asynq-ს ავტომატური გამეორებით, exponential backoff-ით და რიგის პრიორიტეტებით (კრიტიკული:6, ნაგულისხმევი:3, დაბალი:1). internal/async/ პაკეტი TaskClient + TaskServer-ით.ncruces/go-sqlite3-დან tursodatabase/libsql-client-go-ზე. სუფთა Go WASM დრაივერი, CGO_ENABLED=0, ერთი ჩამწერი კავშირი (MaxOpenConns=1), WAL რეჟიმი, busy_timeout=30000. ნულოვანი გარე დამოკიდებულებები — მხოლოდ ლოკალური ფაილი.saveEmailFallback, saveEmailToFolderFallback) ახლა იყენებს ON CONFLICT DO NOTHING-ს, რათა თავიდან იქნას აცილებული კონკურენტული გადარჩენის სირბილის პირობები (race conditions). MIME საზღვრის რეგექსი გაფართოვდა -სიმბოლოს მხარდასაჭერად საზღვრებში.runSyncCycle ახლა იძახებს SyncAllFolders-ს თითოეულ კავშირზე — ახალი აქაუნთები ყველა საქაღალდეს აღმოაჩენენ და სინქრონიზაციას ახდენენ დაუყოვნებლივ (ადრე მხოლოდ INBOX-ს სანამ CheckWorker არ ჩაერეოდა).ListFolders აღარ ტოვებს მშობელ საქაღალდეებს, რომლებსაც შვილები ჰყავთ — წერილები ჩადგმულ საქაღალდეებში აღარ იკარგება ჩუმად.SendEmail ჰენდლერი ახლა იყენებს Asynq ამოცანების რიგს (EnqueueSendEmailDelayed), როდესაც Redis ხელმისაწვდომია, Scheduler-ის სარეზერვოთი. სრული გაგზავნის პიპლაინი HandleSendEmail callback-ის მეშვეობით./mon/-ზე (მხოლოდ Unified, საჭიროა Redis) ამოცანების რიგის ინსპექტირებისთვის.worker.go, manager.go). JWT გაჟონვა MCP ლოგებში გამოსწორდა (r.URL.Path r.URL.String()-ის ნაცვლად). Identity CREATE/DELETE ახლა მოითხოვს CheckAccountAccess-ს. OAuth შეცდომის პასუხები გაიწმინდა სრული HTTP სხეულიდან. გაზიარებული http.Client webhook-ის დისპეტჩერიზაციისთვის სათანადო სხეულის დრენაჟით.JWTAuthMiddleware-ში. Mono ვერსია არის უსაფრთხო no-op (nil Redis).webhook_queue.go და job_worker.go აგზავნიდნენ RAW SECRET-ს როგორც X-Signature-256 ჰედერს — ახლა ითვლის HMAC-SHA256(secret, payload). საიდუმლო არასოდეს ტოვებს სერვერს.encryptPassword ახლა გამოიმუშავებს per-domain AES გასაღებებს SHA256(raw || ":" || domain)-ის მეშვეობით — IMAP პაროლები, OAuth ტოკენები, MCP გასაღებები და Telegram ტოკენები თითოეული იყენებს დამოუკიდებელ გასაღების მასალას (imap_password, oauth_token, mcp_key, telegram_token). დეკრიპტირება გადადის ნედლ გასაღებზე ძველი მონაცემებისთვის.sessionAccountID-ის მიხედვით — აქაუნთ A-ზე მიბმული MCP კლიენტი ვერ მიიღებს new-emailივენთებს აქაუნთ B-დან. SSE-ს (ჩვეულებრივს) უკვე ჰქონდა CheckAccountAccess.?token=-ის მეშვეობით non-SSE მარშრუტებზე. ორივე Authorization: Bearer და ?token= მიიღება; ფრონტენდი უკვე იყენებს ჰედერებს.MaxOpenConns გაიზარდა 1-დან 25-მდე, MaxIdleConns 1-დან 5-მდე. WAL რეჟიმი ახლა რეალურად აპარალელებს: მკითხველები არ დგებიან რიგში ჩამწერების უკან. DSN მოიცავს _synchronous=NORMAL, _cache_size=-64000, _foreign_keys=ON ასე რომ ყველა პულის კავშირი იღებს მათ. შედეგი: M ვერსია უმკლავდება 20-30 ერთდროულ მომხმარებელს რიგში დგომის გარეშე.maybeRotateWorkers აძევებს უძველეს ვორკერს ყოველ 5 წუთში, როცა ყველა maxWorkers სლოტი დაკავებულია. bootstrapMissingWorkers ავსებს განთავისუფლებულ სლოტებს მომლოდინე რიგიდან. ხელს უშლის ერთ მძიმე აქაუნთს სხვების შიმშილში განუსაზღვრელი ვადით.created_at DESC) იღებენ პრიორიტეტს საწყისი სინქრონიზაციისთვის.waitWithTimeout goroutine ახლა პატივს სცემს მშობელი კონტექსტის გაუქმებას — არ იბლოკება გაგზავნაზე, როცა გამომძახებელმა მიატოვა არხი. IMAP-ის დონის კონტექსტის გაცნობიერება რჩება სამომავლო გაუმჯობესებად.recoverFromRedis/recoverFromDB goroutine-ები ახლა იწყება მკაფიო Start()გამოძახების შემდეგ SetStore/SetContext-ის მერე, რაც აქრობს რბოლის ფანჯარას, სადაც ისინი ამოწმებდნენ ნულოვან დამოკიდებულებებს.http.Client-ს webhook-ის დისპეტჩერებში, რათა თავიდან იქნას აცილებული სოკეტების დაგროვება დატვირთვის ქვეშ.CASE WHEN UPPER(name) = 'INBOX' THEN 1 ... THEN 3 ELSE 2 END — INBOX ყოველთვის პირველი, მორგებული ლეიბლები ანბანურად, Trash/Spam/Junk იძულებით ბოლოში. Gmail [Gmail]/Trash და [Gmail]/Spam ამოცნობილია. გამოყენებულია როგორც Postgres-ზე, ასევე SQLite-ზე.smart_categories-ის გადართვა ახლა იძახებს RefreshUnreadCounts-ს დაუყოვნებლივ, 30-წამიანი პერიოდული განახლების ლოდინის ნაცვლად.WakeUpAccount ახლა იძახებს TriggerRefresh()-ს.var-დან Manager.OnNewEmail ველში. დაკავშირებულია Fetcher სტრუქტურის მეშვეობით.sync.Map container/list-ზე დაფუძნებული LRU-თი (10K ჩანაწერი, პერიოდული გაწმენდა 80% ტევადობაზე).strings.NewReplacer-ით &, <, >, ", ', — აუმჯობესებს AI შეჯამების ხარისხს.slog.Debug-ს slog.Info-ს ნაცვლად, რათა თავიდან იქნას აცილებული ლოგების სპამი ნაგულისხმევ დონეზე.%d pending) ჩამოგდებისას.context.Background() ჩანაცვლდა სათანადო ctx-ით; უგულებელყოფილი შეცდომა GetAccount-ზე ახლა ლოგირდება; AppendToDraftsDeduplicatedსინქრონულია (შეცდომა → სამუშაოს გამეორება ჩუმი დაკარგვის ნაცვლად).LockChecker გამოძახება თითოეული ვორკერის დაწყებამდე — აქაუნთი ჩაკეტილი GetAccounts-სა და ვორკერის დაწყებას შორის ახლა გამოტოვებულია.ProcessMessage და ProcessMessageToFolder ახლა იყენებს os.Remove(bodyPath)-ს, როცა SaveEmail ვერ ხერხდება მას შემდეგ, რაც EML ფაილი უკვე ჩაიწერა.HandleGenerateAIDraft ახლა გადასცემს OnGenerateAIDraft callback-ს → ProcessAutoDraftJob — ეტაპი 3 ოპერატიულია.sendWebhookWithRetry ახლა რიგდება AsyncClient.EnqueueDispatchWebhook-ის მეშვეობით როცა Redis ხელმისაწვდომია. Webhook-ები იღებენ მუდმივ რიგს, ავტომატურ გამეორებებს (5x) და asynqmon დაფის ხილვადობას. უბრუნდება Redis ZSET-ს (Unified Asynq-ის გარეშე) → SQLite (Mono).handleAutoDraft ახლა ამჯობინებს EnqueueGenerateAIDraft-ს DB ამოცანების რიგზე როცა AsyncClient ხელმისაწვდომია. უბრუნდება DB რიგს Mono-სთვის.email_sync_queue, imap_move_queue, scheduled_emails, email_comments და emails_fts (SQLite) აქაუნთის გადატვირთვისას. მანამდე, email_sync_queueინარჩუნებდა completed ამოცანებს, რომლებიც ბლოკავდა ხელახალ რიგში ჩადგომას WHERE status != 'completed' მცველის მეშვეობით EnqueueUIDs-ში — რაც იწვევდა 0 ელფოსტას სინქრონიზაციის შემდეგ. ახლა სინქრონიზაციის მთელი მდგომარეობა სრულად იწმინდება, ასე რომ ვორკერი ასრულებს სუფთა სრულ ხელახალ სინქრონიზაციას.OnNewEmail და SetEventBroadcast callback-ები აღარ იძახებენ store.GetEmail-ს მთავარ DB პულზე. მანამდე, ყოველი სინქრონიზებული ელფოსტა იწვევდა 2 GetEmail მოთხოვნას მთავარ პულზე (ერთი OnNewEmail-ში, ერთი InvalidateEmailCacheByEmailID-ში). 200K+ Gmail ინბოქსებით, ეს წარმოქმნიდა 400K მოთხოვნას, რომლებიც კონკურენციას უწევდნენ HTTP ჰენდლერებს — რაც იწვევდა 10-წუთიან API პარალიზებას გადატვირთვის შემდეგ. ახლა OnNewEmail იღებს subject/senderName/senderAddr-ს პირდაპირ Fetcher-იდან (ნულოვანი DB მოთხოვნები) და გადაცემის ქეშის ინვალიდაცია იყენებს account_id-ს payload-იდან ელფოსტის ხელახლა მოთხოვნის ნაცვლად.InvalidateMetaCache-ის მეშვეობით მუტაციებზე:GetEmails (5min), GetFolders (30s), GetAccounts (10s), GetGroups (30s)GetLabels (60s), GetRules (30s), GetTemplates (60s), GetContacts (30s)GetIdentities (60s), GetWebhooks (30s), AIModels (1h)InvalidateMetaCache ასუფთავებს ყველა per-account ქეშს CRUD ოპერაციებზე. InvalidateEmailCacheასევე ასუფთავებს საქაღალდის ქეშებს. ახალი InvalidateMetaCache დამხმარე bulk ინვალიდაციისთვის აქაუნთის/ჯგუფის მუტაციებზე. ამცირებს DB დატვირთვას ყოველ გვერდის ჩატვირთვაზე 4+ მოთხოვნიდან 0-მდე (cache hit) ან 1-მდე (cache miss + write).Bug Fix — Resync Produced Zero Emails
ResetAccountSync შლიდა ელფოსტას DB-დან, მაგრამ ტოვებდა 6148 completed რიგს email_sync_queue-ში. როდესაც SyncAllFolders-მა ხელახლა ჩადგა რიგში UID-ები, ON CONFLICT ... DO UPDATE ... WHERE status != 'completed' პუნქტმა გამოტოვა ყველა. მომხმარებლის ციკლმა ამოიღო არაფერი → 0 ელფოსტა გამოჩნდა ხელახალი სინქრონიზაციის შემდეგ.
Fix (postgres/storage.go, sqlite/storage.go):
DELETE FROM email_sync_queue WHERE account_id = $1 დაემატა საქაღალდის/აქაუნთის UID გადატვირთვამდე.imap_move_queue, scheduled_emails, email_comments-სთვის (ქამარი-და-საკიდი ცხრილებისთვის FK CASCADE-ით).DELETE FROM emails_fts (FTS5 ვირტუალურ ცხრილს არ აქვს FK მხარდაჭერა, დააგროვებდა ობოლ ინდექსის ჩანაწერებს).GetEmailsCursor ორივე Postgres და SQLite საცავებში: ანაცვლებს LIMIT 50 OFFSET 50000-ს კომპოზიტური კურსორით (date_sent, id) — მუდმივი დრო გვერდის სიღრმის მიუხედავად.X-Next-Cursor პასუხის ჰედერი Access-Control-Expose-Headers-ით CORS კონფიგურაციაში.useEmailsInfinite იყენებს pageParam-ს როგორც კურსორის სტრიქონს, კითხულობს X-Next-Cursor-ს axios პასუხის ჰედერებიდან.?offset=-ს, აგრძელებენ მუშაობას ორიგინალური GetEmails-ის მეშვეობით.GetFolders, GetUnreadCountByAccount, GetUnreadInboxCountByAccount, GetUnreadCountByFolder ახლა იყენებენ პირდაპირ COUNT(*) FROM emails WHERE is_read=falseქეშირებული folders.unread_count სვეტის ნაცვლად. ნაწილობრივი ინდექსი idx_emails_folder_unreadხდის მათ მილიწამზე ნაკლებს.MarkEmailRead, BulkMarkEmailsRead, BulkMarkEmailsUnread ანახლებს folders.unread_count ატომურად CTE-ს მეშვეობით — მაგრამ ავტორიტეტული წყარო ახლა არის ცოცხალი COUNT.RefreshUnreadCounts ამოღებულია queue_manager.go-დან — აღარ არის 30-წამიანი ფონური ტაიმერი, რომელიც კარგავს CPU-ს უსაქმურ მთვლელებზე.publishEvent მოიცავს account_id-ს ჯგუფური მოქმედების შეტყობინებებში — ქეშის ინვალიდაცია ახლა სწორად სრულდება read/unread გადართვებისთვის.MemoryCache TTL-ით და პერიოდული გაწმენდით. როცა Redis მიუწვდომელია (Mono), 11 GET ენდპოინტი იყენებს in-memory cacheGet/cacheSet/tryCacheდამხმარეებს — გამჭვირვალედ ირთვება Redis-სა და ლოკალურ მეხსიერებას შორის.cache:account:meta:{id} (30s TTL) GetAccount DB მოთხოვნის თავიდან ასაცილებლად ყოველ API გამოძახებაზე. ამცირებს ავთენტიფიკაციის ზედნადებს N+1-დან O(1)-მდე.redisOpTimeout=3s გამოყენებულია Redis-ის ყველა ოპერაციაზე — ხელს უშლის HTTP ვორკერების გაჭედვას ნელ Redis-ზე.ctx-ს context.Background()-ის ნაცვლად სათანადო სასიცოცხლო ციკლის მენეჯმენტისთვის.cache:account:meta:{id} ანაცვლებს სრულ accounts:list JSON პარსირებას — O(1) ძიება O(n) სკანირების ნაცვლად.publishEvent იყენებს ctx-ს context.Background()-ის ნაცვლად Redis ინვალიდაციისთვის.MaxConns=100 ნაგულისხმევი PostgreSQL პულისთვის (PG_MAX_CONNS env აჭარბებს)._synchronous=NORMAL&_cache_size=-64000&_foreign_keys=ON პულის ყველა კავშირისთვის.Shutdown() syncMgr.StopAll()-მდე — აძლევს საშუალებას in-flight SMTP/webhook ამოცანებს დასრულდეს.Asynq.Start(ctx) მიბმულია აპლიკაციის კონტექსტზე context.Background()-ის ნაცვლად.WithTimeout(1s)-ით — ხელს უშლის goroutine გაჟონვას გამორთვის დროს.case "webhook" StartJobWorker-დან (მკვდარი კოდი — webhook-ები გადიან Asynq/Redis ZSET/SQLite poller-ის მეშვეობით).EnqueueRefreshUnread (მკვდარი ასინქრონული ტიპი — წაუკითხავი რაოდენობა ახლა ცოცხალია).HandleSendEmail SkipRetry გაუქმებული ამოცანებისთვის — აჩერებს 10x გამეორების სპამს.SendScheduler.Start() მკაფიო სასიცოცხლო ციკლი — goroutine-ები იწყება დამოკიდებულებების დაყენების შემდეგ.callAIChatWithTools და callAIChat ai_handlers.go-ში ჩაანაცვლა 80 ხაზიანი in-place გაზიარებული-პროვაიდერის მუტაცია 6 ხაზიანი ზედაპირული ასლით ai.OverrideProviderSettings()-ის მეშვეობით. ერთდროული AI მოთხოვნები სხვადასხვა მოდელებით/გასაღებებით აღარ იჯიბრებიან.gateway.go — გადაერქვა overrideProviderSettings-დან ექსპორტირებულ OverrideProviderSettings-ზე. დაემატა *OpenCodeProvider ვარიანტი.resolveAPIKey ახლა იმეორებს ყველა გასაღებს ENCRYPTION_KEYS-დან ახალი crypto.GetAllEncryptionKeys()-ის მეშვეობით და არა მხოლოდ პირველ გასაღებს. როტაციის შემდეგ ძველი გასაღებებით დაშიფრული AI პარამეტრები ახლა სწორად იშიფრება.sync.RWMutex, რომელიც იცავს map-ის ყველა ოპერაციას — ხელს უშლის fatal error: concurrent map writes კრახს პარალელური Telegram ტრაფიკის ქვეშ.decryptTelegramToken main.go-ში ცდის დომენიდან მიღებულ ყველა გასაღებს (telegram_token) და ნედლ გასაღებს, სანამ დაუბრუნდება შიფრტექსტს — ასწორებს ბოტის ავტორიზაციის წარუმატებლობას, როცა ტოკენი იტვირთება DB-დან.UpsertAISettings ახლა აბრუნებს HTTP 500-ს, როცა ENCRYPTION_KEYარ არის კონფიგურირებული, მაგრამ API გასაღებები ინახება — ხელს უშლის ჩუმად ტექსტური სახით შენახვას.strings.Split ai.ParseCategories-ით — სწორად უმკლავდება ბულიტებიან სიებს და მრავალხაზიან AI პასუხებს.GetWebhooks აღარ აბრუნებს Secret ველს ფრონტენდში.GetTelegramSettings აბრუნებს XXXX...XXXX-ს რეალური ტოკენის ნაცვლად.err.Error() == "no rows in result set" → იდიომატური errors.Is(err, pgx.ErrNoRows) ხუთივე ლოკაციაზე.mixtral-8x7b → mixtral-8x7b-32768 ProviderModels-ში.callAIChat Ollama-სთვის ახლა გადასცემს effectiveKey-ს მყარად ჩაწერილი ""-ის ნაცვლად.EnqueueWebhook ახლა ლოგირებს, როცა Redis მიუწვდომელია.localStorage-ს მხოლოდ მაშინ, როცა მნიშვნელობები რეალურად განსხვავდება — აღმოფხვრის ხილულ ციმციმს გვერდის ჩატვირთვისას.refetch() გამოძახება განახლების დროს — იწვევდა სამმაგ re-render-ს.FetchAnthropicModels(Anthropic API) და FetchOpenAICompatModels (OpenCode) fetchProviderModels-ში. ყველა პროვაიდერი ახლა იღებს ცოცხალ მოდელებს მათი API-ებიდან მყარად ჩაწერილი სიების ნაცვლად. მყარად ჩაწერილი ProviderModels განახლებულია მიმდინარე 2026 წლის მოდელებით 10-ვე პროვაიდერისთვის.FetchGeminiModels ახლა ფილტრავს მხოლოდ gemini-* მოდელებს, გამორიცხავს ძველ PaLM მოდელებს (chat-bison, text-bison, embedding, aqa).FetchGeminiModels და FetchOllamaModels-ში — მანამდე ჩუმად ყლაპავდა API შეცდომებს.ChatWithTools ახლა იყენებს BaseURL-ს მყარად ჩაწერილი api.opencode.com-ის ნაცვლად. ListModels იყო return nil, nil — ახლა ითხოვს პროვაიდერისგან. ProviderEnvKey დაემატა "opencode" ვარიანტი. Cloud URL ამოცნობა (/zen/go/v1 opencode.ai-სთვის, /v1/modelsლოკალურისთვის).callAPI და callAPIWithTools გადაიწერა სათანადო Gemini ფორმატზე: system → systemInstruction (ცალკე ველი), assistant → model როლის რუკა. მანამდე ყველა შეტყობინება იყრიდა თავს ერთ ტექსტურ ბლოკში.UpsertAISettings ახლა აერთიანებს შემომავალ გასაღებებს არსებულ შენახულ გასაღებებთან. ერთი პროვაიდერის გასაღების შეყვანა აღარ შლის ყველა სხვა შენახულ გასაღებს. დეკრიპტირებას უკეთებს არსებულს, აერთიანებს არაცარიელ მნიშვნელობებს, ხელახლა შიფრავს.resolveAPIKey გლობალური უკანდახევა აღარ მოითხოვს ALLOW_GLOBAL_AI_KEYS=true-ს (ახლა ჩართულია ნაგულისხმევად, ითიშება =false-ით). ამოღებულია setting.Preset == "" ბლოკერი. სათანადო ციკლი კანდიდატ აქაუნთების ID-ებზე.fetchProviderModels ახლა ცდის ყველა გასაღებს ENCRYPTION_KEYS-დან (მძიმით გამოყოფილი) და არა მხოლოდ პირველს — ასწორებს მოდელის ამოღებას გასაღების როტაციის შემდეგ.AIChat, summarizeEmail, AICategorize, AICategorizeEmail) ახლა აბრუნებს {"error":"..."} JSON-ს ზოგადი "internal error"-ის ნაცვლად — მომხმარებლებს შეუძლიათ ნახონ რეალური API შეცდომის შეტყობინებები.callAIChat და callAIChatWithTools აღარ გადააწერენ პროვაიდერის მოდელს ცარიელი სტრიქონით, როცა apiKey არის წარმოდგენილი, მაგრამ model ცარიელია.grok-2 DeepSeek API-ზე).localStorage), ავტომატურად ირჩევს პირველ ხელმისაწვდომ მოდელს. Force-refreshes მაუნთზე.return, როცა Redis არის nil (Mono ვერსია). ახლა სწორად აინვალიდებს ორივეს, Redis-ს (Unified) და MemCache-ს (Mono). დაემატა MemoryCache.Keys() მეთოდი პრეფიქსზე დაფუძნებული ინვალიდაციისთვის.Del("folders:unified", "folders:") scanAndDel("folders:*")-ით — სწორად ასუფთავებს ყველა აქაუნთის სპეციფიკურ საქაღალდის ქეშს.markEmailRead, toggleFlagEmail, togglePinEmail, toggleMuteEmail, snoozeEmail, saveDraftReply, clearDraftReply, moveEmail, SetEmailLabels ახლა იძახებენ InvalidateEmailCache + publishEvent("email_updated")-ს სათანადო account_id-ით.account_id არ არის მოწოდებული ფრონტენდის მიერ, მკაფიოდ იძახებს InvalidateEmailCache("")-ს — მანამდე ჩუმად გამოიტოვებოდა.email_updated ივენთის ჰენდლერი, რომელიც აინვალიდებს ["email", id], ["emails-infinite"] და ["folders"] React Query ქეშებს — მანამდე მხოლოდ new-email მუშავდებოდა.selectedEmail ობიექტის რეფერენსი იცვლებოდა თითოეულ ხელახალ მოთხოვნაზე. ახლა იყენებს useRef-ს ელფოსტის ID თვალთვალით — ტაიმერი იწყება ერთხელ და ეშვება საიმედოდ კონფიგურირებული შეფერხების შემდეგ.__pending__ guard: ხელს უშლის ხელახალ ტაიმერს წარმატებული მუტაციის შემდეგ, სანამ React Query ქეში ჯერ არ განახლებულა. იწმინდება როცა is_read რეალურად გადაირთვება.["accounts"] invalidation in useMarkEmailRead: Auto-mark-read აინვალიდებდა ["folders"]-ს, მაგრამ არა ["accounts"]-ს — გვერდითა პანელის ჰედერის მთვლელი (accounts.reduce(... a.unread_inbox)) არასოდეს ახლდებოდა. მექანიკურმა ღილაკმა იმუშავა, რადგან useBulkEmailActionაინვალიდებდა აქაუნთებს.["folders"]: useFlagEmail, usePinEmail, useSnoozeEmail მანამდე აინვალიდებდა მხოლოდ ["emails-infinite"]-ს — გვერდითა პანელის საქაღალდის მთვლელები იყო მოძველებული flag/pin/snooze ქმედებების შემდეგ. დაემატა refetchQueries საქაღალდეებისთვის staleTime-ის გვერდის ავლით.is_dirty_locally dot removed: ელფოსტის ბარათებიდან — დამაბნეველი UX, მომხმარებლებს არ აინტერესებთ IMAP სინქრონიზაციის სტატუსი./api/users-ზე Unified და Mono ვერსიებში.IsTeams() ბლოკიდან — მუშაობს U, M, T-ზე.[SSE] connecting, [SSE] connected, [SSE] event source error) useEmails.ts-დან.* FetchItemData ტიპებიდან