Skip to content

Conversation

@aguxez
Copy link
Contributor

@aguxez aguxez commented Jan 7, 2026

closes #114

Summary by CodeRabbit

  • New Features

    • Declined card transactions now appear in the activity feed with USD amount, merchant, transaction hash, timestamp, and decline reason; users receive push notifications when transactions are declined.
    • Activity items support a new "requested" action and may include an optional "declined" status and reason.
  • Tests

    • Added coverage for declined-transaction persistence, payload merging/concurrency, and push notification scenarios.
  • Chores

    • Added a changeset to prepare this patch release.

✏️ Tip: You can customize this high-level summary in your review settings.


Open with Devin

@changeset-bot
Copy link

changeset-bot bot commented Jan 7, 2026

🦋 Changeset detected

Latest commit: f80b47a

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@exactly/server Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@coderabbitai
Copy link

coderabbitai bot commented Jan 7, 2026

Walkthrough

Persist declined Panda card transactions, derive human-readable decline reasons, send push notifications for declines, extend activity schemas to represent declined/requested states, add tests for decline persistence and notifications, and include a changeset.

Changes

Cohort / File(s) Summary
Changeset
.changeset/honest-peas-stand.md
Add new changeset for @exactly/server (patch) with description "✨ add activity and push notification on card decline".
Webhook handler
server/hooks/panda.ts
Add decline handling primitives (getDeclineReason, saveDeclinedTransaction, handleDeclinedTransaction, sendRejectedTransactionNotification); persist/upsert declined payloads; send push notifications; integrate decline handling into main flow and error branches.
API activity schema
server/api/activity.ts
Introduce PandaBody shape; change PandaActivity.bodies to PandaBody[]; add optional status: "declined" and reason; broaden CardActivity.action to include "requested"; update transform to emit declined activity shape and adjust icon handling.
Tests — hooks
server/test/hooks/panda.test.ts
Add concurrency tests for declined transactions and push notification tests (insufficient funds, completed declined transactions, unknown errors); import OneSignal helper and assert notification payloads.
Tests — activity
server/test/api/activity.test.ts
Split log fetching into cardLogs and withdrawLogs; adjust borrow/withdraw mapping and spend body shape to match updated activity schema.

Sequence Diagram(s)

sequenceDiagram
    actor PaymentProcessor as Payment Processor
    participant PandaHook as "server/hooks/panda.ts"
    participant DB as Database
    participant PushSvc as "Push Notification Service"
    participant Sentry as Sentry

    PaymentProcessor->>PandaHook: POST webhook event (payload)
    PandaHook->>PandaHook: parse payload, determine flow
    alt payload indicates declined
        PandaHook->>PandaHook: getDeclineReason(payload/error)
        PandaHook->>DB: saveDeclinedTransaction(payload, body, reason)
        DB-->>PandaHook: upsert result
        PandaHook->>PushSvc: sendRejectedTransactionNotification(account, payload, reason)
        PushSvc-->>PandaHook: ack / error
        alt push error
            PandaHook->>Sentry: captureException(error)
        end
    else non-declined / other flow
        PandaHook->>PandaHook: continue existing processing (mutex, create/complete flows)
    end
    PandaHook-->>PaymentProcessor: 200 / ack
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • cruzdanilo
🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main changeset, describing the addition of activity logging and push notification functionality on card decline events.
Linked Issues check ✅ Passed The PR successfully implements both objectives from issue #114: storing activity records on card decline and sending push notifications to inform relevant parties.
Out of Scope Changes check ✅ Passed All changes are directly related to handling card decline events—adding decline reason mapping, transaction persistence, notification logic, and activity tracking with appropriate tests.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch card-declined-events

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 and usage tips.

@gemini-code-assist
Copy link

Summary of Changes

Hello @aguxez, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request enhances the server's transaction handling by introducing a mechanism to process declined card transactions. The immediate user-facing change is the implementation of push notifications, which will inform users instantly about rejected purchases. Although the foundational code for recording these declined transactions as user activity has been added, this specific database logging functionality is temporarily disabled, pending the development of corresponding user interface elements.

Highlights

  • New Transaction Handling: Implemented a new handleDeclinedTransaction function to process card rejections within the panda hook.
  • Push Notifications for Declines: Enabled push notifications for users when their card transactions are declined, providing immediate feedback on rejected purchases.
  • Deferred Activity Logging: Prepared the server-side logic for logging declined transactions as activity in the database, though this feature is currently commented out and awaiting UI implementation.
  • Test Coverage for Future Features: Added it.todo test cases to cover the future enablement of declined transaction activity logging, ensuring proper functionality once the UI is ready.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request adds push notifications for declined card transactions. The implementation in server/hooks/panda.ts introduces a handleDeclinedTransaction function to send these notifications. The logic for adding declined transactions to the user's activity feed is included but commented out, pending UI designs, which is a good approach. Corresponding tests have been added as it.todo.

My review includes a couple of suggestions for improvement:

  • Improving type safety in the new handleDeclinedTransaction function.
  • Pointing out a discrepancy between a new test case and the commented-out implementation logic, which should be resolved before the feature is fully enabled.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In @server/hooks/panda.ts:
- Around line 955-998: Remove the large commented-out DB logic inside
handleDeclinedTransaction and track the work in your issue tracker: create an
issue describing the pending UI changes needed to handle declined transactions
and include its ID; then replace the commented block with a single-line comment
in handleDeclinedTransaction referencing that issue (e.g., "See ISSUE-1234:
enable declined-transaction persistence once UI supports it"). Ensure the rest
of the function (push notification and error capture) remains unchanged.
- Line 965: Replace the existing comment "// TODO: Enable once UI has proper
designs to handle declined transactions in activity" with the coding-guideline
compliant format: use uppercase tag, a single space, and a fully lowercase
comment body (e.g. "// TODO enable once ui has proper designs to handle declined
transactions in activity"); update the line containing that TODO comment in the
server/hooks/panda.ts hook to remove the colon and capitalize only the TODO tag.
📜 Review details

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0860357 and 118a340.

📒 Files selected for processing (3)
  • .changeset/honest-peas-stand.md
  • server/hooks/panda.ts
  • server/test/hooks/panda.test.ts
🧰 Additional context used
📓 Path-based instructions (7)
**/.changeset/*.md

📄 CodeRabbit inference engine (.cursor/rules/style.mdc)

Use a lowercase sentence in the imperative present tense for changeset summaries

Files:

  • .changeset/honest-peas-stand.md
server/**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/server.mdc)

server/**/*.ts: Use c.var object to pass strongly-typed data between Hono middleware and route handlers; do not use c.set
All request validation (headers, body, params) must be handled by @hono/valibot-validator middleware; do not perform manual validation inside route handlers
Use Hono's built-in error handling by throwing new HTTPException() for expected errors; unhandled errors will be caught and logged automatically
Enforce Node.js best practices using ESLint plugin:n/recommended configuration
Enforce Drizzle ORM best practices using ESLint plugin:drizzle/all configuration, including requiring where clauses for update and delete operations
Use Drizzle ORM query builder for all database interactions; do not write raw SQL queries unless absolutely unavoidable
All authentication and authorization logic must be implemented in Hono middleware
Do not access process.env directly in application code; load all configuration and secrets once at startup and pass them through dependency injection or context
Avoid long-running, synchronous operations; use async/await correctly and be mindful of CPU-intensive tasks to prevent blocking the event loop

Files:

  • server/hooks/panda.ts
  • server/test/hooks/panda.test.ts
**/*.{js,ts,tsx,jsx,sol}

📄 CodeRabbit inference engine (AGENTS.md)

Follow linter/formatter (eslint, prettier, solhint) strictly with high strictness level. No any type.

Files:

  • server/hooks/panda.ts
  • server/test/hooks/panda.test.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Omit redundant type names in variable declarations - let the type system explain itself

**/*.{ts,tsx}: Use PascalCase for TypeScript types and interfaces
Use valibot for all runtime validation of API inputs, environment variables, and other data; define schemas once and reuse them
Infer TypeScript types from valibot schemas using type User = v.Input<typeof UserSchema> instead of manually defining interfaces

Files:

  • server/hooks/panda.ts
  • server/test/hooks/panda.test.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx,js,jsx}: Omit contextual names - don't repeat class/module names in members
Omit meaningless words like 'data', 'state', 'manager', 'engine', 'value' from variable and function names unless they add disambiguation

**/*.{ts,tsx,js,jsx}: Prefer function declarations for all multi-line functions; use function expressions or arrow functions only for single-line implementations
Prefer const for all variable declarations by default; only use let if the variable's value will be reassigned
Declare each variable on its own line with its own const or let keyword, not multiple declarations on one line
Use camelCase for TypeScript variables and functions
Always use import type { ... } for type imports
Use relative paths for all imports within the project; avoid tsconfig path aliases
Follow eslint-plugin-import order: react, external libraries, then relative paths
Use object and array destructuring to access and use properties
Use object method shorthand syntax when a function is a property of an object
Prefer optional chaining (?.), nullish coalescing (??), object and array spreading (...), and for...of loops over traditional syntax
Do not use abbreviations or cryptic names; write out full words like error, parameters, request instead of err, params, req
Use Number.parseInt() instead of the global parseInt() function when parsing numbers
All classes called with new must use PascalCase
Use Buffer.from(), Buffer.alloc(), or Buffer.allocUnsafe() instead of the deprecated new Buffer()
Use @ts-expect-error instead of @ts-ignore; follow it immediately with a single-line lowercase comment explaining why the error is expected, without separators like - or :
Do not include the type in a variable's name; let the static type system do its job (e.g., use const user: User not const userObject: User)
Do not repeat the name of a class or module within its members; omit contextual names (e.g., use `class User { getProfil...

Files:

  • server/hooks/panda.ts
  • server/test/hooks/panda.test.ts
server/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

server/**/*.{ts,tsx}: Server API: implement schema-first approach using OpenAPI via hono with validation via valibot middleware
Server database: drizzle schema is source of truth. Migrations required. No direct database access in handlers - use c.var.db

Files:

  • server/hooks/panda.ts
  • server/test/hooks/panda.test.ts
**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/style.mdc)

For files with a single default export, name the file identically to the export; for files with multiple exports, use camelCase with a strong preference for a single word

Files:

  • server/hooks/panda.ts
  • server/test/hooks/panda.test.ts
