| Age | Commit message (Collapse) | Author | Files | Lines |
|
--message naming the first message of a multi-message conversation
selected the conversation row, so the pane showed the dashboard rather
than the message. The recovery matched the conversation row on its own
message id, which is the root's. The rule is that --message always
targets the message, shown inside its expanded thread, and that
--thread is the way to ask for the conversation.
Since item 177 setThreadMessages keeps a conversation's first message as
child 0, so the root already has a row of its own. applyPendingRecovery()
now lets the thread row answer for a named message only when that row is
not a conversation, which leaves a thread of one opening on its message
as before, and finds the root among the children like any reply. The
provisional pass still selects the thread while the tree loads and is
refined to the root's row when the children arrive.
The same recovery serves the stale notice and a double-click. A stale
notice raised while reading the root's row now reopens that row, which
is the same request. A double-click on a conversation row asks for the
conversation, so it no longer names the root and still lands on the
dashboard. recoveryOnTheFirstMessageSelectsTheThreadRow asserted the old
behaviour on the pre-177 premise that the root has no child row; it is
retargeted as recoveryOnTheFirstMessageSelectsItsOwnRow.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
A --message launch handed to a window already in use switched the list
to the conversation and made the reply row current, and the message
pane stayed empty. The same launch into a fresh window worked.
selectRowAt() selected the row and then called the view's
setCurrentIndex(). QAbstractItemView::setCurrentIndex() asks
selectionCommand() what to do with the selection, and with no event to
read it answers from QGuiApplication::keyboardModifiers(), which is the
modifier state of the last input event the application received rather
than anything the user is doing now. With Control in that state the
command is a Toggle: the row selected a line earlier was deselected
again, onThreadSelected() refused a current row that is not selected,
and nothing was loaded. The provisional thread row the recovery selects
first went the same way, so the pane never left the placeholder. A
fresh process has received no input at all, which is why only a window
in use was affected.
The current index is now moved through the selection model with
NoUpdate, so the selection stays exactly what select() made it. Every
caller of selectRowAt() shared the defect (the stale-thread recovery,
double-click, the dashboard's message entries and the launch selectors)
and every one of them means "select exactly this row", so all of them
take the fix.
The test puts a window into use, leaves Control as the last modifier
the application saw, launches --message for a reply in another thread,
and asserts the reply is current, selected, and rendered in the pane.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
Completes the options, and the account keys after --account, read from the
[account.<key>] sections of the config the application itself reads. Thread
and message ids are deliberately not completed: offering them means querying
the whole notmuch index on every Tab, which on a real Maildir is tens of
thousands of ids nobody would pick from a list anyway.
Installed under share/bash-completion/completions, where bash-completion
loads a command's file on demand by name.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
recoverStaleThread() runs thread:<id> in the account dropdown's scope, so a
--thread or --message for a conversation in another account than the one
the window was left on came back with no rows, and the list went blank with
nothing said.
A thread or message selector now switches the dropdown to All accounts
first, unless the launch named an account with --account, in which case
that scope is kept. For a message the switch waits until the id has
resolved, so a message that does not exist leaves the dropdown alone.
When the selector's own thread:<id> query returns no rows, the miss is
named in the status bar, reusing the existing "No thread matched" and "No
message matched" strings, and the view that was on screen when the launch
arrived comes back: the dropdown, the bar's text and the list, re-run in
the scope it was built in. The notice waits for that query to land, since
the query writes its row count to the bar and would cover it. A refused
thread id and an unresolved message restore the dropdown the same way, and
re-run nothing, since the list never changed.
The judgement is keyed to a flag the selector paths set after the query
and runQuery() clears, so the stale-thread notice and double-click, which
share recoverStaleThread(), behave as before.
README and CHANGELOG describe the selectors as they now behave: --account
alone opens its startup view, the other two look in every account unless
narrowed, brackets are accepted, and a miss keeps the view the user had.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
The account dropdown deliberately does not re-run the query: by hand the
user picks a filter next. A launch from another program has no next click,
so `--account work` on its own moved the dropdown and changed nothing the
user could see.
When the account selector applies and no thread or message was given, the
startup view now runs again in the new account. The constructor's startup
path moves into runStartupView() and both callers use it, so the view is
resolved the same way in each: a generated filter is asked for the
account's own query rather than having its all-accounts query wrapped in
the account's path.
The test starts on the trash view because its per-account query is the
account's own trash path; for a tag filter the generated and the wrapped
queries are the same string, and a test on one passed against the wrap.
It asserts on the generated string and on the rows, and fails with the
wrap put back.
anEmptySelectorSetChangesNothing also asserts that no query ran, which the
bar and the dropdown alone cannot show.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
A Message-ID is written <local@domain> in every header a caller copies it
from, and notmuch stores it without the angle brackets, so
`--message '<abc@example.org>'`, the form the README itself shows, matched
nothing and was reported as a miss. applySelectors() now trims the value
and strips one enclosing pair of brackets before anything else sees it, so
the worker's resolve and the recovery's selection target both use the bare
id. Stripping for the resolve alone would open the right thread and then
fail to select the message inside it, which is why the test targets a reply
rather than a thread root.
No refusal of a slashed id was added: notmuch 0.39 reads `/.../` as a regex
for mid: but not for id:, measured as 0 matches for id:"/.*/" on an index
where mid:"/.*/" matches every message.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
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>
|
|
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>
|
|
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>
|
|
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>
|
|
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>
|