Skip to content

Split hooks/use-bounty-application.ts into domain files. Closes #276 - #311

Merged
Benjtalkshow merged 1 commit into
boundlessfi:mainfrom
Emmo00:file-split/hook
Jun 28, 2026
Merged

Split hooks/use-bounty-application.ts into domain files. Closes #276#311
Benjtalkshow merged 1 commit into
boundlessfi:mainfrom
Emmo00:file-split/hook

Conversation

@Emmo00

@Emmo00 Emmo00 commented Jun 28, 2026

Copy link
Copy Markdown
Contributor

Closes #276


Summary by CodeRabbit

  • New Features

    • Added new application, milestone, review, and dispute actions for managing bounty workflows.
    • Added support for optimistic updates and cache refreshes so changes appear faster in the UI.
    • Improved ID handling and client resolution for contract-backed actions.
  • Bug Fixes

    • Fixed GraphQL requests to consistently send queries as strings, improving compatibility with typed documents.
  • Refactor

    • Reorganized bounty-related hooks into smaller, dedicated modules for easier maintenance.

@vercel

vercel Bot commented Jun 28, 2026

Copy link
Copy Markdown

@Emmo00 is attempting to deploy a commit to the Threadflow Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai

coderabbitai Bot commented Jun 28, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

hooks/use-bounty-application.ts (~750 lines) is decomposed into five dedicated modules: use-application-contracts.ts (contract client type, error class, ID helpers), use-application-mutations.ts (five application lifecycle hooks), use-application-review-mutations.ts (useRequestRevisions), use-milestone-mutations.ts (five milestone/contributor hooks), and use-dispute-mutations.ts (useRaiseDispute). The original file becomes a barrel of export * re-exports. A one-line fix in lib/server-graphql.ts corrects GraphQL query serialization to use queryString.

Changes

Bounty Hook Decomposition and GraphQL Fix

Layer / File(s) Summary
Application contract client types and helpers
hooks/use-application-contracts.ts
Defines ApplicationContractClient interface with five contract methods, ApplicationErrorCode union, ApplicationError class, toBountyIdBigInt ID parser, and resolveApplicationClient that reads from globalThis.__applicationContracts.
Application lifecycle mutation hooks
hooks/use-application-mutations.ts
Adds useApplyToBounty, useSelectApplicant, useDeclineApplicant, useSubmitApplicationWork, and useApproveApplicationSubmission, each with optimistic cache updates (status transitions: IN_PROGRESS, UNDER_REVIEW, COMPLETED, DECLINED), rollback on error, and query invalidation.
Review revision mutation hook
hooks/use-application-review-mutations.ts
Adds useRequestRevisions calling ReviewSubmissionDocument with REVISION_REQUESTED status, optimistically setting bounty to UNDER_REVIEW with rollback and invalidation.
Milestone and contributor mutation hooks
hooks/use-milestone-mutations.ts
Implements useApplyForSlot, useReleasePayment, useAdvanceContributor, useRemoveContributor, and useSendMessage with optimistic slot/escrow/progress cache updates, delay-based placeholders, an in-memory message store, and rollback support.
Dispute mutation hook
hooks/use-dispute-mutations.ts
Exports RaiseDisputeInput/RaiseDisputeResult interfaces and useRaiseDispute POSTing to /api/disputes with bounty detail and list query invalidation on success.
Barrel re-export and GraphQL serialization fix
hooks/use-bounty-application.ts, lib/server-graphql.ts
Replaces use-bounty-application.ts body with five export * re-exports; fixes graphqlRequest to serialize TypedDocumentString via .toString() before embedding in the request body.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related issues

Possibly related PRs

  • boundlessfi/bounties#188: Both modify lib/server-graphql.ts to correctly serialize TypedDocumentString queries via queryString in the request body.
  • boundlessfi/bounties#251: Both implement useApplyForSlot for MULTI_WINNER_MILESTONE bounties; this PR moves that logic into use-milestone-mutations.ts.
  • boundlessfi/bounties#262: Both implement useRequestRevisions with the same optimistic UNDER_REVIEW update, rollback, and query invalidation pattern.

Suggested reviewers

  • Benjtalkshow

Poem

