Skip to content

fix(gmail): render quoted and forwarded dates in Gmail's human style#873

Merged
steipete merged 1 commit into
openclaw:mainfrom
malob:feat/gmail-quote-date-format
Jun 23, 2026
Merged

fix(gmail): render quoted and forwarded dates in Gmail's human style#873
steipete merged 1 commit into
openclaw:mainfrom
malob:feat/gmail-quote-date-format

Conversation

@malob

@malob malob commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

What

Reply and forward attributions emitted the original message's raw RFC 2822 Date header verbatim — On Wed, 17 Jun 2026 00:29:51 -0500, … wrote: and Date: Wed, 17 Jun 2026 00:29:51 -0500. This reformats it to the human style Gmail uses — On Wed, Jun 17, 2026 at 12:29 AM, … wrote: — across all four render paths: reply plain, reply HTML, forward plain, forward HTML.

Why

The raw RFC 2822 date is jarring in a quoted reply/forward; Gmail (and most clients) render a human-readable attribution. This brings gog's reply/forward output in line.

Timezone behavior

Like Gmail, the timestamp is converted into the active user's timezone (Gmail renders the quoted date in your timezone, not the original sender's offset). The location is resolved via the existing mailDateLocation helper — the same one already used for the outgoing Date header — so it honors GOG_TIMEZONE / default_timezone and falls back to local. An unparseable Date is left as-is so it never breaks the block. (A parseable date's weekday is recomputed from the instant, which also corrects a wrong weekday in the source header.)

Changes

  • New formatQuoteDate(raw, loc), applied in formatQuotedMessage, formatQuotedMessageHTMLWithContent, and forwardedMessageHeaders.
  • prepareComposeReply and GmailForwardCmd.Run resolve loc via mailDateLocation.
  • formatQuoteDate and the existing formatGmailDateInLocation now share a small formatMailDateInLocation core — they differed only in the output layout, so this removes the duplication rather than adding a near-twin.

Behavior proof

Live against a real account. Redacted in both the commands and the output: the real address appears as <me>, message ids as <src> / <reply> / <reply-utc> / <fwd>, and thread ids as <thread> / <fwd-thread>. Each gmail get is piped to the relevant lines. (gmail get renders the text/plain part; the text/html attribution carries the identical date via the same formatQuoteDate and is covered by unit tests.)

# Source message — its raw RFC 2822 Date header is the input that gets reformatted:
$ ./bin/gog gmail get <src> --account <me> | grep '^date'
date	Tue, 23 Jun 2026 01:00:54 -0500

# Reply (local tz, PDT). The send returns the new message id used below:
$ ./bin/gog gmail reply <src> --account <me> --to <me> --body "Reply body for proof (default tz)." --json
{
  "from": "<me>",
  "messageId": "<reply>",
  "threadId": "<thread>"
}
# read it back — quote attribution reformatted to Gmail style:
$ ./bin/gog gmail get <reply> --account <me> | grep 'wrote:'
On Mon, Jun 22, 2026 at 11:00 PM, <me> wrote:

# Same source replied with GOG_TIMEZONE=UTC — converted, not just reformatted
# (11:00 PM Mon PDT == 6:00 AM Tue UTC):
$ GOG_TIMEZONE=UTC ./bin/gog gmail reply <src> --account <me> --to <me> --body "Reply body (UTC tz)." --json
{
  "from": "<me>",
  "messageId": "<reply-utc>",
  "threadId": "<thread>"
}
$ ./bin/gog gmail get <reply-utc> --account <me> | grep 'wrote:'
On Tue, Jun 23, 2026 at 6:00 AM, <me> wrote:

# Forward — the forwarded header block's Date is reformatted too:
$ ./bin/gog gmail forward <src> --account <me> --to <me> --note "Forward proof note." --json
{
  "from": "<me>",
  "messageId": "<fwd>",
  "threadId": "<fwd-thread>"
}
$ ./bin/gog gmail get <fwd> --account <me> | sed -n '/Forwarded message/,/^$/p'
---------- Forwarded message ---------
From: <me>
Date: Mon, Jun 22, 2026 at 11:00 PM
Subject: gog date-format proof — source
To: <me>

Source message body for the quoted-date proof.

Testing

  • Unit: formatQuoteDate (offset→tz conversion, empty, unparseable fallback); formatQuotedMessage and formatQuotedMessageHTMLWithContent assert the reformatted attribution (incl. the empty-date → "an earlier date" fallback); formatForwardedMessage/…HTML assert the reformatted Date: line.
  • Command: draft and forward tests assert the rendered attribution end-to-end, with GOG_TIMEZONE pinned for determinism.
  • make fmt-check lint docs-check clean.

Reply and forward attributions emitted the original message's raw RFC 2822
Date header verbatim, e.g. "On Wed, 17 Jun 2026 00:29:51 -0500, ... wrote:"
and "Date: Wed, 17 Jun 2026 00:29:51 -0500". Reformat it to the
human-readable style Gmail uses ("Wed, Jun 17, 2026 at 12:29 AM") across
all four render paths: reply plain, reply HTML, forward plain, forward HTML.

Like Gmail, the timestamp is converted into the user's timezone rather than
left in the sender's offset. The location is resolved via the existing
mailDateLocation helper, so it honors GOG_TIMEZONE / default_timezone and
falls back to the local timezone -- the same resolution already used for the
outgoing Date header. An unparseable Date is left as-is (trimmed) so it
never breaks the quote or forward block.

The new formatQuoteDate and the existing formatGmailDateInLocation now share
a formatMailDateInLocation core (they differed only in the output layout).
Includes unit coverage for formatQuoteDate and the HTML reply/forward
attribution paths; existing reply/forward/draft tests updated to the new
format.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
@clawsweeper

clawsweeper Bot commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs maintainer review before merge. Reviewed June 23, 2026, 2:55 AM ET / 06:55 UTC.

Summary
The PR reformats Gmail reply and forward attribution dates from raw RFC 2822 headers into Gmail-style human-readable timestamps using the configured mail date location.

Reproducibility: yes. from source: current main's reply and forward renderers pass the original Date header through unchanged. The PR body also gives a concrete live-account command path showing the before input and after output, though I did not run Gmail commands in this read-only review.

Review metrics: 2 noteworthy metrics.

  • Diff surface: 7 files changed; 131 additions, 31 deletions. The patch stays within Gmail compose, reply, forward, date formatting, and focused tests.
  • Live proof paths: 3 command flows shown. The PR body demonstrates reply in local time, reply in UTC, and forward header formatting against a real redacted account.

Merge readiness
Overall: 🦞 diamond lobster
Proof: 🦞 diamond lobster
Patch quality: 🦞 diamond lobster
Result: ready for maintainer review.

Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch.

Next step before merge

  • No ClawSweeper repair lane is needed; this is a clean implementation PR for normal maintainer review and required-check gating.

Security
Cleared: The diff only changes local Gmail message formatting and tests; it adds no dependency, secret, workflow, or code-execution surface.

Review details

Best possible solution:

Land the focused formatter change after normal required checks pass, keeping reply and forward quote date rendering aligned with the existing configured timezone behavior.

Do we have a high-confidence way to reproduce the issue?

Yes from source: current main's reply and forward renderers pass the original Date header through unchanged. The PR body also gives a concrete live-account command path showing the before input and after output, though I did not run Gmail commands in this read-only review.

Is this the best way to solve the issue?

Yes: reusing a shared mail date formatter and applying it to the reply and forward render paths is the narrowest maintainable fix for this output polish.

AGENTS.md: found and applied where relevant.

Codex review notes: model internal, reasoning high; reviewed against fc13b4147e20.

Label changes

Label changes:

  • add P3: The PR improves user-visible Gmail quote formatting with limited blast radius and no urgent runtime failure.
  • add proof: sufficient: Contributor real behavior proof is sufficient. The PR body includes redacted live CLI output showing the changed reply and forward behavior after the fix.
  • add rating: 🦞 diamond lobster: Overall readiness is 🦞 diamond lobster; proof is 🦞 diamond lobster and patch quality is 🦞 diamond lobster.
  • add status: 👀 ready for maintainer look: ClawSweeper has no concrete contributor-facing blocker left for this PR. Sufficient (live_output): The PR body includes redacted live CLI output showing the changed reply and forward behavior after the fix.

Label justifications:

  • P3: The PR improves user-visible Gmail quote formatting with limited blast radius and no urgent runtime failure.
  • rating: 🦞 diamond lobster: Overall readiness is 🦞 diamond lobster; proof is 🦞 diamond lobster and patch quality is 🦞 diamond lobster.
  • status: 👀 ready for maintainer look: ClawSweeper has no concrete contributor-facing blocker left for this PR. Sufficient (live_output): The PR body includes redacted live CLI output showing the changed reply and forward behavior after the fix.
  • proof: sufficient: Contributor real behavior proof is sufficient. The PR body includes redacted live CLI output showing the changed reply and forward behavior after the fix.
Evidence reviewed

What I checked:

  • Repository policy read: AGENTS.md was read fully and its PR review guidance was applied by inspecting the PR with gh without changing branches or files. (AGENTS.md:1, fc13b4147e20)
  • Current reply behavior: Current main passes info.Date directly into the reply quote renderers, and formatQuotedMessage writes that raw header into the attribution. (internal/cmd/gmail_reply.go:281, fc13b4147e20)
  • Current forward behavior: Current main stores the original Date header unchanged in forwardedMessageHeaders, so forwarded plain and HTML bodies render the raw header. (internal/cmd/gmail_forward.go:133, fc13b4147e20)
  • PR implementation: The patch adds a shared quote/list date formatter, threads a resolved time location through reply and forward rendering, and updates command/unit tests for the new quote and forward date output. (internal/cmd/gmail_date.go:9, bd07a7a892ba)
  • Real behavior proof: The PR body includes redacted live gmail get, gmail reply, and gmail forward output showing local timezone, UTC conversion, and forwarded Date header behavior after the change. (bd07a7a892ba)
  • Related-item search: Searches for quote date and forwarded date variants did not find an existing same-problem issue or PR; broader Gmail human-style results were adjacent, not canonical for this formatting change.

Likely related people:

  • steipete: Commit ba350d6 added first-class Gmail reply commands, quote rendering, and related tests touching the central reply files. (role: introduced reply quote behavior and recent Gmail reply owner; confidence: high; commits: ba350d6824ce; files: internal/cmd/gmail_reply.go, internal/cmd/gmail_compose.go, internal/cmd/gmail_send_quote_test.go)
  • spencer-c-reed: Commit 13a1aae added the Gmail forward command, forwarded headers, and forward tests that this PR updates. (role: forward command introducer; confidence: high; commits: 13a1aaea1348; files: internal/cmd/gmail_forward.go, internal/cmd/execute_gmail_forward_test.go)
  • salmonumbrella: Commit 05986fb introduced Gmail timezone support, GOG_TIMEZONE/default_timezone behavior, and formatGmailDateInLocation, which this PR extends. (role: timezone/date formatting contributor; confidence: medium; commits: 05986fb6cebd; files: internal/cmd/gmail_date.go, internal/cmd/timezone.go)
What the crustacean ranks mean
  • 🦀 challenger crab: rare, exceptional readiness with strong proof, clean implementation, and convincing validation.
  • 🦞 diamond lobster: very strong readiness with only minor maintainer review expected.
  • 🐚 platinum hermit: good normal PR, likely mergeable with ordinary maintainer review.
  • 🦐 gold shrimp: useful signal, but proof or patch confidence is still limited.
  • 🦪 silver shellfish: thin signal; proof, validation, or implementation needs work.
  • 🧂 unranked krab: not merge-ready because proof is missing/unusable or there are serious correctness or safety concerns.
  • 🌊 off-meta tidepool: rating does not apply to this item.

Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics.

How this review workflow works
  • ClawSweeper keeps one durable marker-backed review comment per issue or PR.
  • Re-runs edit this comment so the latest verdict, findings, and automation markers stay together instead of adding duplicate bot comments.
  • A fresh review can be triggered by eligible @clawsweeper re-review comments, exact-item GitHub events, scheduled/background review runs, or manual workflow dispatch.
  • PR/issue authors and users with repository write access can comment @clawsweeper re-review or @clawsweeper re-run on an open PR or issue to request a fresh review only.
  • Maintainers can also comment @clawsweeper review to request a fresh review only.
  • Fresh-review commands do not start repair, autofix, rebase, CI repair, or automerge.
  • Maintainer-only repair and merge flows require explicit commands such as @clawsweeper autofix, @clawsweeper automerge, @clawsweeper fix ci, or @clawsweeper address review.
  • Maintainers can comment @clawsweeper explain to ask for more context, or @clawsweeper stop to stop active automation.

@clawsweeper clawsweeper Bot added proof: sufficient Contributor real behavior proof is sufficient. rating: 🦞 diamond lobster Very strong PR readiness with only minor maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. P3 Low-risk cleanup, docs, polish, ergonomics, or speculative feature. labels Jun 23, 2026
@steipete
steipete merged commit e35aad9 into openclaw:main Jun 23, 2026
5 checks passed
@malob
malob deleted the feat/gmail-quote-date-format branch June 23, 2026 16:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

P3 Low-risk cleanup, docs, polish, ergonomics, or speculative feature. proof: sufficient Contributor real behavior proof is sufficient. rating: 🦞 diamond lobster Very strong PR readiness with only minor maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants