Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Add experimental Thunderbird bridge for local Inbox Zero.#3015

Open
Vanquish-6 wants to merge 1 commit into
elie222:mainelie222/inbox-zero:mainfrom
Vanquish-6:feat/thunderbird-bridgeVanquish-6/inbox-zero:feat/thunderbird-bridgeCopy head branch name to clipboard
Open

Add experimental Thunderbird bridge for local Inbox Zero.#3015
Vanquish-6 wants to merge 1 commit into
elie222:mainelie222/inbox-zero:mainfrom
Vanquish-6:feat/thunderbird-bridgeVanquish-6/inbox-zero:feat/thunderbird-bridgeCopy head branch name to clipboard

Conversation

@Vanquish-6

@Vanquish-6 Vanquish-6 commented Jul 17, 2026

Copy link
Copy Markdown

Wire a MailExtension and server APIs so Thunderbird mailboxes can run AI rules and apply archive/tag/draft actions without Gmail/Outlook OAuth.

Review in cubic

Wire a MailExtension and server APIs so Thunderbird mailboxes can run AI rules and apply archive/tag/draft actions without Gmail/Outlook OAuth.
@vercel

vercel Bot commented Jul 17, 2026

Copy link
Copy Markdown

@Vanquish-6 is attempting to deploy a commit to the Inbox Zero OSS Program Team on Vercel.

A member of the Team first needs to authorize it.

@CLAassistant

CLAassistant commented Jul 17, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@cubic-dev-ai cubic-dev-ai Bot 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.

38 issues found across 50 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="apps/web/prisma/schema.prisma">

<violation number="1" location="apps/web/prisma/schema.prisma:944">
P2: Reprocessing a message creates another pending review item instead of refreshing its suggestion, so Pending can show duplicate approvals and retain stale actions. Add an account-scoped message identity constraint and make the save path upsert/replace the current item (or explicitly preserve history and filter it).</violation>
</file>

<file name="apps/web/utils/email/provider.ts">

<violation number="1" location="apps/web/utils/email/provider.ts:28">
P2: Rate-limit check is skipped for Thunderbird because the early return runs before `assertProviderNotRateLimited`. If Thunderbird should respect rate-limit state (the infra already supports it), move the check before the Thunderbird branch; if intentionally skipped for this experimental provider, consider adding a brief comment so the gap is documented.</violation>
</file>

<file name="apps/web/utils/thunderbird/parse-message.ts">

<violation number="1" location="apps/web/utils/thunderbird/parse-message.ts:13">
P2: Nested replies without a supplied `threadId` are split into parent-specific threads, so thread actions and reply context omit earlier messages. Derive the root ID from `references` before falling back to `inReplyTo`.</violation>

<violation number="2" location="apps/web/utils/thunderbird/parse-message.ts:28">
P2: Sent mail submitted through the bridge is indexed as unread Inbox mail, so Inbox/sender statistics include outbound messages. Propagate `isSent` (and the corresponding inbox state) when indexing this parsed label.</violation>
</file>

<file name="apps/thunderbird-addon/options.js">

<violation number="1" location="apps/thunderbird-addon/options.js:21">
P2: `pollSeconds: 0` is silently promoted to 15 because `Number(0)` is falsy and the `|| 15` fallback kicks in. A user who manually enters 0 in the number input (to disable polling without unchecking the enabled toggle) sees "Saved." but the stored value is 15, so polling continues at the default rate. The HTML `min="5"` only constrains the stepper arrows, not typed input. Either clamp to `Math.max(5, val)` like the background script already does, or use an explicit `Number.isFinite()` guard.</violation>
</file>

<file name="apps/web/utils/actions/stats.ts">

<violation number="1" location="apps/web/utils/actions/stats.ts:35">
P2: Toast is misleading for Thunderbird accounts: the action returns `{ skipped: true }` on success for Thunderbird, so the caller in StatLoaderProvider will display 'Stats loaded!' even though the action skipped the load. Consider returning `null` or another signal the caller can distinguish, or suppress the toast when the response indicates a skip.</violation>
</file>

<file name="apps/thunderbird-addon/popup.js">

<violation number="1" location="apps/thunderbird-addon/popup.js:33">
P3: Failed popup commands render an error but also produce unhandled promise rejections, since every caller leaves `run()`'s rethrow unhandled. Return a failure value after updating status, or attach rejection handlers at each click handler.</violation>
</file>

<file name="apps/web/utils/redis/thunderbird-actions.ts">

<violation number="1" location="apps/web/utils/redis/thunderbird-actions.ts:191">
P1: Actions enqueued while the add-on ACK is in flight can be lost, so approved work may never reach Thunderbird. Remove acknowledged IDs atomically (for example with a Lua/WATCH-retry operation) instead of rebuilding the shared list from a stale read.</violation>

<violation number="2" location="apps/web/utils/redis/thunderbird-actions.ts:191">
P1: Approved actions can disappear when Thunderbird processes another message before its next action poll. `clearThunderbirdProposalActions` deletes every non-bulk item from the same queue used for approved actions; keep proposals separate or mark them so this cleanup only removes unapproved work.</violation>
</file>

<file name="apps/thunderbird-addon/manifest.json">

<violation number="1" location="apps/thunderbird-addon/manifest.json:6">
P2: Use `browser_specific_settings` instead of the deprecated `applications` key. The Thunderbird add-on documentation explicitly marks `applications` as deprecated and recommends `browser_specific_settings` as the replacement. While it still works, this will show warnings during submission and may break in a future Thunderbird version.</violation>

<violation number="2" location="apps/thunderbird-addon/manifest.json:31">
P3: Remove the `"persistent": true` property from the background block. Thunderbird 115's Manifest V2 does not implement a `persistent` property on the `background` key — background scripts are always persistent by default, so this property is silently ignored. Omitting it avoids confusion and keeps the manifest aligned with supported keys.</violation>
</file>

<file name="apps/web/app/api/thunderbird/inbox/route.ts">

<violation number="1" location="apps/web/app/api/thunderbird/inbox/route.ts:135">
P1: Retried or concurrent approvals can enqueue the same side effects multiple times because the pending-status check is not an atomic claim and Redis unconditionally appends every action. Atomically claim the review item and deduplicate jobs with a stable key (for example item/action ID) before exposing them to the add-on.

(Based on your team's feedback about idempotent background job enqueue operations.) [FEEDBACK_USED]</violation>
</file>

<file name="apps/web/utils/ai/thunderbird/suggest-triage.ts">

<violation number="1" location="apps/web/utils/ai/thunderbird/suggest-triage.ts:110">
P2: An LLM outage now proposes Archive + Mark read for every message with no rule action. Fall back to `keep`/no actions so a transient provider failure cannot turn unclassified mail into archive suggestions.</violation>

<violation number="2" location="apps/web/utils/ai/thunderbird/suggest-triage.ts:152">
P2: `mark_read` action is emitted for `needs_attention` with `read: true`, which marks time-sensitive emails as read. Emails that need attention should remain unread so the user sees them as actionable in their inbox. Consider omitting the `mark_read` action or setting `read: false` for the `needs_attention` category so the flagged email stays visually prominent.</violation>
</file>

<file name="apps/thunderbird-addon/popup.html">

<violation number="1" location="apps/thunderbird-addon/popup.html:39">
P2: The connection status `<p id="conn">` and action status `<p id="status">` are updated dynamically by popup.js (with results, errors, connection state) but lack `aria-live="polite"` or `role="status"`. Screen reader users won't be notified when processing finishes, errors occur, or connection state changes. Add `aria-live="polite"` to both elements so assistive technology announces the updates.</violation>
</file>

<file name="apps/web/app/api/thunderbird/actions/route.ts">

<violation number="1" location="apps/web/app/api/thunderbird/actions/route.ts:43">
P1: Overlapping add-on polls can fetch and apply the same action before either acknowledgement arrives, duplicating side effects such as sent messages or drafts. Claim/lease actions atomically when serving GET (or serialize polling) so an action is invisible to concurrent workers before execution.</violation>
</file>

<file name="apps/web/app/api/thunderbird/accounts/route.ts">

<violation number="1" location="apps/web/app/api/thunderbird/accounts/route.ts:109">
P2: A failed mailbox insert can leave a Thunderbird `Account` without its `EmailAccount`, so a retry may fail permanently on the unique provider-account ID. Create both records in one transaction so registration is all-or-nothing.</violation>
</file>

<file name="scripts/local-upstash-stub.mjs">

<violation number="1" location="scripts/local-upstash-stub.mjs:40">
P1: Concurrent lock or nonce claims both succeed against this stub because `SET ... NX` overwrites an existing key and returns `"OK"`. Honor `NX` (across all key types) and return `null` without writing when the key exists, so local bridge testing preserves one-time/lock semantics.</violation>

<violation number="2" location="scripts/local-upstash-stub.mjs:57">
P1: Recently queued Thunderbird actions can disappear at the first action's original expiry instead of the latest refresh. Track and replace each key's pending expiry timer; clear it on `DEL` and on writes that remove TTL.</violation>

<violation number="3" location="scripts/local-upstash-stub.mjs:169">
P3: IPv6-loopback local setups cannot reach this stub because the listener is hardcoded to IPv4. Make the bind host configurable (using the existing `REDIS_HTTP_BIND_HOST` convention) and document/select a loopback host compatible with the configured URL.

(Based on your team's feedback about local service connectivity across IPv4 and IPv6 loopback.) [FEEDBACK_USED]</violation>
</file>

<file name="apps/web/app/(app)/[emailAccountId]/automation/ThunderbirdPendingReview.tsx">

<violation number="1" location="apps/web/app/(app)/[emailAccountId]/automation/ThunderbirdPendingReview.tsx:324">
P1: Quick actions can mark a review item approved even when it has no Thunderbird message reference, but the add-on silently no-ops and acknowledges those queued actions. Disable every quick action when `thunderbirdMessageId` is absent, as Delete already does.</violation>
</file>

<file name="apps/web/utils/thunderbird/index-message-stats.ts">

<violation number="1" location="apps/web/utils/thunderbird/index-message-stats.ts:33">
P2: Malformed Thunderbird `date` values silently prevent stats indexing because `new Date(message.date)` can be invalid and the Prisma error is swallowed. Validate the parsed date and fall back to `new Date()` before the upsert.</violation>
</file>

<file name="apps/web/utils/thunderbird/load-stored-message.test.ts">

<violation number="1" location="apps/web/utils/thunderbird/load-stored-message.test.ts:18">
P3: `reviewItemToParsedMessage` derives `historyId` from `thunderbirdMessageId` (99 → "99"), but the test never asserts this derived field. Missing verification of a key mapping in the review-item path.</violation>

<violation number="2" location="apps/web/utils/thunderbird/load-stored-message.test.ts:49">
P3: `emailMessageToParsedMessage` formats `from` as `"${fromName} <${from}>"`, but the test uses `.toContain("news@example.com")`. A substring check passes even if the `fromName` formatting logic breaks (e.g., returns just the bare address). Use exact equality to confirm the full formatted string.</violation>
</file>

<file name="apps/web/scripts/setup-thunderbird-bridge.ts">

<violation number="1" location="apps/web/scripts/setup-thunderbird-bridge.ts:12">
P2: The rule creation logic for Archive receipts, Trash junk, and Needs attention is duplicated verbatim between `createStarterRules` (lines 149–212) and `ensureTriageRules` (lines 247–340). Each pair creates a rule with identical name, instructions, enabled/automate settings, and matching actions. If these seed rule definitions need to change (instructions, label names, action types), both copies must be kept in sync or one will diverge and silently produce different behavior depending on whether the account is new or existing.</violation>

<violation number="2" location="apps/web/scripts/setup-thunderbird-bridge.ts:101">
P2: An interruption after creating `Account` leaves an orphan that this script cannot recover from; the next run fails on the unique provider-account key. Create/link the account and mailbox atomically, or look up and reuse the existing Thunderbird account before creating it.</violation>

<violation number="3" location="apps/web/scripts/setup-thunderbird-bridge.ts:103">
P1: Mailboxes created with the optional owner argument lose the bridge user's premium entitlement, so processing reports no AI access unless that owner separately has premium or checks are bypassed. Connect the owner to the bridge grant (without making them an admin), or retain an entitled mailbox owner for the bridge flow.</violation>

<violation number="4" location="apps/web/scripts/setup-thunderbird-bridge.ts:258">
P2: When `ensureTriageRules` renames the old "Archive low-priority mail" rule to "Archive newsletters" and updates its instructions, it does not update the rule's actions. If the old rule was created with different actions (e.g., just ARCHIVE without LABEL "newsletter" nor MARK_READ), the renamed rule's instructions will say "Archive and tag newsletters" but the rule will still execute the old actions. Instructions and behavior diverge. Consider deleting stale actions and re-creating the standard action set after the rename, or migrating the actions explicitly.</violation>
</file>

<file name="apps/web/utils/email/thunderbird.ts">

<violation number="1" location="apps/web/utils/email/thunderbird.ts:95">
P1: Archive/read/trash actions can target no real messages for threads outside the 50 newest stored messages. Fetch by `threadId` in storage (with pagination if needed) before enqueueing actions, rather than filtering a global 50-message window.</violation>

<violation number="2" location="apps/web/utils/email/thunderbird.ts:120">
P2: Latest-message callers receive the oldest stored message because thread messages are loaded newest-first and this selects the final item. Select the first item (or sort explicitly by date) so reply/rule logic evaluates the actual latest message.</violation>

<violation number="3" location="apps/web/utils/email/thunderbird.ts:156">
P2: The `forwardEmail` method (line 494) destructures `args.content` and passes it to the enqueue call, but `forwardEmail` also accepts `cc?`, `bcc?`, and `from?` in its `args`. Neither `cc`, `bcc`, nor `from` is included in the forward action payload or schema, so any CC/BCC recipients specified when forwarding an email are silently dropped. The Thunderbird add-on receives `content` and `to` but has no way to know about CC or BCC recipients, so forwarded emails may miss intended recipients.</violation>

<violation number="4" location="apps/web/utils/email/thunderbird.ts:529">
P1: Sending a Thunderbird draft reports success but performs no send operation, leaving the draft unsent while callers treat it as delivered. Queue a send action supported by the add-on or fail explicitly until draft sending is implemented.</violation>

<violation number="5" location="apps/web/utils/email/thunderbird.ts:533">
P1: Inbox views can show sent, archived, or otherwise non-inbox mail. Query/filter for the `INBOX` state before returning this method's results.</violation>

<violation number="6" location="apps/web/utils/email/thunderbird.ts:562">
P1: Filtered and paginated callers receive unfiltered first-page results; date bounds are also lost whenever `query` is set. Apply the requested filters and pagination in both branches, or reject unsupported options instead of returning incorrect data.</violation>
</file>

<file name="apps/thunderbird-addon/background.js">

<violation number="1" location="apps/thunderbird-addon/background.js:104">
P1: The `api()` function calls `fetch()` without a timeout. If the local Inbox Zero server hangs or is unresponsive, the background script accumulates unresolved promises that never settle. Since `pollAndApplyActions` is invoked from alarms, `setInterval`, focus events, and new-mail events concurrently, a hung request can block the add-on's action pipeline indefinitely. Add an AbortController with a reasonable timeout (e.g., 15-30 seconds) and handle the abort on the catch path.</violation>

<violation number="2" location="apps/thunderbird-addon/background.js:406">
P1: `pollAndApplyActions` is called from 8 different code paths: two browser alarms (POLL_ALARM and CATCHUP_ALARM), a `setInterval`, the window focus-change event, the new-mail handler, the message router, and `restartPollTimer` itself — all without any mutual-exclusion guard. When overlapping calls occur (e.g., the catch-up alarm fires while the `setInterval` poll is still applying actions), actions can be applied concurrently, and the acknowledgment POST to `/api/thunderbird/actions` can race with other invocations. Add a simple lock (e.g., a module-level `let polling = false;` guard that returns early if a poll is already in-flight).</violation>

<violation number="3" location="apps/thunderbird-addon/background.js:436">
P1: Actions with a missing Thunderbird message ID are acknowledged as applied even though no mailbox change occurred, permanently dropping them from the queue. Only append IDs after a successful mutation, or make skipped/unknown actions throw so they remain pending.</violation>

<violation number="4" location="apps/thunderbird-addon/background.js:605">
P2: The `"send"` action type opens a compose window with `browser.compose.beginNew()`, saves the message as a draft, then closes the tab — it never calls `browser.compose.sendMessage()` or actually transmits the email. The name "send" is misleading for any developer reading the server-side action contract or debugging why approved "send" actions result in drafts rather than sent messages. Rename the action type to `"draft_new"`, `"compose_draft"`, or similar, or add a `sendMessage()` call if the intent is to truly send.</violation>
</file>

Tip: cubic used a learning from your PR history. Let your coding agent read cubic learnings directly with the cubic MCP.

Re-trigger cubic

) {
const key = actionsKey(emailAccountId);
if (!actionIds?.length) {
await redis.del(key);

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.

P1: Actions enqueued while the add-on ACK is in flight can be lost, so approved work may never reach Thunderbird. Remove acknowledged IDs atomically (for example with a Lua/WATCH-retry operation) instead of rebuilding the shared list from a stale read.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/web/utils/redis/thunderbird-actions.ts, line 191:

<comment>Actions enqueued while the add-on ACK is in flight can be lost, so approved work may never reach Thunderbird. Remove acknowledged IDs atomically (for example with a Lua/WATCH-retry operation) instead of rebuilding the shared list from a stale read.</comment>

<file context>
@@ -0,0 +1,269 @@
+) {
+  const key = actionsKey(emailAccountId);
+  if (!actionIds?.length) {
+    await redis.del(key);
+    return;
+  }
</file context>

) {
const key = actionsKey(emailAccountId);
if (!actionIds?.length) {
await redis.del(key);

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.

P1: Approved actions can disappear when Thunderbird processes another message before its next action poll. clearThunderbirdProposalActions deletes every non-bulk item from the same queue used for approved actions; keep proposals separate or mark them so this cleanup only removes unapproved work.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/web/utils/redis/thunderbird-actions.ts, line 191:

<comment>Approved actions can disappear when Thunderbird processes another message before its next action poll. `clearThunderbirdProposalActions` deletes every non-bulk item from the same queue used for approved actions; keep proposals separate or mark them so this cleanup only removes unapproved work.</comment>

<file context>
@@ -0,0 +1,269 @@
+) {
+  const key = actionsKey(emailAccountId);
+  if (!actionIds?.length) {
+    await redis.del(key);
+    return;
+  }
</file context>

});
}

await enqueueProposedActions(emailAccount.id, actions);

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.

P1: Retried or concurrent approvals can enqueue the same side effects multiple times because the pending-status check is not an atomic claim and Redis unconditionally appends every action. Atomically claim the review item and deduplicate jobs with a stable key (for example item/action ID) before exposing them to the add-on.

(Based on your team's feedback about idempotent background job enqueue operations.)

View Feedback

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/web/app/api/thunderbird/inbox/route.ts, line 135:

<comment>Retried or concurrent approvals can enqueue the same side effects multiple times because the pending-status check is not an atomic claim and Redis unconditionally appends every action. Atomically claim the review item and deduplicate jobs with a stable key (for example item/action ID) before exposing them to the add-on.

(Based on your team's feedback about idempotent background job enqueue operations.) </comment>

<file context>
@@ -0,0 +1,147 @@
+    });
+  }
+
+  await enqueueProposedActions(emailAccount.id, actions);
+  const updated = await updateThunderbirdInboxItem(emailAccount.id, body.itemId, {
+    status: "approved",
</file context>

return NextResponse.json({ error: "Email account not found" }, { status: 404 });
}

const actions = await listThunderbirdActions(emailAccount.id);

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.

P1: Overlapping add-on polls can fetch and apply the same action before either acknowledgement arrives, duplicating side effects such as sent messages or drafts. Claim/lease actions atomically when serving GET (or serialize polling) so an action is invisible to concurrent workers before execution.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/web/app/api/thunderbird/actions/route.ts, line 43:

<comment>Overlapping add-on polls can fetch and apply the same action before either acknowledgement arrives, duplicating side effects such as sent messages or drafts. Claim/lease actions atomically when serving GET (or serialize polling) so an action is invisible to concurrent workers before execution.</comment>

<file context>
@@ -0,0 +1,66 @@
+    return NextResponse.json({ error: "Email account not found" }, { status: 404 });
+  }
+
+  const actions = await listThunderbirdActions(emailAccount.id);
+  return NextResponse.json({ emailAccountId: emailAccount.id, actions });
+});
</file context>

}
case "EXPIRE": {
const [key, seconds] = args;
setTimeout(() => {

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.

P1: Recently queued Thunderbird actions can disappear at the first action's original expiry instead of the latest refresh. Track and replace each key's pending expiry timer; clear it on DEL and on writes that remove TTL.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At scripts/local-upstash-stub.mjs, line 57:

<comment>Recently queued Thunderbird actions can disappear at the first action's original expiry instead of the latest refresh. Track and replace each key's pending expiry timer; clear it on `DEL` and on writes that remove TTL.</comment>

<file context>
@@ -0,0 +1,171 @@
+    }
+    case "EXPIRE": {
+      const [key, seconds] = args;
+      setTimeout(() => {
+        store.delete(key);
+        lists.delete(key);
</file context>

return result;
} catch (error) {
setStatus(String(error?.message || error));
throw error;

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.

P3: Failed popup commands render an error but also produce unhandled promise rejections, since every caller leaves run()'s rethrow unhandled. Return a failure value after updating status, or attach rejection handlers at each click handler.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/thunderbird-addon/popup.js, line 33:

<comment>Failed popup commands render an error but also produce unhandled promise rejections, since every caller leaves `run()`'s rethrow unhandled. Return a failure value after updating status, or attach rejection handlers at each click handler.</comment>

<file context>
@@ -0,0 +1,77 @@
+    return result;
+  } catch (error) {
+    setStatus(String(error?.message || error));
+    throw error;
+  } finally {
+    setBusy(false);
</file context>

],
"background": {
"scripts": ["background.js"],
"persistent": true

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.

P3: Remove the "persistent": true property from the background block. Thunderbird 115's Manifest V2 does not implement a persistent property on the background key — background scripts are always persistent by default, so this property is silently ignored. Omitting it avoids confusion and keeps the manifest aligned with supported keys.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/thunderbird-addon/manifest.json, line 31:

<comment>Remove the `"persistent": true` property from the background block. Thunderbird 115's Manifest V2 does not implement a `persistent` property on the `background` key — background scripts are always persistent by default, so this property is silently ignored. Omitting it avoids confusion and keeps the manifest aligned with supported keys.</comment>

<file context>
@@ -0,0 +1,41 @@
+  ],
+  "background": {
+    "scripts": ["background.js"],
+    "persistent": true
+  },
+  "options_ui": {
</file context>

}
});

server.listen(PORT, "127.0.0.1", () => {

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.

P3: IPv6-loopback local setups cannot reach this stub because the listener is hardcoded to IPv4. Make the bind host configurable (using the existing REDIS_HTTP_BIND_HOST convention) and document/select a loopback host compatible with the configured URL.

(Based on your team's feedback about local service connectivity across IPv4 and IPv6 loopback.)

View Feedback

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At scripts/local-upstash-stub.mjs, line 169:

<comment>IPv6-loopback local setups cannot reach this stub because the listener is hardcoded to IPv4. Make the bind host configurable (using the existing `REDIS_HTTP_BIND_HOST` convention) and document/select a loopback host compatible with the configured URL.

(Based on your team's feedback about local service connectivity across IPv4 and IPv6 loopback.) </comment>

<file context>
@@ -0,0 +1,171 @@
+  }
+});
+
+server.listen(PORT, "127.0.0.1", () => {
+  console.log(`Local Upstash stub on http://127.0.0.1:${PORT} (token=${TOKEN})`);
+});
</file context>

snippet: "Your invoice",
textPlain: "Please find your invoice attached.",
messageDate: new Date("2026-07-01T12:00:00.000Z"),
thunderbirdMessageId: 99,

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.

P3: reviewItemToParsedMessage derives historyId from thunderbirdMessageId (99 → "99"), but the test never asserts this derived field. Missing verification of a key mapping in the review-item path.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/web/utils/thunderbird/load-stored-message.test.ts, line 18:

<comment>`reviewItemToParsedMessage` derives `historyId` from `thunderbirdMessageId` (99 → "99"), but the test never asserts this derived field. Missing verification of a key mapping in the review-item path.</comment>

<file context>
@@ -0,0 +1,52 @@
+      snippet: "Your invoice",
+      textPlain: "Please find your invoice attached.",
+      messageDate: new Date("2026-07-01T12:00:00.000Z"),
+      thunderbirdMessageId: 99,
+      thunderbirdAccountId: "account1",
+    });
</file context>

});

expect(message.id).toBe("em-1");
expect(message.headers.from).toContain("news@example.com");

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.

P3: emailMessageToParsedMessage formats from as "${fromName} <${from}>", but the test uses .toContain("news@example.com"). A substring check passes even if the fromName formatting logic breaks (e.g., returns just the bare address). Use exact equality to confirm the full formatted string.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/web/utils/thunderbird/load-stored-message.test.ts, line 49:

<comment>`emailMessageToParsedMessage` formats `from` as `"${fromName} <${from}>"`, but the test uses `.toContain("news@example.com")`. A substring check passes even if the `fromName` formatting logic breaks (e.g., returns just the bare address). Use exact equality to confirm the full formatted string.</comment>

<file context>
@@ -0,0 +1,52 @@
+    });
+
+    expect(message.id).toBe("em-1");
+    expect(message.headers.from).toContain("news@example.com");
+    expect(message.labelIds).toContain("INBOX");
+  });
</file context>
Suggested change
expect(message.headers.from).toContain("news@example.com");
expect(message.headers.from).toBe("Weekly News <news@example.com>");

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

Morty Proxy This is a proxified and sanitized view of the page, visit original site.