Skip to content

feat(elections): self-authored candidate profiles over the static export - #109

Open
douglance wants to merge 1 commit into
siwe/drafts-pagesfrom
siwe/candidates
Open

feat(elections): self-authored candidate profiles over the static export#109
douglance wants to merge 1 commit into
siwe/drafts-pagesfrom
siwe/candidates

Conversation

@douglance

Copy link
Copy Markdown
Contributor

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.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.

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. twitter is normalized: the export stores a URL, SIWE stores a bare handle.

Attribution, and an open server-side gap

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. 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.

@vercel

vercel Bot commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
tally-zero Ready Ready Preview Aug 27, 2026 6:10pm

Request Review

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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant