Skip to content

Trash the thread you are reading with t - #349

Merged
monorkin merged 1 commit into
basecamp:mainfrom
badta5te:trash-open-thread
Sep 8, 2026
Merged

Trash the thread you are reading with t#349
monorkin merged 1 commit into
basecamp:mainfrom
badta5te:trash-open-thread

Conversation

@badta5te

@badta5te badta5te commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

The trash key only worked on a list, so a thread you had opened could not be trashed without first leaving it, and the help bar in a thread never offered the key at all. The web app trashes from an open thread.

t and T now trash the open thread and return to the list it came from. The posting is the one the thread was opened from rather than the list's selection: a thread opened out of search results or a bundle leaves the box list's cursor on a different email. A topic opened by its id carries no posting, and HEY trashes postings, so the key says so instead.

A completed removal also drops the row from the search results or the bundle the thread was opened from. Those lists were navigate-only until this key — no action could reach a row in them — so nothing had left them stale before; without it the thread would go to the Trash while its row stayed listed, ready to be reopened or trashed again.

Fixes #339


Summary by cubic

Adds t/T to trash the thread you're reading, returning to the list it came from. The key uses the posting the thread was opened from (not the list's cursor), and the thread help bar now offers it; threads opened by id carry no posting, so the key declines with a notice. Trashing also removes the thread's row from the search results, bundle, or Previously Seen list it was opened from.

Fixes #339.

Written for commit 8939779. Summary will update on new commits.

Review in cubic

Copilot AI balanced review requested due to automatic review settings August 27, 2026 19:31
@badta5te
badta5te requested a review from a team as a code owner August 27, 2026 19:31

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds t/T support for trashing an open mail thread and returning to its originating list.

Changes:

  • Tracks the posting used to open a thread.
  • Adds thread-view trash handling and help.
  • Tests list, search, direct-ID, and Previously Seen behavior.

Tip

