Skip to content

Documentation program: ~20 new help pages for voters and administrators (28 PRs) #1556

Description

@masiarek

What this is

A documentation and localisation program for the help site and the app's own text, submitted as a batch of pull requests. This issue is the index: what each PR does, a suggested merge order, the structure they assume, and the decisions taken along the way.

The short version: the help site had 7 pages, all written for the person running an election. There was nothing addressed to voters — who are the larger audience, and who the app links into these docs from en.yaml. This adds roughly 25 pages, gives both audiences a front door, documents features that had no written explanation anywhere, and takes the app from 4 languages to 7.

Nothing here needs to merge as a set. Every page PR links only to pages already on main, precisely so merge order never matters.


Suggested merge order

1. Two one-line config changes — merge first, they make everything cheaper

PR What Why first
#1535 enable jekyll-redirect-from Without it, redirect_from: is silently discarded and renaming any published page is a permanent 404. Eight doc URLs are hardcoded in the app. This is the safety net for every future change.
#1536 enable mermaid One key. Lets a diagram render instead of publishing as its own source text.

Neither installs anything — both ship inside the github-pages bundle already in use.

2. Structure

PR What
#1543 Gives the site a voter door and an admin door. Front-matter only — no file moves, no URL changes. Supersedes #1505, which can be closed.

3. Small fixes

PR What
#1493 spelling and grammar on the paper ballots page
#1503 the Montserrat font URL is malformed, so headings never use it
#1501 lets any contributor preview the docs site locally
#1557 the Adding Documentation page tells contributors to copy a parent: value that doesn't work — see below

4. Pages for voters

PR Page
#1539 How to Vote · After You Vote
#1542 Is My Vote Secret?
#1550 Voting Problems — symptom → cause → fix
#1544 How Voters Get Access
#1561 Using BetterVoting in Another Language

5. Pages for administrators

PR Page
#1537 Election or Poll?
#1538 Choosing a Voting Method
#1540 Before You Open Your Election · Electing More Than One Winner
#1546 Managing Your Voters
#1551 Emails to Voters
#1547 Ballot Options
#1548 Letting Voters Change Their Vote
#1552 Polls and Multiple Races
#1545 Exporting Your Data
#1502 Election States

6. Results and counting

PR Page
#1541 Reading Your Results
#1528 why the highest score doesn't always win
#1474 Bloc STAR Voting

7. Ties — three pages, three audiences

PR Page For
#1553 Ties (rewrite) everyone; holds the #random-tie-breakers anchor the app links to
#1554 Choosing a Tie-Breaking Rule organisations writing bylaws, before any election
#1555 Verifying a Tie-Break a candidate who lost one, or an auditor certifying a result

8. Reference

PR Page
#1549 Glossary — 41 terms
#1498 Tips and Tricks
#1520 CONTRIBUTING.md
#1521 guidance for AI-assisted PR descriptions

9. Localisation — 4 languages to 7

PR What
#1562 Fills the Priority 0 gaps in es, pl, pt-BR — 44, 51 and 51 missing strings, mostly the whole election_history section none of them had
#1560 German (de) — all 255 Priority 0 strings
#1563 French (fr) — all 255
#1564 Italian (it) — all 255
#1565 Completes Polish. 149 strings were still sitting in English, including the star-column headers and every method's ballot instructions — the most-read screen in the product. Now 28, all legitimately English (brand and method names). Builds on #1562; merge that first

Polish was the worst affected: a Polish voter has been reading their ballot instructions in English. #1565 fixes that.

Every locale file was validated against English programmatically: key count, missing keys, extra keys, and every {{interpolation}}, $t() and !tip() compared token-by-token. All seven now sit at 255/255 with zero token mismatches.

#1562 also repaired pre-existing damage found on the way: !tip(random_tie_order) and {{n}} had gone missing from strings in all three files, and Polish ordinals were still English-shaped (1st/2nd/3rd).

These are AI-produced translations. The contribution guide requires proof-reading by at least one native speaker, and that hasn't happened. Each PR names the specific choices a native reviewer should rule on. Please treat them as drafts that pass the mechanical checks, not as finished work.

10. App text

PR What
#1559 Stops the wizard promising BetterVoting emails the voting links. Fixes #1558

11. Fixes for bugs the documentation work found

The bug list at the bottom of this issue is no longer just a list — the top items now have PRs:

PR Fixes
#1572 "Invalid Voter ID" shown before the voter types anything — frontend compared against "Voter ID Required", backend sends "Voter ID Required for closed elections"; the error branch was true on first paint in every ID-list election
#1573 the receipt typo ("update you ballot") — one character, in the sentence that hands the voter their vote-changing link
#1576 CSV export column collisionovervote_rank/has_duplicate_rank weren't race-prefixed, so with two ranked races every duplicate column showed the last race's values; the demo harness showed a real Mayor overvote erased and a Council duplicate falsely attributed. Keys now always race-scoped; single-race exports stay byte-identical
#1577 The lying "Email invite status" roll column — only the legacy sendInvites endpoints wrote email_data.inviteResponse, the field the roll table reads, so invitations sent via the current email-blast tool left every row on "Not Sent". The blast path now records it through the same helper the legacy path uses, gated to invitation-template blasts only (a free-form update marking a voter "Sent" would be the reverse bug). Backend suite green: 25 suites / 177 tests, 3 new
#1578 The draggable ranked ballot ignoring the ranking cap — a voter could rank past max_rankings and build a ballot the server then rejects. The drop is now refused at the limit with a self-clearing warning and an "N of M rankings used" counter; the limit logic is extracted into one getMaxRankings() shared with the classic ranked view, matching the server rule. Bonus fix found while in there: the draggable early-return sat above the classic view's hooks — a React hooks-order violation that white-screens when paging from a draggable IRV race to another ranked race

Two systemic findings from the translation work are now issues of their own: #1574 (interpolated nouns can't be grammatical in gendered languages — hit independently by de, fr, it and pl) and #1575 (a ~20-line CI check for translation values still byte-identical to English — the check that caught Polish shipping its ballot instructions in English). The unclaimable "Publish Now" quick poll is a product question, asked in this thread below.


The structure these assume

BetterVoting Documentation
├── For Voters                    ← landing page
└── Running an Election           ← landing page
    ├── Setting Up Your Election
    ├── Counting and Results
    └── Reference

Role first, then lifecycle. Every election has one admin and many voters, and the app already sends voters here from the results page. Those readers currently land in an admin manual.

Comparable services organise by object and have no voter home at all; election authorities split by role, because their audience includes voters. So does BetterVoting's.

Note on sequencing. Every content PR currently carries parent: BetterVoting Documentation so it can merge independently. Once #1543 lands, a single follow-up re-parents all of them into the tree above. That map is done and verified locally against a full build — 31 pages, none lost from the nav. If a content PR merges before #1543, its page still appears, just at the top level. Nothing breaks either way.


How pages were written

Every page was written from the source code, not from assumption, and each PR body cites the files behind its claims. Where the code is definite the page is definite — no "usually" or "typically" for behaviour a controller makes certain.

Each page was built locally with the real github-pages toolchain and checked for dangling links before submission.

One mechanical note: jekyll-relative-links only rewrites links whose target exists. A link to a page not in the build ships the raw href="foo.md". That's why cross-references between these pages are deliberately thin — a single follow-up PR wiring them up is the natural last step, once every target exists. A consistency review across the whole set has already produced that list: roughly 52 link insertions across 24 pages.


A consistency review has been run across all of it

Around 25 pages written in parallel by eleven authors who couldn't see each other's work. Reading them as a set found 9 contradictions, each checked against origin/main rather than guessed at. All are now fixed in their respective PRs. The most serious:

  • Three pages said BetterVoting emails invitations automatically. It doesn't — which is how Election wizard promises BetterVoting will email voting links automatically; nothing is ever sent #1558 was found.
  • Two pages said the ballot ID appears on the confirmation screen. It's deliberately scrubbed there, with the comment "prevent voters from creating receipts (vote buying/coercion)". Documenting it as visible would have quietly undermined an intentional anti-coercion measure.
  • The glossary contradicted itself on who can archive (owner-only).
  • A voter page hedged ballot secrecy as configuration-dependent. The scrub is unconditional.

Still outstanding from that review, and better done after merging: nine duplication trims, terminology alignment ("organiser" vs "admin" splits three ways across voter pages), and the wiring PR.


Decisions taken, and the alternative rejected

Offered so reviewers can disagree with the reasoning rather than guess at it.

  1. Role-first navigation over lifecycle-only. Rejected: docs: group the help pages into three sections #1505's three lifecycle sections alone — they leave voters nowhere.
  2. Named the lean. The method chooser states outright that BetterVoting is built by Equal Vote, which advocates STAR. Rejected: silent neutrality. Readers detect a lean anyway; naming it keeps the page usable by someone who has already chosen another method.
  3. RCV introduced once as instant-runoff, with Ranked Robin named as the other way to count the same ballot.
  4. Editable ballots framed as a genuine trade-off, with Estonia's re-voting (a coercion countermeasure, verified against valimised.ee) as the counterweight. Rejected: "bad practice, but we shipped it."
  5. Method comparisons state limits as well as strengths — including STAR's.
  6. The secrecy and verification pages state what they do not prove. A verification page that overclaims is worse than none.
  7. The tie-break shuffle is explained rather than deferred. The old page said "randomly" and pointed at the source tree. The truth is better: it's deterministic, the full order is published, and it's reproducible.

Open questions for maintainers

Genuine questions, not rhetorical ones.

  1. Deleting an election — the controller has a permission check but no state gate, so an open election with cast ballots can be deleted. Intended? Not documented either way pending an answer.
  2. Roll states — flag and invalidate are permissioned admin actions. Should they affect anything beyond bookkeeping? The Managing Your Voters page makes no enforcement claim.
  3. break_ties_randomly appears in exactly one place — a display toggle for the results title — and doesn't control whether ties are broken randomly. Left undocumented rather than guessed at.
  4. Skipped-rank exhaustion isn't exposed in the settings UI and defaults to effectively never. Deliberate?
  5. Abstain / None of the Above — deliberately not written; When 'None of the Above' wins a race, it's seated with no special handling — intended? #1421 is open on the product question.
  6. Nav label — "Running an Election" versus "For Administrators". Task phrasing chosen over the role noun; trivially changeable.
  7. Interpolated nouns break grammar in gendered languages. German lost a string entirely, Polish can't satisfy the accusative, French needed six rephrasings, Italian used invariant constructions throughout. results.details is the clearest case: no single string works for both the election and poll vocabularies because the two race words have different genders. This is a limit of the i18n design, not translator error, and it will hit every gendered language. The fix would be separate keys per vocabulary. Happy to open a separate issue if useful.
  8. Register is inconsistent across languages. Italian uses informal tu; German and Spanish use formal. Nobody has made that call deliberately.

Bugs noticed while writing

Explaining a feature turns out to be an effective way to find it misbehaving. None are documented as features; several are one-line fixes.

A small number of further observations are better discussed privately than in a public issue — happy to share them with a maintainer on request.


What's deliberately not covered

A page listing known documentation gaps is worth publishing in its own right — it stops readers hunting for something that doesn't exist, and turns each gap into a claimable task for the Writers contribution path. Happy to add one, with the convention that the PR filling a gap removes its line from the list.


Feedback welcome, including "this is wrong"

These were written without maintainer input, deliberately — a blank page attracts no feedback, a written one attracts corrections. Every page is a starting point. Rewrite, reject, or reshape freely; the structure in #1543 is front-matter only, so re-parenting anything costs one line.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Role: WritingImprovements or additions to documentation

    Projects

    • Status
      Questions / In Review

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions