Skip to content

refactor(newsletter): migrate the web client onto the SDK newsletter module - #1681

Merged
feruzm merged 3 commits into
developfrom
feat/web-newsletter-sdk-migration
Aug 25, 2026
Merged

refactor(newsletter): migrate the web client onto the SDK newsletter module#1681
feruzm merged 3 commits into
developfrom
feat/web-newsletter-sdk-migration

Conversation

@feruzm

@feruzm feruzm commented Aug 25, 2026

Copy link
Copy Markdown
Member

Closes #1680. Follow-up to #1677: web and mobile now share one newsletter transport implementation.

Web

  • newsletter-api.ts delegates subscribe/list/leave/unsubscribe-all to the SDK request functions. What stays is web-specific: fresh-token sourcing via ensureValidToken and the email-token confirm/unsubscribe flows, whose pages exist only on the web origin.
  • author-send-api.ts delegates preview/send/candidates/issues; SendRefusedError is a re-export of the SDK's NewsletterSendRefusedError, so instanceof keeps working everywhere.
  • useSenderStanding uses getNewsletterSenderRequest instead of its inline fetch; SenderStanding aliases the SDK shape.
  • types.ts re-exports the SDK types under the existing web names.
  • No behavior change on ecency.com: on any *.ecency.com browser the SDK host is empty, so requests stay same-origin relative exactly as before.

SDK

  • The two error classes move to a dependency-free errors.ts (public surface unchanged via the module barrel) so the spec setup can hand out the real classes without pulling the config chain.
  • Dist is rebuilt in this PR because web typecheck now needs the new API from dist (the sanctioned exception).

Specs

  • Web specs stop re-pinning the wire format the SDK's own api.spec.ts already pins. They now mock the SDK request functions (exposed as stubs by the global @ecency/sdk mock, error classes real) and pin what web owns: delegation with a fresh token, cache behavior and rendering. author-send-api.spec also pins that the re-exported error class IS the SDK class.
  • The issue sketched a relative-leaf-imports approach for a full module passthrough; the errors-extraction plus function stubs turned out lighter (no SDK-internal alias changes, no config chain loaded into every spec file).

Verification: SDK 854 tests, web full suite 361 files / 3583 tests, workspace typecheck and lint all green.

Summary by CodeRabbit

  • Improvements
    • Newsletter subscriptions, sender status, previews, sends, and issue management now use a unified service, improving consistency and reliability.
    • Authentication tokens are refreshed for newsletter author actions.
    • Newsletter errors provide more consistent status and refusal details.
  • Bug Fixes
    • Improved handling of newsletter subscription, sending, and sender-status errors.
  • Tests
    • Expanded coverage for newsletter workflows, including empty states, failures, refusals, and authentication scenarios.

…sletter module

The web transport in features/newsletter (newsletter-api.ts, author-send-api.ts
and the inline sender-standing fetch) now delegates to the SDK request
functions, keeping only what is web-specific: fresh-token sourcing via
ensureValidToken and the email-token confirm/unsubscribe flows, whose pages
exist only on the web origin. Types re-export the SDK shapes under the
existing web names and SendRefusedError IS the SDK's refusal error, so
instanceof keeps working everywhere.

SDK side: the two error classes move to a dependency-free errors.ts so the
spec setup can hand out the real classes without pulling the config chain;
dist is rebuilt in this commit because web typecheck now needs the new API.

Specs stop re-pinning the wire format the SDK's own api.spec.ts already pins
and instead pin delegation and rendering, mocking the SDK request functions
through the global @ecency/sdk mock. Closes #1680
@qodo-code-review

Copy link
Copy Markdown

ⓘ Your Qodo trial ends soon. Ask your workspace admin to set up billing to keep reviews running after the trial. Manage billing

@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Aug 25, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (1) 📎 Requirement gaps (0) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)

Grey Divider


Remediation recommended

1. Non-production newsletter calls break ✓ Resolved 🐞 Bug ≡ Correctness
Description
The new SDK delegation sends browser requests from localhost, preview, or self-hosted origins to
https://ecency.com/api/newsletter/* because the web SDK config only uses a relative host on
*.ecency.com; these JSON and X-HS-Token requests become cross-origin/preflighted instead of
reaching the current deployment's route handlers. The newsletter routes expose normal GET/POST
handlers without CORS handling, so these environments can no longer use the newsletter UI and
preview environments may target production rather than their own backend.
Code

apps/web/src/features/newsletter/newsletter-api.ts[35]

+    return subscribeDigestRequest(input, code ?? undefined);
Relevance

●●● Strong

Recent newsletter history consistently accepts concrete correctness regressions affecting auth,
cache, and request behavior.

PR-#1516
PR-#1528

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The web initializes privateApiHost to an empty string only when the browser hostname is
ecency.com or ends in .ecency.com; every other browser gets https://ecency.com. The SDK
concatenates that host with /api/newsletter, while the migrated wrapper now calls that SDK
function; the target web route is implemented as an ordinary POST handler returning standard
Response.json responses rather than a cross-origin API surface.

apps/web/src/core/sdk-init.ts[16-34]
packages/sdk/src/modules/newsletter/api.ts[33-35]
packages/sdk/src/modules/newsletter/api.ts[63-73]
apps/web/src/app/api/newsletter/subscribe/route.ts[41-52]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The web wrappers now delegate to SDK functions whose host comes from `CONFIG.privateApiHost`. On any browser origin outside `ecency.com`/`*.ecency.com`, that host is `https://ecency.com`, changing previously relative newsletter calls into cross-origin production calls that require unsupported CORS preflights.
## Issue Context
Keep mobile/SSR host selection intact while ensuring the web newsletter client targets the current web origin in local, preview, and self-hosted browser deployments. An explicit SDK newsletter base-host option or a web-specific same-origin configuration is preferable to silently routing these calls to production.
## Fix Focus Areas
- apps/web/src/features/newsletter/newsletter-api.ts[33-47]
- apps/web/src/features/newsletter/author-send-api.ts[43-53]
- apps/web/src/features/newsletter/sender-status.tsx[42-44]
- apps/web/src/core/sdk-init.ts[16-34]
- packages/sdk/src/modules/newsletter/api.ts[33-35]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Informational

2. Newsletter specs assert SDK calls 📘 Rule violation ▣ Testability
Description
Modified newsletter UI specs assert internal implementation details (SDK request function
calls/arguments) instead of only user-visible behavior. This violates the testing compliance rule
and makes the tests brittle to refactors that preserve UI behavior.
Code

apps/web/src/specs/features/newsletter/digest-subscribe.spec.tsx[R150-152]