If you aren't ready for review, convert to a draft PR.
Click "Convert to draft" or run gh pr ready --undo.
Click "Ready for review" or run gh pr ready to reengage.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
internal/tui/mail.go Adds open-thread trash behavior and posting tracking.
internal/tui/mail_test.go Tests trash actions and help visibility.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread internal/tui/mail.go Outdated
return nil
}
postingID := v.topicPostingID
cmd := v.doPostingAction("Thread moved to Trash", postingActionRemove, v.currentBoxID(), postingID, func() error {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in dabb874

The trash key only worked on a list, so a thread you had opened could not
be trashed without leaving it first, and the help bar in a thread never
offered the key at all. The web app binds t directly on the topic
toolbar, so it trashes from an open thread.

t and T now file the open thread the way a and l already do, through the
same posting action the list uses. The key acts on the posting the thread
was opened from rather than wherever the list's cursor has since landed.
Over search results, bundles and topics opened by id there is no row to
file and the key says so, which is the rule the other filing keys follow.

Trash also closes the thread, where Set Aside and Reply Later leave it
open in the box it landed in. The Trash is not a box you file out of, and
the web app returns to the list too.

Closes basecamp#339
@monorkin

monorkin commented Sep 8, 2026

Copy link
Copy Markdown
Collaborator

Thanks for the PR @badta5te !
We introduced a common function for dealing with posting actions since you opened your PR, I adjusted it to use it.

@monorkin
monorkin merged commit 97aeee1 into basecamp:main Sep 8, 2026
20 checks passed
monorkin added a commit that referenced this pull request Sep 8, 2026
A thread on screen answered to a and l. The web app's topic toolbar keeps
every filing key live while a topic is open, so u, i, d, p, b and v now
work there too.

The moves needed wiring rather than writing: postingAction has handled
all four since the list gained them, and the thread's key switch simply
never named them. It has no default the way the list's does, so anything
unnamed reaches the viewport and scrolls.

The two pickers needed more. Both read the list's selection, which is not
the thread's row once the automatic mark-seen has resorted it under the
cover, so they take the posting from actionPosting and answer to
fileablePosting's rule: a thread with no row to file gets the notice the
filing keys already give rather than a picker aimed at whatever the
cursor has landed on.

Marking unseen needed the snapshot kept in step. Opening an unseen thread
marks it seen, and the snapshot is taken before that lands, so u measured
against a stale row and said the thread it had just marked seen was
already unseen.

Left out, because the web leaves them out of an open thread too: spam,
ignore, and collections. Mark seen is left out because opening the thread
has already done it. Trash is #349.

Closes #358
monorkin added a commit that referenced this pull request Sep 8, 2026
A thread on screen answered to a, l and t. The web app's topic toolbar keeps
every filing key live while a topic is open, so u, i, d, p, b and v now
work there too.

The moves needed wiring rather than writing: postingAction has handled
all four since the list gained them, and the thread's key switch simply
never named them. It has no default the way the list's does, so anything
unnamed reaches the viewport and scrolls.

The two pickers needed more. Both read the list's selection, which is not
the thread's row once the automatic mark-seen has resorted it under the
cover, so they take the posting from actionPosting and answer to
fileablePosting's rule: a thread with no row to file gets the notice the
filing keys already give rather than a picker aimed at whatever the
cursor has landed on.

Marking unseen needed the snapshot kept in step. Opening an unseen thread
marks it seen, and the snapshot is taken before that lands, so u measured
against a stale row and said the thread it had just marked seen was
already unseen.

Left out, because the web leaves them out of an open thread too: spam,
ignore, and collections. Mark seen is left out because opening the thread
has already done it. Trash landed separately in #349.

Closes #358
monorkin added a commit that referenced this pull request Sep 8, 2026
A thread on screen answered to a, l and t. The web app's topic toolbar keeps
every filing key live while a topic is open, so u, i, d, p, b and v now
work there too.

The moves needed wiring rather than writing: postingAction has handled
all four since the list gained them, and the thread's key switch simply
never named them. It has no default the way the list's does, so anything
unnamed reaches the viewport and scrolls.

The two pickers needed more. Both read the list's selection, which is not
the thread's row once the automatic mark-seen has resorted it under the
cover, so they take the posting from actionPosting and answer to
fileablePosting's rule: a thread with no row to file gets the notice the
filing keys already give rather than a picker aimed at whatever the
cursor has landed on.

Marking unseen needed the snapshot kept in step. Opening an unseen thread
marks it seen, and the snapshot is taken before that lands, so u measured
against a stale row and said the thread it had just marked seen was
already unseen.

Left out, because the web leaves them out of an open thread too: spam,
ignore, and collections. Mark seen is left out because opening the thread
has already done it. Trash landed separately in #349.

Closes #358
monorkin added a commit that referenced this pull request Sep 8, 2026
The filing keys refused a thread opened from a bundle, a contact's
threads or a search. That is the customer report behind the card, and
the customer is right that all three file fine on the web.

The web manages it because its toolbar reads the posting's own box —
posting.box.kind, off the row rather than off the page you came from.
HEY serves that box to every client: postings/_object.jbuilder writes
box_id on every posting, and the SDK's generated type has carried BoxId
all along. mail.Posting was dropping it on the way through, so the TUI
had nothing to ask and fell back to asking the list.

A list can draw rows from several boxes at once — a search, a label, a
collection, a contact's threads — so the list's box was the wrong thing
to ask. Filing measured "already in Paper Trail" against whatever box sat
behind the results, and the fix for that was to refuse those lists
outright rather than to ask the row.

So: carry BoxID through, take the box from the posting, and the refusals
go with it. Only a topic opened by its id has no row and nothing to file.
The same lookup fixes filing from a label or a collection list, where the
source carries no box kind at all and the already-in guard could never
have fired.

A filed row also leaves whichever list was drawn over the box list.
Nothing re-reads a search's results or a bundle's threads — both are
drawn once when they open — so a row left behind stays there offering to
file a thread that has already moved. That is the second commit of #349,
which I dropped while rebasing it as unreachable; this is what makes it
reachable.
monorkin added a commit that referenced this pull request Sep 8, 2026
The filing keys refused a thread opened from a bundle, a contact's
threads or a search. That is the customer report behind the card, and
the customer is right that all three file fine on the web.

The web manages it because its toolbar reads the posting's own box —
posting.box.kind, off the row rather than off the page you came from.
HEY serves that box to every client: postings/_object.jbuilder writes
box_id on every posting, and the SDK's generated type has carried BoxId
all along. mail.Posting was dropping it on the way through, so the TUI
had nothing to ask and fell back to asking the list.

A list can draw rows from several boxes at once — a search, a label, a
collection, a contact's threads — so the list's box was the wrong thing
to ask. Filing measured "already in Paper Trail" against whatever box sat
behind the results, and the fix for that was to refuse those lists
outright rather than to ask the row.

So: carry BoxID through, take the box from the posting, and the refusals
go with it. Only a topic opened by its id has no row and nothing to file.
The same lookup fixes filing from a label or a collection list, where the
source carries no box kind at all and the already-in guard could never
have fired.

A filed row also leaves whichever list was drawn over the box list.
Nothing re-reads a search's results or a bundle's threads — both are
drawn once when they open — so a row left behind stays there offering to
file a thread that has already moved. That is the second commit of #349,
which I dropped while rebasing it as unreachable; this is what makes it
reachable.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Inability to delete opened email.

3 participants