Skip to content

Mod elections: the results post charts the whole count - #357

Merged
TripleU613 merged 2 commits into
mainfrom
claude/remove-disqualified-ballot-h5nhiv
Oct 25, 2026
Merged

TripleU613 merged 2 commits into
mainfrom
claude/remove-disqualified-ballot-h5nhiv

Conversation

@TripleU613

Copy link
Copy Markdown
Owner

Summary

The results post in the election's topic only listed the winners and linked to the elections page. Now it also shows the whole count, so anyone reading the topic can see it was honest:

  • First choices: each candidate's votes, share of all votes, and a bar.
  • Stage by stage: what each stage did, in words. Who was elected with how many votes, whose extra votes moved on, who went out.
  • Where everyone finished: votes, share, and "Elected at stage N" / "Out at stage N".
  • Ties: how each was settled, with the lot seed.
  • Every stage in numbers: the full table, in a folded [details].

It's built from the stored result alone (elections.result), the same data as the elections page, so it never says who ranked whom.

The charts are text: bars of block characters, to an eighth of a cell, in Markdown tables. They read the same on the full site, in Dumbcourse and in email, and mod-elections.scss tints them on the full site. Everything after the winners is inside [wrap=mod-election-count].

Elections already published get it too. The tick runs ResultsPost.backfill!. For each published election not yet in the PluginStore list for ResultsPost::VERSION, it finds the results post by its link (/elections?id=N)) and revises it in place with bypass_bump. If there's no such post, it replies. Either way the election is recorded, so this happens once per version. It waits while the module is off. Bumping VERSION rewrites every election's post.

Visibility: the post is as visible as the election topic's category. In a public category, visitors who aren't signed in can now read the full count; until now only members could, on the elections page.

Affected module(s)

  • Moderator tools
  • Mini-mod
  • Dislike
  • Disteleplus
  • REQ-PM
  • Mod elections
  • Dumbcourse
  • Smart search
  • Pop-ups
  • Another SMTP
  • Translator tweaks
  • JTech theme
  • Shared (plugin.rb, settings, locales, docs, lint/CI)

Test plan

  • New spec/lib/mod_elections_results_post_spec.rb (4 examples) covers:
    • publishing writes the charted count, with the right shares and bars, and no voter's name
    • a past election's existing results post is revised in place without bumping the topic, and only once
    • a past election whose topic has no results post gets one added, but not while the module is off
    • an uncontested election (no Vote Week) says it was elected unopposed, with no charts
  • Election lib and request specs on this head: 120 examples, 0 failures.
  • I also generated a sample post and checked the raw Markdown and the cooked HTML: the wrap, three tables, and the folded details.
  • pnpm lint clean; rubocop and stree clean.

Checklist

  • pnpm lint is clean
  • bundle exec rubocop is clean
  • New / changed settings have entries in config/locales/server.en.yml or client.en.yml (no settings; the post's wording is in server.en.yml)
  • New / changed behavior has a spec (a bug fix has one that fails without it)
  • Follows the repository rules: no access past core, visibility checked, nothing core already does
  • No Ruby file references a non-existent setting name

Generated by Claude Code

The results post in the election's topic listed the winners and linked
to the elections page. Now it shows the whole count too, so anyone
reading the topic can see it was honest:
- everyone's first-choice votes and share of all votes, as a bar chart
- what each stage did: who was elected with how many votes, whose extra
  votes moved on, who went out
- where everyone finished
- how any tie was settled
- every stage in numbers, folded away
It's built from the stored result alone, like the page, so it never says
who ranked whom. The charts are text (block-character bars in Markdown
tables, to an eighth of a cell), so they read the same on the full site,
in Dumbcourse and in email; the stylesheet tints them on the full site.

Elections already published get the same post: the tick finds each one's
results post by its link and revises it in place without bumping the
topic, or replies if there's none, and records the election in the
PluginStore so it happens once per ResultsPost::VERSION. It waits while
the module is off.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FGn9A1EjvUZh9u1zSaCHPh
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 25, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-25T01:07:37.020199Z 235e917 New commits
🔒 Security Review ✅ Completed 2026-10-25T01:11:14.652706Z 235e917 New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f5a1a75354

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

.where.not(topic_id: nil)
.where.not(id: done)
.order(:id)
.each { |election| write!(election) }

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Batch the per-election backfill queries

When this version is deployed, the loop performs separate topic, user, post, and PluginStore queries for every published election, so the backfill scales as an N+1 query sequence over election history. Load the relevant topics, candidates/users, and posts in batches and persist the completed IDs once per batch rather than querying inside write! for every row.

AGENTS.md reference: AGENTS.md:L24-L24

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not changing this one. The per-election queries run once per published election, ever: the first tick after deploy writes each election's post and records it. Elections are quarterly by default, so a forum has a handful, not one per post, topic or recipient (which is what AGENTS.md's batching rule is about). After that first pass, each tick is one PluginStore read plus one election query that returns nothing. Batching topics, users and posts across elections would add code to save a few queries one time. If the maintainer wants it batched anyway, I'll do it.


Generated by Claude Code

Publishing and the tick could both find no results post for an election
just published and both add one. Writes now go one at a time under a
DistributedMutex, so whichever comes second finds the first's post.

A failed write was still recorded as done, so that election never got its
post: Announcer.reply rescued the error and returned the logger's true.
It now returns nil on failure, and an election is recorded only once its
post is there (or already says the same), so the next tick tries again.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FGn9A1EjvUZh9u1zSaCHPh
@TripleU613
TripleU613 merged commit c411a33 into main Oct 25, 2026
10 checks passed
@TripleU613
TripleU613 deleted the claude/remove-disqualified-ballot-h5nhiv branch October 25, 2026 01:16
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.

2 participants