The UI reads several fields from bounty that aren't on the codegen BountyFieldsFragment: claimCount, maxParticipants, maxSlots, totalSlotsOccupied, milestones, contributorProgress, submissions, applications. Components and hooks work around this with inline casts like (bounty as { submissions?: Array<{ ... }> | null }) scattered everywhere. The casts drift in shape from one file to the next, and TypeScript can't help when one of them is wrong.
What to do
Define each of these as optional fields on the Bounty interface in types/bounty.ts. Most of them are already there; the gap is consistency. Specifically:
submissions?: BountySubmission[] | null
applications?: BountyApplication[] | null (add BountyApplication if missing)
claimCount?: number | null
maxParticipants?: number | null
maxSlots?: number | null
totalSlotsOccupied?: number | null
milestones?: Milestone[] | null
contributorProgress?: ContributorProgress[] | null
Where useBountyDetail already returns BountyFieldsFragment & Partial<Bounty>, that covers most call sites. The remaining ad-hoc casts in bounty-detail-client.tsx, bounty-detail-sidebar-cta.tsx, model4-maintainer-dashboard.tsx, and use-competition-join-state.ts should be removed in favor of the typed shape.
Acceptance criteria
- No
(bounty as { ... }) casts remaining in the bounty detail tree
pnpm tsc --noEmit and pnpm lint pass
- No behavioral change
Files
types/bounty.ts
components/bounty-detail/bounty-detail-client.tsx
components/bounty-detail/bounty-detail-sidebar-cta.tsx
components/bounty-detail/model4-maintainer-dashboard.tsx
hooks/use-competition-join-state.ts
The UI reads several fields from
bountythat aren't on the codegenBountyFieldsFragment:claimCount,maxParticipants,maxSlots,totalSlotsOccupied,milestones,contributorProgress,submissions,applications. Components and hooks work around this with inline casts like(bounty as { submissions?: Array<{ ... }> | null })scattered everywhere. The casts drift in shape from one file to the next, and TypeScript can't help when one of them is wrong.What to do
Define each of these as optional fields on the
Bountyinterface intypes/bounty.ts. Most of them are already there; the gap is consistency. Specifically:submissions?: BountySubmission[] | nullapplications?: BountyApplication[] | null(addBountyApplicationif missing)claimCount?: number | nullmaxParticipants?: number | nullmaxSlots?: number | nulltotalSlotsOccupied?: number | nullmilestones?: Milestone[] | nullcontributorProgress?: ContributorProgress[] | nullWhere
useBountyDetailalready returnsBountyFieldsFragment & Partial<Bounty>, that covers most call sites. The remaining ad-hoc casts inbounty-detail-client.tsx,bounty-detail-sidebar-cta.tsx,model4-maintainer-dashboard.tsx, anduse-competition-join-state.tsshould be removed in favor of the typed shape.Acceptance criteria
(bounty as { ... })casts remaining in the bounty detail treepnpm tsc --noEmitandpnpm lintpassFiles
types/bounty.tscomponents/bounty-detail/bounty-detail-client.tsxcomponents/bounty-detail/bounty-detail-sidebar-cta.tsxcomponents/bounty-detail/model4-maintainer-dashboard.tsxhooks/use-competition-join-state.ts