Skip to content

feat(newsletter): complete the upstream newsletter surface - #30

Merged
jlucaso1 merged 4 commits into
mainfrom
feat/newsletter-compat
Aug 8, 2026
Merged

jlucaso1 merged 4 commits into
mainfrom
feat/newsletter-compat

Conversation

@jlucaso1

@jlucaso1 jlucaso1 commented Aug 8, 2026 •

Copy link
Copy Markdown
Member

Summary

Fifteen methods that did not exist, plus the three signatures that did not match, all delegating to the bridge. The neutral to Baileys translation lives in src/Compatibility/newsletter-results.ts.

New: newsletterAdminCount, newsletterChangeOwner, newsletterDelete, newsletterDemote, newsletterFetchMessages, newsletterFollow, newsletterRemovePicture, newsletterSubscribers, newsletterUnfollow, newsletterUnmute, newsletterUpdate, newsletterUpdateDescription, newsletterUpdateName, newsletterUpdatePicture, subscribeNewsletterUpdates.

Breaking

Three public shapes changed. Pre-1.0, and each is a fix rather than a preference.

newsletterMetadata(jid) becomes newsletterMetadata(type, key). This is the one that matters. Code written against upstream calls newsletterMetadata('jid', id), and the old signature read that as a newsletter whose JID is the literal string 'jid'. It type checked on the caller's side and failed against the server, which is the worst kind of incompatibility and why it outranks any missing method.

Migration: newsletterMetadata(jid) becomes newsletterMetadata('jid', jid).

A real gain comes with it: the 'invite' path now works. The core has a lookup per key kind (newsletterMetadata, newsletterMetadataByInvite), and choosing between them on a discriminator is exactly the translation this layer exists for.

newsletterMute(jid, mute) splits into newsletterMute(jid) and newsletterUnmute(jid), as upstream has them.

Migration: newsletterMute(jid, true) becomes newsletterMute(jid); newsletterMute(jid, false) becomes newsletterUnmute(jid).

Both route to newsletterFollowerMute, not to the bridge's ambiguous newsletterMute alias. The core has two newsletter mutes: follower activity, which is what a subscriber toggles, and admin activity, which is a different control for owners and admins. Upstream's is the subscriber one, so the explicit spelling is used to make that choice visible instead of resting on an alias.

newsletterCreate returns NewsletterMetadata in upstream's field names, not the bridge's NewsletterMetadataResult. Field renames: jid to id, subscriberCount to subscribers, inviteCode to invite, creationTime to creation_time, pictureUrl to picture.url.

Layer classification

Every item is (a), implementable here by delegation. Nothing needed the core or the bridge, and no gap prompt was opened. That matches the prompt's expectation after bridge 0.7.0.

upstream bridge note
newsletterCreate newsletterCreate result mapped
newsletterMetadata('jid'|'invite', key) newsletterMetadata / newsletterMetadataByInvite dispatch on the discriminator
newsletterUpdate newsletterUpdate + newsletterSetPicture / newsletterRemovePicture see picture note
newsletterUpdateName / newsletterUpdateDescription newsletterUpdate(jid, name, description) one field, other null
newsletterUpdatePicture newsletterSetPicture via generateProfilePicture
newsletterRemovePicture newsletterRemovePicture
newsletterFollow / newsletterUnfollow newsletterSubscribe / newsletterUnsubscribe
newsletterMute / newsletterUnmute newsletterFollowerMute(jid, bool)
newsletterSubscribers newsletterMetadata(jid).subscriberCount upstream returns a count, not a list
newsletterFetchMessages newsletterMessages see refusal note
subscribeNewsletterUpdates newsletterSubscribeLiveUpdates number wrapped as upstream's { duration: string }
newsletterAdminCount newsletterAdminInfo(jid).adminCount a field, not an operation; see note
newsletterChangeOwner newsletterChangeOwner
newsletterDemote newsletterDemoteAdmin
newsletterDelete newsletterDelete

Three places the shapes do not line up

newsletterAdminCount when the server omits the count. It rides on the admin-info result and is optional there. It rejects rather than returning 0, because 0 reads as "no admins", which no newsletter can be, and a caller acting on that would draw the wrong conclusion silently.

newsletterFetchMessages(jid, count, since, after) refuses since and after by name. I checked the core rather than mapping by resemblance: get_messages builds <messages count before> and has no time filter. Upstream's after pages forward from a point; the core's before pages backward from one. They are opposite directions, so mapping after onto before would return a plausible wrong page, which is worse than refusing. count works, which covers the common call.

Four NewsletterMetadata fields have no source and stay absent: owner (the bridge result carries the viewer's own role, not the owner's JID), mute_state, reaction_codes, thread_metadata. state was the tempting wrong answer for mute_state: it is the newsletter's lifecycle, Active/Suspended/Geosuspended, not a mute.

This also settles a question left open in #26. The bridge sends verification in the core's camel case (Verified), while upstream's type is 'VERIFIED' | 'UNVERIFIED'. The mapper writes the pairs out rather than upper-casing blindly, so a variant the core adds later arrives as undefined instead of as a string nobody declared.

Deferred

Nothing. No gap prompt was written for the bridge or the core.

Out of scope by the task's own boundary and untouched: channel directory, recommendations, insights, admin invites, pinning a message.

Coverage

npm run compat:audit before: 97.15% (21717/22354)
after: 97.34% (21759/22354)

Missing dropped sharply: functions 243 to 198, fields 235 to 190.

Mismatches rose (functions 70 to 94, fields 27 to 51) and that needs an honest explanation rather than being left to look like damage. They are the return types, and every one is stronger than upstream's:

newsletterFollow    expected (jid: string) => Promise<unknown>
                    actual   (jid: string) => Promise<NewsletterMetadata>
newsletterUnfollow  expected (jid: string) => Promise<unknown>
                    actual   (jid: string) => Promise<void>

Upstream types most of these Promise<unknown> because its MEX executor returns an unparsed result. Returning the parsed metadata is assignable to unknown, so code written against upstream compiles and runs unchanged; only the auditor's string comparison counts it as a difference. newsletterFetchMessages is the same story with Promise<any> against Promise<NewsletterMessageResult[]>, plus optional-vs-required on the two parameters it refuses.

Making these Promise<unknown> would score better and be worse to use. Leaving them is the deliberate choice; the signature-mismatch task is where this class of item gets triaged in bulk.

Validation

  • npm run format
  • npm run lint (tsc plus oxlint)
  • npm test: 698 tests, 0 failures, up from 666, with no existing test adapted
  • npm run compat:audit

Not run: npm run test:e2e, which needs a mock server this environment does not have.

Mutation checked by making the edit and running the suite, not by reading the assertions:

  • making newsletterMetadata ignore type and always look up by JID fails 1 test
  • letting newsletterAdminCount fall back to 0 fails 1 test
  • mapping after onto the before cursor instead of refusing fails 1 test

Generated by Claude Code


Summary by cubic

Completes the upstream newsletter API: adds 15 missing methods, fixes 3 signatures, and aligns results to upstream types. Keeps legacy subscribe/unsubscribe, optimizes picture-only updates, and rejects unsupported paging flags.

  • New Features

    • Added: newsletterAdminCount, newsletterChangeOwner, newsletterDelete, newsletterDemote, newsletterFetchMessages, newsletterFollow, newsletterRemovePicture, newsletterSubscribers, newsletterUnfollow, newsletterUnmute, newsletterUpdate, newsletterUpdateDescription, newsletterUpdateName, newsletterUpdatePicture, subscribeNewsletterUpdates.
    • Map bridge metadata to upstream in src/Compatibility/newsletter-results.ts; support newsletterMetadata('invite', code).
    • newsletterFetchMessages rejects non-zero since/after (zeros pass), subscribeNewsletterUpdates returns { duration: string }, newsletterAdminCount throws if the server omits the count.
    • newsletterUpdate({ picture }) skips an extra metadata read; picture writes already return refreshed metadata.
  • Migration

    • newsletterMetadata(jid) → newsletterMetadata('jid', jid); also enables newsletterMetadata('invite', code).
    • newsletterMute(jid, true|false) → newsletterMute(jid) and newsletterUnmute(jid) (maps to follower-activity mute).
    • newsletterCreate now returns upstream NewsletterMetadata field names: jid→id, subscriberCount→subscribers, inviteCode→invite, creationTime→creation_time, pictureUrl→picture.url.
    • No change if you use newsletterSubscribe/newsletterUnsubscribe; they remain. newsletterSubscribe keeps the bridge result shape; use newsletterFollow for upstream field names.

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

@coderabbitai

coderabbitai Bot commented Aug 8, 2026 •

Copy link
Copy Markdown

Warning

Review limit reached

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

Next review available in: 23 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

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: ASSERTIVE

Plan: Pro Plus

Run ID: 3cdb4f3c-48aa-4d02-9099-b244c45830fa

📥 Commits

Reviewing files that changed from the base of the PR and between c95a6a0 and e5b5be2.

📒 Files selected for processing (3)
  • src/Compatibility/newsletter-results.ts
  • src/Socket/newsletter.ts
  • src/__tests__/newsletter-compatibility.test.ts

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.

@greptile-apps

greptile-apps Bot commented Aug 8, 2026 •

Copy link
Copy Markdown

Greptile Summary

The PR expands the newsletter compatibility layer to match the upstream API while translating bridge-specific inputs and results.

  • Adds newsletter administration, subscription, message retrieval, metadata, mute, and update methods.
  • Introduces a centralized bridge-to-upstream metadata mapper.
  • Adds compatibility tests covering delegation, result translation, unsupported pagination arguments, and legacy aliases.

Confidence Score: 5/5

The PR appears safe to merge because no blocking failure remains.

No blocking failure remains.

Important Files Changed

Filename Overview
src/Compatibility/newsletter-results.ts Adds explicit translation of bridge newsletter metadata fields and enum values into the upstream-compatible shape.
src/Socket/newsletter.ts Expands the newsletter socket API, dispatches key-specific and picture operations, preserves legacy aliases, and translates results.
src/tests/newsletter-compatibility.test.ts Adds broad unit coverage for newsletter delegation, compatibility signatures, result mapping, unsupported arguments, and error propagation.

Sequence Diagram

sequenceDiagram
    participant App
    participant Compatibility as Newsletter compatibility layer
    participant Bridge
    App->>Compatibility: Upstream newsletter method
    Compatibility->>Compatibility: Translate arguments
    Compatibility->>Bridge: Delegate bridge operation
    Bridge-->>Compatibility: Bridge result
    Compatibility->>Compatibility: Map metadata/result shape
    Compatibility-->>App: Upstream-compatible result
Loading

Reviews (4): Last reviewed commit: "fix(newsletter): keep the legacy subscri..." | Re-trigger Greptile

greptile-apps[bot]
greptile-apps Bot previously approved these changes Aug 8, 2026

@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 3 files

Confidence score: 3/5

  • In src/Socket/newsletter.ts, validation currently treats since=0 and after=0 as absent, so unsupported cursor/epoch inputs can be silently ignored instead of failing as documented, which risks inconsistent client behavior and harder-to-debug pagination/sync bugs—check argument presence with !== undefined so any supplied unsupported value is rejected.
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="src/Socket/newsletter.ts">

<violation number="1" location="src/Socket/newsletter.ts:102">
P2: Epoch `since=0` and cursor `after=0` are silently dropped instead of rejected; test for presence (`!== undefined`) so every supplied unsupported argument fails as documented.</violation>
</file>

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

Re-trigger cubic

Comment thread src/Socket/newsletter.ts
newsletterMute: async (jid: string, mute: boolean) => {
await (await ctx.getClient()).newsletterMute(jid, mute)
newsletterFetchMessages: async (jid: string, count: number, since?: number, after?: number) => {
if (since) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: Epoch since=0 and cursor after=0 are silently dropped instead of rejected; test for presence (!== undefined) so every supplied unsupported argument fails as documented.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/Socket/newsletter.ts, line 102:

<comment>Epoch `since=0` and cursor `after=0` are silently dropped instead of rejected; test for presence (`!== undefined`) so every supplied unsupported argument fails as documented.</comment>

<file context>
@@ -1,31 +1,147 @@
-	newsletterMute: async (jid: string, mute: boolean) => {
-		await (await ctx.getClient()).newsletterMute(jid, mute)
+	newsletterFetchMessages: async (jid: string, count: number, since?: number, after?: number) => {
+		if (since) {
+			throw new Boom('newsletterFetchMessages: `since` is not supported, the message query carries no time filter', {
+				statusCode: 400
</file context>

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Keeping the truthiness check, and the code now says why rather than leaving it to be read as an oversight.

For after, upstream itself uses the same guard: newsletter.js:130 is if (after) { messageUpdateAttrs.after = after.toString() }, so upstream also treats 0 as no cursor. Rejecting it would refuse a call upstream accepts.

For since, upstream does include it whenever it is a number (if (typeof since === 'number')), so 0 reaches the wire there. But since is a unix timestamp and 0 is the epoch, which filters nothing: the unfiltered query this delegates to returns the same set. So the intent is met, and by the rule the rest of this series follows, that is a no-op rather than a rejection.

What this protects is the common (jid, count, 0, 0) call, which is what a caller writes when the signature makes both required and they want neither. A non-zero value in either still rejects, since mapping after onto the core's backward before cursor would page the wrong direction and return a plausible wrong answer. Three tests cover the two rejections and the zero case.


Generated by Claude Code

Comment thread src/Socket/newsletter.ts Outdated
@greptile-apps
greptile-apps Bot dismissed their stale review August 8, 2026 21:25

Dismissed because a newer commit was pushed; Greptile will re-review the current head.

jlucaso1 commented Aug 8, 2026

Copy link
Copy Markdown
Member Author

One fixed, one answered.

The wasted metadata read is real (a08691e). A picture-only update fetched the metadata and then discarded it, because every picture write already answers with the refreshed metadata. The read now happens only when the delta asked for nothing at all and there is otherwise nothing to return. Three tests pin it: picture-only makes one call, empty-picture-means-remove makes one call, and an empty delta still reads.

The zero handling is deliberate, and I have made the comment say so. Switching to !== undefined would reject newsletterFetchMessages(jid, count, 0, 0), which is a very ordinary call meaning "no time filter, no cursor" — and that is exactly what the unfiltered query already does. So the caller's intent is met, and refusing would break a working call to enforce a rule about arguments that are not actually asking for anything.

It is the same distinction that came up on #28 and that I now apply across this series: reject when the caller's intent cannot be met, do nothing when it already is. since: 1700000000 cannot be met and rejects; since: 0 is met by doing nothing. Added a test for the (jid, count, 0, 0) call so the allowance is pinned rather than incidental.

npm test 701 passing, lint clean.


Generated by Claude Code

greptile-apps[bot]
greptile-apps Bot previously approved these changes Aug 8, 2026

@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: a08691eba2

ℹ️ 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".

Comment thread src/Socket/newsletter.ts
Comment on lines +69 to +73
newsletterFollow: async (jid: string): Promise<NewsletterMetadata> => {
return bridgeNewsletterMetadataToBaileys(await (await ctx.getClient()).newsletterSubscribe(jid))
},

newsletterUnfollow: async (jid: string): Promise<void> => {

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 Preserve the existing subscribe and unsubscribe methods

Existing baileyrs clients that call the previously public newsletterSubscribe or newsletterUnsubscribe methods now receive a missing-method error because these entries were replaced by newsletterFollow/newsletterUnfollow. The same bridge operations remain available, so retain the old method names as compatibility aliases rather than silently removing them; unlike the other documented breaking signatures, this removal has no stated migration.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Valid, and restored. Removing them was accidental rather than a decision: the rename to newsletterFollow/newsletterUnfollow took the old names with it, and unlike the signature changes this PR does document, that one had no migration behind it.

Both are back, delegating to the same bridge operations, with the follow-up in the thread below making newsletterSubscribe return the bridge result unmapped so the shape does not shift either.


Generated by Claude Code

@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.

0 issues found across 2 files (changes from recent commits).

Confidence score: 5/5

  • Automated review surfaced no issues in the provided summaries.
  • No files require special attention.

Requires human review: Auto-approval blocked by 1 unresolved issue from previous reviews.

Re-trigger cubic

@greptile-apps
greptile-apps Bot dismissed their stale review August 8, 2026 21:33

Dismissed because a newer commit was pushed; Greptile will re-review the current head.

jlucaso1 commented Aug 8, 2026

Copy link
Copy Markdown
Member Author

Correct, and fixed in 217c6ec. Both are back, delegating to the same bridge calls.

Adding upstream's newsletterFollow/newsletterUnfollow had replaced the two names this package already shipped. That is a removal nobody asked for, and unlike the three signature changes in this PR it had no migration note because I had not noticed it was a removal at all. The other breaks are deliberate and documented; this one was accidental.


Generated by Claude Code

greptile-apps[bot]
greptile-apps Bot previously approved these changes Aug 8, 2026

@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.

0 issues found across 2 files (changes from recent commits).

Confidence score: 5/5

  • Automated review surfaced no issues in the provided summaries.
  • No files require special attention.

Requires human review: Auto-approval blocked by 1 unresolved issue from previous reviews.

Re-trigger cubic

@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: 217c6ec3fb

ℹ️ 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".

Comment thread src/Socket/newsletter.ts Outdated
Comment on lines +81 to +82
newsletterSubscribe: async (jid: string): Promise<NewsletterMetadata> => {
return bridgeNewsletterMetadataToBaileys(await (await ctx.getClient()).newsletterSubscribe(jid))

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 Preserve the legacy subscribe result shape

Although newsletterSubscribe has now been restored, this revision newly transforms its result from the bridge's NewsletterMetadataResult into NewsletterMetadata. Existing callers of this legacy method that read fields such as result.jid, result.subscriberCount, or result.inviteCode will now receive undefined because the mapper renames them to id, subscribers, and invite; keep the raw bridge result for this compatibility alias and reserve the mapped shape for the new newsletterFollow method.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Valid, fixed. newsletterSubscribe kept its name while quietly changing its shape, which is the worse half of a rename: the callers it was restored for read jid, subscriberCount and inviteCode, and the mapper renames all three.

It now returns the bridge result untouched, typed NewsletterMetadataResult. newsletterFollow stays the one that speaks upstream's names, so the two names mean two shapes on purpose rather than by accident.

Two tests pin it: one asserting the alias returns the bridge result verbatim, one asserting newsletterFollow answers with id/subscribers. Checked by putting the mapper back and watching the first fail.


Generated by Claude Code

claude added 4 commits August 8, 2026 21:53
Fifteen methods that did not exist, and three signatures that did not
match, all delegating to the bridge. The neutral to Baileys translation
lives in `src/Compatibility/newsletter-results.ts`.

`newsletterMetadata` is the one worth reading twice. Upstream takes
`(type, key)`; this package took `(jid)`. Code written against upstream
calls `newsletterMetadata('jid', id)`, and the old signature read that as
a newsletter whose JID is the literal string `'jid'`. It type checked on
the caller's side and failed against the server. The `'invite'` path now
works rather than being unreachable, because the core has a lookup per
key kind and the dispatch belongs in this layer.

`newsletterMute(jid, boolean)` splits into upstream's `newsletterMute` and
`newsletterUnmute`. Both go to `newsletterFollowerMute`, which is the mute
a subscriber toggles; the core's other newsletter mute is for admin
activity and is a different control, so the ambiguous alias is avoided.

Three places where the shapes genuinely do not line up, each answered
rather than papered over:

`newsletterAdminCount` rides on the admin-info result and the server omits
it. Absent is reported as absent, because `0` here reads as "no admins",
which no newsletter can be.

`newsletterFetchMessages` takes `since` and `after`; the core's query
carries no time filter and pages backward from a `before` cursor. Mapping
`after` onto `before` would page the other direction and return a
plausible wrong answer, so either argument is refused by name.

`NewsletterMetadata` has four fields the bridge result has no source for:
`owner`, `mute_state`, `reaction_codes` and `thread_metadata`. They stay
absent. `state` is the newsletter's lifecycle, not a mute, and filling
`mute_state` from it would have been the easy wrong answer.
…date

`newsletterUpdate({picture})` fetched the metadata first and then threw
the result away, because every picture write already answers with the
refreshed metadata. The read now happens only when the delta asked for
nothing at all and there is otherwise nothing to return.

Also says why a zero `since` or `after` is allowed through rather than
refused: the epoch and no-cursor are what the unfiltered query already
does, so `(jid, count, 0, 0)` is a caller asking for nothing special, not
a caller asking for something we cannot do.
Adding upstream's `newsletterFollow` and `newsletterUnfollow` replaced
the two names this package already had, which is a removal nobody asked
for and the only one in this PR without a migration note. Both are back,
delegating to the same bridge calls.
… result

newsletterSubscribe predates the upstream names this branch adds, and its
callers read jid, subscriberCount and inviteCode off what it returns.
Mapping it to upstream's field names renamed all three, so the alias kept
its name while quietly changing its shape.

It now returns the bridge result untouched, and newsletterFollow stays
the one that speaks upstream's names.
@jlucaso1
jlucaso1 force-pushed the feat/newsletter-compat branch from 217c6ec to e5b5be2 Compare August 8, 2026 21:54
@greptile-apps
greptile-apps Bot dismissed their stale review August 8, 2026 21:54

Dismissed because a newer commit was pushed; Greptile will re-review the current head.

@jlucaso1
jlucaso1 merged commit f9abe6f into main Aug 8, 2026
7 checks passed
@jlucaso1
jlucaso1 deleted the feat/newsletter-compat branch September 9, 2026 23:31
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