+    const [input, code] = subscribeMock.mock.calls[0];
+    expect(input).toMatchObject({
     email: "reader@example.com",
Relevance

● Weak

A closely matching newsletter UI-test finding about exact internal API arguments was explicitly
rejected.

PR-#1579

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
PR Compliance ID 2667994 prohibits UI tests from asserting internal implementation details like
which helper/SDK function was called. The updated newsletter specs include assertions on SDK mock
call arguments (e.g., reading subscribeMock.mock.calls[0] and asserting
toHaveBeenCalledWith(...)), which are not user-visible behavior assertions.

Rule 2667994: UI tests must verify user-visible behavior rather than internal implementation details
apps/web/src/specs/features/newsletter/digest-subscribe.spec.tsx[149-164]
apps/web/src/specs/features/newsletter/author-send-api.spec.ts[28-40]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Newsletter UI specs assert internal implementation details (e.g., `subscribeMock.mock.calls` / `toHaveBeenCalledWith(...)`) rather than focusing on user-visible behavior.
## Issue Context
PR Compliance ID 2667994 requires UI tests to verify rendered output and user interactions, and to avoid assertions about which helper function was called when the same behavior can be verified via UI.
## Fix Focus Areas
- apps/web/src/specs/features/newsletter/digest-subscribe.spec.tsx[149-196]
- apps/web/src/specs/features/newsletter/author-send-api.spec.ts[28-40]
- apps/web/src/specs/features/newsletter/sender-status.spec.tsx[62-76]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Tip of the day
💡 Did you know, you can hide the parts of a finding you never read, like the evidence or the agent prompt

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Aug 25, 2026

Copy link
Copy Markdown

PR Summary by Qodo

Refactor web newsletter client to delegate to @ecency/sdk module

✨ Enhancement 🧪 Tests 🕐 40+ Minutes

Grey Divider

AI Description

• Migrate web newsletter API calls to @ecency/sdk request functions, keeping web-only flows local.
• Re-export SDK newsletter types/errors under existing web names to preserve call sites and
 instanceof checks.
• Update web specs to mock SDK requests and assert delegation/token-refresh behavior instead of wire
 format.
Diagram

graph TD
UI["Web newsletter UI"] --> WebWrap["Web wrappers"] --> SDK["@ecency/sdk newsletter"] --> Relay["/api/newsletter routes"] --> Svc["Newsletter service"]
WebWrap --> Token["ensureValidToken"]
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Keep web-owned transport; share only types
  • ➕ Avoids any coupling between web runtime and SDK request code
  • ➕ No need to rebuild SDK dist for web typechecking
  • ➖ Duplicates transport logic across web/mobile (drift risk)
  • ➖ Requires web specs to keep re-pinning wire format
2. SDK passthrough via leaf imports / internal aliasing in tests
  • ➕ Can mock at lower level without adding stubs to the SDK mock
  • ➕ Potentially less wrapper code in web
  • ➖ Introduces brittle imports into SDK internals / alias changes
  • ➖ Risks pulling SDK config chain into many specs and increasing setup cost
3. Introduce an injectable transport adapter in the SDK
  • ➕ Cleaner test seam than globally mocking request functions
  • ➕ Keeps error classes real while swapping HTTP implementation
  • ➖ Larger API/architecture change to the SDK
  • ➖ More complexity than needed for a straightforward migration

Recommendation: The chosen approach (web delegates to SDK request functions, while web remains responsible for ensureValidToken and web-only email-link flows) is the best tradeoff: it eliminates duplicated transport, keeps the public surface stable via type/error re-exports, and preserves instanceof semantics by ensuring the error classes are shared and dependency-free for spec setup. The added request-function stubs in the global SDK mock provide a simple, explicit seam for web specs without dragging SDK config into the test environment.

Files changed (17) +374 / -490

Refactor (7) +116 / -238
author-send-api.tsDelegate author send flows to SDK and re-export SDK types/errors +41/-100

Delegate author send flows to SDK and re-export SDK types/errors

• Replaces inline fetch-based send/preview/candidates/issues calls with @ecency/sdk request functions, while keeping per-call fresh token sourcing via ensureValidToken. Re-exports SDK shapes under existing web names and aliases NewsletterSendRefusedError as SendRefusedError to preserve instanceof behavior.

apps/web/src/features/newsletter/author-send-api.ts

newsletter-api.tsWrap SDK digest subscription requests; keep web-only email-link flows +22/-43

Wrap SDK digest subscription requests; keep web-only email-link flows

• Moves subscribe/list/leave/unsubscribe-all transport to SDK request functions and re-exports NewsletterApiError from the SDK. Retains web-local relative fetches for confirm/unsubscribe flows that only exist on the web origin.

apps/web/src/features/newsletter/newsletter-api.ts

sender-status.tsxUse SDK sender-standing request and SDK standing type +5/-24

Use SDK sender-standing request and SDK standing type

• Replaces inline sender-standing fetch with getNewsletterSenderRequest from the SDK. SenderStanding is now a type alias of NewsletterSenderStanding for shared shape parity with mobile.

apps/web/src/features/newsletter/sender-status.tsx

types.tsRe-export newsletter contract types from SDK under existing web names +19/-48

Re-export newsletter contract types from SDK under existing web names

• Deletes local type definitions and re-exports Digest* types from @ecency/sdk. Preserves web naming by aliasing DigestSubscribeInput/Result to SubscribeInput/SubscribeResult and documents the remaining route-locked source union contract.

apps/web/src/features/newsletter/types.ts

api.tsMove newsletter error classes out of api.ts +1/-23

Move newsletter error classes out of api.ts

• Removes inline NewsletterApiError/NewsletterSendRefusedError definitions and imports them from ./errors. Keeps transport and request logic unchanged while decoupling errors from CONFIG/getBoundFetch dependencies.

packages/sdk/src/modules/newsletter/api.ts

errors.tsAdd dependency-free newsletter error classes module +27/-0

Add dependency-free newsletter error classes module

• Introduces a standalone errors.ts exporting NewsletterApiError and NewsletterSendRefusedError. This supports test setups needing the real classes without importing the SDK config chain.

packages/sdk/src/modules/newsletter/errors.ts

index.tsExport newsletter errors from module barrel +1/-0

Export newsletter errors from module barrel

• Adds export * from ./errors so the public SDK surface remains consistent and consumers can import error classes from the newsletter module.

packages/sdk/src/modules/newsletter/index.ts

Tests (8) +215 / -216
publish-success-first-publish.spec.tsxMock SDK subscriptions request for first-publish prompt behavior +5/-8

Mock SDK subscriptions request for first-publish prompt behavior

• Stops stubbing global fetch for newsletter subscriptions and instead mocks getDigestSubscriptionsRequest to return an empty list. Pins the prompt gating behavior based on loaded subscriptions rather than wire format.

apps/web/src/specs/app/publish/publish-success-first-publish.spec.tsx

author-send-api.spec.tsSpec asserts delegation to SDK and error class identity +36/-25

Spec asserts delegation to SDK and error class identity

• Rewrites tests to mock SDK request functions and verify calls include a freshly ensured token. Adds an assertion that SendRefusedError is the exact SDK class to keep instanceof branching correct.

apps/web/src/specs/features/newsletter/author-send-api.spec.ts

author-send.spec.tsxDialog specs mock SDK newsletter requests instead of fetch +67/-58

Dialog specs mock SDK newsletter requests instead of fetch

• Replaces fetch-based newsletter stubs with mocked SDK request functions for preview/send/issues/posts. Keeps assertions focused on dialog rendering, cache invalidation behavior, and refusal/unavailable outcomes via SDK error classes.

apps/web/src/specs/features/newsletter/author-send.spec.tsx

digest-subscribe.spec.tsxDigest subscribe dialog/button specs mock SDK requests +48/-63

Digest subscribe dialog/button specs mock SDK requests

• Switches from fetch mocking to mocking subscribeDigestRequest/getDigestSubscriptionsRequest/leaveDigestRequest. Updates assertions to validate token vs anonymous call behavior and UI outcomes for success/refusal/error states.

apps/web/src/specs/features/newsletter/digest-subscribe.spec.tsx

email-digests-settings.spec.tsxSettings specs mock SDK mutations and ensure list refetch resolves +17/-15

Settings specs mock SDK mutations and ensure list refetch resolves

• Moves cadence change and unsubscribe-all tests to mocked SDK request functions. Ensures subscription list refetch is stubbed to avoid query error states and validates cache behavior after mutations.

apps/web/src/specs/features/newsletter/email-digests-settings.spec.tsx

first-publish-digest-prompt.spec.tsxFirst-publish prompt specs use SDK list/subscribe mocks +22/-25

First-publish prompt specs use SDK list/subscribe mocks

• Replaces fetch stubs with getDigestSubscriptionsRequest and subscribeDigestRequest mocks. Maintains coverage for prompt gating (failed load, already subscribed, answered, feature off) and subscribe call payload + token handling.

apps/web/src/specs/features/newsletter/first-publish-digest-prompt.spec.tsx

sender-status.spec.tsxSender status specs mock SDK sender-standing request +19/-20

Sender status specs mock SDK sender-standing request

• Updates tests to mock getNewsletterSenderRequest and assert when it is (and isn’t) called. Preserves coverage for sender-only visibility and cache scoping by viewer key.

apps/web/src/specs/features/newsletter/sender-status.spec.tsx

api.spec.tsSDK newsletter API spec imports errors from new errors module +1/-2

SDK newsletter API spec imports errors from new errors module

• Adjusts the SDK’s own api.spec.ts to import NewsletterApiError and NewsletterSendRefusedError from ./errors, matching the new file split.

packages/sdk/src/modules/newsletter/api.spec.ts

Other (2) +43 / -36
setup-any-spec.tsExpose real newsletter error classes and stub SDK request functions +18/-0

Expose real newsletter error classes and stub SDK request functions

• Extends the global @ecency/sdk mock to import the real newsletter error classes from source (dependency-free) while providing vi.fn stubs for request functions. Enables web specs to assert delegation and preserve instanceof semantics.

apps/web/src/specs/setup-any-spec.ts

index.d.tsRebuilt SDK dist types to expose extracted newsletter errors +25/-36

Rebuilt SDK dist types to expose extracted newsletter errors

• Updates generated browser typings so NewsletterApiError and NewsletterSendRefusedError appear as dependency-free exports. Reflects the source refactor and unblocks web typechecking against dist.

packages/sdk/dist/browser/index.d.ts

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

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

body: JSON.stringify({ ...input, ...(code ? { code } : {}) })
});
return parse<SubscribeResult>(res);
return subscribeDigestRequest(input, code ?? undefined);

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 Keep newsletter requests on the configured deployment