🧠 Learnings (2)
📚 Learning: 2025-12-31T00:23:55.034Z
Learnt from: cruzdanilo
Repo: exactly/exa PR: 610
File: .changeset/ready-experts-fly.md:1-2
Timestamp: 2025-12-31T00:23:55.034Z
Learning: In the exactly/exa repository, allow and require empty changeset files (containing only --- separators) when changes are not user-facing and do not warrant a version bump. This is needed because CI runs changeset status --since origin/main and requires a changeset file to exist. Ensure such empty changesets are used only for non-user-facing changes and document the rationale in the commit or changelog notes.

Applied to files:

  • .changeset/honest-peas-stand.md
📚 Learning: 2025-12-23T19:58:16.574Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-23T19:58:16.574Z
Learning: Zero config local dev environment: no `.env` files, mock all external services

Applied to files:

  • server/test/hooks/panda.test.ts
🧬 Code graph analysis (2)
server/hooks/panda.ts (1)
server/utils/onesignal.ts (1)
  • sendPushNotification (7-25)
server/test/hooks/panda.test.ts (1)
server/database/schema.ts (1)
  • transactions (36-43)
🔇 Additional comments (5)
server/test/hooks/panda.test.ts (2)

2-2: LGTM!

Import adjustments for mocks are clean and improve organization.

Also applies to: 7-7


1352-1439: Test stubs properly scaffolded for future implementation.

The two test cases for declined transaction handling are well-structured and align with the commented-out database logic in server/hooks/panda.ts. Using it.todo is appropriate while waiting for UI designs.

server/hooks/panda.ts (2)

533-533: LGTM!

The call to handleDeclinedTransaction is correctly placed after the mutex is released, and the type cast is necessary due to TypeScript's union type handling.


988-998: LGTM!

Push notification implementation properly formats the transaction details and includes error handling consistent with the rest of the codebase.

.changeset/honest-peas-stand.md (1)

1-5: LGTM!

Changeset properly documents the feature addition with appropriate version bump and clear description.

@aguxez aguxez force-pushed the card-declined-events branch from 118a340 to dc0ac8a Compare January 7, 2026 18:15
@sentry
Copy link

sentry bot commented Jan 7, 2026

Codecov Report

❌ Patch coverage is 53.22581% with 29 lines in your changes missing coverage. Please review.
✅ Project coverage is 61.53%. Comparing base (c1fb4c2) to head (f80b47a).

Files with missing lines Patch % Lines
server/hooks/panda.ts 58.69% 13 Missing and 6 partials ⚠️
server/api/activity.ts 37.50% 9 Missing and 1 partial ⚠️
Additional details and impacted files
@@             Coverage Diff             @@
##             main     #622       +/-   ##
===========================================
+ Coverage   51.29%   61.53%   +10.24%     
===========================================
  Files         169      169               
  Lines        5188     5247       +59     
  Branches     1457     1477       +20     
===========================================
+ Hits         2661     3229      +568     
+ Misses       2415     1851      -564     
- Partials      112      167       +55     
Flag Coverage Δ
e2e 42.96% <1.61%> (-0.46%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 Fix all issues with AI agents
In @server/hooks/panda.ts:
- Line 955: The function handleDeclinedTransaction currently types its first
parameter as string but should use the Address type for consistency and type
safety; change the parameter signature from account: string to account: Address
so it matches the Address value passed in callers (e.g., the call site that
supplies an Address) and update any internal uses if needed to reflect Address
methods/properties.
- Line 965: The TODO comment "// TODO: Enable once UI has proper designs to
handle declined transactions in activity" violates the guideline (no colon,
lowercase after tag); replace it with a formatted comment like "// TODO enable
once ui has proper designs to handle declined transactions in activity" — remove
the colon and make the text lowercase after the TODO tag.

In @server/test/hooks/panda.test.ts:
- Line 1390: Update the TODO comment to match the coding guideline: remove the
colon and make the text lowercase after the tag; replace the existing "// TODO:
Enable once UI has proper designs to handle declined transactions in activity"
comment with "// TODO enable once ui has proper designs to handle declined
transactions in activity" in the panda.test.ts test hook.
- Line 1352: The TODO comment currently uses a colon and mixed case; update the
comment so the tag is uppercase "TODO" followed by a single space and the rest
of the comment is lowercase with no colon or dashes (e.g., replace "// TODO:
Enable once UI has proper designs to handle declined transactions in activity"
with "// TODO enable once ui has proper designs to handle declined transactions
in activity"), leaving the comment text content otherwise intact.
📜 Review details

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 118a340 and dc0ac8a.

📒 Files selected for processing (3)
  • .changeset/honest-peas-stand.md
  • server/hooks/panda.ts
  • server/test/hooks/panda.test.ts
🧰 Additional context used
📓 Path-based instructions (7)
server/**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/server.mdc)

server/**/*.ts: Use c.var object to pass strongly-typed data between Hono middleware and route handlers; do not use c.set
All request validation (headers, body, params) must be handled by @hono/valibot-validator middleware; do not perform manual validation inside route handlers
Use Hono's built-in error handling by throwing new HTTPException() for expected errors; unhandled errors will be caught and logged automatically
Enforce Node.js best practices using ESLint plugin:n/recommended configuration
Enforce Drizzle ORM best practices using ESLint plugin:drizzle/all configuration, including requiring where clauses for update and delete operations
Use Drizzle ORM query builder for all database interactions; do not write raw SQL queries unless absolutely unavoidable
All authentication and authorization logic must be implemented in Hono middleware
Do not access process.env directly in application code; load all configuration and secrets once at startup and pass them through dependency injection or context
Avoid long-running, synchronous operations; use async/await correctly and be mindful of CPU-intensive tasks to prevent blocking the event loop

Files:

  • server/test/hooks/panda.test.ts
  • server/hooks/panda.ts
**/*.{js,ts,tsx,jsx,sol}

📄 CodeRabbit inference engine (AGENTS.md)

Follow linter/formatter (eslint, prettier, solhint) strictly with high strictness level. No any type.

Files:

  • server/test/hooks/panda.test.ts
  • server/hooks/panda.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Omit redundant type names in variable declarations - let the type system explain itself

**/*.{ts,tsx}: Use PascalCase for TypeScript types and interfaces
Use valibot for all runtime validation of API inputs, environment variables, and other data; define schemas once and reuse them
Infer TypeScript types from valibot schemas using type User = v.Input<typeof UserSchema> instead of manually defining interfaces

Files:

  • server/test/hooks/panda.test.ts
  • server/hooks/panda.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx,js,jsx}: Omit contextual names - don't repeat class/module names in members
Omit meaningless words like 'data', 'state', 'manager', 'engine', 'value' from variable and function names unless they add disambiguation

**/*.{ts,tsx,js,jsx}: Prefer function declarations for all multi-line functions; use function expressions or arrow functions only for single-line implementations
Prefer const for all variable declarations by default; only use let if the variable's value will be reassigned
Declare each variable on its own line with its own const or let keyword, not multiple declarations on one line
Use camelCase for TypeScript variables and functions
Always use import type { ... } for type imports
Use relative paths for all imports within the project; avoid tsconfig path aliases
Follow eslint-plugin-import order: react, external libraries, then relative paths
Use object and array destructuring to access and use properties
Use object method shorthand syntax when a function is a property of an object
Prefer optional chaining (?.), nullish coalescing (??), object and array spreading (...), and for...of loops over traditional syntax
Do not use abbreviations or cryptic names; write out full words like error, parameters, request instead of err, params, req
Use Number.parseInt() instead of the global parseInt() function when parsing numbers
All classes called with new must use PascalCase
Use Buffer.from(), Buffer.alloc(), or Buffer.allocUnsafe() instead of the deprecated new Buffer()
Use @ts-expect-error instead of @ts-ignore; follow it immediately with a single-line lowercase comment explaining why the error is expected, without separators like - or :
Do not include the type in a variable's name; let the static type system do its job (e.g., use const user: User not const userObject: User)
Do not repeat the name of a class or module within its members; omit contextual names (e.g., use `class User { getProfil...

Files:

  • server/test/hooks/panda.test.ts
  • server/hooks/panda.ts
server/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

server/**/*.{ts,tsx}: Server API: implement schema-first approach using OpenAPI via hono with validation via valibot middleware
Server database: drizzle schema is source of truth. Migrations required. No direct database access in handlers - use c.var.db

Files:

  • server/test/hooks/panda.test.ts
  • server/hooks/panda.ts
**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/style.mdc)

For files with a single default export, name the file identically to the export; for files with multiple exports, use camelCase with a strong preference for a single word

Files:

  • server/test/hooks/panda.test.ts
  • server/hooks/panda.ts
