Skip to content

feat(wallet): test-mode top-up RPC + Add Money UI - #16

Merged
shaiksohelll merged 3 commits into
mainfrom
feat/wallet-topup
May 17, 2026
Merged

shaiksohelll merged 3 commits into
mainfrom
feat/wallet-topup

Conversation

@shaiksohelll

@shaiksohelll shaiksohelll commented May 17, 2026

Copy link
Copy Markdown
Owner

User description

First wallet mutation flow. Unblocks Phase 3 smoke (no more direct SQL workarounds) and dogfooding.

Migration (supabase/migrations/20260517201500_topup_wallet.sql)

  • Relaxes escrow_ledger.job_id to nullable. The ledger_type enum already includes topup / withdraw which aren't job-scoped; the NOT NULL was an oversight.
  • Adds topup_wallet(p_amount numeric, p_idempotency_key uuid)SECURITY DEFINER, locked to authenticated.
    • Validates auth.uid() is non-null.
    • Validates 100 ≤ amount ≤ 100000 (test-mode bounds).
    • Idempotent on p_idempotency_key via existing escrow_ledger.reference_id.
    • Credits wallets.available_balance and writes a type='topup' audit row.
  • Partial index escrow_ledger_topup_reference_idx keeps the idempotency lookup fast.

Server action (src/app/_actions/wallet.ts)

  • Mirrors the escrow.ts pattern exactly: getAuthUserId() helper, Zod parse, supabase.rpc("topup_wallet", ...), try/catch wrapper, revalidatePath(..., "layout").
  • Maps invalid_amount / not_authenticated to friendly strings.

UI (src/components/features/topup-dialog.tsx + wired into wallet-view.tsx)

Build

  • pnpm build ✅ green (only pre-existing unrelated useEffect 'load' warnings)

Test mode disclaimer

Real payments (Razorpay/Stripe) come later. This unblocks smoke testing and dogfooding by giving us a clean Add Money UI instead of direct SQL updates.

Summary by CodeRabbit

  • New Features
    • Added "Add Money" top-up dialog with amount input, quick-select buttons, and disabled/adding states.
    • Users can top up between ₹100 and ₹1,00,000; success shows a toast and updates the displayed balance in real time.
  • Bug Fixes / Reliability
    • Prevents duplicate top-ups via idempotency and improves transaction titles/statuses for top-up and withdrawal entries.

Review Change Stack


CodeAnt-AI Description

Add test-mode wallet top-ups from the wallet page

What Changed

  • Added an Add Money dialog so users can top up their wallet directly from the wallet screen
  • The dialog includes quick amount options, amount limits, and prevents closing while a top-up is in progress
  • Wallet transaction history now clearly labels top-ups and withdrawals instead of showing them as generic entries
  • Top-up requests now return the updated balance and refresh wallet views after success
  • Top-ups now handle repeated clicks and retry cases more safely, reducing duplicate credits and confusing failures

Impact

✅ Faster wallet funding
✅ Fewer duplicate top-up charges
✅ Clearer wallet transaction history
✅ Safer test-mode payments

💡 Usage Guide

Checking Your Pull Request

Every time you make a pull request, our system automatically looks through it. We check for security issues, mistakes in how you're setting up your infrastructure, and common code problems. We do this to make sure your changes are solid and won't cause any trouble later.

Talking to CodeAnt AI

Got a question or need a hand with something in your pull request? You can easily get in touch with CodeAnt AI right here. Just type the following in a comment on your pull request, and replace "Your question here" with whatever you want to ask:

@codeant-ai ask: Your question here

This lets you have a chat with CodeAnt AI about your pull request, making it easier to understand and improve your code.

Example

@codeant-ai ask: Can you suggest a safer alternative to storing this secret?

Preserve Org Learnings with CodeAnt

You can record team preferences so CodeAnt AI applies them in future reviews. Reply directly to the specific CodeAnt AI suggestion (in the same thread) and replace "Your feedback here" with your input:

@codeant-ai: Your feedback here

This helps CodeAnt AI learn and adapt to your team's coding style and standards.

Example

@codeant-ai: Do not flag unused imports.

Retrigger review

Ask CodeAnt AI to review the PR again, by typing:

@codeant-ai: review

Check Your Repository Health

To analyze the health of your code repository, visit our dashboard at https://app.codeant.ai. This tool helps you identify potential issues and areas for improvement in your codebase, ensuring your repository maintains high standards of code health.

Copilot AI review requested due to automatic review settings May 17, 2026 16:53
@vercel

vercel Bot commented May 17, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
pakka Ready Ready Preview, Comment May 17, 2026 6:56pm

@codeant-ai

codeant-ai Bot commented May 17, 2026

Copy link
Copy Markdown

CodeAnt AI is reviewing your PR.


Thanks for using CodeAnt! 🎉

We're free for open-source projects. if you're enjoying it, help us grow by sharing.

Share on X ·
Reddit ·
LinkedIn

@coderabbitai

coderabbitai Bot commented May 17, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

Adds an end-to-end wallet top-up flow: DB migrations (idempotency, constraints, RLS), a Zod input schema, a Next.js server action that calls an idempotent Supabase RPC, and a client TopUpDialog integrated into WalletView.

Changes

Wallet Top-Up Feature

Layer / File(s) Summary
Database schema & initial RPC
supabase/migrations/20260517201500_topup_wallet.sql
Relax escrow_ledger.job_id nullability; create/replace topup_wallet(p_amount, p_idempotency_key) RPC with auth, amount validation, idempotency lookup, wallet crediting, ledger audit insert, and partial index; grant EXECUTE to authenticated.
DB hardening & constraints
supabase/migrations/20260517223900_topup_wallet_hardening.sql
Replace partial index with UNIQUE partial index, add CHECK restricting NULL job_id to certain types, update RLS SELECT policy for wallet owners, and replace topup_wallet implementation to perform ledger-first insert with unique-violation handling and DB-level param/auth checks.
Input validation schema
src/lib/schemas/wallet.ts
New topupWalletSchema (Zod) validating amount (number, 100–100000 with custom messages) and idempotency_key (UUID); exports TopupWalletInput.
Server action for wallet top-up
src/app/_actions/wallet.ts
topupWalletAction authenticates via Supabase server client, validates input, calls topup_wallet RPC with idempotency key, maps RPC errors to user-facing messages, normalizes RPC payloads, revalidates /client/wallet and /worker/wallet on success, and returns { availableBalance, ledgerId } in an ActionResult.
Client UI component & wallet integration
src/components/features/topup-dialog.tsx, src/components/features/wallet-view.tsx
TopUpDialog client modal manages amount input, validation (₹100–₹100,000), duplicate-submission prevention, mounted-safety, calls topupWalletAction with crypto.randomUUID() idempotency key, invalidates React Query ["wallet"] on success, shows toasts, resets UI state, and is rendered right-aligned in WalletView; transaction title and StatusBadge mappings updated for topup/withdraw.

Sequence Diagram(s)

sequenceDiagram
  participant ComponentA
  participant ComponentB
  ComponentA->>ComponentB: observable interaction
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Suggested labels

size:XL

Poem

🐰 I hopped through SQL, schema, and UI bright,
RPCs and idempotency kept things right,
A wallet topped gently, no double-pay fright,
I nibble some carrots, the balance in sight,
thumps a happy note ✨💸

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main changes: a test-mode wallet top-up RPC function and corresponding Add Money UI, which aligns with the core functionality added across migrations, server action, and components.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/wallet-topup

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.

@codeant-ai codeant-ai Bot added the size:L This PR changes 100-499 lines, ignoring generated files label May 17, 2026
Comment thread supabase/migrations/20260517201500_topup_wallet.sql
Comment thread supabase/migrations/20260517201500_topup_wallet.sql

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (1)
src/components/features/topup-dialog.tsx (1)

64-67: 🏗️ Heavy lift

Add optimistic wallet cache update with rollback for this mutation path.

This path only invalidates after success; guideline expects optimistic UI with rollback toast on error.

As per coding guidelines, Implement optimistic UI on mutations with rollback toast on error.

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

