| Age | Commit message (Collapse) | Author | Files | Lines |
|
For --message (item 200), which knows an id and needs the conversation:
opening a message means opening its thread with that message selected, never
an id: query showing one card out of a conversation (item 91).
The id is quoted before it reaches notmuch, unlike every other id in this
class. Those came out of notmuch; this one comes off another program's command
line, and notmuch parses garbage happily while matching nothing, so an id
carrying query syntax would be read AS syntax with no error anywhere. The
quoting is local rather than SearchTerm::quote(): that escapes an embedded
quote with a backslash, which notmuch does not honour inside a phrase, so the
quote closed the phrase and the injected query matched a real thread (the new
test failed against it). notmuch escapes a quote by doubling it.
threadIdForTesting() keeps its name and gains a shared helper rather than being
promoted: it is documented as not being a slot, and the new entry point has to
answer asynchronously.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
A QLocalServer under the state directory. The first launch listens; a later one
connects, hands over its selectors and exits.
Connect-first ordering, and on Qt 6.11 the probe is the ONLY guard for a live
instance: with UserAccessOption, listen() binds in a private directory and
renames the socket onto the path, which replaces whatever is there, a stale
file and a live socket alike. Measured: with the probe disabled, a second
launch takes the first one's socket and both handover tests fail. The same
rename is what reclaims a stale file after a crash; the removeServer() retry
on AddressInUse is kept for a listen that binds in place.
The server reads each connection asynchronously and parses on disconnect,
rather than blocking in waitForReadyRead() on the UI thread. The client's one
write followed by a disconnect is what marks the payload complete, a reader
past the payload cap is aborted, and a connection that never hangs up is
dropped after two seconds. A connection that writes nothing at all is the other
launch's probe and is ignored without a warning.
A socket that cannot be created does NOT stop the window opening. A read-only
state directory costs single-instance behaviour, which is a degradation; it
must not cost the user their mail client.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
--account, --thread and --message, plus the payload that crosses the socket.
A value type with no GUI dependency: it is parsed before QApplication exists
and both halves need tests no window has to be built for.
QDataStream rather than a line-based payload, because a Message-ID may contain
a newline. Every read is status-checked, which is what catches a truncated
payload: a short read otherwise leaves the fields default-constructed and a
half-written id would be applied as an empty one.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
A unix domain socket between two copies of this program, for item 200. Not
network protocol work: the rule in AGENTS.md is about IMAP and SMTP.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
aSuccessfulCronSyncDrainsTheEditedAccounts named a log but no status
file, so Config fell back to ~/.local/state/qtmaildir/syncstatus.json
and the test passed or failed on the developer's last cron run.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
Escape was scoped to the pane and only cancelled an edit, so once an
event was selected nothing closed the pane again. It is a window shortcut
now, labelled Close event: with no edit open it clears the selection and
the pane hides. With an edit open it cancels only when focus is inside
the pane, so a stray Escape after clicking the grid cannot discard the
form.
That guard needed the month grid to take focus on a click, which it never
did; the view-scoped PgUp/PgDn, Delete and Ctrl+Z were likewise reachable
only by Tab until now.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
The pane reserved a quarter of the window even when empty. It now shows
only on the details or edit page, so the grid takes the full width until
an event is selected or a new one is started.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
The month grid drew each cell's outline with drawRect while the brush was
still set from the previous cell's all-day chip or today pill, so every
day after one carrying an event was filled with that event's colour.
Reset the brush before the outline.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
|
|
|
|
Give RepeatRule its own Q_DECLARE_TR_FUNCTIONS context so lupdate extracts
describe() and the ordinal words, mirroring the array's QT_TRANSLATE_NOOP;
without it lupdate warned and RepeatRule/last was never extractable.
|
|
|
|
save be retried
|
|
|
|
pane
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Sixteen TDD tasks from the libical build wiring to the hand-test
hand-off. Four rulings against the spec are recorded at the top:
calendars_dir absent means off, preserved properties are compared by
value rather than bytes, views read CalendarItem, and expansion runs
from DTSTART without icalrecur_iterator_set_start.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
Its section moves to the closed-items file with the execution record,
and the status row points there. The reconcile against the user's notes
found every open note line already covered by an existing item.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
Month and agenda views over the vdirsyncer vdir, editing in the side
pane, atomic stale-checked writes with a debounced sync and a post-sync
comparison, and undo for create, edit and delete. Names and colours come
from the vdir's own metadata files; qtmaildir depends on neither khal
nor its config.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
A vCard FN is untrusted and ContactStore faithfully decodes `\n` to a real
newline, so `Evil\nBcc: x` was inserted raw into a recipient field and flowed
through splitRecipients() to MessageBuilder. contactInsertionText() only quoted
a name carrying a comma or a double quote, so every other RFC 5322 special
(<, >, ;, @) and any control character reached the header unguarded.
The name now has control characters and whitespace runs replaced by single
spaces, and every non-empty name is quoted, with `\` escaped before `"`.
Quoting contains all the specials in one step. The parser is left faithful;
this is fixed at the consumer/trust boundary.
Tests: a name with a decoded newline inserts no control character and yields
one recipient; a name with <, >, ;, @ is quoted and yields one recipient. The
three tests that expected an unquoted plain name now expect the quoted form.
|
|
Translate the new config warning for contacts_dir, add the README
subsection with the vdirsyncer path and the Akonadi warning, correct
the query bar's from:/to: entry which had become false, and add the
[Unreleased] changelog entry.
|
|
MainWindow reads contactsDir() once while building its UI, holds the
result in m_contacts, and hands it to the query bar's completer right
after that is constructed and to every ComposeWindow as it opens. An
empty contactsDir() skips the call, so a machine with no address book
pays nothing and warns about nothing.
The directory is not watched: a restart picks up a vdirsyncer update,
and a QFileSystemWatcher would be a live-index feature nobody asked for.
No action is added, so none of the five places in "Adding an action is
FIVE places" applies: there is no name in KeyMap::knownActions(), no
default binding, no icon-table entry and no menu entry to add.
|
|
from: and to: now offer the vCard store's addresses, each quoted via
SearchTerm::quote() with the contact's name as the description. The store
is the enumerator libnotmuch does not expose, which is what the old
complete-nothing comment said was missing; it keeps that role for folder:,
subject:, attachment:, thread: and id:.
With no store configured the branch returns {} and the behaviour is
unchanged.
|
|
One shared QCompleter serves To, Cc and Bcc, attached with setWidget and
never setCompleter, which resets the prefix to the whole field and stops
matching after the first comma. The prefix is the comma-delimited token
under the cursor, set by hand from textEdited; accepting replaces only
that token and leaves the rest of the field alone.
Candidates match the name and the address case-insensitively. A display
name containing a comma is quoted on insertion, and splitRecipients() is
now quote-aware so the quoted name survives as one recipient.
Contacts reach the composer through setContacts() rather than a fourth
constructor argument, so every existing three-argument construction and
test stays as it was. An empty list leaves the fields behaving exactly
as before completion existed.
|
|
Adds Config::contactsDir(), the [general] key Task 5 will read to locate
the ContactStore. Empty means the feature is off.
The key is read WITHOUT the general/ prefix, like notmuch_config, because
QSettings' INI backend strips a section literally named [general]. Absent
or empty is silent; a set path that does not exist is reported through
addProblem(). A leading ~ is expanded by a local helper, since config.cpp
expands no other path and this is the first one to need it.
|
|
Reads a vdirsyncer contacts directory of vCard 3.0 files into a
QList<Contact> for the completion work that follows. Pure over values,
no widget and no QCompleter, so the parse is testable without a window.
unfold() joins folded lines before any field is looked at; parseCard()
splits property from value on the first colon outside a quoted
parameter, unescapes FN, and yields one contact per EMAIL line;
loadDirectory() walks recursively, skips unreadable or addressless
cards, de-duplicates on the address case-insensitively, and sorts by
name then address. N, PHOTO, ADR and TEL are deliberately not used.
23 new tests. No user-facing strings, so no tr() change.
|
|
Item 204, six tasks, no spec: the shape was settled with the user on
2026-09-18 rather than brainstormed, so the plan carries the four decisions
itself (the config key, loading once, matching on name and address, and the
four vCard fields that matter).
No new dependency. libical is installed but its vCard parser is 4.0 and this
machine has 3.0.20, and libicalvcal is the old vCalendar converter rather
than a vCard reader, so four fields of a hand parse is proportionate.
The measurements are in the plan because two of them change what a correct
implementation looks like. Of 117 cards in the real store only 14 carry an
address, so a short candidate list is right rather than broken, and a fixture
where every card completes would not be representative. The store is
unfolded, so the folded-card fixture is the one the real data could never
have caught.
The largest trap is one this repository has already paid for twice:
QLineEdit::setCompleter() is unusable for a field holding a list, and a test
written with setText() passes against that bug because setText does not drive
a completer at all. The plan requires typed keys and a mutation check on the
second-recipient case.
The query bar half comes with a correction: querycompleter.cpp:652-656 says
addresses need an enumerator libnotmuch does not expose, which stops being
true for from: and to: the moment the store exists.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
|
The 2026-09-18 pass over the user's notes. One new defect, one item split
into five, and one stale blocker cleared.
Item 203 is new: marking a message as spam crashed the application, and the
user can replicate it. The cause is NOT verified and the entry says so. The
path was read without finding a null dereference, and the account the note
names now carries a spam key, so the observation may predate 0.29.0's config
change. It needs a reproduction with the terminal output or a backtrace
before it can be worked.
Item 72 held four different features in one line of the notes, which is why
it sat unplannable for six weeks. The user named what they want, so it splits
into 204 (recipient completion, in the composer and the query bar), 205
(editing contacts), 206 (a calendar window: its own top-level window and
libical, both settled by the user) and 207 (invitations, blocked on 206).
Item 208 came out of the same conversation: an "add to contacts" gesture,
which writes a vcard and so lands on 205's writer rather than 204's reader.
A sender's card in the message pane was offered and declined; the notes'
edge-tts vocal reminders are their own project, since nothing here runs on a
timer. 72 stays as the index entry the notes' single line maps to.
The data those items need is on disk and was verified rather than assumed:
117 vcards and 312 events under the vdirsyncer paths, libical 3.0.20 with
headers and a .pc file. Two traps are recorded because they cost a wrong
answer each: the contacts vdir is not the obvious path, which belongs to
Akonadi, and libical does not parse vCard at all (that is 4.0; libicalvcal
is the old vCalendar converter), so contacts are a hand parse.
Item 123 (send) was still marked "specified" although it shipped, so five
items read as blocked on it. Both corrected.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
|
187 (Spam view), 190 (Mark spam on the bar + icon), 195 (Mark spam leaves
unread), 197 (no way to say not spam), 201 (un-spam from the Spam view) and
202 (spam kept inbox). Sections moved to the closed file and the status rows
mark them done.
|