ADD now probes both connections, lists folders on each side, and opens a
mapping modal to route source->destination folders (e.g. Спам -> Spam) so we
append into the existing folder instead of creating a duplicate.
- store: SetTaskFolderMapping (+ round-trip test)
- httpapi: POST /tasks/{id}/probe (test both, return folder lists),
PUT /tasks/{id}/folder-mapping
- web: FolderMappingModal (reuses Modal, size=lg), submitAccount probes then
opens the modal; confirm creates the account and saves the task mapping
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MMHQTtnQtQqL8muAXHr9kd
CopyFolder now streams envelope metadata via Next() in a first pass (dedup +
queue new messages), then streams bodies for new ones in a second pass —
no more blocking Collect of the whole folder with zero feedback, and memory
stays flat (only new-message meta is held).
- imapx: two-pass streaming CopyFolder + CopyDeps.OnScan(scanned,total)
- orchestrator: throttled 'scan' events during the metadata pass
- web: per-account 'scanning folder: X/N' line under the progress bar;
scan events kept out of the log to avoid flooding
Verified on greenmail: idempotency and internal-date preservation still hold.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MMHQTtnQtQqL8muAXHr9kd
Before copying, EXAMINE every folder to sum the account's total message count
and emit a 'plan' event; progress events now carry account_total so the UI
shows a real overall bar, percent and ETA (not just per-folder).
- imapx.FolderMessageCount: read-only count of a folder
- orchestrator: plan pass + grandTotal, plan event, account_total in progress
- web: live progress keyed on account total; PLAN log line; overall bar/ETA
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MMHQTtnQtQqL8muAXHr9kd
- orchestrator: progress events now carry account-level cumulative copied/
skipped plus current folder done/total, throttled to ~3/sec per account
- web: RUN CONTROL counters and account copied/skipped read live WS values
(DB only advances per folder, so the summary lagged); new Progress column
shows a bar, percent, avg messages/sec and folder ETA while running
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MMHQTtnQtQqL8muAXHr9kd
The run-cancel registry is in-memory; a container restart mid-run leaves
accounts/tasks persisted as 'running' with no goroutine, wedging cancel
(not-in-map -> 409) and blocking remove/re-run.
- startup: ResetRunningOnStartup clears stale 'running' -> 'idle' on boot
- cancel handler: when no live goroutine, ClearStuckAccount + ReconcileTaskStatus
reset the stuck account (and its task) instead of returning 409
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MMHQTtnQtQqL8muAXHr9kd
- orchestrator: per-account cancellable context registry + CancelAccount;
on cancel, close IMAP connections to unblock in-flight FETCH; account ends
in 'cancelled' status with a cancelled event
- imapx: CopyDeps.OnFolder callback fires after EXAMINE with the folder's
message count (before the long fetch) for visibility
- httpapi: POST /tasks/{id}/accounts/{accountId}/cancel
- web: per-row cancel button while running, folder event shows N messages,
cancelled/done_with_errors status badges
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MMHQTtnQtQqL8muAXHr9kd
Pasted app passwords (e.g. mail.ru) often carry a trailing space/newline that
the IMAP server rejects; the CSV path already trims, so make manual add match.
Source and destination passwords remain fully independent (src_pass_enc /
dst_pass_enc), verified end-to-end — this only strips surrounding whitespace.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MMHQTtnQtQqL8muAXHr9kd
Modal component: portal, ESC to close, Tab focus-trap, focus-on-open
(prefers [data-modal-autofocus]), focus restore, overlay click, scroll lock.
ConfirmProvider exposes useConfirm(): async confirm({...}) as a drop-in for
window.confirm; Enter confirms, ESC cancels. Task/account deletes now use it.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MMHQTtnQtQqL8muAXHr9kd
Root cause: store List* methods used 'var out []T' which stays nil on empty
result sets and serializes to JSON null; the SPA then crashed on .length/.map
(e.g. endpoints.length on the Tasks page right after deploy). Return []T{}
at the source; coerce null->[] on the frontend load sites as defense-in-depth.
Regression test asserts List* are non-nil when empty.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MMHQTtnQtQqL8muAXHr9kd
Go's encoding/json does not bridge snake_case <-> PascalCase field names,
so store.Endpoint, store.Task and the anonymous request bodies in
accounts.go/auth.go were silently decoding empty/zero values from the
frontend's snake_case JSON contract (tls_mode, role_label,
src_endpoint_id, dst_endpoint_id, src_login/pass, dst_login/pass).
Adds explicit json tags; DB layer is unaffected since pgx binds by
positional params, not struct-tag reflection.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MMHQTtnQtQqL8muAXHr9kd
Vite + React 19 + TS console-style operator UI: hash-routed Login,
Endpoints, Tasks, and TaskDetail (realtime accounts table over /ws,
Run gated on all accounts testing ok on both sides).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MMHQTtnQtQqL8muAXHr9kd