In `@src/components/features/topup-dialog.tsx` around lines 64 - 67, Before
calling queryClient.invalidateQueries(["wallet"]) implement an optimistic
update: in your mutation's onMutate handler (the mutation that triggers the
top-up), read and store the previous wallet state via
queryClient.getQueryData(["wallet"]), then immediately update the cache with
queryClient.setQueryData(["wallet"], updatedValue) to reflect the optimistic
top-up; in onError restore the previous state with
queryClient.setQueryData(["wallet"], previous) and show a rollback toast/error
notification; finally in onSettled or onSuccess call
queryClient.invalidateQueries({ queryKey: ["wallet"] }) to reconcile with the
server. Ensure you reference the same queryKey ["wallet"] and keep the previous
value variable scoped so onError can restore it.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/app/_actions/wallet.ts`:
- Around line 67-70: The catch block in the server action inside
src/app/_actions/wallet.ts currently skips reporting errors to Sentry (there's a
TODO) — update that catch to call Sentry.captureException(err) (or
Sentry.captureException(new Error(msg)) if err is not an Error) before returning
the failure object; ensure the module imports your Sentry client (e.g., import
Sentry from your Sentry wrapper) and preserve the existing behavior of returning
{ success: false, error: msg } after capturing the exception.

In `@src/components/features/topup-dialog.tsx`:
- Around line 26-29: Replace the handwritten amount state/validation (amountStr,
setAmountStr, inFlightRef and related manual checks around submit) with
react-hook-form driven validation using zodResolver and the shared schema:
import topupWalletSchema and use topupWalletSchema.pick({ amount: true }) as the
resolver; wire the amount input into useForm's register (remove local amountStr
state) and run handleSubmit to perform submission inside startTransition; ensure
any inline validation logic in the submit handler (and around inFlightRef) is
removed or replaced by formState.isValid/isSubmitting from react-hook-form so
the client uses the single source-of-truth Zod schema for validation.
- Around line 70-72: Replace the manual rupee interpolation in the toast success
message with the shared INR currency formatter: instead of
`₹${amount.toLocaleString("en-IN")}` and
`₹${newBalance.toLocaleString("en-IN")}`, call the shared formatter (imported
from your common/utils e.g. formatINR or INRCURRENCY_FORMATTER) like
`formatINR(amount)` and `formatINR(newBalance)` inside the `toast.success` call
in topup-dialog.tsx; make the same replacement at the other occurrence (~line
130) and add the appropriate import for the shared formatter at the top of the
file.

In `@supabase/migrations/20260517201500_topup_wallet.sql`:
- Around line 39-43: The current read-before-write idempotency check using
v_ledger_id and p_idempotency_key against the escrow_ledger table is race-prone;
instead enforce atomic idempotency by adding a unique constraint on
(reference_id, type) for escrow_ledger and change the write path to perform an
atomic insert that uses that constraint (e.g., INSERT ... ON CONFLICT DO
NOTHING/DO UPDATE and return the existing row) so concurrent transactions cannot
both apply available_balance + p_amount; update the migration to create a UNIQUE
constraint/index (not a non-unique index) and switch the procedure that
references v_ledger_id/p_idempotency_key to rely on the atomic insert/upsert and
returned id to determine whether to apply the credit.

---

Nitpick comments:
In `@src/components/features/topup-dialog.tsx`:
- Around line 64-67: Before calling queryClient.invalidateQueries(["wallet"])
implement an optimistic update: in your mutation's onMutate handler (the
mutation that triggers the top-up), read and store the previous wallet state via
queryClient.getQueryData(["wallet"]), then immediately update the cache with
queryClient.setQueryData(["wallet"], updatedValue) to reflect the optimistic
top-up; in onError restore the previous state with
queryClient.setQueryData(["wallet"], previous) and show a rollback toast/error
notification; finally in onSettled or onSuccess call
queryClient.invalidateQueries({ queryKey: ["wallet"] }) to reconcile with the
server. Ensure you reference the same queryKey ["wallet"] and keep the previous
value variable scoped so onError can restore it.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 2f4fa7d6-6145-4352-8750-211596427de7

📥 Commits

Reviewing files that changed from the base of the PR and between e95a91b and 0262eee.

📒 Files selected for processing (5)
  • src/app/_actions/wallet.ts
  • src/components/features/topup-dialog.tsx
  • src/components/features/wallet-view.tsx
  • src/lib/schemas/wallet.ts
  • supabase/migrations/20260517201500_topup_wallet.sql

Comment thread src/app/_actions/wallet.ts Outdated
Comment thread src/components/features/topup-dialog.tsx Outdated
Comment thread src/components/features/topup-dialog.tsx Outdated
Comment thread supabase/migrations/20260517201500_topup_wallet.sql

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR adds the first wallet mutation path: a test-mode wallet top-up RPC, server action, and Add Money dialog integrated into the wallet page.

Changes:

  • Adds topup_wallet SQL RPC and relaxes escrow_ledger.job_id for non-job ledger entries.
  • Adds wallet top-up input schema and server action.
  • Adds an Add Money dialog to wallet views with quick amount chips and cache invalidation.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
supabase/migrations/20260517201500_topup_wallet.sql Adds top-up wallet RPC, ledger write, grants, and idempotency lookup index.
src/lib/schemas/wallet.ts Adds Zod validation schema/type for wallet top-up input.
src/app/_actions/wallet.ts Adds server action wrapping the top-up RPC and wallet path revalidation.
src/components/features/topup-dialog.tsx Adds client dialog UI for test-mode wallet top-ups.
src/components/features/wallet-view.tsx Wires the Add Money dialog into the wallet view and includes formatting-only changes.
Comments suppressed due to low confidence (2)

src/components/features/topup-dialog.tsx:93

  • The close guard only checks isPending, which updates after React renders. Because inFlightRef.current is set synchronously before the transition starts, there is a window where a user can close the dialog with Escape/outside click even though the request is already in flight. Use the synchronous in-flight flag for onOpenChange as well so the dialog truly cannot be closed mid-request.
            <Dialog open={open} onOpenChange={(o) => !isPending && setOpen(o)}>

src/components/features/wallet-view.tsx:259

  • This reformatting doesn't match the repository's Prettier style and makes the template literal less readable. Please run the formatter or revert this unrelated formatting-only change.
                    className={`text-sm font-bold ${isIncoming ? "text-emerald-700" : "text-foreground"
                      }`}

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread supabase/migrations/20260517201500_topup_wallet.sql
Comment thread supabase/migrations/20260517201500_topup_wallet.sql
Comment thread supabase/migrations/20260517201500_topup_wallet.sql
Comment thread supabase/migrations/20260517201500_topup_wallet.sql
Comment thread src/components/features/wallet-view.tsx
Comment thread src/components/features/topup-dialog.tsx Outdated
Comment thread src/app/_actions/wallet.ts Outdated
Comment thread src/lib/schemas/wallet.ts Outdated
Comment thread src/components/features/wallet-view.tsx Outdated
Comment thread supabase/migrations/20260517201500_topup_wallet.sql
@codeant-ai

codeant-ai Bot commented May 17, 2026

Copy link
Copy Markdown

CodeAnt AI finished reviewing your PR.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
src/components/features/wallet-view.tsx (2)

166-169: 💤 Low value

Consider responsive alignment for mobile UX.

The justify-end class right-aligns the TopUpDialog button on all screen sizes. On mobile devices, centering or full-width alignment might provide better UX.

📱 Suggested responsive alignment
-      <div className="flex justify-end">
+      <div className="flex justify-center sm:justify-end">
         <TopUpDialog />
       </div>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/features/wallet-view.tsx` around lines 166 - 169, The
TopUpDialog is always right-aligned by the surrounding <div className="flex
justify-end"> which hurts mobile UX; change the wrapper around TopUpDialog (the
div containing TopUpDialog) to use responsive alignment classes (e.g., use
default mobile center and right-align on larger screens) and/or make the button
width responsive (e.g., full-width on mobile and auto on larger screens) so the
<div> that currently uses justify-end becomes something like mobile-centered and
sm:justify-end and the TopUpDialog itself can use sm:w-auto w-full as needed.

251-255: ⚡ Quick win

Consider using "released" variant for withdrawals.

The withdraw type is currently mapped to the "refunded" variant, which typically implies a transaction reversal. Since a withdrawal represents funds successfully leaving the wallet (similar to how escrow funds are released), the "released" variant would be more semantically appropriate and less confusing to users.

🎨 Suggested variant mapping
                   : entry.type === "topup"
                     ? "funded"
                     : entry.type === "withdraw"
-                      ? "refunded"
+                      ? "released"
                       : "pending"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/features/wallet-view.tsx` around lines 251 - 255, The
withdrawal entry currently maps entry.type === "withdraw" to the "refunded"
variant which is misleading; update the mapping in the wallet view rendering
logic (the code block that computes the variant for entries in
src/components/features/wallet-view.tsx, likely inside the WalletView component
or its entry renderer where entry.type is checked) to return "released" for
withdraw instead of "refunded" so withdrawals are labeled semantically as funds
released.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@src/components/features/wallet-view.tsx`:
- Around line 166-169: The TopUpDialog is always right-aligned by the
surrounding <div className="flex justify-end"> which hurts mobile UX; change the
wrapper around TopUpDialog (the div containing TopUpDialog) to use responsive
alignment classes (e.g., use default mobile center and right-align on larger
screens) and/or make the button width responsive (e.g., full-width on mobile and
auto on larger screens) so the <div> that currently uses justify-end becomes
something like mobile-centered and sm:justify-end and the TopUpDialog itself can
use sm:w-auto w-full as needed.
- Around line 251-255: The withdrawal entry currently maps entry.type ===
"withdraw" to the "refunded" variant which is misleading; update the mapping in
the wallet view rendering logic (the code block that computes the variant for
entries in src/components/features/wallet-view.tsx, likely inside the WalletView
component or its entry renderer where entry.type is checked) to return
"released" for withdraw instead of "refunded" so withdrawals are labeled
semantically as funds released.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 2273806c-09bf-4864-8478-f438e4a1eade

📥 Commits

Reviewing files that changed from the base of the PR and between 0262eee and dead663.

📒 Files selected for processing (5)
  • src/app/_actions/wallet.ts
  • src/components/features/topup-dialog.tsx
  • src/components/features/wallet-view.tsx
  • src/lib/schemas/wallet.ts
  • supabase/migrations/20260517223900_topup_wallet_hardening.sql
✅ Files skipped from review due to trivial changes (1)
  • src/lib/schemas/wallet.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/app/_actions/wallet.ts
  • src/components/features/topup-dialog.tsx

@shaiksohelll

Copy link
Copy Markdown
Owner Author

@CodeAnt-AI review

@codeant-ai

codeant-ai Bot commented May 17, 2026

Copy link
Copy Markdown

CodeAnt AI is running the review.


Thanks for using CodeAnt! 🎉

We're free for open-source projects. if you're enjoying it, help us grow by sharing.

Share on X ·
Reddit ·
LinkedIn

@codeant-ai codeant-ai Bot added size:L This PR changes 100-499 lines, ignoring generated files and removed size:L This PR changes 100-499 lines, ignoring generated files labels May 17, 2026
@codeant-ai

codeant-ai Bot commented May 17, 2026

Copy link
Copy Markdown

Sequence Diagram

This PR adds a test mode wallet top up flow where the client dialog calls a server action that invokes a database function to credit the wallet, write a ledger entry, and refresh wallet views.

sequenceDiagram
    participant User
    participant WalletUI as Wallet dialog
    participant Backend as Wallet server action
    participant Database as Top up function

    User->>WalletUI: Open dialog and enter amount
    WalletUI->>Backend: Submit top up request
    Backend->>Database: Run topup wallet with amount and idempotency key
    Database-->>Backend: Return new balance and ledger id
    Backend-->>WalletUI: Respond with top up success
    WalletUI->>WalletUI: Invalidate wallet data and show updated balance
Loading

Generated by CodeAnt AI

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

Comment thread supabase/migrations/20260517223900_topup_wallet_hardening.sql
Comment thread supabase/migrations/20260517223900_topup_wallet_hardening.sql
@codeant-ai

codeant-ai Bot commented May 17, 2026

Copy link
Copy Markdown

CodeAnt AI finished running the review.


Thanks for using CodeAnt! 🎉

We're free for open-source projects. if you're enjoying it, help us grow by sharing.

Share on X ·
Reddit ·
LinkedIn

@shaiksohelll

Copy link
Copy Markdown
Owner Author

@copilot review

Copilot AI commented May 17, 2026

Copy link
Copy Markdown

@copilot review

Reviewed on commit dead663: no additional code changes are needed. pnpm typecheck, pnpm lint, and pnpm test pass (with the same pre-existing lint warnings), and the prior Copilot workflow failure was due to a Copilot API rate-limit (429), not a code failure.

@shaiksohelll

Copy link
Copy Markdown
Owner Author

@claude[agent] review

@shaiksohelll

Copy link
Copy Markdown
Owner Author

@codex[agent] review

@shaiksohelll

Copy link
Copy Markdown
Owner Author

@CodeAnt-AI review

@codeant-ai

codeant-ai Bot commented May 17, 2026

Copy link
Copy Markdown

CodeAnt AI is running the review.


Thanks for using CodeAnt! 🎉

We're free for open-source projects. if you're enjoying it, help us grow by sharing.

Share on X ·
Reddit ·
LinkedIn

@codeant-ai codeant-ai Bot added size:XL This PR changes 500-999 lines, ignoring generated files and removed size:L This PR changes 100-499 lines, ignoring generated files labels May 17, 2026
@codeant-ai

codeant-ai Bot commented May 17, 2026

Copy link
Copy Markdown

Sequence Diagram

This PR adds a client-side wallet top up flow where the Add Money UI sends a request to a server action, which calls a database function to apply an idempotent balance credit and ledger entry, then refreshes the wallet view.

sequenceDiagram
    participant User
    participant WalletUI
    participant ServerAction
    participant Database

    User->>WalletUI: Open Add Money and submit amount
    WalletUI->>ServerAction: Request wallet top up with amount and key
    ServerAction->>Database: Call topup_wallet with amount and idempotency key
    Database->>Database: Validate user, enforce bounds and idempotency, update wallet and ledger
    Database-->>ServerAction: Return new available balance and ledger id
    ServerAction-->>WalletUI: Return success with new balance
    WalletUI->>WalletUI: Refresh wallet data and show updated balance and top up entry
Loading

Generated by CodeAnt AI

@shaiksohelll
shaiksohelll merged commit cf4845b into main May 17, 2026
3 of 5 checks passed
@shaiksohelll
shaiksohelll deleted the feat/wallet-topup branch May 17, 2026 19:08
if (inFlightRef.current) return;
inFlightRef.current = true;