On any configured web deployment whose hostname is not ecency.com or *.ecency.com (including local development), this delegation changes the request from same-origin /api/newsletter/... to the SDK host. apps/web/src/core/sdk-init.ts lines 29–34 sets that host to https://ecency.com for such browsers, while NewsletterRuntimeProvider enables the feature based on the current deployment's own newsletter credentials. Consequently subscriptions are sent cross-origin to Ecency—typically failing CORS, and in any case bypassing the deployment's configured relay. The SDK call needs a same-origin host override for web newsletter routes; the same issue applies to the migrated list/leave/unsubscribe and sender APIs.

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.

Fixed in 66eaa2b. The SDK config gains a newsletterHost override (undefined falls back to privateApiHost, which is right for mobile; an empty string means same-origin) and the web client pins it to "" in the browser via sdk-init, so newsletter requests stay on the current origin for every deployment: production, localhost dev and custom hostnames alike. SSR never calls the relay, so the override is browser-only. A new SDK spec case pins both the fallback and the same-origin override.

@coderabbitai

coderabbitai Bot commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: e50fcd36-795f-4880-abef-9c39f6bdaaa5

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: f4610a33-c832-4a51-bd08-ef1db7a95a26

📥 Commits

Reviewing files that changed from the base of the PR and between a0da25e and ed8d07e.

