Skip to content

feat(gmail): wire draft writeback to the Gmail drafts API - #257

Merged
khaliqgant merged 3 commits into
mainfrom
feat/gmail-drafts-writeback
Aug 3, 2026
Merged

feat(gmail): wire draft writeback to the Gmail drafts API#257
khaliqgant merged 3 commits into
mainfrom
feat/gmail-drafts-writeback

Conversation

@khaliqgant

@khaliqgant khaliqgant commented Aug 3, 2026

Copy link
Copy Markdown
Member

Problem

Gmail drafts were declared everywhere but dispatched nowhere. resources.ts, the discovery doc (.adapter.md: "Creates a Gmail draft."), the writeback-path catalog, and scripts/writeback-discovery-data.mjs (/gmail/drafts/new.json) all advertise a drafts resource — but resolveWritebackRequest collapsed every non-lifecycle path to resource: "threads" with the messages/{messageId}/modify endpoint, because parseRelayfilePath could only return object | lifecycle | unknown.

A draft create was therefore dispatched as a label modify.

queries.ts confirms it from the other direction: it declared exactly two provider actions (objectWritemessages/modify, lifecycleWritewatch). There was no drafts endpoint anywhere in the adapter.

Why this survived

scripts/storage-bridge-smoke.mjs appears to assert createOperation: "gmail.drafts.create". It doesn't. Its local helper was:

function resolveWriteback(item, operation) {
  return { providerOperation: operation === "delete" ? item.deleteOperation : item.createOperation, ... }
}

It returns the fixture's own expected value, so the assertion compared the fixture to itself and exercised no adapter code. integration.test.ts asserted only .operation === 'create' — never the action or endpoint, which is exactly where the bug lived.

Changes

  • queries.ts — declares the two missing actions: draftCreate (/gmail/v1/users/{account}/drafts) and draftWrite (/gmail/v1/users/{account}/drafts/{draftId}). The adapter already requests gmail.modify, which covers draft creation, so no scope or consent change is needed.
  • path-mapper.tsparseRelayfilePath recognizes a drafts resource under both addressing forms: rootless /gmail/drafts/<id>.json (writeback-discovery) and account-scoped /gmail/<account>/drafts/<id>.json (mounted), across canonical and legacy roots.
  • writeback.ts — drafts route to the drafts endpoints: create POSTs the collection, update PUTs the addressed draft (Gmail's drafts.update replaces, unlike messages.modify's PATCH), delete addresses the draft id. Thread and watch resolution are byte-for-byte unchanged.

Testing

  • New packages/gmail/src/writeback.test.ts pins action, method, and endpoint for drafts create/update/delete plus the unchanged thread and watch routes, and explicitly asserts a draft update does not land on objectWrite.
  • integration.test.ts now asserts the draft writeback's action, method, and endpoint.
  • scripts/storage-bridge-smoke.mjs — helper renamed to expectedWriteback and documented as a fixture-consistency check, pointing at the adapter test for real resolution. No behavior change.
packages/gmail: 22 tests, 22 pass, 0 fail   (typecheck clean)
npm run test:writeback-discovery  → Verified 101 writeback discovery endpoints.
npm run test:digest-contracts     → 11 category + 5 fallback + 11 regression contracts
node scripts/storage-bridge-smoke.mjs → 11 events, 11 writeback mappings, 6 Nango fallbacks

Not in this PR

Cloud must also declare the resource. cloud/packages/core/src/relayfile/provider-contracts.tsGOOGLE_MAIL_RESOURCES lists labels, filters, send-as, messages, threads, watch-renewals — no drafts — so drafts won't materialize until that side lands too. Companion PR to follow.

The /gmail vs /google-mail root is deliberately left alone. identity.ts documents a considered migration (canonicalWrites: canonical-only, legacyReads: supported, retireLegacyAfter: zero-references-and-explicit-cutover), and identity.test.ts pins it. Cloud still materializes /google-mail; that's the migration in progress, not a defect, so this PR doesn't touch it.

Context

Found while benchmarking mail surfaces (Gmail MCP vs Superhuman MCP vs relayfile google-mail) for a set of personal-assistant personas. The claude.ai Gmail connector can't archive (missing modify scope) and relayfile couldn't draft — this closes the relayfile half.

🤖 Generated with Claude Code

Review in cubic

Draft writes were declared everywhere but dispatched nowhere. `resources.ts`,
the discovery doc, the writeback-path catalog, and
`scripts/writeback-discovery-data.mjs` all advertise `/gmail/drafts`, but
`resolveWritebackRequest` collapsed every non-lifecycle path to
`resource: "threads"` with the `messages/{messageId}/modify` endpoint —
`parseRelayfilePath` could only return `object | lifecycle | unknown`. A draft
create was therefore dispatched as a label modify.

Changes:

- `queries.ts` declares the two missing provider actions: `draftCreate`
  (`/gmail/v1/users/{account}/drafts`) and `draftWrite`
  (`/gmail/v1/users/{account}/drafts/{draftId}`). The existing `gmail.modify`
  scope already covers drafts, so no consent change is required.
- `parseRelayfilePath` recognizes a `drafts` resource under both addressing
  forms — rootless `/gmail/drafts/<id>.json` (writeback-discovery) and
  account-scoped `/gmail/<account>/drafts/<id>.json` (mounted) — across the
  canonical and legacy roots.
- `resolveWritebackRequest` routes drafts to the drafts endpoints: create POSTs
  to the collection, update PUTs the addressed draft (Gmail's drafts.update
  replaces, unlike messages.modify's PATCH), delete addresses the draft id.
  Thread and watch resolution are unchanged.

Testing:

- New `packages/gmail/src/writeback.test.ts` pins action, method, and endpoint
  for drafts create/update/delete plus the unchanged thread and watch routes,
  and asserts a draft update does NOT land on `objectWrite`.
- `integration.test.ts` now asserts the draft writeback's action, method, and
  endpoint rather than only its operation.
- `scripts/storage-bridge-smoke.mjs`: renamed `resolveWriteback` to
  `expectedWriteback` and documented that it is a fixture-consistency check.
  It returned `item.createOperation` verbatim, so its
  `createOperation: "gmail.drafts.create"` assertion compared the fixture to
  itself and exercised no adapter code — which is why this gap survived.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@cursor

cursor Bot commented Aug 3, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

@coderabbitai

coderabbitai Bot commented Aug 3, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@khaliqgant, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 21 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 044ae7d8-760d-42dd-94f6-6b5e44350352

📥 Commits

Reviewing files that changed from the base of the PR and between 3c4c846 and 3a817b9.

📒 Files selected for processing (7)
  • docs/migration/file-native-writeback.md
  • packages/gmail/discovery/gmail/.adapter.md
  • packages/gmail/src/path-mapper.ts
  • packages/gmail/src/resources.ts
  • packages/gmail/src/writeback.test.ts
  • packages/gmail/src/writeback.ts
  • scripts/writeback-discovery-normalizer.mjs
📝 Walkthrough

Walkthrough

Gmail writeback now supports draft creation, updates, and deletion. Draft paths are recognized across supported root forms. Routing selects draft-specific endpoints and methods while preserving thread and watch behavior.

Changes

Gmail draft writeback

Layer / File(s) Summary
Draft path and endpoint contracts
packages/gmail/src/path-mapper.ts, packages/gmail/src/queries.ts
Draft paths are classified as drafts. Gmail draft create and write endpoints are defined.
Resource-aware writeback routing
packages/gmail/src/writeback.ts
Drafts use collection creation, draft-specific update and delete endpoints, POST for creation, and PUT for updates. Threads and watches retain their existing routing.
Writeback validation and supporting updates
packages/gmail/src/writeback.test.ts, packages/gmail/src/__tests__/integration.test.ts, scripts/storage-bridge-smoke.mjs, CHANGELOG.md
Tests cover draft operations, regression behavior, request bodies, and read-only fields. Smoke fixtures and the changelog reflect the updated contract.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Writeback as writeback.ts
  participant PathMapper as parseRelayfilePath
  participant Queries as providerQueries
  Writeback->>PathMapper: parse Gmail resource path
  PathMapper-->>Writeback: return drafts, threads, or lifecycle resource
  Writeback->>Queries: resolve resource-specific endpoint
  Queries-->>Writeback: return draftCreate or draftWrite endpoint
Loading

Possibly related PRs

Poem

I’m a rabbit with drafts in a row,
POST makes new ones grow.
PUT updates, DELETE clears,
Threads and watches keep their gears.
Gmail paths now guide the way—
Hop, hop, write back today!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 20.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main change: Gmail draft writeback through the Gmail drafts API.
Description check ✅ Passed The description directly explains the draft writeback bug, implementation changes, testing, and scope of the pull request.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/gmail-drafts-writeback

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/gmail/src/writeback.test.ts`:
- Around line 11-24: Remove the magic “new.json” create convention from the
draft resolver and its test. Update the draft create/update logic used by
resolveWritebackRequest so any non-canonical filename in the resource directory
resolves to a create, while canonical draft paths continue resolving as updates.
Replace the “new.json” test case with a neutral non-canonical filename and
preserve the expected POST/create assertions.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 291b425e-cc41-4737-8124-78185b42a21b

📥 Commits

Reviewing files that changed from the base of the PR and between e29ee3a and 3c4c846.

📒 Files selected for processing (7)
  • CHANGELOG.md
  • packages/gmail/src/__tests__/integration.test.ts
  • packages/gmail/src/path-mapper.ts
  • packages/gmail/src/queries.ts
  • packages/gmail/src/writeback.test.ts
  • packages/gmail/src/writeback.ts
  • scripts/storage-bridge-smoke.mjs

Comment thread packages/gmail/src/writeback.test.ts Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3c4c846efe

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

export const RELAYFILE_ROOT = GMAIL_PATH_ROOT;
export const OBJECT_RESOURCE_PATH = `${RELAYFILE_ROOT}/{account}/threads`;
export const LIFECYCLE_RESOURCE_PATH = `${RELAYFILE_ROOT}/watches`;
export const DRAFTS_RESOURCE_PATH = `${RELAYFILE_ROOT}/drafts`;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Add a typed draft path composer

The new draft support exports only a collection constant, so consumers still have to hand-build both /gmail/drafts/<id>.json and /gmail/<account>/drafts/<id>.json; even the added tests use literal paths. Export a typed draft record-path helper and cover compose-to-parse round trips so the two accepted shapes cannot drift from the parser.

AGENTS.md reference: AGENTS.md:L5-L7

Useful? React with 👍 / 👎.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

All reported issues were addressed across 7 files

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread packages/gmail/src/path-mapper.ts
Review feedback: the draft resolver inherited the legacy reserved-prefix
heuristic (`draft|create|new|upload|tmp|temp`) plus a permissive
`^[A-Za-z0-9_-]+$` canonical check. A neutral filename that matched neither —
`ask-storebrand.json` — resolved as canonical and issued a PUT for a draft id
that does not exist. Routing drafts to the real drafts endpoint turned that
latent misclassification into a live bad request.

Drafts now follow the file-native writeback contract: a filename matching the
resource's declared `idPattern` edits that draft, anything else creates one, and
`new.json` has no special privilege.

- `writeback-discovery-normalizer.mjs` declares gmail's drafts id pattern as
  `^r-?\d+$` — the real Gmail draft id shape (`r-4692061400304996596`). The
  permissive default classified prose filenames as canonical. Regenerated
  `resources.ts` and `.adapter.md` from it; no other adapter's artifacts are
  touched.
- `resolveWritebackRequest` reads canonicality from `resources.ts` for drafts.
  Threads and watches keep the legacy heuristic: gmail is absent from the
  file-native migration table, and changing their create/update split would
  alter behavior this change has no reason to touch.
- `path-mapper.ts` exports `toDraftRelayfilePath({ account?, id })` so both
  accepted shapes are composed, not hand-built (AGENTS.md adapter contract).
  Account segments keep a literal `@`; percent-encoding it would compose a path
  that never matches a mounted file.

Tests: `new.json` replaced with neutral non-canonical names (including one with
a space, per the migration doc); added canonical-id-implies-update, a
compose-to-parse round trip for both path shapes, and direct id-pattern
coverage. 25 tests pass, typecheck clean.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@khaliqgant

Copy link
Copy Markdown
Member Author

Addressed both review comments in a630b2e.

CodeRabbit — magic new.json create convention (Major). Confirmed and fixed at the source, not just in the test. The draft resolver inherited the legacy reserved-prefix heuristic (draft|create|new|upload|tmp|temp) plus a permissive ^[A-Za-z0-9_-]+$ canonical check, so a neutral filename matching neither — ask-storebrand.json — resolved as canonical and issued a PUT for a draft id that does not exist. Routing drafts to the real drafts endpoint is what turned that latent misclassification into a live bad request, so it belongs in this PR.

Drafts now derive create vs update from the resource's declared idPattern:

  • scripts/writeback-discovery-normalizer.mjs declares gmail's drafts pattern as ^r-?\d+$ — the actual Gmail draft id shape (r-4692061400304996596). The permissive default classified prose filenames as canonical, which is the root cause. resources.ts and .adapter.md are regenerated from it; no other adapter's artifacts are touched.
  • resolveWritebackRequest reads canonicality from resources.ts, so the resolver and the published discovery docs cannot drift.
  • Tests drop new.json for neutral non-canonical names — including one with a space, per the migration doc's safe example — and add a canonical-id-implies-update case.

Scope note: threads and watches keep the legacy heuristic. gmail is absent from the file-native migration table in docs/migration/file-native-writeback.md, and switching their create/update split would change behavior this PR has no reason to touch (it flips /gmail/{account}/threads/draft.json from create to update, which an existing test pins). Migrating gmail's remaining resources is a separate change — happy to open it if you want it.

cubic — typed draft path composer (P1). Added toDraftRelayfilePath({ account?, id }), exported from path-mapper.ts, composing both accepted shapes so consumers stop hand-building them (AGENTS.md adapter contract). The tests now use it, with a compose-to-parse round trip covering both shapes so they can't drift from the parser.

One real bug fell out of writing it: encodePathSegment percent-encodes @, so the composer produced /gmail/me%40example.com/drafts/…, which never matches a mounted file (the tree materializes a literal @). Account segments now preserve it.

packages/gmail: 25 tests, 25 pass, 0 fail — typecheck clean
node scripts/storage-bridge-smoke.mjs → 11 events, 11 writeback mappings, 6 Nango fallbacks
npm run test:digest-contracts → 11 category + 5 fallback + 11 regression contracts

npm run test:writeback-discovery reports three failures on this branch — jira: /jira/projects/.schema.json system field url must be readOnly and two dropbox equivalents. They reproduce identically on a clean main, so they're pre-existing and untouched here.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

1 issue found across 6 files (changes from recent commits).

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="packages/gmail/src/resources.ts">

<violation number="1" location="packages/gmail/src/resources.ts:23">
P1: Existing drafts whose provider ID falls outside this undocumented numeric format will now be treated as create filenames, so saving them POSTs a duplicate rather than updating the draft. Avoid inferring canonicality from a private Gmail ID shape; carry an explicit operation/provider-ID marker, or preserve a compatible identifier strategy.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

path: "/gmail/drafts",
pathPattern: /^\/gmail\/drafts(?:\/[^\/]+(?:\.json)?)?$/,
idPattern: /^[A-Za-z0-9_.:-]+$/,
idPattern: /^r-?\d+$/,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1: Existing drafts whose provider ID falls outside this undocumented numeric format will now be treated as create filenames, so saving them POSTs a duplicate rather than updating the draft. Avoid inferring canonicality from a private Gmail ID shape; carry an explicit operation/provider-ID marker, or preserve a compatible identifier strategy.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/gmail/src/resources.ts, line 23:

<comment>Existing drafts whose provider ID falls outside this undocumented numeric format will now be treated as create filenames, so saving them POSTs a duplicate rather than updating the draft. Avoid inferring canonicality from a private Gmail ID shape; carry an explicit operation/provider-ID marker, or preserve a compatible identifier strategy.</comment>

<file context>
@@ -20,7 +20,7 @@ export const resources = [
     path: "/gmail/drafts",
     pathPattern: /^\/gmail\/drafts(?:\/[^\/]+(?:\.json)?)?$/,
-    idPattern: /^[A-Za-z0-9_.:-]+$/,
+    idPattern: /^r-?\d+$/,
     schema: "discovery/gmail/drafts/.schema.json",
     createExample: "discovery/gmail/drafts/.create.example.json",
</file context>

Comment thread packages/gmail/src/path-mapper.ts Outdated
Comment thread packages/gmail/src/path-mapper.ts Outdated
…d pattern

Review feedback on the composer and the canonical pattern.

- `encodeSingleSegment` replaces the slash-preserving encoder for draft stems
  and account segments. `encodePathSegment` restores `%2F` to `/`, so a stem
  like `q3/budget reply` expanded into two segments and shifted the id the
  parser reads off the end of the path — a write or delete could address a
  different draft. An account containing `/` was worse: the extra segment made
  the parser classify the path as `object`, misrouting the writeback away from
  drafts entirely. `@` is still restored for accounts so composed paths match
  the mounted tree.
- Documented the drafts id pattern in the file-native migration table, scoped
  explicitly to drafts, and noted in `writeback.ts` that inference is a default
  a caller can bypass by passing `operation` outright.

Tests pin both slash cases end to end: composition, parse-back, and the
resulting writeback operation.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@khaliqgant

Copy link
Copy Markdown
Member Author

Second round addressed in 3a817b9.

cubic P2 — slash in a draft stem (path-mapper.ts:74). Valid, fixed. encodePathSegment restores %2F to /, so q3/budget reply expanded into two segments and shifted the id the parser reads off the end of the path — a write or delete could have addressed a different draft. Draft stems now go through a strict encodeSingleSegment.

cubic P2 — slash in an account (path-mapper.ts:83). Valid, and worse than the suggestion implies: the extra segment made the parser classify the path as object, misrouting the writeback away from drafts entirely. Same fix rather than a post-hoc .replace(/\//g, '%2F') — both segments now build on one strict encoder, with @ restored only for accounts so composed paths still match the mounted tree. Both cases are pinned end to end (compose → parse-back → resolved operation).

cubic P1 — inferring canonicality from the Gmail id shape (resources.ts:23). Partially taken. The failure mode you describe is real, but I don't think the fix is to stop inferring:

  • Pattern-based canonicality is this repo's contract, not something introduced here. Every file-native adapter does it, and several from shapes just as provider-private: granola ^not_[A-Za-z0-9]{14}$, salesforce ^[A-Za-z0-9]{15}(?:[A-Za-z0-9]{3})?$, google-calendar ^[a-v0-9]{5,1024}$, github ^\d+$. Carrying an explicit operation marker instead would make gmail the one adapter that doesn't follow docs/migration/file-native-writeback.md.
  • The escape hatch already exists. resolveWritebackRequest(path, content, operation) takes an explicit operation that bypasses inference entirely — now called out in the code comment.
  • "Undocumented" was fair, and is fixed. Gmail now has a row in the migration table with the id shape spelled out and scoped explicitly to drafts.
  • Direction of failure. Widening the pattern doesn't remove the risk, it swaps it: the permissive default is what let ask-storebrand.json resolve as canonical and PUT a draft that doesn't exist — the Major finding in the previous round. A narrow pattern fails toward "create a draft the user then sees", a wide one fails toward "silently target the wrong id".
  • No drafts materialize today (cloud has no drafts resource — cloud#2892 adds it write-only), so there is no existing canonical draft file whose stem could be misread. If drafts later materialize with an id outside ^r-?\d+$, that's a one-line pattern change in writeback-discovery-normalizer.mjs plus a regen — and I'd rather widen it against a real id than guess wider now.

Happy to switch to an explicit marker if you'd rather gmail diverge from the shared contract — just say so and I'll change it.

cubic P3 (DRAFTS_RESOURCE_PATH unused) and codex P1 (typed composer) were both resolved in a630b2e — the constant is now consumed by toDraftRelayfilePath.

packages/gmail: 26 tests, 26 pass, 0 fail — typecheck clean
node scripts/storage-bridge-smoke.mjs → 11 events, 11 writeback mappings, 6 Nango fallbacks

@khaliqgant
khaliqgant merged commit 5a4af89 into main Aug 3, 2026
3 checks passed
@khaliqgant
khaliqgant deleted the feat/gmail-drafts-writeback branch August 3, 2026 07:35
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.

1 participant