Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Warning Rate limit exceeded
Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 49 minutes and 51 seconds. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
📝 WalkthroughWalkthroughMigrates newsletter backend from Strapi to Listmonk, adds a Listmonk client and env vars, upgrades ALTCHA to v3 with derived HMAC key flow, removes server-side confirmation/unsubscribe pages and email templates, and updates tests and widget typings to match the new flows. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant Client as Browser/Client
participant ChallengeAPI as /api/newsletter/challenge
participant ALTCHA as ALTCHA Service
User->>Client: Open newsletter form
Client->>ChallengeAPI: GET /api/newsletter/challenge
ChallengeAPI->>ALTCHA: deriveKey + deriveHmacKeySecret -> createChallenge (PBKDF2/SHA-256)
ALTCHA-->>ChallengeAPI: { challenge, expiresAt }
ChallengeAPI-->>Client: Return challenge JSON
Client->>Client: Render altcha-widget with challenge
sequenceDiagram
participant User
participant Client as Browser/Client
participant SubscribeAPI as /api/newsletter/subscribe
participant ALTCHA as ALTCHA Verification
participant Listmonk as Listmonk API
User->>Client: Submit email + ALTCHA payload
Client->>SubscribeAPI: POST (email, base64 payload)
SubscribeAPI->>ALTCHA: verifySolution (derived keys)
alt verification fails
ALTCHA-->>SubscribeAPI: verified = false
SubscribeAPI-->>Client: { error: 'Challenge verification failed' }
else verification succeeds
ALTCHA-->>SubscribeAPI: verified = true
SubscribeAPI->>Listmonk: POST /api/subscribers (email, listIds)
alt success / already exists (200/201/409 handled)
Listmonk-->>SubscribeAPI: subscriber info / 409
SubscribeAPI-->>Client: { success: true } (409 -> generic thank-you)
else validation/error
Listmonk-->>SubscribeAPI: 400/other error
SubscribeAPI-->>Client: 500 { error: 'An internal error occurred' }
end
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~75 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #283 +/- ##
==========================================
+ Coverage 63.77% 65.70% +1.93%
==========================================
Files 67 62 -5
Lines 784 729 -55
Branches 161 153 -8
==========================================
- Hits 500 479 -21
+ Misses 284 250 -34 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
📝 Changed routes:2 deleted routes:
Commit b8d5d85 (https://sentiment-4hvhrl3ke-yl33ly.vercel.app). |
There was a problem hiding this comment.
Actionable comments posted: 4
🧹 Nitpick comments (6)
.env.example (1)
18-24: Optional: reorder Listmonk keys to match dotenv-linter's alphabetical convention.dotenv-linter flags
LISTMONK_API_USERandLISTMONK_API_KEYas out of order relative toLISTMONK_BASE_URL. Purely cosmetic but will silence the lint warnings.💄 Suggested reorder
# Server-side only — listmonk newsletter integration -# Base URL of listmonk instance (no trailing slash preferred) -LISTMONK_BASE_URL=https://newsletter.project-sentiment.org -# listmonk API user (basic auth username) -LISTMONK_API_USER=auth # listmonk API key (basic auth password) LISTMONK_API_KEY=your_listmonk_api_key +# listmonk API user (basic auth username) +LISTMONK_API_USER=auth +# Base URL of listmonk instance (no trailing slash preferred) +LISTMONK_BASE_URL=https://newsletter.project-sentiment.org # list ID to subscribe/unsubscribe against LISTMONK_LIST_ID=1🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.env.example around lines 18 - 24, The dotenv example's keys are out of alphabetical order per dotenv-linter: reorder the LISTMONK variables so their environment variable names are alphabetized (e.g., ensure LISTMONK_API_KEY and LISTMONK_API_USER appear in proper alphabetical order relative to LISTMONK_BASE_URL and LISTMONK_LIST_ID) so entries LISTMONK_API_KEY, LISTMONK_API_USER, LISTMONK_BASE_URL, LISTMONK_LIST_ID (or the correct alphabetical sequence) are placed accordingly to silence the linter.src/components/templates/NewsletterForm.tsx (2)
82-99: Nit: polling foraltcha-widgetis fragile on slow networks.The 100 ms
setIntervalwith a 5 s hard stop silently gives up if the widget's custom element hasn't registered yet (slow network, blocked CDN,AltchaScript's dynamicimport('altcha')still pending). The user then sees "Bot protection loading..." forever and cannot submit, with no error surfaced.Consider listening to
customElements.whenDefined('altcha-widget')instead, which resolves deterministically as soon as the element is registered and avoids the arbitrary 5 s cutoff:♻️ Optional refactor
useEffect(() => { - const checkWidget = setInterval(() => { - const widget = document.querySelector('altcha-widget'); - if (widget) { - setWidgetReady(true); - clearInterval(checkWidget); - } - }, 100); - - const timeout = setTimeout(() => { - clearInterval(checkWidget); - }, 5000); - - return () => { - clearInterval(checkWidget); - clearTimeout(timeout); - }; + if (typeof window === 'undefined' || !window.customElements) return; + let cancelled = false; + window.customElements.whenDefined('altcha-widget').then(() => { + if (!cancelled && document.querySelector('altcha-widget')) { + setWidgetReady(true); + } + }); + return () => { + cancelled = true; + }; }, []);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/templates/NewsletterForm.tsx` around lines 82 - 99, Replace the fragile polling inside the useEffect that queries document.querySelector('altcha-widget') and the 5s timeout with a deterministic registration listener using customElements.whenDefined('altcha-widget') to setWidgetReady(true) as soon as the element is defined; also add a fallback error path (e.g., set an error state or surface a log) if whenDefined rejects or a configurable timeout elapses, and ensure you cancel any pending promise resolution on unmount (use an abort flag or AbortController) so the whenDefined handler and any timeout cleanup do not call setWidgetReady after the component is unmounted.
5-6: Duplicate type-only import.Lines 5 and 6 are identical (
import type {} from 'altcha/types/react';). One copy is enough to pull in the module-augmentation types — remove the duplicate. The same unused/empty import also appears insrc/components/helpers/AltchaScript.tsx, so a single import here is all you need.♻️ Suggested fix
import type { WidgetAttributes } from 'altcha/types'; import type {} from 'altcha/types/react'; -import type {} from 'altcha/types/react'; import { useTheme } from 'next-themes';🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/templates/NewsletterForm.tsx` around lines 5 - 6, Remove the duplicate type-only import line "import type {} from 'altcha/types/react';" from NewsletterForm.tsx so only one such import remains in the file; do the same in AltchaScript.tsx if you want to avoid redundant empty imports across the codebase, but at minimum delete the repeated import in NewsletterForm.tsx.src/lib/listmonk.ts (2)
67-76: Minor: spreadinginit.headerswon't preserveHeaders/[k,v][]forms.Object spread only copies own enumerable keys, so if a future caller passes
headers: new Headers({...})or a[string, string][], those entries will be silently dropped and the request will only carry the defaults set here. Today all call sites use plain objects so this is latent, but normalizing vianew Headers(init.headers)(or an explicitHeadersInitnarrow) avoids the footgun.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/lib/listmonk.ts` around lines 67 - 76, The fetch call builds headers by object-spreading init.headers which drops non-plain forms (Headers instance or [k,v][]); change the headers creation in the fetch invocation to normalize via new Headers(init.headers) and then set/append the defaults (Accept, Authorization from getAuthHeader(), and Content-Type when init.body exists) on that Headers instance so Headers forms are preserved while keeping controller.signal and the rest of init intact.
62-92: Nit: timeout surfaces asAbortError, notListmonkError.On timeout, the
AbortControllercausesfetchto reject with a DOMException/AbortErrorbefore the!res.okbranch runs, so callers can't uniformly rely onerr instanceof ListmonkError(e.g., the subscribe route's duplicate-handling branch won't fire and the timeout will fall through to the generic 500). Wrapping abort signals intoListmonkErrorwith a dedicated status (e.g., 0 or 504) would give callers one error shape to reason about.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/lib/listmonk.ts` around lines 62 - 92, The fetch can reject with an AbortError on timeout so callers don't get a ListmonkError; update the implementation around the fetch call (the AbortController/timeoutMs logic and try/finally surrounding fetch) to catch exceptions from fetch, detect aborts (the AbortError/DOMException coming from controller.abort()) and rethrow a ListmonkError instead (use a dedicated status like 0 or 504 and include any parsed body or contextual message), while still preserving other errors and clearing timeoutId in finally; reference AbortController, timeoutMs, controller.abort, the fetch call, ListmonkError and parseJsonBestEffort to locate the change.src/app/api/newsletter/subscribe/route.ts (1)
67-86: Use altcha-lib's exported types instead ofas nevercasts for better type safety.The
as nevercast works becauseneveris assignable to every type, but it defeats type-checking. altcha-lib v2 exportsChallengeandSolutiontypes—import these from the library and use them to narrow theunknownfields instead of bypassing validation. This surfaces structural issues early and catches future breaking changes in the library.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/app/api/newsletter/subscribe/route.ts` around lines 67 - 86, Replace the unsafe `as never` casts by importing and using altcha-lib v2's exported types (Challenge and Solution): update the parsed variable's type to { challenge?: Challenge; solution?: Solution } (or cast parsed.challenge as Challenge and parsed.solution as Solution) and pass those typed values into verifySolution (the call in this block that uses verifySolution, deriveKey, hmacSignatureSecret, and hmacKeySignatureSecret). Ensure you add the import for Challenge and Solution from altcha-lib v2 and remove the `as never` casts so TypeScript enforces the correct structure before calling verifySolution.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/app/api/newsletter/challenge/route.ts`:
- Around line 1-3: The import for deriveHmacKeySecret is coming from the wrong
subpath; update the import statements in both route files so deriveHmacKeySecret
is imported from the package root 'altcha-lib' (not
'altcha-lib/frameworks/nextjs'), e.g. replace any occurrence importing
deriveHmacKeySecret with an import from 'altcha-lib' alongside
createChallenge/randomInt/deriveKey so the symbol deriveHmacKeySecret resolves
at runtime.
In `@src/app/api/newsletter/subscribe/route.ts`:
- Around line 160-182: The current catch block in route.ts silences both
ListmonkError 400 and 409; change it so only 409 (duplicate) is treated as a
silent-success for user-enumeration protection: update the conditional that
checks err instanceof ListmonkError to test only err.status === 409 and return
the friendly NextResponse.json message for that case, while allowing 400 errors
to fall through to the generic error handling path (log the error via
console.error and return a 500 or propagate the 400 as appropriate) so
validation failures from createSubscriber are surfaced and logged.
In `@src/lib/listmonk.ts`:
- Around line 127-138: findSubscriberByUuid currently embeds the raw uuid into
the Listmonk query DSL after only URL-encoding, which allows
malformed/quote-containing input to break the query or enable injection; add a
strict UUID format check at the start of findSubscriberByUuid (e.g. validate
against a canonical UUID regex for v1/v4) and fail fast (return null or throw a
clear error) for invalid values, only building the query and calling
listmonkRequest when the uuid passes validation; keep encodeURIComponent on the
validated value but do not attempt to embed unvalidated input into the
`subscribers.uuid = '...'` expression.
In `@src/types/altcha.d.ts`:
- Around line 3-25: Replace the global JSX namespace augmentation with module
augmentation for 'react/jsx-runtime' so the custom element <altcha-widget> and
its props (challenge, challengeUrl/challengeurl, hideLogo/hidelogo,
hideFooter/hidefooter, name, strings, style) are recognized under React 19's new
JSX transform; move the IntrinsicElements interface into a "declare module
'react/jsx-runtime' { namespace JSX { interface IntrinsicElements {
'altcha-widget': ... } } }" form, preserving the same prop names and types used
in the existing declaration to ensure type checking for altcha-widget usages.
---
Nitpick comments:
In @.env.example:
- Around line 18-24: The dotenv example's keys are out of alphabetical order per
dotenv-linter: reorder the LISTMONK variables so their environment variable
names are alphabetized (e.g., ensure LISTMONK_API_KEY and LISTMONK_API_USER
appear in proper alphabetical order relative to LISTMONK_BASE_URL and
LISTMONK_LIST_ID) so entries LISTMONK_API_KEY, LISTMONK_API_USER,
LISTMONK_BASE_URL, LISTMONK_LIST_ID (or the correct alphabetical sequence) are
placed accordingly to silence the linter.
In `@src/app/api/newsletter/subscribe/route.ts`:
- Around line 67-86: Replace the unsafe `as never` casts by importing and using
altcha-lib v2's exported types (Challenge and Solution): update the parsed
variable's type to { challenge?: Challenge; solution?: Solution } (or cast
parsed.challenge as Challenge and parsed.solution as Solution) and pass those
typed values into verifySolution (the call in this block that uses
verifySolution, deriveKey, hmacSignatureSecret, and hmacKeySignatureSecret).
Ensure you add the import for Challenge and Solution from altcha-lib v2 and
remove the `as never` casts so TypeScript enforces the correct structure before
calling verifySolution.
In `@src/components/templates/NewsletterForm.tsx`:
- Around line 82-99: Replace the fragile polling inside the useEffect that
queries document.querySelector('altcha-widget') and the 5s timeout with a
deterministic registration listener using
customElements.whenDefined('altcha-widget') to setWidgetReady(true) as soon as
the element is defined; also add a fallback error path (e.g., set an error state
or surface a log) if whenDefined rejects or a configurable timeout elapses, and
ensure you cancel any pending promise resolution on unmount (use an abort flag
or AbortController) so the whenDefined handler and any timeout cleanup do not
call setWidgetReady after the component is unmounted.
- Around line 5-6: Remove the duplicate type-only import line "import type {}
from 'altcha/types/react';" from NewsletterForm.tsx so only one such import
remains in the file; do the same in AltchaScript.tsx if you want to avoid
redundant empty imports across the codebase, but at minimum delete the repeated
import in NewsletterForm.tsx.
In `@src/lib/listmonk.ts`:
- Around line 67-76: The fetch call builds headers by object-spreading
init.headers which drops non-plain forms (Headers instance or [k,v][]); change
the headers creation in the fetch invocation to normalize via new
Headers(init.headers) and then set/append the defaults (Accept, Authorization
from getAuthHeader(), and Content-Type when init.body exists) on that Headers
instance so Headers forms are preserved while keeping controller.signal and the
rest of init intact.
- Around line 62-92: The fetch can reject with an AbortError on timeout so
callers don't get a ListmonkError; update the implementation around the fetch
call (the AbortController/timeoutMs logic and try/finally surrounding fetch) to
catch exceptions from fetch, detect aborts (the AbortError/DOMException coming
from controller.abort()) and rethrow a ListmonkError instead (use a dedicated
status like 0 or 504 and include any parsed body or contextual message), while
still preserving other errors and clearing timeoutId in finally; reference
AbortController, timeoutMs, controller.abort, the fetch call, ListmonkError and
parseJsonBestEffort to locate the change.
🪄 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: defaults
Review profile: CHILL
Plan: Pro
Run ID: caf40077-f9c3-4c76-8821-094490552bee
⛔ Files ignored due to path filters (2)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yamlpublic/images/services/listmonk/sentiment_listmonk-logo.pngis excluded by!**/*.png
📒 Files selected for processing (22)
.env.examplepackage.jsonsrc/__tests__/app/api/newsletter/confirm.test.tssrc/__tests__/app/api/newsletter/subscribe.test.tssrc/__tests__/app/api/newsletter/unsubscribe.test.tssrc/__tests__/lib/listmonk.test.tssrc/app/api/newsletter/challenge/route.tssrc/app/api/newsletter/confirm/route.tssrc/app/api/newsletter/subscribe/route.tssrc/app/api/newsletter/unsubscribe/route.tssrc/app/newsletter/success/page.tsxsrc/app/newsletter/unsubscribed/page.tsxsrc/components/helpers/AltchaScript.tsxsrc/components/layout/Header.tsxsrc/components/templates/NewsletterForm.tsxsrc/constant/env.tssrc/emails/confirm-subscription.tsxsrc/emails/goodbye.tsxsrc/lib/listmonk.tssrc/lib/newsletter-schema.tssrc/types/altcha.d.tstsconfig.json
💤 Files with no reviewable changes (8)
- src/app/newsletter/success/page.tsx
- src/emails/goodbye.tsx
- src/app/newsletter/unsubscribed/page.tsx
- src/app/api/newsletter/unsubscribe/route.ts
- src/tests/app/api/newsletter/confirm.test.ts
- src/tests/app/api/newsletter/unsubscribe.test.ts
- src/app/api/newsletter/confirm/route.ts
- src/emails/confirm-subscription.tsx
| import { createChallenge, randomInt } from 'altcha-lib'; | ||
| import { deriveKey } from 'altcha-lib/algorithms/pbkdf2'; | ||
| import { deriveHmacKeySecret } from 'altcha-lib/frameworks/nextjs'; |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Confirm the subpath is exposed by the installed altcha-lib version.
fd -t f '^package\.json$' --max-depth 3 --exec sh -c '
echo "== {} ==";
jq "{altcha_lib: .dependencies[\"altcha-lib\"], altcha_widget: .dependencies.altcha}" {}
' \;
# Look up the installed package's exports map if node_modules is present
fd -t d '^altcha-lib$' -p 'node_modules/altcha-lib$' --max-depth 4 --exec sh -c '
echo "== {} ==";
jq ".exports // .main" {}/package.json;
ls {}/frameworks 2>/dev/null || echo "(no frameworks dir)"
' \;Repository: dmnktoe/sentiment
Length of output: 266
🌐 Web query:
altcha-lib npm frameworks/nextjs deriveHmacKeySecret export
💡 Result:
altcha-lib is an npm package (https://www.npmjs.com/package/altcha-lib) for creating and verifying ALTCHA proof-of-work challenges on the server side. It supports Node.js, Bun, Deno, and browsers via Web Crypto. Key exports include: - createChallenge(options): Creates a challenge with HMAC signing using hmacKey (v1) or hmacSignatureSecret (v2). - verifySolution(payload, hmacKey) or verifySolution(options): Verifies client-submitted solutions. - deriveHmacKeySecret: Utility function imported alongside others like createChallenge, randomInt, verifySolution. Used in v2 PoW mode for deriving HMAC keys/secrets, e.g., from a base secret for hmacKeySignatureSecret in deterministic challenges. For Next.js (frameworks/nextjs): Install via npm i altcha-lib. Use in API routes or server actions: ts // app/api/challenge/route.ts import { createChallenge } from 'altcha-lib'; export async function GET { const challenge = await createChallenge({ hmacKey: process.env.ALTCHA_HMAC_KEY! }); return Response.json(challenge); } // app/api/verify/route.ts import { verifySolution } from 'altcha-lib'; export async function POST(request: Request) { const formData = await request.formData; const payload = formData.get('altcha') as string; const valid = await verifySolution(payload, process.env.ALTCHA_HMAC_KEY!); return Response.json({ valid }); } For v2 (beta channel: npm i altcha-lib@beta), import deriveHmacKeySecret and use with deriveKey from 'altcha-lib/algorithms/pbkdf2': ts import { createChallenge, deriveHmacKeySecret, randomInt, verifySolution } from 'altcha-lib'; import { deriveKey } from 'altcha-lib/algorithms/pbkdf2'; const HMAC_SECRET = process.env.ALTCHA_HMAC_SECRET!; const HMAC_KEY_SECRET = deriveHmacKeySecret(HMAC_SECRET); // Derives key signature secret const challenge = await createChallenge({ algorithm: 'PBKDF2/SHA-256', cost: 5000, counter: randomInt(5000, 10000), deriveKey, hmacSignatureSecret: HMAC_SECRET, hmacKeySignatureSecret: HMAC_KEY_SECRET, }); Client widget: Use after importing 'altcha'.
Citations:
- 1: https://www.npmjs.com/package/altcha-lib
- 2: https://github.com/altcha-org/altcha-lib
- 3: https://github.com/altcha-org/altcha-lib/blob/c1f2b989c47685ee84fa6ce0c4b11d7de76d07a7/lib/index.ts
- 4: https://linkedin.com/in/varindavid
- 5: https://github.com/altcha-org/altcha-lib/releases
🏁 Script executed:
# Search for all uses of deriveHmacKeySecret in the codebase
rg "deriveHmacKeySecret" -A 2 -B 2
# Search for all imports from altcha-lib to see the current pattern
rg "from\s+['\"]altcha-lib" -A 1 -B 1
# Check if frameworks/nextjs path exists in node_modules structure
fd "frameworks" -t d --max-depth 5 2>/dev/null | head -20Repository: dmnktoe/sentiment
Length of output: 2966
Fix incorrect import path for deriveHmacKeySecret in both challenge and subscribe routes.
The code imports deriveHmacKeySecret from 'altcha-lib/frameworks/nextjs', but according to the official altcha-lib v2 documentation, this function is exported from the package root ('altcha-lib'). The frameworks/nextjs subpath is not documented as an export source. Update the import statement in src/app/api/newsletter/challenge/route.ts and src/app/api/newsletter/subscribe/route.ts:
- import { deriveHmacKeySecret } from 'altcha-lib/frameworks/nextjs';
+ import { deriveHmacKeySecret } from 'altcha-lib';This will resolve the module import error at runtime.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| import { createChallenge, randomInt } from 'altcha-lib'; | |
| import { deriveKey } from 'altcha-lib/algorithms/pbkdf2'; | |
| import { deriveHmacKeySecret } from 'altcha-lib/frameworks/nextjs'; | |
| import { createChallenge, randomInt } from 'altcha-lib'; | |
| import { deriveKey } from 'altcha-lib/algorithms/pbkdf2'; | |
| import { deriveHmacKeySecret } from 'altcha-lib'; |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/app/api/newsletter/challenge/route.ts` around lines 1 - 3, The import for
deriveHmacKeySecret is coming from the wrong subpath; update the import
statements in both route files so deriveHmacKeySecret is imported from the
package root 'altcha-lib' (not 'altcha-lib/frameworks/nextjs'), e.g. replace any
occurrence importing deriveHmacKeySecret with an import from 'altcha-lib'
alongside createChallenge/randomInt/deriveKey so the symbol deriveHmacKeySecret
resolves at runtime.
Summary
altcha-libv2).deriveHmacKeySecretimport usesaltcha-lib/frameworks/shared(notnextjs); listmonk subscribe only treats 409 as silent duplicate; 400 is logged and returns 500; UUID lookup validates canonical UUIDs before building SQL.altcha-widgettypings usereact/jsx-runtimemodule augmentation.Test Plan
pnpm testpnpm typecheckpnpm lintSummary by CodeRabbit
New Features
Refactor
Removed
Style
Chores