You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
GET /v1/candidates/{id}/promises?profile_id= groups a challenger's promises by the voter's own stance_id — the same key AlignmentReason already groups by for incumbents — since a challenger has no candidate_positions rows to drive the existing /alignment endpoint. Promises outside the voter's stated interests are counted in unmatched_count, never silently dropped. A promise with no resolvable promise_sources citation is omitted rather than shown uncited. Prior office comes from people.terms[] — any term whose district isn't the current candidacy's own seat — reusing data app.ballot.service already reads, never fabricated.
frontend/src/candidate/ChallengerPromisesView.tsx reuses story 7.5's Drawer for each promise's source citation, wired into BallotPage.tsx so incumbents route to CandidateDetailPage and challengers route here — a small, backward-compatible widening of BallotView's existing onViewDetails callback to also carry isIncumbent.
Honest state of live data, stated plainly rather than glossed over: the live promises table is empty, confirmed by story 9.4/9.5's finding (PR #182) that the real extractor currently produces zero genuine promises against all 6 live promise_sources rows. This endpoint and view are built and tested only against synthetic fixtures — no live verification script was run or claimed, and every real candidate today correctly shows the honest 'no promises on file yet' empty state rather than a populated-looking fixture presented as live data.
Judgment calls flagged for review: 'linked' prior office is a plain text notice, not a hyperlink, since there's nothing real yet to link to; /alignment was kept untouched rather than widened to also carry promises, since unscored promise data and scored position data shouldn't mix in one response shape.
Activity
developer-ainative commentedon Oct 20, 2026
Closed by PR #184 (merged).
GET /v1/candidates/{id}/promises?profile_id=groups a challenger's promises by the voter's ownstance_id— the same keyAlignmentReasonalready groups by for incumbents — since a challenger has nocandidate_positionsrows to drive the existing/alignmentendpoint. Promises outside the voter's stated interests are counted inunmatched_count, never silently dropped. A promise with no resolvablepromise_sourcescitation is omitted rather than shown uncited. Prior office comes frompeople.terms[]— any term whose district isn't the current candidacy's own seat — reusing dataapp.ballot.servicealready reads, never fabricated.frontend/src/candidate/ChallengerPromisesView.tsxreuses story 7.5'sDrawerfor each promise's source citation, wired intoBallotPage.tsxso incumbents route toCandidateDetailPageand challengers route here — a small, backward-compatible widening ofBallotView's existingonViewDetailscallback to also carryisIncumbent.Honest state of live data, stated plainly rather than glossed over: the live
promisestable is empty, confirmed by story 9.4/9.5's finding (PR #182) that the real extractor currently produces zero genuine promises against all 6 livepromise_sourcesrows. This endpoint and view are built and tested only against synthetic fixtures — no live verification script was run or claimed, and every real candidate today correctly shows the honest 'no promises on file yet' empty state rather than a populated-looking fixture presented as live data.Judgment calls flagged for review: 'linked' prior office is a plain text notice, not a hyperlink, since there's nothing real yet to link to;
/alignmentwas kept untouched rather than widened to also carry promises, since unscored promise data and scored position data shouldn't mix in one response shape.