aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md7
-rw-r--r--README.md15
-rw-r--r--docs/superpowers/plans/2026-08-03-post-0.1.0-usability-closed.md46
-rw-r--r--docs/superpowers/plans/2026-08-03-post-0.1.0-usability.md45
-rw-r--r--src/mainwindow.cpp55
-rw-r--r--src/mainwindow.h9
-rw-r--r--tests/test_mainwindow.cpp196
-rw-r--r--translations/qtmaildir_it_IT.ts12
8 files changed, 322 insertions, 63 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index e1378fc..32c1a34 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -41,6 +41,13 @@ point at which they are stable.
themes draw as an exclamation mark; that was invisible while it only ever
appeared beside its own label, and became an info glyph the moment it
reached the icon-only message bar.
+- **Sync follows the account you are looking at.** With an account selected in
+ the dropdown, Sync collects that account rather than every one; with All
+ accounts selected it collects everything, as before. Any account with
+ unsynced edits is always included, whichever one is on screen, so choosing a
+ narrower view cannot leave a change stranded. The status line now names what
+ a run covers instead of saying only "Syncing...", so a narrowed run is
+ visible as one.
### Fixed
diff --git a/README.md b/README.md
index 4ae0034..affff11 100644
--- a/README.md
+++ b/README.md
@@ -655,11 +655,16 @@ Two things any replacement has to get right, both learned the hard way:
### Per-account sync
-When tag changes are outstanding, a sync passes only the affected accounts'
-channel names to the command. When nothing is outstanding the run is a plain
-fetch and no names are passed, so every account is synced: narrowing a fetch to
-wherever the last edit happened to be would quietly stop collecting mail
-everywhere else.
+A sync covers the account selected in the dropdown, plus any account with
+outstanding tag changes. With **All accounts** selected and nothing
+outstanding, no names are passed and the run is a plain fetch over everything:
+narrowing a fetch to wherever the last edit happened to be would quietly stop
+collecting mail everywhere else.
+
+The two are a union rather than a choice, so an edit is never left behind by
+looking somewhere else. Editing mail in `personal` and then switching the
+dropdown to `work` syncs both: `work` because it is on screen, `personal`
+because it owes a write. The status line names what a run covers as it starts.
The name passed is the **mbsync channel**, which is not always the account's
section key. `[account.mail-first.last]` may well be the channel
diff --git a/docs/superpowers/plans/2026-08-03-post-0.1.0-usability-closed.md b/docs/superpowers/plans/2026-08-03-post-0.1.0-usability-closed.md
index 6613059..bcb6431 100644
--- a/docs/superpowers/plans/2026-08-03-post-0.1.0-usability-closed.md
+++ b/docs/superpowers/plans/2026-08-03-post-0.1.0-usability-closed.md
@@ -10232,3 +10232,49 @@ sends, and asserts the row arrives with no further gesture: no second
against the defect. Mutation-checked by disabling the connection, which fails
on the row count.
+## 101. Sync is account-aware for edits but not for the account the user is looking at
+
+**Done 2026-09-08.** The user chose BOTH halves the entry offered, and then
+narrowed the first one: not a second action beside Sync, but the existing Sync
+made account-aware. "If 'All Accounts' is selected, Sync works as today, if
+'work' is selected, hitting Sync works only for 'work'."
+
+**What was built.** `pendingSyncChannels()` now reads the account dropdown as
+well as `m_editedAccounts`, and the two are a UNION rather than one replacing
+the other. That union is the whole safety property and is the constraint the
+original entry named: looking at one account while having edited another is
+ordinary, and a run that dropped the edited account's channel would strand that
+write with nothing on screen to say so. All accounts is the empty key and
+narrows nothing, so a full fetch is still what an unselected window asks for,
+which is what keeps item 49's reasoning intact. The existing fallback survives
+untouched: an account whose section names no channel still widens the run to
+everything rather than being silently skipped.
+
+The visibility half is `syncStartedText()`, called from `setSyncBusy()`. A run
+used to open with "Syncing..." whether it covered one account or all of them,
+which is precisely the invisibility the entry complained about; it now reads
+"Syncing all accounts..." or "Syncing work-channel, personal-channel...". The
+exit paths pass no channels and overwrite the label with their own wording
+immediately, so the parameter is defaulted rather than threaded through them.
+
+**Four tests**, three on the channel resolution and one on the generated
+string. `syncNarrowsToTheSelectedAccount` asserts the list is EMPTY before the
+gesture, which matters more than it looks: empty is what a full fetch looks
+like, so a test starting from a narrowed state could not tell the fix from a
+window that had never widened. `aNarrowedSyncStillCarriesAnEditFromAnotherAccount`
+is the union, and it is the one that fails under the plausible wrong
+implementation. `theStatusLineNamesWhatASyncWillCover` asserts on the generated
+string rather than on a label after a real run, which would be a test of
+QProcess timing.
+
+Mutation-checked twice: ignoring the selection fails all three channel tests,
+and replacing the pending set instead of unioning with it fails the third one
+alone. That second mutation is the one worth having, since it is the version
+that looks correct and loses a write.
+
+**One thing deliberately not done.** The separator in the channel list is a
+plain `", "`, not a translated string. `lupdate` picked it up as translatable
+when it was written with `tr()`, and a comma joins a list the same way in
+Italian; a translatable separator is a string for a translator to get wrong for
+no gain.
+
diff --git a/docs/superpowers/plans/2026-08-03-post-0.1.0-usability.md b/docs/superpowers/plans/2026-08-03-post-0.1.0-usability.md
index 681200e..47c46fb 100644
--- a/docs/superpowers/plans/2026-08-03-post-0.1.0-usability.md
+++ b/docs/superpowers/plans/2026-08-03-post-0.1.0-usability.md
@@ -167,7 +167,7 @@ taking that too literally.
| 98 | "Important" adds the tag but cannot remove it, unlike every other toggle | defect | XS | **done** 2026-08-17, unreleased. Calls `everySelectedRowHasTag()`, as the entry required. Its reply test needed THREE different states (list-first thread, the reply's own thread, the reply) before it could tell the two wrong answers apart; with the reply defaulted to its thread's state the item 105 mutation stayed green, measured |
| 99 | The unread action is labelled "Toggle unread" whichever way it will go | presentation | S | **done 2026-08-25**, unreleased, with 112: the user's note is ONE design across both. The label names the direction it will go, and the entry is hidden on a selection with no single state. `refreshUnreadAction()` reads the new three-valued `selectionTagPresence()` |
| 100 | The message pane offers Back, Forward, Reload and Save page, none of which mean anything | defect | XS | **done** 2026-08-17, unreleased. `MessageView::removeBrowserActions()` filters the standard menu by `pageAction()` POINTER, never by text; `ViewSource` went with them, and stranded separators are swept |
-| 101 | Sync is account-aware for edits but not for the account the user is looking at | workflow | S | open; item 49 built the edit half deliberately. Needs a decision, see the entry |
+| 101 | Sync is account-aware for edits but not for the account the user is looking at | workflow | S | **done 2026-09-08**, unreleased. The user chose both halves the entry offered and narrowed the first: no second action, the existing Sync reads the account dropdown. Selected account narrows the run, All accounts is a full fetch as before, and the edited accounts are a UNION with the selection so a narrowed run cannot strand a write. The status line names what a run covers. See the closed entry |
| 102 | The rules table shows no note, so the field explaining a rule is invisible until it is opened | workflow | XS | **done** 2026-08-17, unreleased. A Note column before `ColumnCount`, so the appended Matches column stays last. Found a second defect on the way: `restoreState` REFUSES a header state with a different column count, and the sized flags were being set regardless |
| 103 | What Delete does to mail on the server is undocumented and unverified | clarification | S+M | done; Delete moves to the account trash, with Restore and a stranded-mail cleanup. Section in the closed file |
| 104 | Mail visible in Thunderbird never reaches qtmaildir | defect | XS | **done 2026-08-25**, hand-tested. The worker never reopened its read-only notmuch handle, so no query saw mail indexed after startup. Confirmed on a sync run from the application that added 20 messages: they appeared without a restart |
@@ -484,49 +484,6 @@ literals `lupdate` can see; a string built by concatenation is not translatable.
**Size: S.** Mostly the mixed-selection and toolbar decisions, not the code.
-## 101. Sync is account-aware for edits but not for the account the user is looking at
-
-**Observed (user, from the notes):** "sync button should be account-aware."
-
-**Cause (verified in the code).** `MainWindow::pendingSyncChannels()`
-(`src/mainwindow.cpp:3550`) resolves channels from `m_editedAccounts`, the set of
-accounts the user has made EDITS in, and from nothing else. The account dropdown
-is not consulted. With nothing pending it returns empty on purpose, and
-`mailsync.sh` turns that into `mbsync -a`, every channel.
-
-**Item 49 built exactly this and the reasoning still holds.** The comment states
-it: with nothing pending the run is a FETCH, and narrowing a fetch to wherever
-the last edit happened would "quietly stop collecting mail everywhere else".
-Fetching is global by nature; carrying edits is not.
-
-**So this needs a decision, not a fix.** The note does not say which of two
-things the user means, and they are different features:
-
-*Sync only the selected account, on demand.* A deliberate "sync this account"
-that ignores the pending set, presumably beside the existing Sync rather than
-replacing it. Useful when one account is slow and the user wants their mail from
-another one now. The risk is the one item 49 named: a button that looks like
-Sync and quietly does not collect the rest of the mail.
-
-*Show which accounts a sync will cover.* No behaviour change at all, just making
-the existing account-awareness visible, since today the user cannot tell whether
-a run is narrowed or full. The status bar already names each channel as mbsync
-reaches it (item 42), so most of this exists.
-
-**Constraints.**
-
-- The account dropdown is a VIEW filter. Making it also steer sync couples two
- things the user may reasonably want apart: looking at one account while
- fetching all of them is the normal case, not an edge case.
-- Whatever narrows a run must still carry every pending edit, or an edit is
- stranded with nothing on screen to say so. `pendingSyncChannels()` already
- falls back to a full sync when it cannot resolve a channel for an edited
- account, and that safety must survive.
-- An account with no `[account.<key>]` section, or one whose section names no
- channel, has no channel to sync. The fallback covers it today.
-
-**Size: S** for the on-demand button, XS for the visibility half. Ask which.
-
## 113. No way to see a message's HTML source
**Observed (user, 2026-08-17):** reviewing item 100's removals, "view source
diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp
index 41dbe9c..e7cbf33 100644
--- a/src/mainwindow.cpp
+++ b/src/mainwindow.cpp
@@ -5452,7 +5452,7 @@ void MainWindow::feedSyncPhase(const QString &chunk)
m_statusLabel->setText(m_syncPhase.statusText());
}
-void MainWindow::setSyncBusy(bool busy)
+void MainWindow::setSyncBusy(bool busy, const QStringList &channels)
{
m_localSyncBusy = busy;
updateSyncControls();
@@ -5462,7 +5462,22 @@ void MainWindow::setSyncBusy(bool busy)
// output by then. Setting the label is still right, since the tracker has
// nothing to say until a line it recognises arrives.
if (busy && m_syncPhase.statusText().isEmpty())
- m_statusLabel->setText(tr("Syncing..."));
+ m_statusLabel->setText(syncStartedText(channels));
+}
+
+QString MainWindow::syncStartedText(const QStringList &channels) const
+{
+ // Item 101's visibility half. The run is already account-aware and the user
+ // could not tell: the same three dots appeared whether mbsync was about to
+ // collect one account or all of them. Naming the channels makes a narrowed
+ // run legible at the moment it starts, which is what the item asked for.
+ //
+ // Empty means every channel, since that is exactly what pendingSyncChannels()
+ // returns for a full fetch and what mailsync.sh turns into `mbsync -a`.
+ if (channels.isEmpty())
+ return tr("Syncing all accounts...");
+
+ return tr("Syncing %1...").arg(channels.join(QStringLiteral(", ")));
}
void MainWindow::updateSyncControls()
@@ -5515,11 +5530,15 @@ void MainWindow::startSync()
m_syncPhase.reset();
m_syncLineBuffer.clear();
- if (!m_sync->start(pendingSyncChannels())) {
+ // Read once and passed on, rather than asked for twice: the label must
+ // describe the run that actually started, and a second call could disagree
+ // with the first if the selection moved in between.
+ const QStringList channels = pendingSyncChannels();
+ if (!m_sync->start(channels)) {
showTransientStatus(tr("Sync already running"));
return;
}
- setSyncBusy(true);
+ setSyncBusy(true, channels);
}
void MainWindow::scheduleAutoSync()
@@ -5607,16 +5626,34 @@ void MainWindow::recordPendingEdit(const QString &messageId, const QString &tag,
QStringList MainWindow::pendingSyncChannels() const
{
- // Nothing pending means this run is a FETCH, and a fetch must cover every
- // account: narrowing it to wherever the last edit happened to be would
+ // The account the user is LOOKING at narrows the run, per item 101 and the
+ // user's decision on it: one Sync action, steered by the dropdown. All
+ // accounts is the empty key and narrows nothing, which is what keeps item
+ // 49's property that a fetch collects everywhere by default.
+ //
+ // This is deliberately a UNION with the pending set below, never a
+ // replacement for it. Looking at one account while having edited another is
+ // ordinary rather than an edge case, and a run that dropped the edited
+ // account's channel would strand that write with nothing on screen to say
+ // so.
+ const QString selected = m_accountBox
+ ? m_accountBox->currentData().toString()
+ : QString();
+
+ QSet<QString> wanted = m_editedAccounts;
+ if (!selected.isEmpty())
+ wanted.insert(selected);
+
+ // Nothing pending and nothing selected means this run is a FETCH over
+ // everything: narrowing it to wherever the last edit happened to be would
// quietly stop collecting mail everywhere else. Empty is the signal for
// that, and MailSync::start() appends nothing.
- if (m_editedAccounts.isEmpty())
+ if (wanted.isEmpty())
return {};
QStringList channels;
for (const Account &account : m_config.accounts()) {
- if (m_editedAccounts.contains(account.key))
+ if (wanted.contains(account.key))
channels.append(account.syncChannel());
}
@@ -5624,7 +5661,7 @@ QStringList MainWindow::pendingSyncChannels() const
// channel, and syncing a subset that omits it would leave its edits behind
// with nothing to say so. Fall back to a full sync, which is correct if
// wasteful; the alternative is silently stranding an edit.
- if (channels.size() != m_editedAccounts.size())
+ if (channels.size() != wanted.size())
return {};
// Stable order so a run is reproducible and the log reads the same way
diff --git a/src/mainwindow.h b/src/mainwindow.h
index 3829ae2..55d67ae 100644
--- a/src/mainwindow.h
+++ b/src/mainwindow.h
@@ -1012,7 +1012,14 @@ private:
/// the script's output is unstructured, so a bar that filled from left to
/// right would be inventing a fraction nobody knows. An indeterminate one
/// says "working, duration unknown", which is the truth.
- void setSyncBusy(bool busy);
+ /// \p channels is the list the run was started with, used only for the
+ /// opening status line: empty means every account. Defaulted because the
+ /// exit paths overwrite the label with their own wording immediately after.
+ void setSyncBusy(bool busy, const QStringList &channels = {});
+
+ /// The opening status line for a run over \p channels. Item 101's
+ /// visibility half: a narrowed run must say so.
+ Q_INVOKABLE QString syncStartedText(const QStringList &channels) const;
/// Reassembles lines from a sync output chunk and updates the status label
/// when the phase or its detail changes.
diff --git a/tests/test_mainwindow.cpp b/tests/test_mainwindow.cpp
index 1c54ebb..b7f7846 100644
--- a/tests/test_mainwindow.cpp
+++ b/tests/test_mainwindow.cpp
@@ -438,6 +438,10 @@ private slots:
void anUnknownExternalStateClearsNothing();
void aSuccessfulCronSyncDrainsTheEditedAccounts();
void aCronSyncDoesNotClearAnEditMadeWhileItRan();
+ void syncNarrowsToTheSelectedAccount();
+ void syncCoversEveryAccountWhenAllIsSelected();
+ void aNarrowedSyncStillCarriesAnEditFromAnotherAccount();
+ void theStatusLineNamesWhatASyncWillCover();
void everyActionCarriesAnIcon();
void everyActionIsReachableFromAMenu();
@@ -7496,6 +7500,198 @@ void TestMainWindow::aCronSyncDoesNotClearAnEditMadeWhileItRan()
"an edit made after the sync ended was swallowed by it");
}
+// Item 101.
+
+namespace {
+
+/// Writes a config with two accounts, each naming its own sync channel, so a
+/// narrowed run can be told from a full one by the channel list alone.
+void loadTwoAccountConfig(Config &config, const QTemporaryDir &dir,
+ const QString &logPath)
+{
+ const QString path = dir.filePath(QStringLiteral("qtmaildir.conf"));
+ QFile file(path);
+ QVERIFY(file.open(QIODevice::WriteOnly | QIODevice::Text));
+ file.write(QStringLiteral("[sync]\nlog=%1\nstatus=%2\n\n"
+ "[account.work]\n"
+ "maildir=work-mail\n"
+ "channel=work-channel\n\n"
+ "[account.personal]\n"
+ "maildir=personal-mail\n"
+ "channel=personal-channel\n")
+ .arg(logPath, dir.filePath(QStringLiteral("no-status.json")))
+ .toUtf8());
+ file.close();
+
+ config.load(path);
+ QCOMPARE(config.accounts().size(), 2);
+}
+
+/// Records one edit against \p accountKey, by giving the model a thread tagged
+/// for that account and sending a tag change over it. This is the route a real
+/// edit takes, so m_editedAccounts is populated the way production populates it.
+void recordEditForAccount(MainWindow &window, const QString &threadId,
+ const QString &accountKey)
+{
+ auto *model = window.findChild<ThreadListModel *>();
+ QVERIFY(model);
+ ThreadSummary thread;
+ thread.threadId = threadId;
+ thread.subject = QStringLiteral("Subject");
+ thread.totalCount = 1;
+ thread.firstMessageId = threadId + QStringLiteral("-m1");
+ thread.tags = { QStringLiteral("account-") + accountKey,
+ QStringLiteral("inbox") };
+ model->appendBatch({ thread });
+ QCOMPARE(model->accountKeysForThread(threadId), QStringList{ accountKey });
+
+ QVERIFY(QMetaObject::invokeMethod(
+ &window, "sendThreadTagChange",
+ Q_ARG(QStringList, QStringList{ threadId }),
+ Q_ARG(QStringList, QStringList{ QStringLiteral("flagged") }),
+ Q_ARG(QStringList, QStringList{}),
+ Q_ARG(QString, QStringLiteral("Flag"))));
+}
+
+/// QVERIFY2 expands to a bare `return`, so the invoke check cannot live in a
+/// value-returning helper. Out-param instead, called through the macro below.
+void readSyncChannels(MainWindow &window, QStringList &channels)
+{
+ QVERIFY2(QMetaObject::invokeMethod(&window, "pendingSyncChannels",
+ Q_RETURN_ARG(QStringList, channels)),
+ "pendingSyncChannels() could not be invoked");
+}
+
+#define SYNC_CHANNELS(window, out) \
+ QStringList out; \
+ readSyncChannels((window), (out))
+
+} // namespace
+
+void TestMainWindow::syncNarrowsToTheSelectedAccount()
+{
+ // The user's decision on item 101: one Sync action, steered by the account
+ // dropdown. Selecting an account and pressing Sync collects that account
+ // only, rather than every channel.
+ QTemporaryDir dir;
+ QVERIFY(dir.isValid());
+ const QString logPath = dir.filePath(QStringLiteral("mailsync.log"));
+ writeSyncLog(logPath, QStringLiteral("OK"));
+
+ Config config;
+ loadTwoAccountConfig(config, dir, logPath);
+ MainWindow window(config);
+
+ // The guard, and it matters: an empty list is what a FULL sync looks like,
+ // so a test starting from a narrowed state could not tell the fix from a
+ // window that had never widened. Nothing is selected and nothing is edited,
+ // so this must be the full-fetch signal before the gesture.
+ SYNC_CHANNELS(window, before);
+ QVERIFY2(before.isEmpty(),
+ "a fresh window with All accounts selected did not ask for a "
+ "full fetch");
+
+ window.selectAccountForTesting(QStringLiteral("work"));
+
+ SYNC_CHANNELS(window, narrowed);
+ QCOMPARE(narrowed, QStringList{ QStringLiteral("work-channel") });
+}
+
+void TestMainWindow::syncCoversEveryAccountWhenAllIsSelected()
+{
+ // The other half of the same rule, and the one that keeps item 49's
+ // property: All accounts means a full fetch, which mailsync.sh turns into
+ // mbsync -a. Asserted after a narrowing so it proves the selection is read
+ // each time rather than latched once.
+ QTemporaryDir dir;
+ QVERIFY(dir.isValid());
+ const QString logPath = dir.filePath(QStringLiteral("mailsync.log"));
+ writeSyncLog(logPath, QStringLiteral("OK"));
+
+ Config config;
+ loadTwoAccountConfig(config, dir, logPath);
+ MainWindow window(config);
+
+ window.selectAccountForTesting(QStringLiteral("work"));
+ SYNC_CHANNELS(window, narrowed);
+ QCOMPARE(narrowed, QStringList{ QStringLiteral("work-channel") });
+
+ window.selectAccountForTesting(QString());
+
+ SYNC_CHANNELS(window, widened);
+ QVERIFY2(widened.isEmpty(),
+ "going back to All accounts left the run narrowed");
+}
+
+void TestMainWindow::aNarrowedSyncStillCarriesAnEditFromAnotherAccount()
+{
+ // Item 101's standing constraint, and the reason the selection is a UNION
+ // with the pending set rather than a replacement for it. Looking at one
+ // account while having edited another is ordinary, and a run that dropped
+ // the other account's channel would strand that edit with nothing on
+ // screen to say so.
+ QTemporaryDir dir;
+ QVERIFY(dir.isValid());
+ const QString logPath = dir.filePath(QStringLiteral("mailsync.log"));
+ writeSyncLog(logPath, QStringLiteral("OK"));
+
+ Config config;
+ loadTwoAccountConfig(config, dir, logPath);
+ MainWindow window(config);
+
+ // Edit in personal, then go and look at work.
+ recordEditForAccount(window, QStringLiteral("t1"),
+ QStringLiteral("personal"));
+ SYNC_CHANNELS(window, edited);
+ QCOMPARE(edited, QStringList{ QStringLiteral("personal-channel") });
+
+ window.selectAccountForTesting(QStringLiteral("work"));
+
+ // Both: work because it is on screen, personal because it owes a write.
+ const QStringList expected{ QStringLiteral("personal-channel"),
+ QStringLiteral("work-channel") };
+ SYNC_CHANNELS(window, both);
+ QCOMPARE(both, expected);
+}
+
+void TestMainWindow::theStatusLineNamesWhatASyncWillCover()
+{
+ // Item 101's visibility half. The run was already account-aware before this
+ // item and the user could not tell, since a narrowed run and a full one
+ // showed the same three dots. Asserted on the generated STRING, which has a
+ // right answer, rather than on the label after a real run, which would be a
+ // test of QProcess timing.
+ QTemporaryDir dir;
+ QVERIFY(dir.isValid());
+ const QString logPath = dir.filePath(QStringLiteral("mailsync.log"));
+ writeSyncLog(logPath, QStringLiteral("OK"));
+
+ Config config;
+ loadTwoAccountConfig(config, dir, logPath);
+ MainWindow window(config);
+
+ QString text;
+
+ // Empty is the full-fetch signal, so it must not read as a channel list.
+ QVERIFY(QMetaObject::invokeMethod(&window, "syncStartedText",
+ Q_RETURN_ARG(QString, text),
+ Q_ARG(QStringList, QStringList{})));
+ QCOMPARE(text, QStringLiteral("Syncing all accounts..."));
+
+ QVERIFY(QMetaObject::invokeMethod(
+ &window, "syncStartedText", Q_RETURN_ARG(QString, text),
+ Q_ARG(QStringList, QStringList{ QStringLiteral("work-channel") })));
+ QCOMPARE(text, QStringLiteral("Syncing work-channel..."));
+
+ const QStringList two{ QStringLiteral("personal-channel"),
+ QStringLiteral("work-channel") };
+ QVERIFY(QMetaObject::invokeMethod(&window, "syncStartedText",
+ Q_RETURN_ARG(QString, text),
+ Q_ARG(QStringList, two)));
+ QCOMPARE(text,
+ QStringLiteral("Syncing personal-channel, work-channel..."));
+}
+
// Items 56 and 57.
void TestMainWindow::everyActionIsReachableFromAMenu()
diff --git a/translations/qtmaildir_it_IT.ts b/translations/qtmaildir_it_IT.ts
index 4daea21..c0e336d 100644
--- a/translations/qtmaildir_it_IT.ts
+++ b/translations/qtmaildir_it_IT.ts
@@ -739,6 +739,14 @@ Il messaggio È stato inviato. Non inviarlo di nuovo.</translation>
<translation>&amp;Elimina conversazione</translation>
</message>
<message>
+ <source>Syncing all accounts...</source>
+ <translation>Sincronizzazione di tutti gli account...</translation>
+ </message>
+ <message>
+ <source>Syncing %1...</source>
+ <translation>Sincronizzazione di %1...</translation>
+ </message>
+ <message>
<source>Undelete thread</source>
<translation>Ripristina conversazione</translation>
</message>
@@ -1409,10 +1417,6 @@ Il messaggio È stato inviato. Non inviarlo di nuovo.</translation>
<source>%1, waiting for the running sync to finish</source>
<translation>%1, in attesa che termini la sincronizzazione in corso</translation>
</message>
- <message>
- <source>Syncing...</source>
- <translation>Sincronizzazione in corso...</translation>
- </message>
<message numerus="yes">
<source>%n unsynced change(s)</source>
<translation>