Skip to content

FE: features/bounties participant data layer (escrow client + hooks)#629

Open
natemiller23 wants to merge 2 commits into
boundlessfi:mainfrom
natemiller23:main
Open

FE: features/bounties participant data layer (escrow client + hooks)#629
natemiller23 wants to merge 2 commits into
boundlessfi:mainfrom
natemiller23:main

Conversation

@natemiller23

@natemiller23 natemiller23 commented Jun 24, 2026

Copy link
Copy Markdown

Closes #621

Implemented participant-facing bounty escrow hooks and validation for the Bounty Builder Lifecycle.

Changes:

  • features/bounties/hooks/use-bounty-escrow.ts
  • tests/hooks/bounties/use-bounty-escrow.test.ts

Fixes payload handling in useSubmitBounty so submission data is not silently dropped, and corrects useMyBountyActivity to comply with Rules of Hooks by wiring active query state instead of calling hooks from queryFn.

Summary by CodeRabbit

  • New Features

    • Added client-side support for browsing bounties and viewing bounty details.
    • Added bounty actions including apply, join competition, edit, withdraw applications, submit bounty work, and withdraw submissions.
    • Enhanced the personal bounty activity feed to include both application and submission activity in one timeline.
    • Improved user feedback with success/error toasts and more informative error reporting for bounty actions.
  • Tests

    • Added automated coverage for bounty list fetching and the bounty submission flow.

@vercel

vercel Bot commented Jun 24, 2026

Copy link
Copy Markdown

@natemiller23 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 24, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@natemiller23, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 51 minutes and 2 seconds. Learn how PR review limits work.

Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file).

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits.

🚦 How do rate limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ed901749-f0f3-4237-9cdf-7a67349ee02a

📥 Commits

Reviewing files that changed from the base of the PR and between 22539d9 and 1e67164.

📒 Files selected for processing (2)
  • features/bounties/hooks/use-bounty-escrow.ts
  • tests/hooks/bounties/use-bounty-escrow.test.tsx
📝 Walkthrough

Walkthrough

Adds a client-side bounty hook module for listing, detail, applications, submission, withdrawal, and activity flows, plus React Query cache invalidation and toast/error handling. A Vitest file verifies the bounty list fetch and escrow submit request.

Changes

Bounty participant hooks and tests

Layer / File(s) Summary
Helpers and read queries
features/bounties/hooks/use-bounty-escrow.ts
Defines the shared cache prefix, bounty/application types, request<T>, useBountiesList, useBounty, and useMyBountyApplications.
Application mutations
features/bounties/hooks/use-bounty-escrow.ts
Implements useApplyToBounty, useJoinCompetition, useEditApplication, and useWithdrawApplication, with cache invalidation, toasts, and error reporting.
Escrow submission, withdrawal, and activity
features/bounties/hooks/use-bounty-escrow.ts, tests/hooks/bounties/use-bounty-escrow.test.ts
Implements runEscrowOp<T>, useSubmitBounty, useWithdrawSubmission, and useMyBountyActivity; adds React Query test wiring and tests for bounty listing and escrow submission.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related issues

  • #621 — This PR adds the participant bounty hooks and escrow submit/withdraw flow described in the issue, including list/detail/application/activity hooks.
  • #628 — The new hook module and tests implement the bounty participant lifecycle and escrow client behavior covered by this related issue.

Poem

🐇 I hopped through bounties, light and fleet,
With hooks for each path, tidy and neat.
I fetched, I submitted, I watched caches bloom,
Then chirped with a toast and a testsuite boom.

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning The PR covers list/detail/apply/withdraw/submit/activity hooks, but the requested participant escrow client, terminal-state runner flow, and signed/contribute endpoints are not shown. Add the participant escrow client and missing contribute/signed-submit/polling pieces, and route escrow ops through useEscrowOpRunner until terminal state.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise and accurately reflects the bounty participant escrow hooks and client-layer work.
Out of Scope Changes check ✅ Passed The changes stay focused on bounty participant escrow hooks and their tests, with no clearly unrelated code introduced.

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

✨ 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.

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

