From 29063383f80a4af12a1f24638a410ea8ceb02274 Mon Sep 17 00:00:00 2001 From: Tobi <155588579+spiky02plateau@users.noreply.github.com> Date: Wed, 16 Sep 2026 17:15:52 +0200 Subject: [PATCH 1/2] fix(inbox): show source email dates in local time --- MANUAL.md | 7 +++ docs/pr-images/email-date-after.png | Bin 0 -> 36506 bytes docs/pr-images/email-date-before.png | Bin 0 -> 34272 bytes server/store.ts | 19 +++++++- shared/emailTimestamp.ts | 42 +++++++++++++++++ shared/types.ts | 1 + src/feed/CardView.tsx | 3 ++ src/feed/EmailTimestamp.tsx | 17 +++++++ src/styles.css | 1 + test/card-render.test.tsx | 7 +++ test/domain.test.ts | 22 +++++++++ test/email-timestamp.test.tsx | 68 +++++++++++++++++++++++++++ 12 files changed, 186 insertions(+), 1 deletion(-) create mode 100644 docs/pr-images/email-date-after.png create mode 100644 docs/pr-images/email-date-before.png create mode 100644 shared/emailTimestamp.ts create mode 100644 src/feed/EmailTimestamp.tsx create mode 100644 test/email-timestamp.test.tsx diff --git a/MANUAL.md b/MANUAL.md index 24cddaba..2ac0def8 100644 --- a/MANUAL.md +++ b/MANUAL.md @@ -48,6 +48,13 @@ The normal loop is: The local Tend runtime owns feed state. Codex Desktop owns the agent threads and connector access. +### Email dates + +Email cards show the received date and time from their saved source metadata beneath the title, including older cards with that evidence. +Times use your browser's timezone and include the timezone label. +Cards citing several email threads show each matched thread's subject and date. +Collection and card creation times are never substituted for an email date. + ## Creating And Connecting A Feed ### Create The Feed diff --git a/docs/pr-images/email-date-after.png b/docs/pr-images/email-date-after.png new file mode 100644 index 0000000000000000000000000000000000000000..b92538f1072e8ccf78d26d208eb627bd2c367186 GIT binary patch literal 36506 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 ahXsCE;D-f%Sm1{Repuk2w*dS1!T$hX2bUWF literal 0 HcmV?d00001 diff --git a/docs/pr-images/email-date-before.png b/docs/pr-images/email-date-before.png new file mode 100644 index 0000000000000000000000000000000000000000..3df4c2fbeffe4e0076cb131fbfa44c6fed74974a GIT binary patch literal 34272 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 literal 0 HcmV?d00001 diff --git a/server/store.ts b/server/store.ts index 77b962ab..a8de0560 100644 --- a/server/store.ts +++ b/server/store.ts @@ -1,6 +1,7 @@ import { existsSync } from "node:fs"; import { appendFile, mkdir, readdir, readFile, rename, stat } from "node:fs/promises"; import path from "node:path"; +import { sourceEmailDates } from "../shared/emailTimestamp"; import type { AgentPresence, AgentPresenceLiveness, @@ -450,6 +451,21 @@ export class AttentionStore { this.readDrainState(feedId), ]); cards.sort((a, b) => a.createdAt.localeCompare(b.createdAt)); + const emailRunIds = new Set(cards.flatMap((card) => card.sourceRunIds ?? [])); + const emailSnapshots = new Map(await Promise.all(runs.filter((run) => emailRunIds.has(run.id)).map(async (run) => { + try { + const snapshots = await this.readSourceSnapshots(run); + return [run.id, snapshots.map((snapshot) => snapshot.value)] as const; + } catch { + console.warn(`Could not load email dates for source run ${run.id}.`); + return [run.id, []] as const; + } + }))); + for (const card of cards) { + const dates = sourceEmailDates(card, (card.sourceRunIds ?? []).flatMap((id) => emailSnapshots.get(id) ?? [])); + if (dates.length) card.emailDates = dates; + else delete card.emailDates; + } cards.forEach(projectReadingPresentation); runs.sort((a, b) => (a.completedAt ?? "").localeCompare(b.completedAt ?? "") || a.id.localeCompare(b.id)); routineActions.sort((a, b) => a.createdAt.localeCompare(b.createdAt)); @@ -665,7 +681,8 @@ export class AttentionStore { card.updatedAt = card.reading && existing && now <= existing.updatedAt ? new Date(Date.parse(existing.updatedAt) + 1).toISOString() : now; - await this.cards.write(card); + const { emailDates: _emailDates, ...stored } = card; + await this.cards.write(stored); } async removeCard(feedId: string, cardId: string): Promise { diff --git a/shared/emailTimestamp.ts b/shared/emailTimestamp.ts new file mode 100644 index 00000000..da6eef36 --- /dev/null +++ b/shared/emailTimestamp.ts @@ -0,0 +1,42 @@ +import type { Card } from "./types"; + +export function isEmailTimestamp(value: unknown): value is string { + return typeof value === "string" + && /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(?::\d{2}(?:\.\d+)?)?(?:Z|[+-]\d{2}:\d{2})$/.test(value) + && Number.isFinite(Date.parse(value)); +} + +function record(value: unknown): Record | undefined { + return value && typeof value === "object" && !Array.isArray(value) ? value as Record : undefined; +} + +export function sourceEmailDates(card: Card, snapshots: unknown[]): NonNullable { + const references = JSON.stringify([card.id, card.blocks]); + const emails = new Map[number]>(); + for (const snapshot of snapshots) { + const source = record(snapshot); + if (!source) continue; + const threadId = source.threadId ?? (Array.isArray(source.messages) ? source.id : undefined); + if (typeof threadId !== "string" || !threadId) continue; + if (card.sourceMailbox && source.sourceMailbox && source.sourceMailbox !== card.sourceMailbox) continue; + const escapedId = threadId.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + if (!new RegExp(`(?= Date.parse(receivedAt)) continue; + emails.set(threadId, { + threadId, receivedAt, + ...(typeof source.subject === "string" ? { subject: source.subject } : {}), + }); + } + } + return [...emails.values()].sort((a, b) => Date.parse(b.receivedAt) - Date.parse(a.receivedAt)); +} diff --git a/shared/types.ts b/shared/types.ts index 4b6b6b66..05ff3fbc 100644 --- a/shared/types.ts +++ b/shared/types.ts @@ -454,6 +454,7 @@ export interface Card { eyebrow: string; why: string; sourceMailbox?: string; + emailDates?: Array<{ threadId: string; receivedAt: string; subject?: string }>; sourceRunIds?: string[]; contextInfluence?: CardContextInfluence; reading?: CardReading; diff --git a/src/feed/CardView.tsx b/src/feed/CardView.tsx index 0f2abb98..781cdf48 100644 --- a/src/feed/CardView.tsx +++ b/src/feed/CardView.tsx @@ -7,6 +7,7 @@ import type { Card, CardAction, CardBlock, FeedView, WorkItemView } from "../typ import { DetachedLink } from "../ui/DetachedLink"; import { FormattedText } from "../ui/FormattedText"; import { ReadingIdentity } from "./ReadingIdentity"; +import { EmailTimestamp } from "./EmailTimestamp"; import { ReadingPreferenceFooter } from "./ReadingPreferenceFooter"; import { visibleCardActions } from "./selectors"; @@ -460,6 +461,7 @@ function ReadingCardView({ card, active, reaction, group, preference, onVersion,

    {card.title}

    +

    {card.why}

    @@ -545,6 +547,7 @@ export function CardView({
    {card.eyebrow}

    {card.title}

    +

    diff --git a/src/feed/EmailTimestamp.tsx b/src/feed/EmailTimestamp.tsx new file mode 100644 index 00000000..46b80949 --- /dev/null +++ b/src/feed/EmailTimestamp.tsx @@ -0,0 +1,17 @@ +import { isEmailTimestamp } from "../../shared/emailTimestamp"; +import type { Card } from "../../shared/types"; + +export function EmailTimestamp({ card }: { card: Card }) { + const dates = card.emailDates?.filter((email) => isEmailTimestamp(email.receivedAt)) ?? []; + if (!dates.length) return null; + const format = new Intl.DateTimeFormat(undefined, { + year: "numeric", month: "short", day: "numeric", + hour: "2-digit", minute: "2-digit", timeZoneName: "short", + }); + return
    {dates.map((email) => ( +
    + {dates.length > 1 ? `${email.subject ?? "Email"}: ` : "Email received "} + +
    + ))}
    ; +} diff --git a/src/styles.css b/src/styles.css index e31aa8b4..3f67eb77 100644 --- a/src/styles.css +++ b/src/styles.css @@ -57,6 +57,7 @@ kbd { font: 11px/1 var(--mono); color: var(--ink-3); } .attention-card.has-context-influence .card-rule { background: #b68552; } .attention-card.has-context-influence.is-active .card-rule { background: var(--blue); } .card-head { display: flex; align-items: flex-start; gap: 12px; } +.email-timestamp { margin-top: 6px; color: var(--ink-2); font-size: 12px; } .kind-dot { flex: 0 0 auto; width: 10px; height: 10px; margin-top: 7px; border-radius: 50%; background: #82909b; } .kind-dot.proposal { background: #b98649; } .eyebrow { margin-bottom: 5px; color: var(--ink-3); font-size: 11px; font-weight: 700; letter-spacing: .12em; text-transform: uppercase; } diff --git a/test/card-render.test.tsx b/test/card-render.test.tsx index f5513f72..c751ef56 100644 --- a/test/card-render.test.tsx +++ b/test/card-render.test.tsx @@ -1485,3 +1485,10 @@ test("engagement distinguishes source expansion, collapse and links without pres expect(engagementClickTarget(ui.getByRole("link", { name: "Fixture transcript" }))).toBe("source_link"); expect(engagementClickTarget(ui.getByRole("heading", { name: readingCard().title }))).toBe("card"); }); + +test("reading cards keep the source email timestamp visible outside Sources", () => { + const card = readingCard({ emailDates: [{ threadId: "abc123", receivedAt: "2026-07-15T11:47:00+02:00" }] }); + const html = renderToStaticMarkup( {}} onChanged={() => {}} onAction={() => {}} onReturnToReview={() => {}} />); + expect(html).toContain('dateTime="2026-07-15T11:47:00+02:00"'); + expect(html.indexOf("Email received")).toBeLessThan(html.indexOf("")); +}); diff --git a/test/domain.test.ts b/test/domain.test.ts index e79bc39c..09fdac2d 100644 --- a/test/domain.test.ts +++ b/test/domain.test.ts @@ -4073,3 +4073,25 @@ describe("local card dismissal (Tend-only, no source cleanup)", () => { expect((await store.readCard("inbox", legacy.id)).actions).toEqual(legacy.actions); }); }); + +test("feed reads project saved email dates without rewriting old cards", async () => { + const { store, domain } = await setup(); + const snapshot = { + kind: "gmail_thread_metadata", threadId: "abc123", sourceMailbox: "owner@example.com", + subject: "Workshop", receivedAt: "2026-07-15T11:47:00+02:00", + }; + const run = await domain.recordSourceRun("inbox", "gmail-inbox", [snapshot], [], {}); + await domain.recordSweepBatch("inbox", [run]); + const original = await domain.upsertCard("inbox", { + id: "email-abc123", title: "Confirm the attendee list", why: "The organizer needs a reply.", + sourceRunIds: [run], + blocks: [{ id: "source", type: "receipt", text: "Thread ID: abc123." }], + }); + const view = await store.readFeed("inbox"); + expect(view.cards.find((card) => card.id === original.id)?.emailDates).toEqual([ + { threadId: snapshot.threadId, receivedAt: snapshot.receivedAt, subject: snapshot.subject }, + ]); + expect(await store.readCard("inbox", original.id)).toEqual(original); + await store.writeCard(view.cards.find((card) => card.id === original.id)!); + expect(await store.readCard("inbox", original.id)).not.toHaveProperty("emailDates"); +}); diff --git a/test/email-timestamp.test.tsx b/test/email-timestamp.test.tsx new file mode 100644 index 00000000..9cc1dee8 --- /dev/null +++ b/test/email-timestamp.test.tsx @@ -0,0 +1,68 @@ +import { expect, test } from "bun:test"; +import { renderToStaticMarkup } from "react-dom/server"; +import { sourceEmailDates } from "../shared/emailTimestamp"; +import type { Card } from "../shared/types"; +import { CardView } from "../src/feed/CardView"; + +const card: Card = { + id: "mail-abc123", feedId: "inbox", kind: "attention", status: "to_review_new", + title: "Confirm the attendee list", eyebrow: "Workshop", why: "The organizer needs a reply.", + sourceMailbox: "owner@example.com", sourceRunIds: ["run-example"], + blocks: [{ id: "source", type: "receipt", text: "Thread ID: abc123." }], + readyForPass: 1, createdAt: "2026-09-16T12:00:00Z", updatedAt: "2026-09-16T12:00:00Z", history: [], +}; +const source = { + kind: "gmail_thread_metadata", threadId: "abc123", sourceMailbox: "owner@example.com", + receivedAt: "2026-07-15T11:47:00+02:00", subject: "Attendee list", +}; + +test("old cards recover email dates from their exact source thread and mailbox", () => { + expect(sourceEmailDates(card, [ + { ...source, threadId: "abc1234" }, + { ...source, sourceMailbox: "someone@example.com" }, + { ...source, receivedAt: "2026-07-15" }, + { ...source, receivedAt: "2026-07-15T11:47:00" }, + { ...source, receivedAt: "invalid" }, + { kind: "collection_receipt", collectedAt: card.createdAt }, + ])).toEqual([]); + expect(sourceEmailDates(card, [{ ...source, kind: undefined }])).toEqual([ + { threadId: source.threadId, receivedAt: source.receivedAt, subject: source.subject }, + ]); + expect(card).not.toHaveProperty("emailDates"); +}); + +test("source dates are deduplicated per thread and ordered newest first", () => { + const second = { ...source, threadId: "def456", receivedAt: "2026-07-16T12:00:00Z" }; + const referenced = { ...card, blocks: [{ id: "source", type: "receipt" as const, text: "Thread IDs: abc123 and def456." }] }; + const dates = sourceEmailDates(referenced, [source, second, source, { ...source, receivedAt: "2026-07-14T00:00:00Z" }]); + expect(dates.map((date) => date.threadId)).toEqual(["def456", "abc123"]); + expect(dates[1].receivedAt).toBe(source.receivedAt); +}); + +function render(card: Card) { + return renderToStaticMarkup( {}} onChanged={() => {}} onAction={() => {}} onReturnToReview={() => {}} />); +} + +test("the card header formats the source instant in the viewer's timezone", () => { + const dates = sourceEmailDates(card, [source]); + const html = render({ ...card, emailDates: dates }); + const expected = new Intl.DateTimeFormat(undefined, { + year: "numeric", month: "short", day: "numeric", hour: "2-digit", minute: "2-digit", timeZoneName: "short", + }).format(new Date(source.receivedAt)); + expect(html).toContain(`dateTime="${source.receivedAt}"`); + expect(html).toContain(expected); + expect(html.indexOf("Email received")).toBeLessThan(html.indexOf("")); + expect(html).not.toContain(card.createdAt); + expect(render(card)).not.toContain(" { + const legacy = { ...card, id: "gmail-thread-thread-reviewed", sourceMailbox: undefined, + blocks: [{ id: "source", type: "evidence" as const, items: [{ label: "Email", href: "https://mail.google.com/mail/u/0/#inbox/thread-reviewed" }] }], + }; + const instant = "2026-07-15T09:47:00.000Z"; + expect(sourceEmailDates(legacy, [{ id: "thread-reviewed", history_id: "history-1", messages: [ + { id: "message-1", internalDate: String(Date.parse(instant)) }, + ] }])).toEqual([{ threadId: "thread-reviewed", receivedAt: instant }]); + expect(sourceEmailDates(legacy, [{ ...source, threadId: "thread-review" }])).toEqual([]); +}); From fc810777370a1e4f59e14a453966129817f3bfce Mon Sep 17 00:00:00 2001 From: Tobi <155588579+spiky02plateau@users.noreply.github.com> Date: Wed, 16 Sep 2026 17:34:29 +0200 Subject: [PATCH 2/2] fix(inbox): accept readonly snapshot collections --- server/store.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/store.ts b/server/store.ts index a8de0560..b5d95df2 100644 --- a/server/store.ts +++ b/server/store.ts @@ -452,7 +452,7 @@ export class AttentionStore { ]); cards.sort((a, b) => a.createdAt.localeCompare(b.createdAt)); const emailRunIds = new Set(cards.flatMap((card) => card.sourceRunIds ?? [])); - const emailSnapshots = new Map(await Promise.all(runs.filter((run) => emailRunIds.has(run.id)).map(async (run) => { + const emailSnapshots = new Map(await Promise.all(runs.filter((run) => emailRunIds.has(run.id)).map(async (run) => { try { const snapshots = await this.readSourceSnapshots(run); return [run.id, snapshots.map((snapshot) => snapshot.value)] as const;