aboutsummaryrefslogtreecommitdiffstats
AgeCommit message (Collapse)AuthorFilesLines
2 daysdocs: document the launch selectors and translate themDanilo M.3-0/+129
Add an Usage section to the README covering --account, --thread and --message, the hex-only thread id, Qt's own options, the exit status 2 on a bad option, and the single-instance hand-off over the local socket. Add the Unreleased changelog entries for the selectors, the second-launch behaviour and the plain-text status bar. Translate the new strings into Italian. The option names stay untranslated in the help text: they are wire format, and a translated option name is an option the user cannot type. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2 daysfeat: accept the launch selectors on the command lineDanilo M.1-12/+69
Connect first, become the server only if that fails. A live instance is handed the selectors and this process exits without ever opening a database, so two launches no longer mean two notmuch handles. When no instance answers and no socket can be created either, the window still opens: losing single-instance behaviour is a degradation, losing the mail client is not. --version and --help stay hand-checked before QApplication exists: Qt's own versions exit through QCoreApplication, and --version has to work where the GUI cannot open. The selectors themselves are parsed after QApplication, from app.arguments(), because the constructor strips Qt's standard options such as -platform and -style; parsing the raw argv would reject them as unknown. An unknown option prints the error on stderr and exits with status 2 before any window is shown. The socket's parent directory is created first, since the state directory does not exist on a first run and listen() cannot create a socket inside a missing directory. Raising under Wayland is a request rather than a command. The compositor may honour it as a focus hint or ignore it by policy; the selectors apply either way, which is the half that has to work. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2 daysfix: render the status bar as plain textDanilo M.2-0/+5
A launch selector that matches nothing is echoed into the status label so the user can see which value missed. That value comes from another program's command line or from the single-instance socket, and a QLabel left at Qt::AutoText guesses whether a string is markup, so a selector shaped like HTML would have been rendered rather than shown. The format is now stated rather than guessed, the same reasoning AGENTS.md records for MessageDetailsDialog. No existing status message carries markup. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2 daysfeat: apply the launch selectors to the windowDanilo M.4-2/+386
One entry point, called both at startup and by the socket handler when a later launch arrives. Two paths would drift, which is the lesson this file has already learned from every other pair. The account moves first, because a built-in filter composes with the dropdown and a query run before it would carry the old scope. The thread case reuses recoverStaleThread() whole, as item 91's double-click already does. A message id resolves to its thread first through the worker: opening a message means opening its conversation with that message selected. A thread id must be hex or it is treated as a miss and never reaches notmuch. recoverStaleThread() builds thread:<id> unquoted, which is safe for ids notmuch handed out and not for ones from another program's command line, where "0000 or tag:inbox" would widen the query to the whole inbox. The check uses anchoredPattern() because a PCRE $ also matches before a trailing newline. An empty selector set changes nothing. A bare launch against a running window means raise yourself, and a raise is not a navigation. Also drops an empty public: label left in notmuchworker.h by the previous commit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2 daysfeat: resolve a Message-ID to its thread idDanilo M.3-0/+111
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>
2 daysfeat: add the single-instance socketDanilo M.5-0/+413
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>
2 daysfeat: parse the launch selectors as a value typeDanilo M.5-0/+446
--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>
2 daysbuild: link Qt6::Network for the single-instance socketDanilo M.2-2/+8
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>
2 daysrelease: 0.30.0v0.30.0Danilo M.2-1/+7
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2 daystest: keep the cron-drain test off the real sync statusDanilo M.1-2/+6
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>
2 daysdocs: close item 206, the calendar windowcalendarDanilo M.2-37/+37
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
7 daysfeat: close the calendar's event pane with EscapeDanilo M.6-9/+47
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>
7 daysfeat: hide the calendar's event pane while nothing is selectedDanilo M.1-0/+4
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>
7 daysfix: stop a chip's colour flooding the next calendar day cellDanilo M.1-0/+3
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>
7 daysfix: drop orphaned overrides when a series stops, plus review minorsDanilo M.10-2/+162
7 daysdocs: the calendar entry stays when it is unconfigured, and it says soDanilo M.4-8/+15
7 daysdocs: document the calendar, translate it, file its follow-upsDanilo M.9-23/+765
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.
7 daysfeat: open the calendar from the View menu and the toolbarDanilo M.4-0/+87
7 daysfix: add the toolbar New button, sync the month controls, and let a stale ↵Danilo M.3-4/+81
save be retried
7 daysfeat: the calendar window, with undo and a stale-safe saveDanilo M.5-0/+1089
7 daysfix: show the collection swatch and translate the attendee line in the event ↵Danilo M.2-3/+16
pane
7 daysfeat: show and edit a calendar event in the side paneDanilo M.3-0/+621
7 daysfix: swallow the calendar more-line double-click and clear the agenda selectionDanilo M.2-2/+14
7 daysfeat: paint the calendar's month grid and agenda listDanilo M.5-0/+387
7 daysfeat: lay out the calendar's month grid without a painterDanilo M.5-0/+221
7 daystest: a write during a run queues one more calendar syncDanilo M.2-4/+39
7 daysfeat: debounce the calendar sync command after writesDanilo M.5-0/+221
7 daystest: pin the calendar config's reported error pathsDanilo M.1-0/+50
7 daysfeat: read the calendar's config keysDanilo M.3-0/+102
7 daysfeat: write calendar files atomically, refusing stale onesDanilo M.5-0/+233
7 daystest: an occurrence delete keeps the exdates already thereDanilo M.1-0/+19
7 daysfeat: edit or delete one occurrence of a repeating eventDanilo M.2-3/+122
7 daysfix: compare sameMeaning overrides by recurrence id and check the uidDanilo M.2-2/+50
7 daysfeat: edit a calendar event in place and write new onesDanilo M.2-3/+412
7 daysfeat: an event is editable only when the user organises itDanilo M.2-1/+41
7 daysfix: expand a long dense series completely and flag overrides' own all-dayDanilo M.2-4/+39
7 daysfeat: expand calendar occurrences in each event's own zoneDanilo M.2-2/+249
7 daysfix: an empty RRULE means does-not-repeat, not a custom ruleDanilo M.2-0/+16
7 daysfeat: model the calendar's repeat control as RRULE textDanilo M.2-4/+361
7 daysfeat: read every time form and load a calendar vdirDanilo M.2-1/+195
7 daysfeat: parse one calendar event with libical (item 206)Danilo M.11-1/+630
7 daysdocs: plan the calendar window (item 206)Danilo M.1-0/+5468
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>
7 daysdocs: close backlog item 204, contact completion shippedDanilo M.2-82/+100
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>
7 daysdocs: specify the calendar window (item 206)Danilo M.1-0/+372
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>
13 daysfix: sanitise untrusted contact names on insertionDanilo M.2-23/+100
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.
13 daysdocs: document contact completion and refresh Italian translationDanilo M.3-3/+47
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.
13 daysfeat: load the contact store once and feed both consumersDanilo M.2-0/+30
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.
13 daysfeat: complete contacts in the query barDanilo M.3-5/+153
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.
13 daysfeat: complete contacts in the composer recipientsDanilo M.3-5/+515
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.
13 daysfeat: add the contacts_dir config keyDanilo M.3-0/+150
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.