🤖 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 `@features/bounties/hooks/use-bounty-escrow.ts`:
- Around line 225-235: `useMyBountyActivity` is only mapping
`useMyBountyApplications` data inside a query that never re-runs when that data
changes, and it currently omits the `'submission'` activity variant. Update the
hook so the activity result is derived from both applications and submissions in
a way that stays in sync with refetches, using the existing
`useMyBountyApplications` and any submission source you already have instead of
relying on a stale `queryFn` read. Ensure the returned
`ParticipantActivityItem[]` includes both `'application'` and `'submission'`
entries, and adjust the query key/enabled logic in `useMyBountyActivity` so the
combined activity recomputes whenever the underlying participant data changes.
- Line 51: The queryFn return type in use-bounty-escrow.ts has a syntax error
because the Promise generic is not closed before the arrow function, which
breaks parsing. Fix the type annotation on queryFn so the Promise<{ bounties:
BountySummary[]; pagination: Record<string, unknown> }> return type is properly
closed, and verify the surrounding useQuery/async callback syntax in
use-bounty-escrow stays valid.
- Line 194: The invalidation key in useBountyEscrow does not match the bounty
detail query key, so the cached detail entry is not being refreshed after
successful submission or withdrawal. Update the queryClient.invalidateQueries
calls in useBountyEscrow, including the paths in useWithdrawSubmission, to use
the same ['bounties', 'detail', id] shape registered by useBounty so React Query
matches the cache entry correctly.
- Around line 170-192: The submission payload is being dropped in the escrow
flow, so update runEscrowOp and useSubmitBounty to forward the validated
submission data instead of only sending scope/mode. Make sure useSubmitBounty
passes bountyId, description, and links into the /api/escrow/ops request body,
and use the expected parameter in runEscrowOp if it is meant to control the
operation mode. Keep the existing validation, but ensure the mutationFn actually
submits the bounty work data so the backend can associate it with the correct
bounty.

In `@tests/hooks/bounties/use-bounty-escrow.test.ts`:
- Line 20: The test wrapper in use-bounty-escrow.test.ts references
React.ReactNode without importing React, so the type name is unresolved. Update
the wrapper typing to use an imported ReactNode type or add the appropriate
React import near the test setup, and keep the wrapper signature in sync so the
JSX children typing compiles cleanly.
- Around line 44-46: The test in use-bounty-escrow.test.ts is calling act inside
the mutation flow without importing it, which causes a ReferenceError. Update
the test setup by adding act to the existing imports used alongside renderHook
and waitFor, using the same source as the rest of the React testing utilities in
this file. Make sure the act call in the mutateAsync block is covered by that
import so the hook test can run successfully.
- Line 48: The test in use-bounty-escrow.test.ts is using an untyped captured
entry with any, which violates the typing guideline. Update the captured
collection’s element type to a proper Trustless Work entity or request/log type
used by the hook, and then narrow the predicate in the expectation accordingly
so the /api/escrow/ops URL check no longer relies on any.
- Line 15: The test import for the bounty escrow hook points to a missing
root-level module, so update the import in the use-bounty-escrow test to
reference the actual hook location under features/bounties/hooks. Use the
existing useBountyEscrow import in
tests/hooks/bounties/use-bounty-escrow.test.ts as the symbol to locate it, and
change the module path from the old `@/hooks/bounties/use-bounty-escrow` alias to
the correct `@/features/bounties/hooks/use-bounty-escrow` path.
🪄 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: 1a8a09bd-4d73-440e-9cd5-d7ba1c02d1b9

📥 Commits

Reviewing files that changed from the base of the PR and between fd2b990 and e171e95.

📒 Files selected for processing (2)
  • features/bounties/hooks/use-bounty-escrow.ts
  • tests/hooks/bounties/use-bounty-escrow.test.ts

Comment thread features/bounties/hooks/use-bounty-escrow.ts Outdated
Comment thread features/bounties/hooks/use-bounty-escrow.ts Outdated
Comment thread features/bounties/hooks/use-bounty-escrow.ts Outdated
Comment thread features/bounties/hooks/use-bounty-escrow.ts
Comment thread tests/hooks/bounties/use-bounty-escrow.test.ts Outdated
Comment thread tests/hooks/bounties/use-bounty-escrow.test.ts Outdated
Comment thread tests/hooks/bounties/use-bounty-escrow.test.ts
Comment thread tests/hooks/bounties/use-bounty-escrow.test.ts Outdated
- close Promise<> generic in useBountiesList
- forward payload through runEscrowOp / useSubmitBounty
- invalidate bounty detail cache after submit/withdraw
- include submissions in useMyBountyActivity
- update test import path and typing

@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

🤖 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 `@features/bounties/hooks/use-bounty-escrow.ts`:
- Around line 229-233: The file declares ParticipantActivityItem twice, causing
a TypeScript duplicate identifier error; remove the later duplicate type alias
in use-bounty-escrow.ts and keep only the original definition, ensuring
ParticipantSubmission still references the single shared ParticipantActivityItem
union used by the related bounty activity logic.

In `@tests/hooks/bounties/use-bounty-escrow.test.ts`:
- Around line 38-39: The test is waiting on result.current.isSuccess without
making the waitFor callback throw, so it can resolve before the query is
actually successful. Update the use-bounty-escrow test to assert inside waitFor
against result.current.isSuccess (and, if needed, the bounty length) so it keeps
polling until the hook state is ready, using the result.current access pattern
already in the test.
- Around line 42-51: Restore the mocked global fetch in the use-bounty-escrow
test setup so it does not leak into other tests. Update the useSubmitBounty test
to use vi.stubGlobal for fetch (or save the original global.fetch) and add
cleanup in afterEach with vi.unstubAllGlobals or an explicit restore. Keep the
change localized to the test file and reference the useSubmitBounty test case
and the global.fetch mock.
- Around line 18-25: The test helper in wrap uses JSX via QueryClientProvider,
so the file should be treated as TSX. Rename use-bounty-escrow.test.ts to use
the .tsx extension (or replace the JSX with React.createElement), and keep the
existing wrap/renderHook setup intact so TypeScript can parse it under the
current tsconfig.
🪄 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: d8f9ab91-3934-4dc4-8e87-d2cb76428d00

📥 Commits

Reviewing files that changed from the base of the PR and between e171e95 and 22539d9.

📒 Files selected for processing (2)
  • features/bounties/hooks/use-bounty-escrow.ts
  • tests/hooks/bounties/use-bounty-escrow.test.ts

Comment thread features/bounties/hooks/use-bounty-escrow.ts
Comment on lines +18 to +25
function wrap(hook: () => unknown) {
const qc = new QueryClient({ defaultOptions: { queries: { retry: false } } });
return renderHook(() => hook(), {
wrapper: ({ children }: { children: ReactNode }) => (
<QueryClientProvider client={qc}>{children}</QueryClientProvider>
),
});
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Confirm extension and whether the project transforms JSX in .ts files
fd -t f 'use-bounty-escrow.test' tests
fd -t f 'vitest.config*' -X cat
fd -t f 'tsconfig*.json' -X sh -c 'echo "== $1 =="; cat "$1"' _ {}

Repository: boundlessfi/boundless

Length of output: 928


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the test file, package scripts, and any Vitest/Babel/TS config that could affect JSX parsing in .ts files.
sed -n '1,120p' tests/hooks/bounties/use-bounty-escrow.test.ts
printf '\n== package.json ==\n'
sed -n '1,220p' package.json
printf '\n== vitest-related files ==\n'
fd -a 'vitest*' .
printf '\n== babel-related files ==\n'
fd -a '.babel*|babel.config*' .

Repository: boundlessfi/boundless

Length of output: 8872


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Check whether TypeScript treats this file as JSX-capable and whether a .ts file with JSX is syntactically valid in this repo.
python3 - <<'PY'
from pathlib import Path
import re

path = Path("tests/hooks/bounties/use-bounty-escrow.test.ts")
text = path.read_text()

# Minimal scan for JSX-like syntax in the file.
print("contains_jsx_opening_tag=", "<QueryClientProvider" in text)
print("contains_jsx_closing_tag=", "</QueryClientProvider>" in text)

# Show the exact line with JSX.
for i, line in enumerate(text.splitlines(), 1):
    if "QueryClientProvider" in line:
        print(f"line_{i}={line}")
PY

printf '\n== package json test tooling ==\n'
grep -n '"vitest"\|"test"' -n package.json || true

printf '\n== TypeScript compiler availability ==\n'
command -v tsc || true
tsc --version || true

Repository: boundlessfi/boundless

Length of output: 488


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect whether any test tooling overrides the default TypeScript/esbuild JSX handling for .ts files.
printf '== files named vitest or vite config ==\n'
fd -a 'vitest.config.*|vite.config.*|vitest.*' . || true

printf '\n== test-related scripts in package.json ==\n'
node - <<'JS'
const fs = require('fs');
const pkg = JSON.parse(fs.readFileSync('package.json', 'utf8'));
for (const [name, cmd] of Object.entries(pkg.scripts || {})) {
  if (/(vitest|test|tsx?|esbuild|vite)/i.test(cmd)) console.log(name + ': ' + cmd);
}
JS

printf '\n== JSX syntax in the file ==\n'
python3 - <<'PY'
from pathlib import Path
path = Path("tests/hooks/bounties/use-bounty-escrow.test.ts")
for i, line in enumerate(path.read_text().splitlines(), 1):
    if "QueryClientProvider" in line or "renderHook" in line:
        print(f"{i}: {line}")
PY

Repository: boundlessfi/boundless

Length of output: 561


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Check whether JSX appears anywhere else in .ts files and whether TypeScript's parser rejects this file as .ts.
printf '== JSX-in-.ts occurrences ==\n'
rg -n --glob '*.ts' '<[A-Z][A-Za-z0-9]*\b' tests src app . || true

printf '\n== TypeScript parse probe ==\n'
node - <<'JS'
try {
  const ts = require('typescript');
  const fs = require('fs');
  const source = fs.readFileSync('tests/hooks/bounties/use-bounty-escrow.test.ts', 'utf8');
  const sf = ts.createSourceFile(
    'tests/hooks/bounties/use-bounty-escrow.test.ts',
    source,
    ts.ScriptTarget.Latest,
    /*setParentNodes*/ true,
    ts.ScriptKind.TS,
  );
  console.log('parseDiagnostics=' + sf.parseDiagnostics.length);
  for (const d of sf.parseDiagnostics.slice(0, 5)) {
    console.log(ts.flattenDiagnosticMessageText(d.messageText, '\n'));
  }
} catch (err) {
  console.log('require_typescript_failed=' + err.message);
}
JS

Repository: boundlessfi/boundless

Length of output: 50377


Rename tests/hooks/bounties/use-bounty-escrow.test.ts to .tsx — it contains JSX (<QueryClientProvider />), and tsconfig.json includes *.ts files in tsc --noEmit, so this will break TypeScript parsing unless the file becomes .tsx or the JSX is replaced with createElement.

🧰 Tools
🪛 Biome (2.5.0)

[error] 22-22: expected > but instead found client

(parse)


[error] 22-22: Invalid assignment to <QueryClientProvider client

(parse)


[error] 22-22: unterminated regex literal

(parse)

🤖 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 `@tests/hooks/bounties/use-bounty-escrow.test.ts` around lines 18 - 25, The
test helper in wrap uses JSX via QueryClientProvider, so the file should be
treated as TSX. Rename use-bounty-escrow.test.ts to use the .tsx extension (or
replace the JSX with React.createElement), and keep the existing wrap/renderHook
setup intact so TypeScript can parse it under the current tsconfig.

Source: Linters/SAST tools

Comment on lines +38 to +39
await waitFor(() => result.current.isSuccess);
expect(result.current.data?.bounties).toHaveLength(1);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

waitFor callback never asserts — the test can pass before the query succeeds.

waitFor only retries when the callback throws; returning a falsy boolean is treated as success, so it resolves on the first tick regardless of isSuccess. The subsequent toHaveLength(1) then runs against possibly-unsettled data. Assert inside the callback so waitFor actually polls.

🐛 Proposed fix
-    await waitFor(() => result.current.isSuccess);
+    await waitFor(() => expect(result.current.isSuccess).toBe(true));
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
await waitFor(() => result.current.isSuccess);
expect(result.current.data?.bounties).toHaveLength(1);
await waitFor(() => expect(result.current.isSuccess).toBe(true));
expect(result.current.data?.bounties).toHaveLength(1);
🤖 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 `@tests/hooks/bounties/use-bounty-escrow.test.ts` around lines 38 - 39, The
test is waiting on result.current.isSuccess without making the waitFor callback
throw, so it can resolve before the query is actually successful. Update the
use-bounty-escrow test to assert inside waitFor against result.current.isSuccess
(and, if needed, the bounty length) so it keeps polling until the hook state is
ready, using the result.current access pattern already in the test.

Comment thread tests/hooks/bounties/use-bounty-escrow.test.ts
natemiller23 added a commit to natemiller23/boundless that referenced this pull request Jun 24, 2026
…essfi#629

- remove duplicate ParticipantActivityItem type alias
- await success assertion inside waitFor callback
- stub/restore global.fetch in tests with vi.stubGlobal + unstub
- rename test to .tsx for JSX support
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.

FE: features/bounties participant data layer (escrow client + hooks)

1 participant