**/.changeset/*.md

📄 CodeRabbit inference engine (.cursor/rules/style.mdc)

Use a lowercase sentence in the imperative present tense for changeset summaries

Files:

  • .changeset/honest-peas-stand.md
🧠 Learnings (5)
📚 Learning: 2025-12-23T19:58:16.574Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-23T19:58:16.574Z
Learning: Zero config local dev environment: no `.env` files, mock all external services

Applied to files:

  • server/test/hooks/panda.test.ts
📚 Learning: 2025-12-31T00:23:55.034Z
Learnt from: cruzdanilo
Repo: exactly/exa PR: 610
File: .changeset/ready-experts-fly.md:1-2
Timestamp: 2025-12-31T00:23:55.034Z
Learning: In the exactly/exa repository, allow and require empty changeset files (containing only --- separators) when changes are not user-facing and do not warrant a version bump. This is needed because CI runs changeset status --since origin/main and requires a changeset file to exist. Ensure such empty changesets are used only for non-user-facing changes and document the rationale in the commit or changelog notes.

Applied to files:

  • .changeset/honest-peas-stand.md
📚 Learning: 2025-12-30T15:03:28.449Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/style.mdc:0-0
Timestamp: 2025-12-30T15:03:28.449Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : All code comments must be lowercase; special tags (TODO, FIXME) are uppercase followed by a single space and lowercase comment, without colons or dashes, and should include a ticket/issue reference when available

Applied to files:

  • server/hooks/panda.ts
📚 Learning: 2025-12-30T15:03:28.449Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/style.mdc:0-0
Timestamp: 2025-12-30T15:03:28.449Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Use `ts-expect-error` instead of `ts-ignore`; follow it immediately with a single-line lowercase comment explaining why the error is expected, without separators like `-` or `:`

Applied to files:

  • server/hooks/panda.ts
📚 Learning: 2025-12-30T15:03:28.449Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/style.mdc:0-0
Timestamp: 2025-12-30T15:03:28.449Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Prefer self-documenting code; avoid comments explaining 'what' the code does; comments should explain 'why' code is written a certain way

Applied to files:

  • server/hooks/panda.ts
🧬 Code graph analysis (2)
server/test/hooks/panda.test.ts (1)
server/database/schema.ts (1)
  • transactions (36-43)
server/hooks/panda.ts (1)
server/utils/onesignal.ts (1)
  • sendPushNotification (7-25)
🔇 Additional comments (4)
.changeset/honest-peas-stand.md (1)

1-5: LGTM!

The changeset format and description follow the coding guidelines correctly. The description is lowercase, uses imperative present tense, and accurately reflects the changes.

server/hooks/panda.ts (2)

533-533: LGTM!

The fire-and-forget invocation of handleDeclinedTransaction is appropriate here and consistent with the async notification pattern used elsewhere in the codebase.


988-997: LGTM!

The push notification implementation follows the fire-and-forget pattern used consistently throughout the codebase. Error handling, message formatting, and use of localAmount/localCurrency for user-facing content are all correct.

server/test/hooks/panda.test.ts (1)

2-2: LGTM!

The import reordering is a clean organizational improvement with no functional impact.

Also applies to: 7-7

@aguxez aguxez force-pushed the card-declined-events branch 3 times, most recently from 2b6c4a8 to 64325f3 Compare January 12, 2026 14:37
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🤖 Fix all issues with AI agents
In @.changeset/honest-peas-stand.md:
- Line 5: The changeset summary line currently includes a scope prefix
("server:") and should instead begin with a gitmoji and the message only; edit
.changeset/honest-peas-stand.md to remove the "server:" prefix and rewrite the
first line to follow the repo convention like "💥 add activity and push
notification on card decline" (use the appropriate gitmoji for the change).

In @server/hooks/panda.ts:
- Around line 955-958: The function handleDeclinedTransaction currently declares
a jsonBody parameter that is unused (only referenced in commented-out code);
remove jsonBody from the function signature to keep the signature clean and
update any call sites that pass a third argument to call
handleDeclinedTransaction(account, payload) instead; re-add the parameter later
when DB logic requires it.
- Around line 960-986: Replace the non-conforming TODO comment in
server/hooks/panda.ts (the line starting with "// TODO: Enable once UI has
proper designs to handle declined transactions in activity") with the
project-guideline format: start with uppercase "TODO", a single space, a
ticket/issue reference (e.g., "ABC-123"), then a lowercase description without
colons or dashes (for example "enable once ui has proper designs to handle
declined transactions in activity"); ensure the comment is a single line and
follows this pattern so tooling and reviewers can parse the ticket reference.

In @server/test/hooks/panda.test.ts:
- Around line 1352-1353: The TODO in the test suite (the it.todo call labeled
"inserts declined transaction with empty hashes" in
server/test/hooks/panda.test.ts) lacks a ticket/issue reference; update the TODO
comment to append a concise ticket id or issue URL (e.g., "TODO: <TICKET-1234>"
or "TODO: see ISSUE-1234") so it follows the coding guidelines and points
reviewers to the tracking item that explains when/why this test will be enabled.
- Around line 1390-1391: The test uses it.todo which is inconsistent with our
TODO format; replace the it.todo("appends body to existing transaction when
declined", async () => { ... }) with a skipped test using it.skip or add a
proper TODO comment above the test—e.g., convert the declaration to
it.skip("appends body to existing transaction when declined", async () => { /*
test body or placeholder */ }) so the test remains visible but skipped, or add a
preceding // TODO: ... comment and leave the test fully implemented/removed
accordingly; locate the occurrence by the test description string "appends body
to existing transaction when declined".
📜 Review details

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between dc0ac8a and 64325f3.

📒 Files selected for processing (3)
  • .changeset/honest-peas-stand.md
  • server/hooks/panda.ts
  • server/test/hooks/panda.test.ts
🧰 Additional context used
📓 Path-based instructions (7)
server/**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/server.mdc)

server/**/*.ts: Use c.var object to pass strongly-typed data between Hono middleware and route handlers; do not use c.set
All request validation (headers, body, params) must be handled by @hono/valibot-validator middleware; do not perform manual validation inside route handlers
Use Hono's built-in error handling by throwing new HTTPException() for expected errors; unhandled errors will be caught and logged automatically
Enforce Node.js best practices using ESLint plugin:n/recommended configuration
Enforce Drizzle ORM best practices using ESLint plugin:drizzle/all configuration, including requiring where clauses for update and delete operations
Use Drizzle ORM query builder for all database interactions; do not write raw SQL queries unless absolutely unavoidable
All authentication and authorization logic must be implemented in Hono middleware
Do not access process.env directly in application code; load all configuration and secrets once at startup and pass them through dependency injection or context
Avoid long-running, synchronous operations; use async/await correctly and be mindful of CPU-intensive tasks to prevent blocking the event loop

Files:

  • server/test/hooks/panda.test.ts
  • server/hooks/panda.ts
**/*.{js,ts,tsx,jsx,sol}

📄 CodeRabbit inference engine (AGENTS.md)

Follow linter/formatter (eslint, prettier, solhint) strictly with high strictness level. No any type.

Files:

  • server/test/hooks/panda.test.ts
  • server/hooks/panda.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Omit redundant type names in variable declarations - let the type system explain itself

**/*.{ts,tsx}: Use PascalCase for TypeScript types and interfaces
Use valibot for all runtime validation of API inputs, environment variables, and other data; define schemas once and reuse them
Infer TypeScript types from valibot schemas using type User = v.Input<typeof UserSchema> instead of manually defining interfaces

Files:

  • server/test/hooks/panda.test.ts
  • server/hooks/panda.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx,js,jsx}: Omit contextual names - don't repeat class/module names in members
Omit meaningless words like 'data', 'state', 'manager', 'engine', 'value' from variable and function names unless they add disambiguation

**/*.{ts,tsx,js,jsx}: Prefer function declarations for all multi-line functions; use function expressions or arrow functions only for single-line implementations
Prefer const for all variable declarations by default; only use let if the variable's value will be reassigned
Declare each variable on its own line with its own const or let keyword, not multiple declarations on one line
Use camelCase for TypeScript variables and functions
Always use import type { ... } for type imports
Use relative paths for all imports within the project; avoid tsconfig path aliases
Follow eslint-plugin-import order: react, external libraries, then relative paths
Use object and array destructuring to access and use properties
Use object method shorthand syntax when a function is a property of an object
Prefer optional chaining (?.), nullish coalescing (??), object and array spreading (...), and for...of loops over traditional syntax
Do not use abbreviations or cryptic names; write out full words like error, parameters, request instead of err, params, req
Use Number.parseInt() instead of the global parseInt() function when parsing numbers
All classes called with new must use PascalCase
Use Buffer.from(), Buffer.alloc(), or Buffer.allocUnsafe() instead of the deprecated new Buffer()
Use @ts-expect-error instead of @ts-ignore; follow it immediately with a single-line lowercase comment explaining why the error is expected, without separators like - or :
Do not include the type in a variable's name; let the static type system do its job (e.g., use const user: User not const userObject: User)
Do not repeat the name of a class or module within its members; omit contextual names (e.g., use `class User { getProfil...

Files:

  • server/test/hooks/panda.test.ts
  • server/hooks/panda.ts
server/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

server/**/*.{ts,tsx}: Server API: implement schema-first approach using OpenAPI via hono with validation via valibot middleware
Server database: drizzle schema is source of truth. Migrations required. No direct database access in handlers - use c.var.db

Files:

  • server/test/hooks/panda.test.ts
  • server/hooks/panda.ts
**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/style.mdc)

For files with a single default export, name the file identically to the export; for files with multiple exports, use camelCase with a strong preference for a single word

Files:

  • server/test/hooks/panda.test.ts
  • server/hooks/panda.ts
**/.changeset/*.md

📄 CodeRabbit inference engine (.cursor/rules/style.mdc)

Use a lowercase sentence in the imperative present tense for changeset summaries

Files:

  • .changeset/honest-peas-stand.md
🧠 Learnings (5)
📚 Learning: 2025-12-30T15:03:28.449Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/style.mdc:0-0
Timestamp: 2025-12-30T15:03:28.449Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : All code comments must be lowercase; special tags (TODO, FIXME) are uppercase followed by a single space and lowercase comment, without colons or dashes, and should include a ticket/issue reference when available

Applied to files:

  • server/test/hooks/panda.test.ts
  • server/hooks/panda.ts
📚 Learning: 2025-12-30T15:03:28.449Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/style.mdc:0-0
Timestamp: 2025-12-30T15:03:28.449Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Use `ts-expect-error` instead of `ts-ignore`; follow it immediately with a single-line lowercase comment explaining why the error is expected, without separators like `-` or `:`

Applied to files:

  • server/test/hooks/panda.test.ts
  • server/hooks/panda.ts
📚 Learning: 2025-12-23T19:58:16.574Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-23T19:58:16.574Z
Learning: Zero config local dev environment: no `.env` files, mock all external services

Applied to files:

  • server/test/hooks/panda.test.ts
📚 Learning: 2025-12-31T00:23:55.034Z
Learnt from: cruzdanilo
Repo: exactly/exa PR: 610
File: .changeset/ready-experts-fly.md:1-2
Timestamp: 2025-12-31T00:23:55.034Z
Learning: In the exactly/exa repository, allow and require empty changeset files (containing only --- separators) when changes are not user-facing and do not warrant a version bump. This is needed because CI runs changeset status --since origin/main and requires a changeset file to exist. Ensure such empty changesets are used only for non-user-facing changes and document the rationale in the commit or changelog notes.

Applied to files:

  • .changeset/honest-peas-stand.md
📚 Learning: 2026-01-09T16:24:11.460Z
Learnt from: cruzdanilo
Repo: exactly/exa PR: 627
File: .changeset/breezy-planes-repeat.md:5-5
Timestamp: 2026-01-09T16:24:11.460Z
Learning: In the exactly/exa repository, changeset summaries in .changeset/*.md must start with a gitmoji and follow the format '<emoji> <message>' (no scope). This should mirror the commit message format '<emoji> <scope>: <message>' but omit the '<scope>:' portion. Ensure the message is clear and describes the change.

Applied to files:

  • .changeset/honest-peas-stand.md
🧬 Code graph analysis (2)
server/test/hooks/panda.test.ts (1)
server/database/schema.ts (1)
  • transactions (49-56)
server/hooks/panda.ts (2)
server/utils/onesignal.ts (1)
  • sendPushNotification (7-25)
server/test/e2e.ts (1)
  • captureException (56-59)
🔇 Additional comments (5)
server/test/hooks/panda.test.ts (3)

1-7: LGTM!

The mock import reordering is a reasonable organizational change.


1353-1388: Test stub implementation looks correct.

The test logic properly validates that a declined transaction is inserted with empty hashes and the expected payload structure.


1391-1439: Test stub implementation looks correct.

The test properly validates appending a declined status to an existing transaction.

server/hooks/panda.ts (2)

532-534: LGTM!

The call to handleDeclinedTransaction is correctly placed after mutex release in the declined status branch, and uses appropriate parameters.


988-998: Push notification implementation looks good.

The notification correctly formats the declined transaction amount using locale formatting and includes the merchant name. Error handling via .catch() with captureException is appropriate.

@aguxez aguxez force-pushed the card-declined-events branch from 64325f3 to b3814d0 Compare January 13, 2026 11:09
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In @.changeset/honest-peas-stand.md:
- Line 5: Update the changeset summary line so it starts with a gitmoji and
omits the scope: remove the leading "server:" prefix from the summary and
prepend an appropriate emoji (e.g., 🔔, ✨, or 🛠️) so the line becomes "<emoji>
add activity and push notification on card decline".

In @server/hooks/panda.ts:
- Around line 955-998: The function handleDeclinedTransaction currently has an
unused jsonBody parameter (leftover from commented DB logic); either remove
jsonBody from handleDeclinedTransaction's signature and strip it from all call
sites that pass it, or if you want to preserve it for the pending feature, keep
the parameter but add a clear TODO comment inside handleDeclinedTransaction
referencing the disabled DB block and that jsonBody will be used when
re-enabling it (and mark it as intentionally unused to avoid linter warnings);
update usages accordingly referencing the handleDeclinedTransaction symbol.
📜 Review details

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 64325f3 and b3814d0.

📒 Files selected for processing (3)
  • .changeset/honest-peas-stand.md
  • server/hooks/panda.ts
  • server/test/hooks/panda.test.ts
🧰 Additional context used
📓 Path-based instructions (7)
server/**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/server.mdc)

server/**/*.ts: Use c.var object to pass strongly-typed data between Hono middleware and route handlers; do not use c.set
All request validation (headers, body, params) must be handled by @hono/valibot-validator middleware; do not perform manual validation inside route handlers
Use Hono's built-in error handling by throwing new HTTPException() for expected errors; unhandled errors will be caught and logged automatically
Enforce Node.js best practices using ESLint plugin:n/recommended configuration
Enforce Drizzle ORM best practices using ESLint plugin:drizzle/all configuration, including requiring where clauses for update and delete operations
Use Drizzle ORM query builder for all database interactions; do not write raw SQL queries unless absolutely unavoidable
All authentication and authorization logic must be implemented in Hono middleware
Do not access process.env directly in application code; load all configuration and secrets once at startup and pass them through dependency injection or context
Avoid long-running, synchronous operations; use async/await correctly and be mindful of CPU-intensive tasks to prevent blocking the event loop

Files:

  • server/test/hooks/panda.test.ts
  • server/hooks/panda.ts
**/*.{js,ts,tsx,jsx,sol}

📄 CodeRabbit inference engine (AGENTS.md)

Follow linter/formatter (eslint, prettier, solhint) strictly with high strictness level. No any type.

Files:

  • server/test/hooks/panda.test.ts
  • server/hooks/panda.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Omit redundant type names in variable declarations - let the type system explain itself

**/*.{ts,tsx}: Use PascalCase for TypeScript types and interfaces
Use valibot for all runtime validation of API inputs, environment variables, and other data; define schemas once and reuse them
Infer TypeScript types from valibot schemas using type User = v.Input<typeof UserSchema> instead of manually defining interfaces

Files:

  • server/test/hooks/panda.test.ts
  • server/hooks/panda.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx,js,jsx}: Omit contextual names - don't repeat class/module names in members
Omit meaningless words like 'data', 'state', 'manager', 'engine', 'value' from variable and function names unless they add disambiguation

**/*.{ts,tsx,js,jsx}: Prefer function declarations for all multi-line functions; use function expressions or arrow functions only for single-line implementations
Prefer const for all variable declarations by default; only use let if the variable's value will be reassigned
Declare each variable on its own line with its own const or let keyword, not multiple declarations on one line
Use camelCase for TypeScript variables and functions
Always use import type { ... } for type imports
Use relative paths for all imports within the project; avoid tsconfig path aliases
Follow eslint-plugin-import order: react, external libraries, then relative paths
Use object and array destructuring to access and use properties
Use object method shorthand syntax when a function is a property of an object
Prefer optional chaining (?.), nullish coalescing (??), object and array spreading (...), and for...of loops over traditional syntax
Do not use abbreviations or cryptic names; write out full words like error, parameters, request instead of err, params, req
Use Number.parseInt() instead of the global parseInt() function when parsing numbers
All classes called with new must use PascalCase
Use Buffer.from(), Buffer.alloc(), or Buffer.allocUnsafe() instead of the deprecated new Buffer()
Use @ts-expect-error instead of @ts-ignore; follow it immediately with a single-line lowercase comment explaining why the error is expected, without separators like - or :
Do not include the type in a variable's name; let the static type system do its job (e.g., use const user: User not const userObject: User)
Do not repeat the name of a class or module within its members; omit contextual names (e.g., use `class User { getProfil...

Files:

  • server/test/hooks/panda.test.ts
  • server/hooks/panda.ts
server/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

server/**/*.{ts,tsx}: Server API: implement schema-first approach using OpenAPI via hono with validation via valibot middleware
Server database: drizzle schema is source of truth. Migrations required. No direct database access in handlers - use c.var.db

Files:

  • server/test/hooks/panda.test.ts
  • server/hooks/panda.ts
**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/style.mdc)

For files with a single default export, name the file identically to the export; for files with multiple exports, use camelCase with a strong preference for a single word

Files:

  • server/test/hooks/panda.test.ts
  • server/hooks/panda.ts
