feat(elections): self-authored candidate profiles over the static export - #109
Open
douglance wants to merge 1 commit into
Open
feat(elections): self-authored candidate profiles over the static export#109douglance wants to merge 1 commit into
douglance wants to merge 1 commit into
Conversation
Contributor
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
douglance
force-pushed
the
siwe/candidates
branch
from
August 19, 2026 21:02
c3dbdd8 to
8219b0a
Compare
douglance
force-pushed
the
siwe/candidates
branch
from
August 20, 2026 16:45
8219b0a to
59e77b7
Compare
fionnachan
force-pushed
the
siwe/candidates
branch
from
August 20, 2026 16:55
59e77b7 to
411aeb6
Compare
douglance
force-pushed
the
siwe/candidates
branch
from
August 24, 2026 16:26
411aeb6 to
6280d9b
Compare
Adds /elections (author your own candidate profile, per election) and
overlays those profiles onto the contender pages, covering the four
election and candidate-profile routes.
ContenderProfile is unchanged. The merge is a pure function applied in
ContenderProfileLoader, which already existed as the seam between the
static export and that presentational component — so the 434-line
profile renderer needed no edits at all.
Two shape mismatches the earlier plan got wrong, checked against the real
data this time:
- data/election-candidates.json is an object keyed by address (154
records), not an array.
- `skills` does not merge. The export holds a ratings object
({ canVerifySigning, golang, solidity, rust, javascript,
cyberSecurity }) while SIWE holds a flat string[]. There is no
mapping between "8/10 in Rust" and the word "Rust", so the export
keeps the ratings block and SIWE skills render as a separate list.
TallyElectionCandidate.skills was already typed `unknown`, which is
the same admission.
Nine remaining fields do correspond exactly and merge with SIWE winning,
except that a blank self-authored value falls through to the snapshot
rather than erasing a name. `twitter` is normalized: the export stores a
URL, SIWE stores a bare handle.
SelfAuthoredNotice names which fields the candidate wrote. It also warns
when the address is absent from the candidate registry, because the
indexer does not check that a profile's author is actually a contender —
until it does, an arbitrary address must not be able to borrow the
appearance of a vetted candidate page.
The editor mounts only once the stored profile has loaded, seeding form
state directly. Hydrating from an effect would let a background refetch
overwrite edits in progress, and it trips react-hooks/set-state-in-effect.
Verified: lint clean, tsc clean, 1262 unit tests, build still prerenders
all 152 contender pages.
douglance
force-pushed
the
siwe/candidates
branch
from
August 27, 2026 18:08
6280d9b to
1654161
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds
/profile/candidate(author your own candidate profile, per election) and overlays those profiles onto the contender pages, covering the four election and candidate-profile routes.ContenderProfile is unchanged
The merge is a pure function applied in
ContenderProfileLoader, which already existed as the seam between the static export and that presentational component — so the 434-line profile renderer needed no edits at all.Two shape mismatches, checked against the real data
The earlier plan claimed all ten fields aligned. They don't:
data/election-candidates.jsonis an object keyed by address (154 records), not an array.skillsdoes not merge. The export holds a ratings object ({ canVerifySigning, golang, solidity, rust, javascript, cyberSecurity }) while SIWE holds a flatstring[]. There is no mapping between "8/10 in Rust" and the word "Rust", so the export keeps the ratings block and SIWE skills render as a separate list.TallyElectionCandidate.skillswas already typedunknown, which is the same admission.The nine remaining fields do correspond exactly and merge with SIWE winning — except that a blank self-authored value falls through to the snapshot rather than erasing a name.
twitteris normalized: the export stores a URL, SIWE stores a bare handle.Attribution, and an open server-side gap
SelfAuthoredNoticenames which fields the candidate wrote. It also warns when the address is absent from the candidate registry, because the indexer does not check that a profile's author is actually a contender. Until it does, an arbitrary address must not be able to borrow the appearance of a vetted candidate page. That check belongs server-side and is not addressed here.Form hydration
The editor mounts only once the stored profile has loaded, seeding form state directly. Hydrating from an effect would let a background refetch overwrite edits in progress, and it trips
react-hooks/set-state-in-effect.Verified: lint clean, tsc clean, 1262 unit tests, build still prerenders all 152 contender pages.