⛔ Files ignored due to path filters (7)
  • packages/sdk/dist/browser/index.d.ts is excluded by !**/dist/**
  • packages/sdk/dist/browser/index.js is excluded by !**/dist/**
  • packages/sdk/dist/browser/index.js.map is excluded by !**/dist/**, !**/*.map
  • packages/sdk/dist/node/index.cjs is excluded by !**/dist/**
  • packages/sdk/dist/node/index.cjs.map is excluded by !**/dist/**, !**/*.map
  • packages/sdk/dist/node/index.mjs is excluded by !**/dist/**
  • packages/sdk/dist/node/index.mjs.map is excluded by !**/dist/**, !**/*.map
📒 Files selected for processing (16)
  • apps/web/src/features/newsletter/author-send-api.ts
  • apps/web/src/features/newsletter/newsletter-api.ts
  • apps/web/src/features/newsletter/sender-status.tsx
  • apps/web/src/features/newsletter/types.ts
  • apps/web/src/specs/app/publish/publish-success-first-publish.spec.tsx
  • apps/web/src/specs/features/newsletter/author-send-api.spec.ts
  • apps/web/src/specs/features/newsletter/author-send.spec.tsx
  • apps/web/src/specs/features/newsletter/digest-subscribe.spec.tsx
  • apps/web/src/specs/features/newsletter/email-digests-settings.spec.tsx
  • apps/web/src/specs/features/newsletter/first-publish-digest-prompt.spec.tsx
  • apps/web/src/specs/features/newsletter/sender-status.spec.tsx
  • apps/web/src/specs/setup-any-spec.ts
  • packages/sdk/src/modules/newsletter/api.spec.ts
  • packages/sdk/src/modules/newsletter/api.ts
  • packages/sdk/src/modules/newsletter/errors.ts
  • packages/sdk/src/modules/newsletter/index.ts

Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review.


📝 Walkthrough

Walkthrough

The newsletter web clients now use @ecency/sdk request helpers, shared types, and shared errors. Newsletter specifications and the global SDK mock now validate delegation, tokens, responses, and error handling.

Changes

Newsletter SDK migration

Layer / File(s) Summary
Public error module
packages/sdk/src/modules/newsletter/*
Newsletter API errors moved to errors.ts and are re-exported through the newsletter module.
Web client integration
apps/web/src/features/newsletter/*
Newsletter types are re-exported from the SDK. Subscribe, author-send, and sender-status operations now call SDK request helpers.
Delegation test coverage
apps/web/src/specs/features/newsletter/*, apps/web/src/specs/app/publish/*, apps/web/src/specs/setup-any-spec.ts
Tests mock SDK requests and validate request arguments, tokens, results, typed errors, and feature-disabled states.

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

Merge Risk: ⚪ Minimal · up to ed8d0

The change centralizes newsletter transport in the shared SDK while retaining web-specific token and confirmation flows; the reported test, typecheck, and lint checks are green, so no actionable merge-blocking risk remains.

Poem

A rabbit hops through SDK lanes
Shared types flow like springtime rains
Fresh tokens guide each call
Typed errors stand tall
Tests bloom bright across the hall

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 14.29% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 7 functions across 16 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
Linked Issues check ❓ Inconclusive The reviewable changes satisfy the linked issue requirements for SDK delegation, type and error re-exports, local token and email-token handling, sender standing migration, and updated specs. SDK dist… Verify that the rebuilt SDK artifacts include the newsletter APIs required by web typechecking, especially packages/sdk/dist/browser/index.d.ts and the corresponding runtime bundles.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the primary change: migrating the web newsletter client to the SDK newsletter module.
Out of Scope Changes check ✅ Passed The changes remain within the linked issue scope. SDK error extraction, public re-exports, web client delegation, test updates, and SDK mock updates directly support the newsletter migration.
Full details: Linked Issues check

Explanation

The reviewable changes satisfy the linked issue requirements for SDK delegation, type and error re-exports, local token and email-token handling, sender standing migration, and updated specs. SDK distribution rebuild status cannot be verified because all relevant dist files are excluded by the !/dist/ path filter.

✨ 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/web-newsletter-sdk-migration

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.

…ployment

Adds a newsletterHost override to the SDK config (undefined = fall back to
privateApiHost, right for mobile; '' = same-origin). The web client pins it
to '' in the browser, so a configured deployment whose hostname is not
*.ecency.com (local dev, a custom domain) keeps talking to its OWN relay
instead of following privateApiHost cross-origin to ecency.com. SSR never
calls the relay, so the override is browser-only. Dist rebuilt.
@feruzm feruzm added the patch Bug fixes and patches (1.0.0 → 1.0.1) label Aug 25, 2026
@qodo-code-review

qodo-code-review Bot commented Aug 25, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (1) 📜 Skill insights (0)

Grey Divider


Remediation recommended

1. Non-production newsletter calls break ✓ Resolved 🐞 Bug ≡ Correctness
Description
The new SDK delegation sends browser requests from localhost, preview, or self-hosted origins to
https://ecency.com/api/newsletter/* because the web SDK config only uses a relative host on
*.ecency.com; these JSON and X-HS-Token requests become cross-origin/preflighted instead of
reaching the current deployment's route handlers. The newsletter routes expose normal GET/POST
handlers without CORS handling, so these environments can no longer use the newsletter UI and
preview environments may target production rather than their own backend.
Code

apps/web/src/features/newsletter/newsletter-api.ts[35]

+    return subscribeDigestRequest(input, code ?? undefined);
Relevance

●●● Strong

Recent newsletter history consistently accepts concrete correctness regressions affecting auth,
cache, and request behavior.

PR-#1516
PR-#1528

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The web initializes privateApiHost to an empty string only when the browser hostname is
ecency.com or ends in .ecency.com; every other browser gets https://ecency.com. The SDK
concatenates that host with /api/newsletter, while the migrated wrapper now calls that SDK
function; the target web route is implemented as an ordinary POST handler returning standard
Response.json responses rather than a cross-origin API surface.

apps/web/src/core/sdk-init.ts[16-34]
packages/sdk/src/modules/newsletter/api.ts[33-35]
packages/sdk/src/modules/newsletter/api.ts[63-73]
apps/web/src/app/api/newsletter/subscribe/route.ts[41-52]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The web wrappers now delegate to SDK functions whose host comes from `CONFIG.privateApiHost`. On any browser origin outside `ecency.com`/`*.ecency.com`, that host is `https://ecency.com`, changing previously relative newsletter calls into cross-origin production calls that require unsupported CORS preflights.

## Issue Context
Keep mobile/SSR host selection intact while ensuring the web newsletter client targets the current web origin in local, preview, and self-hosted browser deployments. An explicit SDK newsletter base-host option or a web-specific same-origin configuration is preferable to silently routing these calls to production.

## Fix Focus Areas
- apps/web/src/features/newsletter/newsletter-api.ts[33-47]
- apps/web/src/features/newsletter/author-send-api.ts[43-53]
- apps/web/src/features/newsletter/sender-status.tsx[42-44]
- apps/web/src/core/sdk-init.ts[16-34]
- packages/sdk/src/modules/newsletter/api.ts[33-35]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Informational

2. Newsletter specs assert SDK calls 📘 Rule violation ▣ Testability
Description
Modified newsletter UI specs assert internal implementation details (SDK request function
calls/arguments) instead of only user-visible behavior. This violates the testing compliance rule
and makes the tests brittle to refactors that preserve UI behavior.
Code

apps/web/src/specs/features/newsletter/digest-subscribe.spec.tsx[R150-152]

+    const [input, code] = subscribeMock.mock.calls[0];
+    expect(input).toMatchObject({
      email: "reader@example.com",
Relevance

● Weak

A closely matching newsletter UI-test finding about exact internal API arguments was explicitly
rejected.

PR-#1579

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
PR Compliance ID 2667994 prohibits UI tests from asserting internal implementation details like
which helper/SDK function was called. The updated newsletter specs include assertions on SDK mock
call arguments (e.g., reading subscribeMock.mock.calls[0] and asserting
toHaveBeenCalledWith(...)), which are not user-visible behavior assertions.

Rule 2667994: UI tests must verify user-visible behavior rather than internal implementation details
apps/web/src/specs/features/newsletter/digest-subscribe.spec.tsx[149-164]
apps/web/src/specs/features/newsletter/author-send-api.spec.ts[28-40]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Newsletter UI specs assert internal implementation details (e.g., `subscribeMock.mock.calls` / `toHaveBeenCalledWith(...)`) rather than focusing on user-visible behavior.

## Issue Context
PR Compliance ID 2667994 requires UI tests to verify rendered output and user interactions, and to avoid assertions about which helper function was called when the same behavior can be verified via UI.

## Fix Focus Areas
- apps/web/src/specs/features/newsletter/digest-subscribe.spec.tsx[149-196]
- apps/web/src/specs/features/newsletter/author-send-api.spec.ts[28-40]
- apps/web/src/specs/features/newsletter/sender-status.spec.tsx[62-76]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Context sources
✅ Compliance rules (platform): 84 rules
✅ Skills: 6 invoked
  add-feature
  add-query
  add-sdk-mutation
  add-test
  code-review
  debug
Review mode: 🧠 Deep: This is a broad runtime migration across web, SDK, shared types, error identity, transport delegation, and many independent UI/spec paths, creating a dense set of subtle compatibility and behavioral risks.

Grey Divider

Tip of the day
💡 Did you know, you can hide the parts of a finding you never read, like the evidence or the agent prompt

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment thread apps/web/src/features/newsletter/newsletter-api.ts
@feruzm

feruzm commented Aug 25, 2026

Copy link
Copy Markdown
Member Author

On the Low "Newsletter specs assert SDK calls" rule note: intentional, not an oversight. The SDK request functions are the web layer's OUTBOUND CONTRACT after this migration, exactly as the previous fetch-body assertions were before it; the specs assert what leaves the web layer (input shape plus a freshly ensured token), while user-visible behavior is still asserted alongside in every test. Wire-format pinning moved to the SDK's own api.spec.ts, which is the refactor-stability win the rule aims at.

@feruzm
feruzm merged commit 657b5f2 into develop Aug 25, 2026
9 checks passed
@feruzm
feruzm deleted the feat/web-newsletter-sdk-migration branch August 25, 2026 10:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

patch Bug fixes and patches (1.0.0 → 1.0.1)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

web: migrate the newsletter client onto the @ecency/sdk newsletter module

1 participant