**/.changeset/*.md

📄 CodeRabbit inference engine (.cursor/rules/style.mdc)

Use a lowercase sentence in the imperative present tense for changeset summaries

Files:

  • .changeset/honest-peas-stand.md
🧠 Learnings (11)
📚 Learning: 2025-12-30T15:03:28.449Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/style.mdc:0-0
Timestamp: 2025-12-30T15:03:28.449Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : All code comments must be lowercase; special tags (TODO, FIXME) are uppercase followed by a single space and lowercase comment, without colons or dashes, and should include a ticket/issue reference when available

Applied to files:

  • server/test/hooks/panda.test.ts
  • server/hooks/panda.ts
📚 Learning: 2025-12-30T15:03:28.449Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/style.mdc:0-0
Timestamp: 2025-12-30T15:03:28.449Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Use `ts-expect-error` instead of `ts-ignore`; follow it immediately with a single-line lowercase comment explaining why the error is expected, without separators like `-` or `:`

Applied to files:

  • server/test/hooks/panda.test.ts
  • server/hooks/panda.ts
📚 Learning: 2025-12-30T15:03:28.449Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/style.mdc:0-0
Timestamp: 2025-12-30T15:03:28.449Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Prefer self-documenting code; avoid comments explaining 'what' the code does; comments should explain 'why' code is written a certain way

Applied to files:

  • server/test/hooks/panda.test.ts
📚 Learning: 2025-12-23T19:58:16.574Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-23T19:58:16.574Z
Learning: Zero config local dev environment: no `.env` files, mock all external services

Applied to files:

  • server/test/hooks/panda.test.ts
📚 Learning: 2025-12-31T00:23:55.034Z
Learnt from: cruzdanilo
Repo: exactly/exa PR: 610
File: .changeset/ready-experts-fly.md:1-2
Timestamp: 2025-12-31T00:23:55.034Z
Learning: In the exactly/exa repository, allow and require empty changeset files (containing only --- separators) when changes are not user-facing and do not warrant a version bump. This is needed because CI runs changeset status --since origin/main and requires a changeset file to exist. Ensure such empty changesets are used only for non-user-facing changes and document the rationale in the commit or changelog notes.

Applied to files:

  • .changeset/honest-peas-stand.md
📚 Learning: 2026-01-09T16:24:11.460Z
Learnt from: cruzdanilo
Repo: exactly/exa PR: 627
File: .changeset/breezy-planes-repeat.md:5-5
Timestamp: 2026-01-09T16:24:11.460Z
Learning: In the exactly/exa repository, changeset summaries in .changeset/*.md must start with a gitmoji and follow the format '<emoji> <message>' (no scope). This should mirror the commit message format '<emoji> <scope>: <message>' but omit the '<scope>:' portion. Ensure the message is clear and describes the change.

Applied to files:

  • .changeset/honest-peas-stand.md
📚 Learning: 2025-12-30T15:03:28.449Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/style.mdc:0-0
Timestamp: 2025-12-30T15:03:28.449Z
Learning: Applies to **/.changeset/*.md : Use a lowercase sentence in the imperative present tense for changeset summaries

Applied to files:

  • .changeset/honest-peas-stand.md
📚 Learning: 2025-12-23T19:58:16.574Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-23T19:58:16.574Z
Learning: Use gitmoji format for commits: `<emoji> <scope>: <message>` with scopes: `app`, `server`, `contracts`, `common`, `docs`

Applied to files:

  • .changeset/honest-peas-stand.md
📚 Learning: 2025-12-23T19:57:14.465Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/git.mdc:0-0
Timestamp: 2025-12-23T19:57:14.465Z
Learning: Select the most precise gitmoji that best communicates the intent of the change (e.g., ✨ for new feature, 🐛 for bug fix, ♻️ for refactoring, ⚡️ for performance improvement, 📝 for documentation, 🚀 for deployment)

Applied to files:

  • .changeset/honest-peas-stand.md
📚 Learning: 2025-12-23T19:57:14.465Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/git.mdc:0-0
Timestamp: 2025-12-23T19:57:14.465Z
Learning: All commit messages must strictly adhere to the format: `<emoji> <scope>: <message>` where emoji is a gitmoji unicode character, scope is a mandatory short name (app, server, contracts, docs, common, dependencies, github, config, or repo), and message is a short lowercase description

Applied to files:

  • .changeset/honest-peas-stand.md
📚 Learning: 2025-12-23T19:57:14.465Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/git.mdc:0-0
Timestamp: 2025-12-23T19:57:14.465Z
Learning: All commits must start with a single gitmoji from the official gitmoji list; this is not optional and the emoji visually communicates the intent of the change

Applied to files:

  • .changeset/honest-peas-stand.md
🧬 Code graph analysis (2)
server/test/hooks/panda.test.ts (1)
server/database/schema.ts (1)
  • transactions (49-56)
server/hooks/panda.ts (2)
server/utils/onesignal.ts (1)
  • sendPushNotification (7-25)
server/test/e2e.ts (1)
  • captureException (57-60)
🔇 Additional comments (6)
server/test/hooks/panda.test.ts (3)

1352-1388: Test expectation may not align with commented-out implementation.

The test expects hashes: [] (line 1382), but the commented-out implementation in server/hooks/panda.ts (line 980) uses hashes: [zeroHash]. When the feature is enabled, ensure the test expectation matches the actual implementation.


2-7: LGTM!

Import ordering is correct with mocks properly initialized before other imports.


1390-1439: LGTM!

The test stub for appending declined status to existing transactions is well-structured and follows the established patterns in the file.

server/hooks/panda.ts (3)

960-986: TODO comment properly formatted.

The TODO comment follows the coding guidelines (uppercase TODO followed by lowercase message). Consider adding a ticket/issue reference to track when this will be enabled.


988-997: LGTM!

The push notification implementation follows the established pattern in the codebase, with proper error handling via .catch() and captureException. The notification content is user-friendly and includes relevant transaction details.


532-534: LGTM!

The call to handleDeclinedTransaction is correctly placed after the mutex is released, ensuring the notification is sent for declined transactions.

@aguxez aguxez force-pushed the card-declined-events branch from b3814d0 to 8c11c8c Compare January 13, 2026 14:49
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 Fix all issues with AI agents
In @.changeset/honest-peas-stand.md:
- Line 5: Edit the changeset summary line in .changeset/honest-peas-stand.md to
remove the "server:" scope prefix and prepend a gitmoji, e.g., change "server:
add activity and push notification on card decline" to start with an emoji
followed by the message without scope (for example "🔔 add activity and push
notification on card decline").

In @server/hooks/panda.ts:
- Around line 960-986: Remove the large commented-out transaction handling block
(the dead code referencing transactions, txId, spend, getCreatedAt,
TransactionPayload, and zeroHash) and either delete it entirely or move it into
a separate reference file or design doc (e.g., a new file under docs/ or a gist)
so the main codebase remains clean; also update the inline TODO comment to
follow the project's TODO convention (change "TODO: Enable once UI..." to "TODO
Enable once UI has proper designs to handle declined transactions in activity")
so the intent is preserved without leaving commented implementation in
server/hooks/panda.ts.
- Around line 955-958: Update handleDeclinedTransaction to use consistent types
and either remove or rename the unused third parameter: change the type of the
first parameter from string to Address to match callers (i.e., account:
Address), and either drop the unused third parameter entirely or rename it to
_jsonBody (and add a comment) if it’s intentionally reserved for future use;
keep the existing early-return logic and references to txId/spend intact.

In @server/test/hooks/panda.test.ts:
- Around line 1390-1439: In the it.todo test "appends body to existing
transaction when declined" the two inline comments ("First create a pending
transaction" and "Then update it as declined") start with capital letters;
change them to lowercase (e.g., "first create a pending transaction" and "then
update it as declined") so all code comments follow the lowercase guideline
inside the test block for the function/test named "appends body to existing
transaction when declined".
📜 Review details

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b3814d0 and 8c11c8c.

📒 Files selected for processing (3)
  • .changeset/honest-peas-stand.md
  • server/hooks/panda.ts
  • server/test/hooks/panda.test.ts
🧰 Additional context used
📓 Path-based instructions (7)
server/**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/server.mdc)

server/**/*.ts: Use c.var object to pass strongly-typed data between Hono middleware and route handlers; do not use c.set
All request validation (headers, body, params) must be handled by @hono/valibot-validator middleware; do not perform manual validation inside route handlers
Use Hono's built-in error handling by throwing new HTTPException() for expected errors; unhandled errors will be caught and logged automatically
Enforce Node.js best practices using ESLint plugin:n/recommended configuration
Enforce Drizzle ORM best practices using ESLint plugin:drizzle/all configuration, including requiring where clauses for update and delete operations
Use Drizzle ORM query builder for all database interactions; do not write raw SQL queries unless absolutely unavoidable
All authentication and authorization logic must be implemented in Hono middleware
Do not access process.env directly in application code; load all configuration and secrets once at startup and pass them through dependency injection or context
Avoid long-running, synchronous operations; use async/await correctly and be mindful of CPU-intensive tasks to prevent blocking the event loop

Files:

  • server/test/hooks/panda.test.ts
  • server/hooks/panda.ts
**/*.{js,ts,tsx,jsx,sol}

📄 CodeRabbit inference engine (AGENTS.md)

Follow linter/formatter (eslint, prettier, solhint) strictly with high strictness level. No any type.

Files:

  • server/test/hooks/panda.test.ts
  • server/hooks/panda.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Omit redundant type names in variable declarations - let the type system explain itself

**/*.{ts,tsx}: Use PascalCase for TypeScript types and interfaces
Use valibot for all runtime validation of API inputs, environment variables, and other data; define schemas once and reuse them
Infer TypeScript types from valibot schemas using type User = v.Input<typeof UserSchema> instead of manually defining interfaces

Files:

  • server/test/hooks/panda.test.ts
  • server/hooks/panda.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx,js,jsx}: Omit contextual names - don't repeat class/module names in members
Omit meaningless words like 'data', 'state', 'manager', 'engine', 'value' from variable and function names unless they add disambiguation

**/*.{ts,tsx,js,jsx}: Prefer function declarations for all multi-line functions; use function expressions or arrow functions only for single-line implementations
Prefer const for all variable declarations by default; only use let if the variable's value will be reassigned
Declare each variable on its own line with its own const or let keyword, not multiple declarations on one line
Use camelCase for TypeScript variables and functions
Always use import type { ... } for type imports
Use relative paths for all imports within the project; avoid tsconfig path aliases
Follow eslint-plugin-import order: react, external libraries, then relative paths
Use object and array destructuring to access and use properties
Use object method shorthand syntax when a function is a property of an object
Prefer optional chaining (?.), nullish coalescing (??), object and array spreading (...), and for...of loops over traditional syntax
Do not use abbreviations or cryptic names; write out full words like error, parameters, request instead of err, params, req
Use Number.parseInt() instead of the global parseInt() function when parsing numbers
All classes called with new must use PascalCase
Use Buffer.from(), Buffer.alloc(), or Buffer.allocUnsafe() instead of the deprecated new Buffer()
Use @ts-expect-error instead of @ts-ignore; follow it immediately with a single-line lowercase comment explaining why the error is expected, without separators like - or :
Do not include the type in a variable's name; let the static type system do its job (e.g., use const user: User not const userObject: User)
Do not repeat the name of a class or module within its members; omit contextual names (e.g., use `class User { getProfil...

Files:

  • server/test/hooks/panda.test.ts
  • server/hooks/panda.ts
server/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

server/**/*.{ts,tsx}: Server API: implement schema-first approach using OpenAPI via hono with validation via valibot middleware
Server database: drizzle schema is source of truth. Migrations required. No direct database access in handlers - use c.var.db

Files:

  • server/test/hooks/panda.test.ts
  • server/hooks/panda.ts
**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/style.mdc)

For files with a single default export, name the file identically to the export; for files with multiple exports, use camelCase with a strong preference for a single word

Files:

  • server/test/hooks/panda.test.ts
  • server/hooks/panda.ts
**/.changeset/*.md

📄 CodeRabbit inference engine (.cursor/rules/style.mdc)

Use a lowercase sentence in the imperative present tense for changeset summaries

Files:

  • .changeset/honest-peas-stand.md
🧠 Learnings (12)
📚 Learning: 2025-12-30T15:03:28.449Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/style.mdc:0-0
Timestamp: 2025-12-30T15:03:28.449Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : All code comments must be lowercase; special tags (TODO, FIXME) are uppercase followed by a single space and lowercase comment, without colons or dashes, and should include a ticket/issue reference when available

Applied to files:

  • server/test/hooks/panda.test.ts
  • server/hooks/panda.ts
📚 Learning: 2025-12-30T15:03:28.449Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/style.mdc:0-0
Timestamp: 2025-12-30T15:03:28.449Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Use `ts-expect-error` instead of `ts-ignore`; follow it immediately with a single-line lowercase comment explaining why the error is expected, without separators like `-` or `:`

Applied to files:

  • server/test/hooks/panda.test.ts
  • server/hooks/panda.ts
📚 Learning: 2025-12-23T19:58:16.574Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-23T19:58:16.574Z
Learning: Zero config local dev environment: no `.env` files, mock all external services

Applied to files:

  • server/test/hooks/panda.test.ts
📚 Learning: 2025-12-31T00:23:55.034Z
Learnt from: cruzdanilo
Repo: exactly/exa PR: 610
File: .changeset/ready-experts-fly.md:1-2
Timestamp: 2025-12-31T00:23:55.034Z
Learning: In the exactly/exa repository, allow and require empty changeset files (containing only --- separators) when changes are not user-facing and do not warrant a version bump. This is needed because CI runs changeset status --since origin/main and requires a changeset file to exist. Ensure such empty changesets are used only for non-user-facing changes and document the rationale in the commit or changelog notes.

Applied to files:

  • .changeset/honest-peas-stand.md
📚 Learning: 2026-01-09T16:24:11.460Z
Learnt from: cruzdanilo
Repo: exactly/exa PR: 627
File: .changeset/breezy-planes-repeat.md:5-5
Timestamp: 2026-01-09T16:24:11.460Z
Learning: In the exactly/exa repository, changeset summaries in .changeset/*.md must start with a gitmoji and follow the format '<emoji> <message>' (no scope). This should mirror the commit message format '<emoji> <scope>: <message>' but omit the '<scope>:' portion. Ensure the message is clear and describes the change.

Applied to files:

  • .changeset/honest-peas-stand.md
📚 Learning: 2025-12-23T19:58:16.574Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-23T19:58:16.574Z
Learning: Use gitmoji format for commits: `<emoji> <scope>: <message>` with scopes: `app`, `server`, `contracts`, `common`, `docs`

Applied to files:

  • .changeset/honest-peas-stand.md
📚 Learning: 2025-12-23T19:57:14.465Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/git.mdc:0-0
Timestamp: 2025-12-23T19:57:14.465Z
Learning: All commit messages must strictly adhere to the format: `<emoji> <scope>: <message>` where emoji is a gitmoji unicode character, scope is a mandatory short name (app, server, contracts, docs, common, dependencies, github, config, or repo), and message is a short lowercase description

Applied to files:

  • .changeset/honest-peas-stand.md
📚 Learning: 2025-12-23T19:57:14.465Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/git.mdc:0-0
Timestamp: 2025-12-23T19:57:14.465Z
Learning: Select the most precise gitmoji that best communicates the intent of the change (e.g., ✨ for new feature, 🐛 for bug fix, ♻️ for refactoring, ⚡️ for performance improvement, 📝 for documentation, 🚀 for deployment)

Applied to files:

  • .changeset/honest-peas-stand.md
📚 Learning: 2025-12-30T15:03:28.449Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/style.mdc:0-0
Timestamp: 2025-12-30T15:03:28.449Z
Learning: Applies to **/.changeset/*.md : Use a lowercase sentence in the imperative present tense for changeset summaries

Applied to files:

  • .changeset/honest-peas-stand.md
📚 Learning: 2025-12-23T19:57:14.465Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/git.mdc:0-0
Timestamp: 2025-12-23T19:57:14.465Z
Learning: All commits must start with a single gitmoji from the official gitmoji list; this is not optional and the emoji visually communicates the intent of the change

Applied to files:

  • .changeset/honest-peas-stand.md
📚 Learning: 2025-12-23T19:57:14.465Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/git.mdc:0-0
Timestamp: 2025-12-23T19:57:14.465Z
Learning: Do not use conventional commit prefixes like `feat:`, `fix:`, or `docs(...)`; do not use title case or sentence case in the commit subject line; do not create commits without a scope

Applied to files:

  • .changeset/honest-peas-stand.md
📚 Learning: 2025-12-23T19:57:14.465Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/git.mdc:0-0
Timestamp: 2025-12-23T19:57:14.465Z
Learning: Every commit must have a scope; scope must be in lowercase and must be one of: app, server, contracts, docs, common, dependencies, github, config, or repo

Applied to files:

  • .changeset/honest-peas-stand.md
🧬 Code graph analysis (2)
server/test/hooks/panda.test.ts (1)
server/database/schema.ts (1)
  • transactions (49-56)
server/hooks/panda.ts (2)
server/utils/onesignal.ts (1)
  • sendPushNotification (7-25)
server/test/e2e.ts (1)
  • captureException (57-60)
🔇 Additional comments (4)
server/test/hooks/panda.test.ts (2)

1-7: LGTM!

The import additions and reordering for mocks are appropriate for the new test coverage being scaffolded.


1352-1388: LGTM!

The it.todo() test stub for inserting declined transactions with empty hashes is well-structured. The TODO comment correctly follows the format with uppercase TODO followed by a lowercase explanation.

server/hooks/panda.ts (2)

532-534: LGTM!

The placement of handleDeclinedTransaction after mutex release is correct, ensuring the mutex is freed before processing the declined notification flow.


988-997: Push notification implementation looks correct.

The fire-and-forget pattern with .catch() for error handling is consistent with other sendPushNotification calls in this file. The notification content appropriately formats the declined transaction details.

@aguxez aguxez force-pushed the card-declined-events branch from 8c11c8c to 77c26c2 Compare January 14, 2026 13:30
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@server/hooks/panda.ts`:
- Around line 532-534: Make the "fire-and-forget" intent explicit by prefixing
the call with void — change the invocation to void
handleDeclinedTransaction(account, payload as v.InferOutput<typeof Transaction>,
jsonBody); and ensure handleDeclinedTransaction itself cannot throw
synchronously or propagate rejections by wrapping its internal logic in a
try/catch (or returning Promise.resolve().then(() => { ... }).catch(() => {/*
swallow/log error */})), so any errors are handled internally and the caller
remains non-blocking and non-throwing.
♻️ Duplicate comments (5)
server/test/hooks/panda.test.ts (2)

1350-1386: Fix TODO comment format + align “hashes” expectation with intended implementation.

  • TODO comment violates repo guidelines (colon + mixed case + no ticket ref). Based on learnings, TODO should be TODO <ticket> <lowercase message> (no colon).
  • This test expects hashes: [], but the (currently commented) intended insert path in server/hooks/panda.ts uses hashes: [zeroHash]. Either change the test expectation or change the intended DB write behavior.