🐇 Hop, hop, hooray! One big file grew so wide,
So I split it to pieces, each module with pride.
Contracts and milestones, disputes in a row,
A barrel re-exports them wherever you go.
The GraphQL now stringifies, clean as a carrot—
This bunny refactored, and I'm glad you can share it! 🥕

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 6.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
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: splitting the monolithic hook file into domain-specific modules.
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.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@drips-wave

drips-wave Bot commented Jun 28, 2026

Copy link
Copy Markdown

@Emmo00 Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

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

🤖 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 `@hooks/use-application-mutations.ts`:
- Around line 100-116: The decline mutation in use-application-mutations
currently only returns the input payload and a timestamp, so it never writes to
a real source of truth. Update the mutationFn for the decline flow to persist
the decline through the actual contract/API layer used by this hook, using the
existing bountyId, applicantAddress, and reason fields, and only return data
after that write succeeds. Keep the optimistic cache behavior, but ensure the
refetch invalidation in the same hook is backed by a real persistence call so
the declined applicant stays declined after refetch.

In `@hooks/use-milestone-mutations.ts`:
- Around line 157-160: The contributor mutation handlers in
use-milestone-mutations are still mocked, so optimistic milestone/slot updates
are lost after invalidateQueries refetches the bounty data. Update the relevant
mutationFn implementations for contributor add/remove flows to persist the
change through the real backend action instead of only awaiting delay and
returning the input, and keep the existing invalidateQueries calls so the cache
refresh reflects saved state. Locate the mock logic in the contributor mutation
functions and replace it with the actual persistence path used by the milestone
mutation hooks.
- Around line 101-109: In the milestone payout flow inside
use-milestone-mutations.ts, stop deriving amountToRelease from unsafe cache
fallbacks in the mutation handlers. In the releasePayment and related optimistic
update logic, read the bounty detail from the query cache only when it is
present and has a valid rewardAmount plus a non-empty milestones list; otherwise
fail fast by throwing or returning an error before calling
EscrowService.releasePayment. Use the existing mutation functions and
queryClient.getQueryData/ bountyKeys.detail lookup to locate the guard points,
and ensure the escrow update only runs with a finite, positive amount.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 00d1992b-ef8a-4241-8085-4412d5cac1f1

📥 Commits

Reviewing files that changed from the base of the PR and between 9a8ef40 and 28c6bcc.

📒 Files selected for processing (7)
  • hooks/use-application-contracts.ts
  • hooks/use-application-mutations.ts
  • hooks/use-application-review-mutations.ts
  • hooks/use-bounty-application.ts
  • hooks/use-dispute-mutations.ts
  • hooks/use-milestone-mutations.ts
  • lib/server-graphql.ts

Comment on lines +100 to +116
return useMutation({
mutationFn: async ({
bountyId,
applicantAddress,
reason,
}: {
bountyId: string;
applicantAddress: string;
reason?: string;
}) => {
return {
bountyId,
applicantAddress,
reason: reason?.trim() || undefined,
declinedAt: new Date().toISOString(),
};
},

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

This decline mutation never reaches a source of truth.

Lines 110-115 only echo the payload and a timestamp. Since Lines 161-163 then invalidate the optimistic cache, the declined applicant will come back on the next refetch because no contract/API write happened here. This needs a real persistence path before the hook is safe to use.

Also applies to: 161-163

🤖 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 `@hooks/use-application-mutations.ts` around lines 100 - 116, The decline
mutation in use-application-mutations currently only returns the input payload
and a timestamp, so it never writes to a real source of truth. Update the
mutationFn for the decline flow to persist the decline through the actual
contract/API layer used by this hook, using the existing bountyId,
applicantAddress, and reason fields, and only return data after that write
succeeds. Keep the optimistic cache behavior, but ensure the refetch
invalidation in the same hook is backed by a real persistence call so the
declined applicant stays declined after refetch.

Comment on lines +101 to +109
const previous = queryClient.getQueryData<ExtendedBountyQuery>(
bountyKeys.detail(bountyId),
);
const totalAmount = previous?.bounty?.rewardAmount ?? 100;
const milestonesCount = previous?.bounty?.milestones?.length ?? 1;
const amountToRelease = totalAmount / milestonesCount;

await EscrowService.releasePayment(bountyId, amountToRelease);
return { contributorId, milestoneId, amountToRelease };

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Fail fast when payout inputs are missing.

Lines 104-106 and 120-122 derive amountToRelease from cache fallbacks. If the detail query is cold, this sends a hard-coded 100; if milestones is an empty array, milestones.length is 0, so the release amount becomes Infinity. Both values flow into EscrowService.releasePayment() and the optimistic escrow update.

Suggested direction
+function getReleaseAmount(bounty?: ExtendedBountyQuery["bounty"]) {
+  const totalAmount = bounty?.rewardAmount;
+  const milestonesCount = bounty?.milestones?.length;
+
+  if (totalAmount == null || !milestonesCount) {
+    throw new Error(
+      "Bounty reward and milestones must be loaded before releasing payment",
+    );
+  }
+
+  return totalAmount / milestonesCount;
+}
+
 export function useReleasePayment(bountyId: string) {
   const queryClient = useQueryClient();

   return useMutation({
@@
       const previous = queryClient.getQueryData<ExtendedBountyQuery>(
         bountyKeys.detail(bountyId),
       );
-      const totalAmount = previous?.bounty?.rewardAmount ?? 100;
-      const milestonesCount = previous?.bounty?.milestones?.length ?? 1;
-      const amountToRelease = totalAmount / milestonesCount;
+      const amountToRelease = getReleaseAmount(previous?.bounty);
@@
       const prevBounty = queryClient.getQueryData<ExtendedBountyQuery>(
         bountyKeys.detail(bountyId),
       );
-      const totalAmount = prevBounty?.bounty?.rewardAmount ?? 100;
-      const milestonesCount = prevBounty?.bounty?.milestones?.length ?? 1;
-      const amountToRelease = totalAmount / milestonesCount;
+      const amountToRelease = getReleaseAmount(prevBounty?.bounty);

Also applies to: 117-139

🤖 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 `@hooks/use-milestone-mutations.ts` around lines 101 - 109, In the milestone
payout flow inside use-milestone-mutations.ts, stop deriving amountToRelease
from unsafe cache fallbacks in the mutation handlers. In the releasePayment and
related optimistic update logic, read the bounty detail from the query cache
only when it is present and has a valid rewardAmount plus a non-empty milestones
list; otherwise fail fast by throwing or returning an error before calling
EscrowService.releasePayment. Use the existing mutation functions and
queryClient.getQueryData/ bountyKeys.detail lookup to locate the guard points,
and ensure the escrow update only runs with a finite, positive amount.

Comment on lines +157 to +160
mutationFn: async ({ contributorId }: { contributorId: string }) => {
await delay(1000);
return { contributorId };
},

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

These contributor mutations are still mocks.

Lines 157-159 and 222-224 only wait and return the input, but Lines 212-214 and 263-265 invalidate the authoritative bounty query. That means the optimistic milestone/slot updates disappear as soon as the refetch completes because nothing was persisted.

Also applies to: 212-214, 222-225, 263-265

🤖 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 `@hooks/use-milestone-mutations.ts` around lines 157 - 160, The contributor
mutation handlers in use-milestone-mutations are still mocked, so optimistic
milestone/slot updates are lost after invalidateQueries refetches the bounty
data. Update the relevant mutationFn implementations for contributor add/remove
flows to persist the change through the real backend action instead of only
awaiting delay and returning the input, and keep the existing invalidateQueries
calls so the cache refresh reflects saved state. Locate the mock logic in the
contributor mutation functions and replace it with the actual persistence path
used by the milestone mutation hooks.

@Benjtalkshow
Benjtalkshow merged commit 948ff12 into boundlessfi:main Jun 28, 2026
4 of 5 checks passed
Benjtalkshow added a commit to Johnpii1/bounties-fork that referenced this pull request Jun 29, 2026
Reset branch onto current main so this PR no longer deletes the work
from PRs boundlessfi#298, boundlessfi#311, and boundlessfi#313. Only changes left:

- New hooks/__tests__/use-bounty-application.test.tsx with 12 cases
  covering happy and rollback paths for useApplyToBounty,
  useSelectApplicant, useApproveApplicationSubmission, useRequestRevisions,
  useApplyForSlot, useReleasePayment, useRemoveContributor,
  useDeclineApplicant, and useRaiseDispute.
- Validation in useApplyToBounty that throws ApplicationError when
  applicantAddress is missing. Production guard, not test-only.

Verified pnpm lint, pnpm tsc --noEmit, pnpm build, and the new test
suite (12/12 passing) all clean.
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.

Split hooks/use-bounty-application.ts (755 lines) into per-domain files

2 participants