aboutsummaryrefslogtreecommitdiffstats
path: root/docs
AgeCommit message (Collapse)AuthorFilesLines
26 hoursdocs: fold deferred findings into the task 13 checklistDanilo M.1-5/+23
Adds two checks the earlier tasks turned up but only recorded in passing: - Item 21: config.cpp validates the sync command by splitting on a space, while MailSync splits it with QProcess::splitCommand. A quoted program path containing a space makes the two disagree. The failure is safe (sync disables itself) but the message misleads, so the check is to confirm the behaviour and then pick a side. - Item 22: deleting the sync script while running exercises the launch failure path, which config's load-time validation cannot cover. Also corrects two claims the plan had outgrown: task 13 is no longer the notmuch layer's only coverage now that task 8 tests against a throwaway database, and cron runs every 10 minutes rather than hourly. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
26 hoursfeat: add MailSync process wrapperDanilo M.2-6/+6
Runs the configured sync script through QProcess, merging stdout and stderr into one log so a failing mbsync run has something to show. The script is never run through a shell: the command is a config value, and splitCommand keeps its arguments literal. Two fixes against the drafted version: - start() no longer calls waitForStarted(). It blocked the UI thread for up to five seconds, which contradicts the spec's requirement that the UI stay usable during sync, and it swallowed launch failures into a bare false return. A missing script now surfaces asynchronously through errorOccurred as finished(false, -1) with an explanatory log line, so the spinner cannot hang with nothing to explain it. - Removed a double-emit guard I had added on the assumption that QProcess follows errorOccurred(FailedToStart) with finished(). Verified it does not: FailedToStart is emitted instead of finished, never before it. The guard was dead state and the comment justifying it was wrong. Also corrects the sync interval throughout: the user's cron runs every 10 minutes, not hourly. The shorter interval strengthens the flock rationale rather than weakening it, since collisions with a manual sync are that much more likely. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
41 hoursdocs: unit-test NotmuchWorker against a throwaway databaseDanilo M.2-6/+22
The spec deferred NotmuchWorker to manual verification on the grounds that testing it needs a real notmuch database. Building a fake one in a temporary directory answers that objection, so Task 8 now gets real coverage. This is the only code in the project that writes to a notmuch index, so a bug there corrupts real mail state. It was the wrong place to have the weakest verification. Task 13's manual checklist stays, as confirmation against real data rather than as the only coverage. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
42 hoursdocs: scope the document-load exemption to an exact URLDanilo M.2-3/+31
The interceptor previously trusted the whole qtmaildir: scheme, so a hostile message body referencing qtmaildir://anything would have been allowed. That made the interceptor's correctness depend on the scheme handler in a different, later task. It now trusts only the exact base URL passed to setHtml() and fails closed when none is set. Records the resulting obligation on MessageView to call setDocumentUrl() with that same URL. Also corrects the attachment path guard to a separator-aware comparison and notes that it is unreachable defence-in-depth: safeFilename() runs first, so no caller-supplied name can reach it. A test driving saveTo() expecting refusal cannot pass; test safeFilename() instead. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
42 hoursfix: make attachment path-containment guard separator-awareDanilo M.1-5/+20
Attachment::saveTo()'s escape guard compared paths with a bare QString::startsWith(), which is not a path-boundary test: "/tmp/safe-evil" textually starts with "/tmp/safe", so a sibling directory whose name merely extends the target's name would incorrectly pass as contained within it. Extract the check into Attachment::isPathInsideDirectory(), comparing QDir::cleanPath()'d absolute paths and requiring an exact match or a prefix ending at a '/' boundary. Not exploitable today since safeFilename() always reduces the name to a bare basename before saveTo() builds the target, so the guard is unreachable via saveTo()'s public interface; comments on both now say so plainly instead of implying it is currently load-bearing. Add pathInsideDirectoryRejectsSiblingPrefix, testing the guard directly (independent of safeFilename(), which would mask a broken guard by never producing an escaping path), and safeFilenameStripsPathComponents, testing the sanitiser that actually stops traversal today.
42 hoursfeat: add Config with account, query, and sync parsingDanilo M.2-8/+8
Accounts use [account.work] rather than [account/work]: QSettings' INI backend treats "/" as its own hierarchical group separator, so a literal slash in a section header parses as a nested group and trips QSettings::FormatError, silently breaking childGroups() enumeration. A dot carries no such meaning and keeps the format flat. Saved-query order is alphabetical (QSettings::childKeys() sorts), not file order; documented in code and tests rather than left to a false assumption.
42 hoursdocs: fold full thread rendering and batch tagging into spec and planDanilo M.2-89/+541
Both were narrowed in the first draft and are now in scope. Newsletter threads make whole-thread rendering fundamental, and bulk archive is the main thing a GUI buys over neomutt. Two consequences fell out of thread rendering and are recorded in both documents. The thread renders as one document in one web view rather than one view per message, since a view each would spawn a Chromium render process each. Sharing a document makes cid: references collide when two messages use the same Content-ID, which is common across newsletters from one sender, so every reference is namespaced to cid:<prefix>!<id>. Deciding which messages render expanded needs the query, so loadThread now intersects it with the thread and MessageRef carries a matched flag. Batch tagging resolves thread ids to message ids on the worker in one combined query. Undo stores thread ids and re-resolves, staying correct after the selection moves. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
43 hoursdocs: add qtmaildir v1 implementation planDanilo M.1-0/+3988
Fourteen TDD tasks building from pure-logic units outward: keymap, config, MIME parsing, and the request interceptor are fully tested first, then the notmuch layer, then the UI that wires them together. Seven test binaries, up from the three the spec named; config, htmlbuilder, thread model, and sync each earned one. NotmuchWorker stays untested by design, with Task 13 as the compensating manual checklist against a real database. Two narrowings from the spec are recorded rather than silently dropped: thread rendering shows the newest message instead of a flat list of all messages, and tagging applies to the loaded thread rather than every selected one. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
43 hoursdocs: add qtmaildir v1 design specDanilo M.1-0/+430
Qt6/notmuch mail client, GUI counterpart to neomutt. Read and organize only in v1; compose and send deferred to v2. Key decisions and their reasoning are recorded in the spec: C++/Qt6 over PySide6, QtWebEngine over QTextDocument for HTML mail, GMime for parsing, direct libnotmuch rather than shelling out to mailctl, and calling the existing sync script rather than reimplementing it so qtmaildir stays inside the flock guard shared with the hourly cron sync. Dependency availability verified on the development machine: Qt6 6.11.1 (WebEngine included in the monolithic package), libnotmuch 0.39, GMime 3.2.15. Notably notmuch installs no notmuch.pc, so CMake must locate it without pkg-config. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>