Proposed fix
-  // TODO: Enable once UI has proper designs to handle declined transactions in activity
+  // TODO EXA-XXXX enable once ui has proper designs to handle declined transactions in activity
@@
-    expect(transaction).toMatchObject({
+    expect(transaction).toMatchObject({
       id: txId,
       cardId,
-      hashes: [],
+      hashes: [zeroHash],
       payload: {
         type: "panda",
         bodies: [{ action: "created", body: { spend: { status: "declined" } } }],
       },
     });

1388-1437: Fix TODO/comment casing; consider it.skip(...) instead of it.todo(...) once behavior is defined.

  • Same TODO format issue as above (colon + mixed case + no ticket ref). Based on learnings, keep it lowercase after TODO and add a tracking reference.
  • Inline comments should be lowercase per guidelines.
Proposed fix
-  // TODO: Enable once UI has proper designs to handle declined transactions in activity
+  // TODO EXA-XXXX enable once ui has proper designs to handle declined transactions in activity
@@
-    // First create a pending transaction
+    // first create a pending transaction
@@
-    // Then update it as declined
+    // then update it as declined
server/hooks/panda.ts (2)

955-998: Prevent webhook crashes: toLocaleString(..., { currency }) can throw for invalid currency codes.

(spend.localAmount / 100).toLocaleString(... { style: "currency", currency: spend.localCurrency }) may throw a synchronous RangeError for unsupported/invalid currency codes; because it happens before the promise exists, your .catch(...) won’t run and the handler can fail mid-flight.

Proposed fix
 function handleDeclinedTransaction(account: string, payload: v.InferOutput<typeof Transaction>, _: unknown) {
   if (payload.action === "requested" || payload.action === "completed") return;
   const { id: txId, spend } = payload.body;
   if (!txId) return;

+  let formattedAmount = `${spend.localAmount / 100} ${spend.localCurrency}`;
+  try {
+    formattedAmount = (spend.localAmount / 100).toLocaleString(undefined, {
+      style: "currency",
+      currency: spend.localCurrency.toUpperCase(),
+    });
+  } catch (error: unknown) {
+    captureException(error, { level: "warning" });
+  }
+
   sendPushNotification({
     userId: account,
     headings: { en: "Exa Card purchase rejected" },
     contents: {
-      en: `Transaction declined: ${(spend.localAmount / 100).toLocaleString(undefined, {
-        style: "currency",
-        currency: spend.localCurrency,
-      })} at ${spend.merchantName.trim()}`,
+      en: `Transaction declined: ${formattedAmount} at ${spend.merchantName.trim()}`,
     },
   }).catch((error: unknown) => captureException(error, { level: "error" }));
 }

955-987: Fix type mismatch + remove/comment-in the disabled DB “activity” block (and fix TODO format).

  • handleDeclinedTransaction(account: string, ...): call site passes Address — use Address for consistency.
  • The large commented-out DB write block is dead code (and the TODO format violates guidelines: colon + mixed case). Prefer deleting it and tracking in an issue, or re-enabling behind a real flag.
  • As-is, the PR/changeset claims “activity”, but this block means activity is not actually shipped yet.
Proposed fix
-function handleDeclinedTransaction(account: string, payload: v.InferOutput<typeof Transaction>, _: unknown) {
+function handleDeclinedTransaction(account: Address, payload: v.InferOutput<typeof Transaction>) {
   if (payload.action === "requested" || payload.action === "completed") return;
   const { id: txId, spend } = payload.body;
   if (!txId) return;

-  // const tx = await database.query.transactions.findFirst({
-  //   where: and(eq(transactions.id, txId), eq(transactions.cardId, spend.cardId)),
-  // });
-  // const createdAt = getCreatedAt(payload) ?? new Date().toISOString();
-  // const body = { ...(jsonBody as object), createdAt };
-  // TODO: Enable once UI has proper designs to handle declined transactions in activity
-  // await (tx
-  //   ? database
-  //       .update(transactions)
-  //       .set({
-  //         payload: {
-  //           ...(tx.payload as object),
-  //           bodies: [...v.parse(TransactionPayload, tx.payload).bodies, body],
-  //         },
-  //       })
-  //       .where(and(eq(transactions.id, txId), eq(transactions.cardId, spend.cardId)))
-  //   : database.insert(transactions).values([
-  //       {
-  //         id: txId,
-  //         cardId: spend.cardId,
-  //         hashes: [zeroHash],
-  //         payload: {
-  //           bodies: [body],
-  //           type: "panda",
-  //         },
-  //       },
-  //     ]));
+  // TODO EXA-XXXX add declined-transaction activity once ui supports it
.changeset/honest-peas-stand.md (1)

1-5: Fix changeset summary format (gitmoji + no scope) and avoid claiming “activity” until it ships.

Based on learnings, changeset summaries must start with a gitmoji (unicode) and must not include server:. Also, “activity” is currently not enabled in server/hooks/panda.ts, so the summary should not claim it yet.

Proposed fix
-server: add activity and push notification on card decline
+<gitmoji> add push notification on card decline
📜 Review details

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8c11c8c and 77c26c2.

📒 Files selected for processing (3)
  • .changeset/honest-peas-stand.md
  • server/hooks/panda.ts
  • server/test/hooks/panda.test.ts
🧰 Additional context used
📓 Path-based instructions (7)
server/**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/server.mdc)

server/**/*.ts: Use c.var object to pass strongly-typed data between Hono middleware and route handlers; do not use c.set
All request validation (headers, body, params) must be handled by @hono/valibot-validator middleware; do not perform manual validation inside route handlers
Use Hono's built-in error handling by throwing new HTTPException() for expected errors; unhandled errors will be caught and logged automatically
Enforce Node.js best practices using ESLint plugin:n/recommended configuration
Enforce Drizzle ORM best practices using ESLint plugin:drizzle/all configuration, including requiring where clauses for update and delete operations
Use Drizzle ORM query builder for all database interactions; do not write raw SQL queries unless absolutely unavoidable
All authentication and authorization logic must be implemented in Hono middleware
Do not access process.env directly in application code; load all configuration and secrets once at startup and pass them through dependency injection or context
Avoid long-running, synchronous operations; use async/await correctly and be mindful of CPU-intensive tasks to prevent blocking the event loop

Files:

  • server/hooks/panda.ts
  • server/test/hooks/panda.test.ts
**/*.{js,ts,tsx,jsx,sol}

📄 CodeRabbit inference engine (AGENTS.md)

Follow linter/formatter (eslint, prettier, solhint) strictly with high strictness level. No any type.

Files:

  • server/hooks/panda.ts
  • server/test/hooks/panda.test.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Omit redundant type names in variable declarations - let the type system explain itself

**/*.{ts,tsx}: Use PascalCase for TypeScript types and interfaces
Use valibot for all runtime validation of API inputs, environment variables, and other data; define schemas once and reuse them
Infer TypeScript types from valibot schemas using type User = v.Input<typeof UserSchema> instead of manually defining interfaces

Files:

  • server/hooks/panda.ts
  • server/test/hooks/panda.test.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx,js,jsx}: Omit contextual names - don't repeat class/module names in members
Omit meaningless words like 'data', 'state', 'manager', 'engine', 'value' from variable and function names unless they add disambiguation

**/*.{ts,tsx,js,jsx}: Prefer function declarations for all multi-line functions; use function expressions or arrow functions only for single-line implementations
Prefer const for all variable declarations by default; only use let if the variable's value will be reassigned
Declare each variable on its own line with its own const or let keyword, not multiple declarations on one line
Use camelCase for TypeScript variables and functions
Always use import type { ... } for type imports
Use relative paths for all imports within the project; avoid tsconfig path aliases
Follow eslint-plugin-import order: react, external libraries, then relative paths
Use object and array destructuring to access and use properties
Use object method shorthand syntax when a function is a property of an object
Prefer optional chaining (?.), nullish coalescing (??), object and array spreading (...), and for...of loops over traditional syntax
Do not use abbreviations or cryptic names; write out full words like error, parameters, request instead of err, params, req
Use Number.parseInt() instead of the global parseInt() function when parsing numbers
All classes called with new must use PascalCase
Use Buffer.from(), Buffer.alloc(), or Buffer.allocUnsafe() instead of the deprecated new Buffer()
Use @ts-expect-error instead of @ts-ignore; follow it immediately with a single-line lowercase comment explaining why the error is expected, without separators like - or :
Do not include the type in a variable's name; let the static type system do its job (e.g., use const user: User not const userObject: User)
Do not repeat the name of a class or module within its members; omit contextual names (e.g., use `class User { getProfil...

Files:

  • server/hooks/panda.ts
  • server/test/hooks/panda.test.ts
server/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

server/**/*.{ts,tsx}: Server API: implement schema-first approach using OpenAPI via hono with validation via valibot middleware
Server database: drizzle schema is source of truth. Migrations required. No direct database access in handlers - use c.var.db

Files:

  • server/hooks/panda.ts
  • server/test/hooks/panda.test.ts
**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/style.mdc)

For files with a single default export, name the file identically to the export; for files with multiple exports, use camelCase with a strong preference for a single word

Files:

  • server/hooks/panda.ts
  • server/test/hooks/panda.test.ts
**/.changeset/*.md

📄 CodeRabbit inference engine (.cursor/rules/style.mdc)

Use a lowercase sentence in the imperative present tense for changeset summaries

Files:

  • .changeset/honest-peas-stand.md
🧠 Learnings (14)
📚 Learning: 2025-12-30T15:03:28.449Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/style.mdc:0-0
Timestamp: 2025-12-30T15:03:28.449Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : All code comments must be lowercase; special tags (TODO, FIXME) are uppercase followed by a single space and lowercase comment, without colons or dashes, and should include a ticket/issue reference when available

Applied to files:

  • server/hooks/panda.ts
  • server/test/hooks/panda.test.ts
📚 Learning: 2025-12-30T15:03:28.449Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/style.mdc:0-0
Timestamp: 2025-12-30T15:03:28.449Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Use `ts-expect-error` instead of `ts-ignore`; follow it immediately with a single-line lowercase comment explaining why the error is expected, without separators like `-` or `:`

Applied to files:

  • server/hooks/panda.ts
  • server/test/hooks/panda.test.ts
📚 Learning: 2025-12-30T15:03:28.449Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/style.mdc:0-0
Timestamp: 2025-12-30T15:03:28.449Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Prefer self-documenting code; avoid comments explaining 'what' the code does; comments should explain 'why' code is written a certain way

Applied to files:

  • server/hooks/panda.ts
  • server/test/hooks/panda.test.ts
📚 Learning: 2025-12-23T19:57:22.339Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/markdown.mdc:0-0
Timestamp: 2025-12-23T19:57:22.339Z
Learning: Applies to **/*.md : All code comments must be lowercase

Applied to files:

  • server/test/hooks/panda.test.ts
📚 Learning: 2025-12-23T19:58:16.574Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-23T19:58:16.574Z
Learning: All internal documentation and commit messages must be lowercase

Applied to files:

  • server/test/hooks/panda.test.ts
📚 Learning: 2025-12-23T19:56:43.683Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/contracts.mdc:0-0
Timestamp: 2025-12-23T19:56:43.683Z
Learning: Applies to contracts/**/*.t.sol : Test functions must start with 'test' and use descriptive names explaining the scenario (e.g., test_revert_when_caller_is_not_owner())

Applied to files:

  • server/test/hooks/panda.test.ts
📚 Learning: 2025-12-23T19:58:16.574Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-23T19:58:16.574Z
Learning: Zero config local dev environment: no `.env` files, mock all external services

Applied to files:

  • server/test/hooks/panda.test.ts
📚 Learning: 2025-12-31T00:23:55.034Z
Learnt from: cruzdanilo
Repo: exactly/exa PR: 610
File: .changeset/ready-experts-fly.md:1-2
Timestamp: 2025-12-31T00:23:55.034Z
Learning: In the exactly/exa repository, allow and require empty changeset files (containing only --- separators) when changes are not user-facing and do not warrant a version bump. This is needed because CI runs changeset status --since origin/main and requires a changeset file to exist. Ensure such empty changesets are used only for non-user-facing changes and document the rationale in the commit or changelog notes.

Applied to files:

  • .changeset/honest-peas-stand.md
📚 Learning: 2026-01-09T16:24:11.460Z
Learnt from: cruzdanilo
Repo: exactly/exa PR: 627
File: .changeset/breezy-planes-repeat.md:5-5
Timestamp: 2026-01-09T16:24:11.460Z
Learning: In the exactly/exa repository, changeset summaries in .changeset/*.md must start with a gitmoji and follow the format '<emoji> <message>' (no scope). This should mirror the commit message format '<emoji> <scope>: <message>' but omit the '<scope>:' portion. Ensure the message is clear and describes the change.

Applied to files:

  • .changeset/honest-peas-stand.md
📚 Learning: 2025-12-23T19:58:16.574Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-23T19:58:16.574Z
Learning: Use gitmoji format for commits: `<emoji> <scope>: <message>` with scopes: `app`, `server`, `contracts`, `common`, `docs`

Applied to files:

  • .changeset/honest-peas-stand.md
📚 Learning: 2025-12-23T19:57:14.465Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/git.mdc:0-0
Timestamp: 2025-12-23T19:57:14.465Z
Learning: All commit messages must strictly adhere to the format: `<emoji> <scope>: <message>` where emoji is a gitmoji unicode character, scope is a mandatory short name (app, server, contracts, docs, common, dependencies, github, config, or repo), and message is a short lowercase description

Applied to files:

  • .changeset/honest-peas-stand.md
📚 Learning: 2025-12-23T19:57:14.465Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/git.mdc:0-0
Timestamp: 2025-12-23T19:57:14.465Z
Learning: Select the most precise gitmoji that best communicates the intent of the change (e.g., ✨ for new feature, 🐛 for bug fix, ♻️ for refactoring, ⚡️ for performance improvement, 📝 for documentation, 🚀 for deployment)

Applied to files:

  • .changeset/honest-peas-stand.md
📚 Learning: 2025-12-30T15:03:28.449Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/style.mdc:0-0
Timestamp: 2025-12-30T15:03:28.449Z
Learning: Applies to **/.changeset/*.md : Use a lowercase sentence in the imperative present tense for changeset summaries

Applied to files:

  • .changeset/honest-peas-stand.md
📚 Learning: 2025-12-23T19:57:14.465Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/git.mdc:0-0
Timestamp: 2025-12-23T19:57:14.465Z
Learning: All commits must start with a single gitmoji from the official gitmoji list; this is not optional and the emoji visually communicates the intent of the change

Applied to files:

  • .changeset/honest-peas-stand.md
🧬 Code graph analysis (1)
server/hooks/panda.ts (2)
server/utils/onesignal.ts (1)
  • sendPushNotification (7-25)
server/test/e2e.ts (1)
  • captureException (58-61)
🔇 Additional comments (1)
server/test/hooks/panda.test.ts (1)

1-7: Mock import order looks safe.

Side-effect mock imports still run before app is imported, so module mocking should apply as intended.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

@aguxez aguxez force-pushed the card-declined-events branch 2 times, most recently from 98e690f to a16d92d Compare January 15, 2026 22:41
@aguxez aguxez force-pushed the card-declined-events branch from a16d92d to 685fbf3 Compare January 19, 2026 12:04
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@server/hooks/panda.ts`:
- Around line 963-966: Rename the abbreviated identifier txId to transactionId
for clarity wherever it's introduced and used in this block: change "const { id:
txId, spend } = payload.body;" to destructure into transactionId (e.g., "const {
id: transactionId, spend } = payload.body;") and update all subsequent
references of txId in this scope to transactionId (including the early return
check "if (!transactionId) return;"). Ensure variable name changes preserve
behavior and any surrounding logic that references txId (in functions, logs, or
conditionals) is updated to the new transactionId identifier.
♻️ Duplicate comments (3)
server/hooks/panda.ts (1)

967-994: Normalize TODO format and avoid keeping a large commented-out block.

The TODO line uses a colon and uppercase text. Also, keeping a long commented-out implementation invites drift—consider moving it to a doc/issue or removing until it’s ready. As per coding guidelines, ...

🔧 Suggested TODO format fix
-    // TODO: Enable once UI has proper designs to handle declined transactions in activity
+    // TODO enable once ui has proper designs to handle declined transactions in activity
server/test/hooks/panda.test.ts (2)

1351-1352: Update TODO comments to the required format (and add a ticket if available).

The TODOs use a colon and mixed case; update them to “TODO ”. As per coding guidelines, ...

🔧 Suggested fix
-  // TODO: Enable once UI has proper designs to handle declined transactions in activity
+  // TODO enable once ui has proper designs to handle declined transactions in activity
-  // TODO: Enable once UI has proper designs to handle declined transactions in activity
+  // TODO enable once ui has proper designs to handle declined transactions in activity

Also applies to: 1389-1390


1377-1384: Align declined-transaction hash expectations with the intended placeholder.

This test expects hashes: [], while the planned declined insert path uses zeroHash. Please align the expectation with the intended behavior before enabling the test.

🔧 If zeroHash is the intended placeholder
-      hashes: [],
+      hashes: [zeroHash],

@aguxez aguxez force-pushed the card-declined-events branch from 685fbf3 to 4bcf82f Compare January 19, 2026 17:03
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@server/hooks/panda.ts`:
- Around line 957-994: The decline activity persistence was commented out in
handleDeclinedTransaction; restore and enable it by making
handleDeclinedTransaction async, reintroducing the transactions lookup (const tx
= await database.query.transactions.findFirst(...) matching transactionId and
spend.cardId), compute createdAt with getCreatedAt(payload) ?? new
Date().toISOString(), build body from _jsonBody (cast to object) plus createdAt,
then re-enable the upsert logic that uses database.update(...) when tx exists
(merging bodies via v.parse(TransactionPayload, tx.payload).bodies) or
database.insert(transactions).values([...]) when not, and keep the existing push
notification; ensure any referenced symbols (handleDeclinedTransaction,
transactions, database.update, database.insert, TransactionPayload,
getCreatedAt, zeroHash) are imported/available and adjust variable names (use
_jsonBody) and error handling as needed.

In `@server/test/hooks/panda.test.ts`:
- Around line 1354-1355: Rename the abbreviated variable txId to the full
transactionId across this test file: replace the declaration const txId =
"declined-tx-insert"; and all its usages (including the other occurrences
referenced around the later block) with const transactionId =
"declined-tx-insert"; and update any assertions, mocks, helper calls, or test
setup that reference txId so names remain consistent (e.g., in
functions/variables that accept transactionId).
♻️ Duplicate comments (1)
server/test/hooks/panda.test.ts (1)

1351-1352: Fix TODO format and include the issue reference.

These TODOs should be lowercase after the tag, without a colon, and include the tracking issue. As per coding guidelines, ...

📝 Proposed fix
-  // TODO: Enable once UI has proper designs to handle declined transactions in activity
+  // TODO `#114` enable once ui has proper designs to handle declined transactions in activity
-  // TODO: Enable once UI has proper designs to handle declined transactions in activity
+  // TODO `#114` enable once ui has proper designs to handle declined transactions in activity

Also applies to: 1389-1390

@aguxez aguxez force-pushed the card-declined-events branch from 4bcf82f to bbacd86 Compare January 22, 2026 14:15
Copy link

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

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

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no potential bugs to report.

View in Devin Review to see 4 additional flags.

Open in Devin Review

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@server/hooks/panda.ts`:
- Around line 995-1004: The push notification currently formats the amount with
spend.localCurrency which can throw RangeError for invalid ISO codes; create a
helper (e.g., formatCurrency(amount: number, currency: string)) that wraps
amount.toLocaleString(...) in a try/catch and on catch returns a safe fallback
like `${currency.toUpperCase()} ${amount.toFixed(2)}`; then replace the inline
toLocaleString call in the sendPushNotification contents with
formatCurrency(spend.localAmount / 100, spend.localCurrency) so invalid currency
codes won't prevent sending the notification (preserve the existing
captureException handling).
♻️ Duplicate comments (5)
server/test/hooks/panda.test.ts (4)

1351-1352: Fix TODO comment format per coding guidelines.

The TODO comment should not include a colon and should be fully lowercase after the tag. As per coding guidelines: "special tags (TODO, FIXME) are uppercase followed by a single space and lowercase comment, without colons or dashes." Also consider adding an issue reference.

📝 Proposed fix
-  // TODO: Enable once UI has proper designs to handle declined transactions in activity
+  // TODO enable once ui has proper designs to handle declined transactions in activity

1354-1354: Rename txIdtransactionId for naming compliance.

Avoid abbreviations in identifiers. As per coding guidelines, write out full words.

♻️ Proposed rename
-    const txId = "declined-tx-insert";
+    const transactionId = "declined-tx-insert";

Update all subsequent usages of txId to transactionId in this test.


1389-1390: Same TODO format issue as above.

📝 Proposed fix
-  // TODO: Enable once UI has proper designs to handle declined transactions in activity
+  // TODO enable once ui has proper designs to handle declined transactions in activity

1392-1392: Same variable naming issue — rename txId to transactionId.

♻️ Proposed rename
-    const txId = "declined-tx-update";
+    const transactionId = "declined-tx-update";
server/hooks/panda.ts (1)

972-972: Fix TODO comment format per coding guidelines.

The TODO comment should not include a colon and should be fully lowercase after the tag.

📝 Proposed fix
-    // TODO: Enable once UI has proper designs to handle declined transactions in activity
+    // TODO enable once ui has proper designs to handle declined transactions in activity

@aguxez aguxez force-pushed the card-declined-events branch 2 times, most recently from 7980e54 to 2a7cbc2 Compare January 26, 2026 09:51
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@server/api/activity.ts`:
- Around line 306-353: The current transform uses bodies.find(...) which returns
the first (earliest) matching entry; change it to pick the most recent body
instead so declined activity uses up-to-date details: within the transform that
checks status === "declined", replace the bodies.find(...) logic with code that
filters entries that have a body (e.g., "body" in b && b.body), then select the
entry with the newest createdAt (parse or compare createdAt strings, treating
missing createdAt as minimal) and assign that to body; keep the same validation
(throw if no body.body), and use the chosen body's id/spend/createdAt and
hashes[0] ?? zeroHash as before so merchant/amount use the latest values.

In `@server/test/hooks/panda.test.ts`:
- Around line 1351-1387: The test title is misleading — update the jest test
description string used in the it(...) call (currently "inserts declined
transaction with empty hashes") to explicitly mention the zero-hash placeholder
(e.g., "inserts declined transaction with zero-hash placeholder") so it matches
the assertion that expects hashes: [zeroHash]; leave the test body, txId, and
assertions (transactions.id, zeroHash usage, and payload expectations)
unchanged.

@aguxez aguxez force-pushed the card-declined-events branch from 9dfeccf to 124b984 Compare January 26, 2026 16:33
Copy link

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

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

Devin Review found 1 new potential issue.

View issue and 10 additional flags in Devin Review.

Open in Devin Review

@aguxez aguxez force-pushed the card-declined-events branch from 124b984 to a49b4cb Compare January 26, 2026 16:52
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@server/hooks/panda.ts`:
- Around line 988-1029: The local variable named "tx" in saveDeclinedTransaction
should be renamed to a non-abbreviated identifier (e.g., "transaction") to meet
naming guidelines; update its declaration (const tx = ...) and every usage
within saveDeclinedTransaction (checks like if (!transactionId) return;, the
conditional ternary that branches on tx, tx.payload, and the database.update
path that references tx) so all references use the new full name and the logic
remains identical.
♻️ Duplicate comments (1)
server/test/hooks/panda.test.ts (1)

1351-1439: Rename txIdtransactionId to match naming rules.

As per coding guidelines, avoid abbreviations.

♻️ Proposed rename
-    const txId = "declined-tx-insert";
+    const transactionId = "declined-tx-insert";
...
-          id: txId,
+          id: transactionId,
...
-    const transaction = await database.query.transactions.findFirst({ where: eq(transactions.id, txId) });
+    const transaction = await database.query.transactions.findFirst({ where: eq(transactions.id, transactionId) });
...
-    const txId = "declined-tx-update";
+    const transactionId = "declined-tx-update";
...
-          id: txId,
+          id: transactionId,
...
-    const transaction = await database.query.transactions.findFirst({ where: eq(transactions.id, txId) });
+    const transaction = await database.query.transactions.findFirst({ where: eq(transactions.id, transactionId) });

Copy link

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

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

Devin Review found 1 new potential issue.

View issue and 11 additional flags in Devin Review.

Open in Devin Review

@aguxez aguxez force-pushed the card-declined-events branch from a49b4cb to 6ffd01c Compare January 26, 2026 17:05
@aguxez aguxez force-pushed the card-declined-events branch 3 times, most recently from 71fc1e1 to 849cd73 Compare January 26, 2026 20:58
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@server/api/activity.ts`:
- Around line 335-342: The TypeScript error occurs because reduce on
bodiesWithData doesn't preserve the fact that each element has a non-optional
body; fix by narrowing the array before reducing: create a filtered/narrowed
array (e.g., const bodies = bodiesWithData.filter((b): b is typeof b & { body:
NonNullable<typeof b.body> } => !!b.body)) and run the reduce over bodies (or
provide a non-optional initial value) so that when you access body.body in the
reduce result (variable body) TypeScript knows it's defined; update the
subsequent destructure (const { id, spend } = body.body) to use the narrowed
type (no non-null assertion needed).

@aguxez aguxez force-pushed the card-declined-events branch from 849cd73 to 905f69f Compare January 26, 2026 21:20
Copy link

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

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

Devin Review found 1 new potential issue.

View issue and 14 additional flags in Devin Review.

Open in Devin Review

@aguxez aguxez force-pushed the card-declined-events branch from 905f69f to 1b454c2 Compare January 26, 2026 23:07
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@server/api/activity.ts`:
- Line 410: The expression accessing icon on a union-typed merchant (const icon
= flow.completed?.merchant.icon ?? flow.updates.at(-1)?.merchant.icon) causes a
TypeScript error because some merchant variants lack icon; add a small
type-guard/helper (e.g., getMerchantIcon(merchant) that checks merchant !==
undefined && "icon" in merchant and returns merchant.icon) and replace both
direct accesses with calls to that helper (use
getMerchantIcon(flow.completed?.merchant) ??
getMerchantIcon(flow.updates.at(-1)?.merchant)); update references to the
symbols flow, completed, updates, and icon accordingly so TS understands icon
may be undefined.

@aguxez aguxez force-pushed the card-declined-events branch from 1b454c2 to d39150b Compare January 26, 2026 23:35
@aguxez aguxez force-pushed the card-declined-events branch from d39150b to ec7696f Compare January 26, 2026 23:54
@aguxez aguxez force-pushed the card-declined-events branch from ec7696f to f80b47a Compare January 27, 2026 00:07
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.

server: handle card declined events

2 participants