startTransition(async () => {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Suggestion: startTransition is being passed an async callback, but transition pending state is not reliably tied to the awaited RPC lifecycle. As a result, isPending can flip false before the request finishes, so the dialog/buttons may become interactive mid-request and allow close/cancel while the top-up is still in flight. Use an explicit pending state for the async mutation lifecycle (or a mutation hook) instead of relying on async startTransition. [api mismatch]

Severity Level: Major ⚠️
- ⚠️ Dialog can be closed while top-up still processing.
- ⚠️ Pending label and disabled states not aligned with RPC.
- ⚠️ Users may see success toast after dialog already closed.
Steps of Reproduction ✅
1. Render `WalletView` from `src/components/features/wallet-view.tsx:116-136`, which
includes `<TopUpDialog />` at `wallet-view.tsx:166-167`.

2. In the rendered UI, click the "Add Money" button defined at `topup-dialog.tsx:83-86` to
open the dialog controlled by `open` / `setOpen` at `topup-dialog.tsx:27,81-87`.

3. Enter a valid amount (e.g., 1000) in the input at `topup-dialog.tsx:96-109` and click
the primary "Add Money" button at `topup-dialog.tsx:137-139`, which calls `handleTopUp()`
at `topup-dialog.tsx:40-79`.

4. Observe that `handleTopUp()` sets `inFlightRef.current = true` at
`topup-dialog.tsx:50-51` and wraps the async RPC call `topupWalletAction(...)` at
`topup-dialog.tsx:55-58` in `startTransition(async () => { ... })` at
`topup-dialog.tsx:53`. Because the only state updates (`setAmountStr("")` and
`setOpen(false)`) occur after the `await` inside this async callback
(`topup-dialog.tsx:72-73`), React's `useTransition` `isPending` flag at
`topup-dialog.tsx:29` is not reliably true during the RPC. In practice, the
`disabled={isPending}` props on the input, quick-amount buttons, Cancel, and primary
button at `topup-dialog.tsx:108-109,119-120,132,137` remain false while the request is in
flight, and the `onOpenChange={(o) => !isPending && setOpen(o)}` handler at
`topup-dialog.tsx:87` allows the dialog to be closed mid-request. This breaks the intended
"dialog can't be closed mid-request" behavior even though `inFlightRef` still guards
against double submission.

Fix in Cursor | Fix in VSCode Claude

(Use Cmd/Ctrl + Click for best experience)

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** src/components/features/topup-dialog.tsx
**Line:** 53:53
**Comment:**
	*Api Mismatch: `startTransition` is being passed an async callback, but transition pending state is not reliably tied to the awaited RPC lifecycle. As a result, `isPending` can flip false before the request finishes, so the dialog/buttons may become interactive mid-request and allow close/cancel while the top-up is still in flight. Use an explicit pending state for the async mutation lifecycle (or a mutation hook) instead of relying on async `startTransition`.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎

@codeant-ai

codeant-ai Bot commented May 17, 2026

Copy link
Copy Markdown

CodeAnt AI finished running the review.


Thanks for using CodeAnt! 🎉

We're free for open-source projects. if you're enjoying it, help us grow by sharing.

Share on X ·
Reddit ·
LinkedIn

shaiksohelll added a commit that referenced this pull request Jun 3, 2026
Group A (money/runtime):
- #7: GRANT EXECUTE withdraw_wallet to authenticated + REVOKE anon/public
- #6: restore NULL guard on p_amount in withdraw_wallet
- #1-#4: reject NULL idempotency keys in all 4 escrow functions

Group B (correctness):
- #5: document p_idempotency_key API-consistency in submit_milestone
- #8,#9: state-machine doc: submitted->released, annotate dead 'approved'
- #20: remove 'approved' from MilestoneStatus union in escrow-machine.ts
- #16: fix blob URL leak in worker-form.tsx (useEffect + revokeObjectURL)
- #17: fix comment attribution CodeAnt -> CodeRabbit in wallet.ts
- #18: add .max(500000) to withdrawWalletSchema

Group C (dialog hardening):
- #10,#11: change-phone-dialog: Zod validation + inFlightRef/mountedRef
- #12,#13: delete-account-dialog: Zod validation + inFlightRef/mountedRef
- #14: edit-profile-dialog: inFlightRef/mountedRef guards
- #15: sign-out-button: useMutation refactor

Group D (doc/consistency):
- #22: README: Phase 2->4, Edge Functions->pg_cron, approved->released
- #21: reconcile phase status README<->AGENTS.md
- #23: ADR-0004: Proposed->Accepted
- #19: client account page: race-safe requestIdRef + authError capture
shaiksohelll added a commit that referenced this pull request Jun 3, 2026
…ardening (#20)

* feat(wallet): worker withdrawal flow + auto-release cron

* fix(wallet): address PR #19 review feedback

- withdraw_wallet: add is_worker() check (A), p_amount NULL guard (F),
  p_idempotency_key NULL guard (C), insert-first/catch unique_violation
  pattern mirroring topup (D), public. schema qualifier (I), w. table
  alias to disambiguate available_balance vs RETURNS TABLE column
- worker-form: replace as any cast with typed cast to fix pre-existing
  lint error surfaced during PR #19 pipeline
- guard_milestones_status + guard_jobs_status: replace GUC bypass with
  direct session_user='postgres' check (E)
- auto_release_milestones: drop set_config GUC line (E), add RAISE NOTICE
  on insufficient-locked-balance skip (H)
- wallet.ts: mapWalletRpcError helper routing by error.code + exact
  RAISE EXCEPTION token (G), defense-in-depth is_worker() pre-check in
  withdrawWalletAction (A), raw error logging before generic fallback
- topup-dialog + withdraw-dialog: stable idempotencyKey via useState
  initializer + useEffect reset on close (B), normalize 4-space to
  2-space indent on withdraw-dialog (Copilot #9)

* fix(wallet): PR #19 follow-up review (uuid fail-fast + ADR-0005)

- uuid.ts: remove Math.random fallback, throw if secure crypto API
  unavailable; collision resistance is a correctness requirement for
  wallet idempotency keys (CodeRabbit follow-up #2)
- docs/adr/0005: ADR approving in-place modification of
  auto_release_milestones per Copilot #1 (drop GUC bypass) and
  CodeRabbit #3 (RAISE NOTICE on silent skip), as required by ADR 0004
- pr19_review_fixes.sql: add ADR reference comment above
  auto_release_milestones rewrite for discoverability (CodeRabbit
  follow-up #3)

Deferred to PR #20: Sentry.captureException wiring (CodeRabbit
follow-up #1) — Sentry SDK installation is the dedicated scope of
PR #20 per the roadmap; adding capture calls now would not compile.

* fix: address PR #19 CodeAnt security and correctness findings

- onboarding/actions.ts: remove PII console.log (fullName, categories,
  skillTags) that leaked KYC data into server logs on every onboarding
  attempt (CodeAnt Critical, #discussion_r3280686499)
- onboarding/actions.ts: clean up orphaned selfie from kyc bucket when
  profiles or worker_profiles upsert fails after upload succeeds
  (CodeAnt Major, #discussion_r3280686511)
- client/jobs/[id]/milestones/client-milestones.tsx: scope wallet fetch
  by profile_id of authenticated user instead of returning arbitrary
  first visible wallet row; prevents wrong-balance display and funding
  decisions if RLS ever surfaces multiple wallets (CodeAnt Critical,
  #discussion_r3280693926)
- worker/feed/worker-feed.tsx: gate useInfiniteQuery on auth-ready
  via enabled:!!user?.id and scope queryKey by user?.id; prevents
  spurious unauthorized errors on initial mount before auth propagates
  (CodeAnt Major, #discussion_r3280707356)
- worker/jobs/[id]/worker-job-detail.tsx: surface errors from msRes,
  matRes, appRes in Promise.all instead of silently coercing to empty
  arrays; CodeAnt flagged matRes specifically but the same pattern
  affected msRes and appRes (CodeAnt Major, #discussion_r3280707371)

* fix: address PR #19 round-4 review findings

- _actions/wallet.ts (topup+withdraw catch): replace raw err.message
  return with fixed generic copy to prevent backend internals (SQL,
  Supabase, runtime details) from being toasted to the user; server
  logs retain the full error via console.error (CodeAnt Major,
  #discussion_r3283709865, #discussion_r3283709876)
- client/jobs/[id]/milestones/client-milestones.tsx: move auth
  resolution out of the fetcher; ClientMilestones now passes user.id
  into fetchMilestonesData and the useQuery is gated by enabled on
  user.id with user.id added to queryKey, matching the worker-feed
  pattern (CodeAnt Major, #discussion_r3283709880)
- client-milestones.tsx: surface msRes.error and walletRes.error
  (with PGRST116 no-rows exception preserving zero-balance fallback)
  instead of silently rendering empty milestones / zero wallet on
  query failure (CodeRabbit Major, outside-diff line 91)
- client-milestones.tsx + worker/jobs/[id]/milestones/worker-milestones.tsx:
  migrate idempotency_key generation from crypto.randomUUID to
  generateUuid so LAN/HTTP origins without secure context do not
  TypeError on submission (CodeRabbit Major, outside-diff line 208)
- onboarding/actions.ts: capture and console.error storage cleanup
  failures in profileError and workerError branches; orphan
  accumulation is now observable in server logs pending Sentry
  wiring (CodeRabbit Nitpick, lines 148-171)
- worker/feed/worker-feed.tsx: remove leftover WorkerFeed DEBUG
  console.log that fired on every render and dumped paginated job
  data to the browser console; also dropped the now-unused
  queryStatus destructure that the log was its only consumer
  (CodeRabbit Nitpick, line 148)

* fix(escrow): mapEscrowRpcError + wallet scope + auth gates + migration style

Fix 1 - escrow.ts: add mapEscrowRpcError helper (mirrors mapWalletRpcError);
  replace raw error.message returns and raw outer-catch msg with user-friendly
  strings + structured console.error across fund/submit/approve/dispute actions.

Fix 2 - worker-milestones.tsx: scope wallet query to profile_id, add
  enabled on user.id and user.id to queryKey in useQuery (CodeRabbit Major
  + CodeAnt Critical sibling-pattern follow-up to client-milestones.tsx).

Fix 3 - client-job-detail.tsx: add enabled on user.id and user.id to
  queryKey in useQuery to prevent cold-start firing before auth resolves.

Fix 4 - 20260518135600_withdraw_wallet.sql: rename unique index to
  idx_escrow_ledger_withdraw_owner_reference per repo idx_table_purpose
  naming convention (Copilot Medium).

Fix 5 - 20260518181500_withdraw_wallet_fix_ambiguous_column.sql: add
  public. schema qualifier to function definition and GRANT; change
  search_path style to set search_path to 'public' for consistency with
  other SECURITY DEFINER functions (Copilot Med/Low x3).

Fix 6 - worker-form.tsx: replace className block/hidden step mounting
  with conditional render so only the active Step component is mounted;
  hooks and subscriptions on inactive steps no longer run (CodeAnt
  Major perf). All form state hoisted in FormProvider so unmount is safe.

Fix 7 - 20260521080000_pr19_review_fixes.sql: expand header comment to
  explicitly document that pakka.allow_milestone_status_change GUC was
  DROPPED entirely, and enumerate the three allowed guard bypass
  conditions (SECURITY DEFINER, postgres superuser/pg_cron, is_admin).

* fix(withdraw-wallet): INSERT-first idempotency, doc drift, auth hydration UX, count aggregates

Fix 1 - 20260521080000_pr19_review_fixes.sql: reorder withdraw_wallet to
INSERT into escrow_ledger BEFORE acquiring the wallet FOR UPDATE lock.
Race-losers now deterministically hit the function-level EXCEPTION WHEN
unique_violation handler instead of racing past the pre-check and hitting
insufficient_balance. Removes the now-redundant pre-check SELECT fast-path
(the INSERT/unique_violation handles it).

Fix 2 - docs/state-machine.md: update two Edge Function cron references
to pg_cron (database scheduler) at transition 5 caller and Key Callers
table, matching the scheduling mechanism introduced in 20260518225500.

Fix 3 - client-milestones.tsx: add !user?.id guard before isLoading and
error checks so auth hydration renders MilestonesSkeleton instead of the
error UI.

Fix 4 - client-job-list.tsx: replace milestones(id)/job_applications(id)
full ID-array fetches with milestones_count:milestones(count) /
applications_count:job_applications(count) Supabase aggregate syntax;
update mapping to read [0]?.count ?? 0 instead of .length.

* chore: strip embedded NULL bytes from .gitignore (was treated as binary)

* fix(round-7): 7 in-scope fixes for CR/Copilot/CodeAnt round-7 review

- adr/0006-nav-shell.md: strip indent from Decision:/Consequences: labels
- worker/account/page.tsx: replace null blank-screen with empty-state card + retry
- features/withdraw-dialog.tsx: use formatInr(100) instead of hardcoded '₹100'
- _actions/escrow.ts: correct mapEscrowRpcError JSDoc (routes by error.message, not SQLSTATE)
- worker-milestones.tsx: throw on msRes.error; throw on walletRes.error unless PGRST116
- worker-milestones.tsx: replace err.message leak in handleSubmit catch with fixed user-safe toast
- worker/feed/worker-feed.tsx: milestones(id) full-array -> milestones_count:milestones(count) aggregate

16 other findings deferred:
- PR #20 (Sentry): escrow.ts 8 capture sites
- PR #22 (canonical dialog migration): apply-modal, change-phone-dialog, delete-account-dialog,
  edit-profile-dialog, worker-form, client-milestones escrow handlers, withdraw-dialog Zod/optimistic
- PR #23 (realtime auth-hydration): client-milestones setAuth
- Reply-dismiss: client/worker-milestones auth guards (middleware-guarded routes)

* fix(round-8): NaN guard, seed wallet locked, Sentry TODO, auth-hydration x3, idempotency-key map x2

- post-job-form.tsx: extract parseFiniteFloat helper; apply to all 5 numeric
  onChange (budget, lat, lng, milestone amounts, material qty/amount) so
  transient '-' or '.' inputs write undefined not NaN into form state.
- seed-demo-users.ts: correct wallet locked_balance to match seeded ledger:
  Priya 40k -> 60k (J1-M2 funded), Rohit 0 -> 20k (J2-M1 submitted),
  Anjali 120k -> 30k (J3-M2 disputed). Comments reference source ledger rows.
- worker-milestones.tsx: add // TODO: Sentry.captureException(err) as first
  line of handleSubmit catch block (canonical grep target for PR #20 wiring).
- client-job-detail.tsx / client-milestones.tsx / worker-milestones.tsx:
  destructure isAuthLoading from useUser(); gate skeleton on
  (isAuthLoading || !user?.id) before the data/error branch so the hydration
  boundary no longer renders a false 'Failed to load' error path.
- client-milestones.tsx + worker-milestones.tsx: introduce
  idempotencyKeysRef (Map<string, string>) with getOrCreateIdempotencyKey
  and clearIdempotencyKey helpers. fund/approve/dispute (client) and
  handleSubmit (worker) now reuse the same key across retries within a
  single user intent and rotate only on definitive success. Closes the
  client side of the idempotency contract; server-side uniqueness
  enforcement on the escrow RPCs is tracked separately in the Phase 4.5
  escrow idempotency PR.

Refs round-8 review: CodeAnt CRITICAL x3 (idempotency), CodeAnt HIGH (auth
hydration), CodeAnt Major x3, CR Minor.

* docs(adr): heading indent + pg_cron drift (round-9)

- adr/0001-auth.md, 0002-escrow.md, 0003-dispute.md, 0005-realtime-contract.md,
  0007-rpc-pattern.md: strip 2-space indent on Decision/Consequences
  headings so they render as top-level sections matching adr/template.md.
- adr/0004-auto-release.md: same indent fix + rewrite Decision and
  Consequences to reflect pg_cron (not Edge Function cron) as the actual
  scheduler; reference migration 20260518225500_schedule_auto_release_cron.sql
  as source of truth.

Round-9 #32/#33/#34 (CR Realtime setAuth on worker-milestones,
client-milestones, client-job-detail): no code change. The singleton in
src/lib/supabase/client.ts already calls realtime.setAuth() on both
eager-prime and onAuthStateChange (resetting to anon key on sign-out, not
null). Per-file setAuth wiring is unnecessary.

Round-9 #41 + parallel client-milestones/withdraw idempotency findings:
no code change. The escrow RPCs (submit_milestone, fund_escrow,
approve_milestone, dispute_milestone) do not accept p_idempotency_key
in their SQL signatures, so action-layer forwarding has nothing to bind
to. RPC-level uniqueness enforcement is tracked in the Phase 4.5 escrow
idempotency PR; the per-intent client-side key from round-8 Fix 7/8 is
the application-layer half of that contract.

Refs round-9 review: Copilot Medium x1 (auto-release doc drift),
Copilot Low x5 (ADR heading indent), CR Nitpick x3 (Realtime setAuth -
no-op), CodeAnt Major x1 (idempotency forwarding - deferred).

* fix(escrow,wallet): honest idempotency comments + PostgrestError type (round-10)

Fix 1/2 - worker-milestones.tsx + client-milestones.tsx: rewrite the
per-intent idempotency map comment to remove the overpromising
"server can deduplicate" claim. New wording: "forward-compatible with
server-side RPC idempotency". Grep confirmed exactly 2 occurrences
across the codebase; no helper JSDoc affected.

Fix 3 - wallet.ts: tighten mapWalletRpcError error parameter from an
ad-hoc structural type to PostgrestError. Mirrors the escrow.ts pattern
established in round-5. Import added from @supabase/supabase-js. Pure
type-tightening: supabase-js v2 (^2.104.1) types PostgrestError.code as
string (non-nullable), so existing error.code === "42501" / "22023"
comparisons remain valid without narrowing. Both call sites
(mapWalletRpcError("topup", err), mapWalletRpcError("withdraw", err))
already pass a PostgrestError.

Refs round-10 review: Copilot Medium x3. Round-10 Low (migration
bundling, _pr19_review_fixes.sql) reply-dismissed: the consolidation
is an intentional review artifact, section headers A/C/D/F/I/E/H
already document the per-concern split internally, and retroactively
splitting an applied migration on an active branch is destructive.

* fix(round-11): auth error handling, auth-hydration redirect (4 files), withdraw_wallet exception scope, ADR cron alignment

- worker/account/page.tsx: destructure error from supabase.auth.getUser();
  surface a retryable error state on transient auth/network failure
  instead of redirecting signed-in users to /login. Sentry TODO marker
  added.
- worker-milestones.tsx, client-job-detail.tsx: split the combined
  isAuthLoading || !user?.id branch into two — skeleton during hydration,
  router.replace('/login') + return null once auth has resolved with no
  user. useRouter import added to both. Belt-and-suspenders over the
  middleware route guard.
- client-milestones.tsx: upgrade the post-round-8 split so the !user?.id
  branch also router.replace('/login') + return null instead of rendering
  the skeleton indefinitely. Consistent with the two files above.
- 20260521080000_pr19_review_fixes.sql (withdraw_wallet): scope the
  unique_violation handler to only the INSERT INTO public.escrow_ledger
  via a nested BEGIN..EXCEPTION block. Other UNIQUE violations (present
  or future) now surface as errors instead of being misclassified as
  idempotency replays.
- adr/0004-auto-release.md: correct the cron expression in the Decision
  text to 30 20 * * * (20:30 UTC / 02:00 IST, daily) — matches the
  actual schedule registered in
  20260518225500_schedule_auto_release_cron.sql, which is the source of
  truth.

Refs round-11 review: CodeAnt Major x3 (auth error, auth-hydration x2),
Copilot Medium x2 (cron mismatch, withdraw_wallet exception scope).

Round-11 CodeAnt CRITICAL x1 + Major x1 (idempotency key forwarding on
submit/fund) reply-dismissed in PR threads: see Phase 4.5 escrow
idempotency PR.

* fix(auth): move render-phase redirects to useEffect + worker/account race guard (round-12)

Fix 1 - worker-milestones.tsx, client-milestones.tsx, client-job-detail.tsx:
move router.replace("/login") out of the render path and into a
useEffect([isAuthLoading, user, router]). Render guard collapses back to
the combined `isAuthLoading || !user?.id` -> <Skeleton /> shape, so the
user sees a skeleton both during auth hydration and during the
post-resolution navigation tick. No flash of stale UI; no React
render-phase side-effect warning. Dependency array uses `user` (whole
object) rather than `user?.id` to avoid the exhaustive-deps lint warning
about optional chaining in deps. Closes the regression from round-11
Fix 2/3.

Fix 2 - worker/account/page.tsx: clear local profile and worker state
(setProfile(null), setWorker(null)) before router.replace("/login") in
load()'s !user branch, so stale private data cannot flash between
sign-out and navigation.

Fix 3 - worker/account/page.tsx: add a requestIdRef race-condition guard
to load(). Increments at call start; checks before every setState after
each await (post-getUser, post-Promise.all). Concurrent triggers
(initial mount, Retry, onSaved, onChanged) can no longer have an older
in-flight request overwrite fresher state. Pattern is dependency-free;
no AbortController used because supabase-js v2 doesn't support signal
forwarding on .from() queries.

Refs round-12 review: CodeAnt Major x4 (3 render-phase redirects, 1
stale state, 1 race condition; the render-phase finding was applied
proactively to worker-milestones.tsx as well even though CodeAnt only
flagged it on the two client files).

Round-12 CodeAnt CRITICAL x1 (client-milestones.tsx:238 fund
idempotency forwarding) reply-dismissed and conversation resolved in
PR thread: see Phase 4.5 escrow idempotency PR.

* fix(nav): use explicit job-id hrefs for milestone back-links (ADR-0036)

* feat(escrow): wire idempotency keys end-to-end (ADR-0006)

* fix(pr20): address CodeRabbit + Copilot review feedback

Group A (money/runtime):
- #7: GRANT EXECUTE withdraw_wallet to authenticated + REVOKE anon/public
- #6: restore NULL guard on p_amount in withdraw_wallet
- #1-#4: reject NULL idempotency keys in all 4 escrow functions

Group B (correctness):
- #5: document p_idempotency_key API-consistency in submit_milestone
- #8,#9: state-machine doc: submitted->released, annotate dead 'approved'
- #20: remove 'approved' from MilestoneStatus union in escrow-machine.ts
- #16: fix blob URL leak in worker-form.tsx (useEffect + revokeObjectURL)
- #17: fix comment attribution CodeAnt -> CodeRabbit in wallet.ts
- #18: add .max(500000) to withdrawWalletSchema

Group C (dialog hardening):
- #10,#11: change-phone-dialog: Zod validation + inFlightRef/mountedRef
- #12,#13: delete-account-dialog: Zod validation + inFlightRef/mountedRef
- #14: edit-profile-dialog: inFlightRef/mountedRef guards
- #15: sign-out-button: useMutation refactor

Group D (doc/consistency):
- #22: README: Phase 2->4, Edge Functions->pg_cron, approved->released
- #21: reconcile phase status README<->AGENTS.md
- #23: ADR-0004: Proposed->Accepted
- #19: client account page: race-safe requestIdRef + authError capture

* fix(pr20): row_number dedupe-delete + regen db types + type clients + per-request verify

* fix(pr20): H1 shared release reference_id + ON CONFLICT, M3 idempotency error mapper, M1/N1/N5 hardening

* fix(pr20): hard-constraint release ledger (remove ON CONFLICT) + per-row isolated auto-release + milestone-aware fund replay (CR-1)

* fix(pr20): NULL-safe authz in dispute_milestone + audit NOT IN class + remove scratch files from PR
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XL This PR changes 500-999 lines, ignoring generated files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants