Skip to content

Surface adaptBounty's teamSplitsValid result in IssueDetailPage instead of computing and discarding it #85

Description

@chonilius

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

  • A bounty whose teamSplits sum to something other than 100% (within the existing tolerance) renders a visible warning on IssueDetailPage, using the message already computed by validateTeamSplits.
  • A bounty whose teamSplits are valid renders exactly as before, with no new warning.
  • teamSplitsValid is properly typed and accessible wherever Bounty objects flow through the app, not just on the return type of adaptBounty in isolation.
  • A test covers both the valid and invalid team-split rendering cases for IssueDetailPage.

Additional Notes

Precise references:

  • src/lib/adapters.ts:55-82adaptBounty, 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    GrantFox OSSIssue tracked in GrantFox OSSMaybe RewardedIssue may be eligible for a GrantFox rewardOfficial Campaign | FWC26Campaign: Official Campaign | FWC26Third CampaignCampaign: Third CampaignbugSomething isn't workingvery hardVery difficult task, expert-level effort required

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions