Overview
adaptBounty computes a team-split validity result and attaches it to every adapted bounty — but nothing downstream ever reads it:
export function adaptBounty(raw: RawBounty): Bounty & { teamSplitsValid?: { valid: boolean; sum: number; message?: string } } {
const splits = raw.team?.splits?.map(
(split): TeamSplit => ({
role: split.role ?? "Contributor",
percentage: coercePercentage(split.percentage),
contributor: split.user?.username,
}),
);
return {
...
teamSplits: splits,
teamSplitsValid: splits ? validateTeamSplits(splits) : undefined,
};
}
I grepped the entire src/ tree for teamSplitsValid and the only two matches are the declaration and the assignment inside adaptBounty itself — no page, no component, nothing anywhere in src/app/** or src/components/** ever reads bounty.teamSplitsValid. The one place that actually renders team splits, IssueDetailPage, ignores it entirely:
{bounty.teamSplits && (
<div className="mt-8">
<h2 className="font-medium text-slate-900 dark:text-white">Team payout split</h2>
<div className="mt-3 space-y-2">
{bounty.teamSplits.map((split) => (
<div key={split.role} ...>
<span ...>{split.role}{split.contributor ? ` (${split.contributor})` : ""}</span>
<span ...>{split.percentage}%</span>
</div>
))}
</div>
</div>
)}
There's no check of bounty.teamSplitsValid?.valid anywhere in this block, and no warning rendered when it's false. Concretely: if a bounty's team splits arrive from the backend summing to, say, 85% or 110% (a real, computed, and already validated-as-invalid condition per validateTeamSplits's own logic — data corruption, a race between concurrent split edits, or simply a backend bug), a contributor viewing that bounty's detail page sees the individual percentages listed exactly as-is, with no indication that they don't sum to 100%, no warning banner, nothing — a public payout breakdown that's silently wrong is displayed with full visual confidence.
This is a genuine "the validation was built, and then its result was thrown away" bug — the computation exists, is correct, and is completely inert. BountyCard.tsx (the list/grid view of the same bounty) doesn't render team splits at all, so this issue is scoped to IssueDetailPage.
Requirements
- In
IssueDetailPage's team-split rendering block, check bounty.teamSplitsValid?.valid and render a clear, visible warning when it's false — reusing teamSplitsValid.message (already computed by validateTeamSplits, e.g. "Team splits sum to 85.00% (expected 100%)") rather than inventing new copy.
- Decide whether an invalid split should also affect any other UI on the page (e.g. should "Claim this issue" or the payout-related copy in
IssueActions be affected if the splits are known-invalid at claim time?) — at minimum, surface the warning; a broader gating decision is a reasonable follow-up but should be explicitly scoped in/out in the PR rather than silently ignored a second time.
- Fix
adapters.ts's return type: adaptBounty's signature returns Bounty & { teamSplitsValid?: ... }, but Bounty itself (in src/types/index.ts) has no teamSplitsValid field — meaning every consumer of adaptBounty's return value that narrows to the plain Bounty type (e.g. anything typed as Bounty[] elsewhere in the app, like fetchBounties's return type) loses access to teamSplitsValid entirely at the type level, even before considering that nothing reads it at runtime either. Either add teamSplitsValid to the Bounty type itself (recommended, since it's meant to be consumed by UI) or keep it as an adapter-local concern and thread it through explicitly wherever IssueDetailPage needs it.
Acceptance Criteria
Additional Notes
Precise references:
src/lib/adapters.ts:55-82 — adaptBounty, teamSplitsValid computed at line 80.
src/app/issues/[id]/page.tsx:75-95 — the team-split rendering block in IssueDetailPage, confirmed to only check bounty.teamSplits (truthiness/presence), never teamSplitsValid.
- Grep evidence:
grep -rn "teamSplitsValid" src/ returns exactly two lines, both inside adapters.ts (the type-signature declaration and the field assignment) — zero consumers anywhere else in the codebase.
src/types/index.ts:23-40 — the Bounty interface, confirmed to have no teamSplitsValid field, which is the type-level half of this bug (the runtime value is computed and attached, but its own declared return type isn't part of the domain type consumers actually work with elsewhere).
Relationship to other issues: distinct from the separate validateTeamSplits duplicate-implementation issue in this batch (that one is about the function itself failing to compile; this one is about its result never being consumed once it does compile and run correctly). Fixing one doesn't fix the other — they're sequential concerns on the same underlying feature.
Edge cases: validateTeamSplits([]) (an empty splits array, if that's ever produced instead of undefined) returns { valid: true, sum: 0 } per its own short-circuit — confirm this doesn't trigger a spurious warning once the check is wired up, since bounty.teamSplits && (...) already guards against an empty/falsy array at the render level today (an empty array is truthy in JS, so bounty.teamSplits && (...) would currently render an empty "Team payout split" section for an empty-but-present array — worth a quick explicit check on whether that's also worth guarding against while touching this code, though it's a minor secondary finding, not the core of this issue).
Test/reproduction plan: render IssueDetailPage's content (or extract/test the team-split block in isolation) with a bounty fixture whose teamSplits sum to 85%, assert teamSplitsValid.message text is visible in the rendered output; repeat with a fixture summing to exactly 100% and assert no warning is present.
Overview
adaptBountycomputes a team-split validity result and attaches it to every adapted bounty — but nothing downstream ever reads it:I grepped the entire
src/tree forteamSplitsValidand the only two matches are the declaration and the assignment insideadaptBountyitself — no page, no component, nothing anywhere insrc/app/**orsrc/components/**ever readsbounty.teamSplitsValid. The one place that actually renders team splits,IssueDetailPage, ignores it entirely:There's no check of
bounty.teamSplitsValid?.validanywhere in this block, and no warning rendered when it'sfalse. Concretely: if a bounty's team splits arrive from the backend summing to, say, 85% or 110% (a real, computed, and already validated-as-invalid condition pervalidateTeamSplits's own logic — data corruption, a race between concurrent split edits, or simply a backend bug), a contributor viewing that bounty's detail page sees the individual percentages listed exactly as-is, with no indication that they don't sum to 100%, no warning banner, nothing — a public payout breakdown that's silently wrong is displayed with full visual confidence.This is a genuine "the validation was built, and then its result was thrown away" bug — the computation exists, is correct, and is completely inert.
BountyCard.tsx(the list/grid view of the same bounty) doesn't render team splits at all, so this issue is scoped toIssueDetailPage.Requirements
IssueDetailPage's team-split rendering block, checkbounty.teamSplitsValid?.validand render a clear, visible warning when it'sfalse— reusingteamSplitsValid.message(already computed byvalidateTeamSplits, e.g."Team splits sum to 85.00% (expected 100%)") rather than inventing new copy.IssueActionsbe affected if the splits are known-invalid at claim time?) — at minimum, surface the warning; a broader gating decision is a reasonable follow-up but should be explicitly scoped in/out in the PR rather than silently ignored a second time.adapters.ts's return type:adaptBounty's signature returnsBounty & { teamSplitsValid?: ... }, butBountyitself (insrc/types/index.ts) has noteamSplitsValidfield — meaning every consumer ofadaptBounty's return value that narrows to the plainBountytype (e.g. anything typed asBounty[]elsewhere in the app, likefetchBounties's return type) loses access toteamSplitsValidentirely at the type level, even before considering that nothing reads it at runtime either. Either addteamSplitsValidto theBountytype itself (recommended, since it's meant to be consumed by UI) or keep it as an adapter-local concern and thread it through explicitly whereverIssueDetailPageneeds it.Acceptance Criteria
teamSplitssum to something other than 100% (within the existing tolerance) renders a visible warning onIssueDetailPage, using the message already computed byvalidateTeamSplits.teamSplitsare valid renders exactly as before, with no new warning.teamSplitsValidis properly typed and accessible whereverBountyobjects flow through the app, not just on the return type ofadaptBountyin isolation.IssueDetailPage.Additional Notes
Precise references:
src/lib/adapters.ts:55-82—adaptBounty,teamSplitsValidcomputed at line 80.src/app/issues/[id]/page.tsx:75-95— the team-split rendering block inIssueDetailPage, confirmed to only checkbounty.teamSplits(truthiness/presence), neverteamSplitsValid.grep -rn "teamSplitsValid" src/returns exactly two lines, both insideadapters.ts(the type-signature declaration and the field assignment) — zero consumers anywhere else in the codebase.src/types/index.ts:23-40— theBountyinterface, confirmed to have noteamSplitsValidfield, which is the type-level half of this bug (the runtime value is computed and attached, but its own declared return type isn't part of the domain type consumers actually work with elsewhere).Relationship to other issues: distinct from the separate
validateTeamSplitsduplicate-implementation issue in this batch (that one is about the function itself failing to compile; this one is about its result never being consumed once it does compile and run correctly). Fixing one doesn't fix the other — they're sequential concerns on the same underlying feature.Edge cases:
validateTeamSplits([])(an empty splits array, if that's ever produced instead ofundefined) returns{ valid: true, sum: 0 }per its own short-circuit — confirm this doesn't trigger a spurious warning once the check is wired up, sincebounty.teamSplits && (...)already guards against an empty/falsy array at the render level today (an empty array is truthy in JS, sobounty.teamSplits && (...)would currently render an empty "Team payout split" section for an empty-but-present array — worth a quick explicit check on whether that's also worth guarding against while touching this code, though it's a minor secondary finding, not the core of this issue).Test/reproduction plan: render
IssueDetailPage's content (or extract/test the team-split block in isolation) with a bounty fixture whoseteamSplitssum to 85%, assertteamSplitsValid.messagetext is visible in the rendered output; repeat with a fixture summing to exactly 100% and assert no warning is present.