Skip to content

Club leaderboard: list every member unless they opt out, and say when stars start - #399

Merged
openipc-ai merged 2 commits into
masterfrom
leaderboard-opt-out
Oct 6, 2026
Merged

openipc-ai merged 2 commits into
masterfrom
leaderboard-opt-out

Conversation

@openipc-ai

Copy link
Copy Markdown
Collaborator

/club/leaderboard has been empty since it shipped. It lists members who ticked "Show me on the leaderboard" and have stars. On production (2026-10-06):

  • 2 of 10 members had ticked it, and neither has stars;
  • the one member with a star (a published report) had not ticked it;
  • wall_stars is empty: the 4 cameras were linked on 2026-10-05, and joining needs 30 days and 20 qualifying days counted from the link, so the earliest wall stars are about 2026-11-04. The nightly settlement runs (wallstars: settled cameras:4 stars:0).

Changes

  • Opt-out. Migration 027_leaderboard_opt_out.sql sets club_members.listed to default to true and turns it on for every existing member. Unticking the box on /club still takes a member off, and members without stars are still left out.
  • Privacy text. The Open Wall privacy note (wall_club_html) and the leaderboard's footnote (lb_note) now describe opt-out, in en, ru and zh.
  • Empty state. lb_empty explains when stars start: a linked camera earns its first 5 stars 30 days after it was linked, once it has sent live pictures on at least 20 of those days; a report earns stars when a maintainer publishes it.
  • The tests, code comments and CLAUDE.md now describe opt-out.

Names on the board are what members are called where they signed in: the Telegram first and last name, the GitHub name or login, or "OpenIPC member" for an email sign-in. An address is never shown. Existing Telegram members become listed without being asked; drop the UPDATE in 027 if only new members should default to listed.

Testing

  • service/run.sh test: the full suite failed once, on TestLeaderboard, which assumed opt-in. After fixing it, ./internal/wallstars/... and ./internal/club/... pass. The full suite has not been re-run since.
  • In frontend/, with node:24: npm run export, lint, typecheck and test all pass (668 tests).
  • Not yet validated on dev.openipc.org.

… stars start

/club/leaderboard has been empty since it shipped. It lists members who
ticked "Show me on the leaderboard" and have stars; on production 2 of 10
members had ticked it and neither has stars, the one member with a report
star had not ticked it, and no camera can earn wall stars before about
2026-11-04 (30 days and 20 qualifying days counted from the link).

- Migration 027 makes club_members.listed default to true and sets it for
  every existing member. Unticking the box on /club still takes a member
  off; members without stars are still left out.
- The privacy note on the Open Wall page and the leaderboard's footnote say
  so, in en, ru and zh.
- The empty leaderboard explains when stars start: a linked camera's first
  5 stars 30 days after it was linked, with live pictures on at least 20 of
  those days; a report's when a maintainer publishes it.

Names on the board are what members are called where they signed in:
Telegram's first and last name, the GitHub name or login, "OpenIPC member"
for an email sign-in -- never an address.
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

PR Summary by Qodo

Make the club leaderboard opt-out and explain when stars appear

🐞 Bug fix ✨ Enhancement 📝 Documentation 🧪 Tests 🕐 20-40 Minutes

Grey Divider

AI Description

• List members with stars by default, while preserving their ability to opt out.
• Explain star eligibility and leaderboard visibility in English, Russian, and Chinese.
• Update leaderboard tests and documentation for the new default.
Diagram

graph TD
  Migration["Opt-out migration"] --> Members[("Club members")] --> Query["Leaderboard query"] --> API["Club API"] --> Page["Leaderboard page"] --> Copy["Localized copy"]
  Settings["Club settings"] --> Members
  Stars[("Star ledgers")] --> Query
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Default only new members to listed
  • ➕ Preserves existing members' previous visibility setting and avoids publishing their names without a new action.
  • ➖ Existing members with stars remain absent until they explicitly change their setting, so the current empty-board problem may persist.

Recommendation: The default change fits the goal of showing star earners, and the existing preference and query make it a focused solution. The backfill is a separate privacy decision: confirm that making existing members publicly visible without asking them is intended before applying the migration.

Files changed (19) +40 / -28

Bug fix (1) +6 / -0
027_leaderboard_opt_out.sqlDefault and backfill leaderboard listing to true +6/-0

Default and backfill leaderboard listing to true

• Sets the club member listing default to true and changes every existing unlisted member to listed. Members can still turn listing off through the existing setting.

service/internal/db/migrations/027_leaderboard_opt_out.sql

Tests (2) +10 / -4
wall_test.goTest default listing and subsequent opt-out +8/-2

Test default listing and subsequent opt-out

• Checks that a newly joined member with stars appears on the public leaderboard, disappears after opting out, and can opt back in.

service/internal/club/wall_test.go

wallstars_test.goAdapt ranking test to the opt-out default +2/-2

Adapt ranking test to the opt-out default

• Explicitly opts out the hidden test member instead of opting in the visible members, preserving assertions about ranking and star totals.

service/internal/wallstars/wallstars_test.go

Documentation (16) +24 / -24
CLAUDE.mdDocument distinct wall and leaderboard visibility defaults +2/-2

Document distinct wall and leaderboard visibility defaults

• Clarifies that camera-owner attribution remains opt-in while leaderboard visibility becomes opt-out through migration 027.

CLAUDE.md

boards.en.ymlExplain leaderboard visibility and star timing in English +2/-2

Explain leaderboard visibility and star timing in English

• Replaces opt-in wording with the opt-out policy and explains camera and published-report star eligibility in the empty state.

data/locales/boards.en.yml

boards.ru.ymlExplain leaderboard visibility and star timing in Russian +2/-2

Explain leaderboard visibility and star timing in Russian

• Updates the Russian leaderboard note and empty state for opt-out listing and star eligibility.

data/locales/boards.ru.yml

boards.zh.ymlExplain leaderboard visibility and star timing in Chinese +2/-2

Explain leaderboard visibility and star timing in Chinese

• Updates the Chinese leaderboard note and empty state for opt-out listing and star eligibility.

data/locales/boards.zh.yml

pages.en.ymlCorrect English Open Wall privacy disclosure +1/-1

Correct English Open Wall privacy disclosure

• Distinguishes opt-in camera-owner attribution from opt-out leaderboard visibility.

data/locales/pages.en.yml

pages.ru.ymlCorrect Russian Open Wall privacy disclosure +1/-1

Correct Russian Open Wall privacy disclosure

• Distinguishes opt-in camera-owner attribution from opt-out leaderboard visibility.

data/locales/pages.ru.yml

pages.zh.ymlCorrect Chinese Open Wall privacy disclosure +1/-1

Correct Chinese Open Wall privacy disclosure

• Distinguishes opt-in camera-owner attribution from opt-out leaderboard visibility.

data/locales/pages.zh.yml

Leaderboard.tsxCorrect leaderboard component's visibility comment +2/-2

Correct leaderboard component's visibility comment

• Documents that members are included unless they untick the listing preference; rendering behavior is unchanged.

frontend/apps/site/src/components/club/Leaderboard.tsx

boards.en.jsonUpdate bundled English leaderboard messages +2/-2

Update bundled English leaderboard messages

• Mirrors the English opt-out footnote and star-eligibility empty state in frontend translations.

frontend/apps/site/src/i18n/boards.en.json

boards.ru.jsonUpdate bundled Russian leaderboard messages +2/-2

Update bundled Russian leaderboard messages

• Mirrors the Russian opt-out footnote and star-eligibility empty state in frontend translations.

frontend/apps/site/src/i18n/boards.ru.json

boards.zh.jsonUpdate bundled Chinese leaderboard messages +2/-2

Update bundled Chinese leaderboard messages

• Mirrors the Chinese opt-out footnote and star-eligibility empty state in frontend translations.

frontend/apps/site/src/i18n/boards.zh.json

en.jsonUpdate bundled English privacy note +1/-1

Update bundled English privacy note

• States that linked-camera owner attribution requires a choice, while members with stars appear on the leaderboard unless they opt out.

frontend/apps/site/src/i18n/en.json

ru.jsonUpdate bundled Russian privacy note +1/-1

Update bundled Russian privacy note

• Replaces the opt-in leaderboard claim with the opt-out policy while retaining opt-in camera attribution.

frontend/apps/site/src/i18n/ru.json

zh.jsonUpdate bundled Chinese privacy note +1/-1

Update bundled Chinese privacy note

• Replaces the opt-in leaderboard claim with the opt-out policy while retaining opt-in camera attribution.

frontend/apps/site/src/i18n/zh.json

wall.goCorrect public leaderboard endpoint comment +1/-1

Correct public leaderboard endpoint comment

• Documents that the endpoint includes star-earning members who have not opted out; endpoint behavior is unchanged.

service/internal/club/wall.go

wallstars.goCorrect leaderboard query contract comment +1/-1

Correct leaderboard query contract comment

• Describes the existing listed-member filter as opt-out; ranking and star aggregation SQL are unchanged.

service/internal/wallstars/wallstars.go

@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Oct 6, 2026 •

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (1) 📘 Rule violations (1) 📎 Requirement gaps (0) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)

Grey Divider


Action required

1. Rollback lists members without opting in 📘 Rule violation ⛨ Security
Description
027_leaderboard_opt_out.sql changes the listed default to true, while identify inserts new
members without specifying that column. If the prior service image is restored against the migrated
database, it creates members as listed under its opt-in behavior, and their names appear on the
public leaderboard once they earn stars.
Code

service/internal/db/migrations/027_leaderboard_opt_out.sql[5]

+ALTER TABLE club_members ALTER COLUMN listed SET DEFAULT true;
Evidence
Rule 1 requires migrations to preserve schema compatibility with the prior service image. Migration
024 establishes the false default; the changed migration replaces it with true. Member creation
omits listed, and the public leaderboard selects members for whom it is true.

CLAUDE.md: Keep Database Migrations Additive: CLAUDE.md: Keep Database Migrations Additive: CLAUDE.md: Keep Database Migrations Additive: CLAUDE.md: Keep Database Migrations Additive
service/internal/db/migrations/024_wall_stars.sql[111-112]
service/internal/db/migrations/027_leaderboard_opt_out.sql[5-6]
service/internal/club/members.go[128-134]
service/internal/wallstars/wallstars.go[561-570]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The migration changes the default relied on by the prior service image, so a rollback can list newly created members who did not opt in.
## Fix Focus Areas
- service/internal/db/migrations/027_leaderboard_opt_out.sql[5-6]
- service/internal/wallstars/wallstars.go[529-567]
## Recommended Fix
Preserve the prior `listed` default and existing values for rollback compatibility. Add separate opt-out state for the new service, and use it for the new leaderboard and listing controls.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Existing opt-outs become public 🐞 Bug ⛨ Security
Description
Migration 027 sets every existing listed = false value to true, including values members
deliberately saved through the listing setting. When an affected member has stars, the public
leaderboard includes their name and stars without requiring them to change that setting.
Code

service/internal/db/migrations/027_leaderboard_opt_out.sql[6]

+UPDATE club_members SET listed = true WHERE NOT listed;
Evidence
The old default was false, but members could also explicitly save false. The migration overwrites
both cases, and the public leaderboard filters solely on the resulting listed value.

service/internal/db/migrations/024_wall_stars.sql[110-112]
service/internal/club/wall.go[116-133]
service/internal/db/migrations/027_leaderboard_opt_out.sql[5-6]
service/internal/wallstars/wallstars.go[556-574]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The bulk update resets deliberate opt-outs and can publish those members on the public leaderboard.
## Fix Focus Areas
- service/internal/db/migrations/027_leaderboard_opt_out.sql[5-6]
## Recommended Fix
Keep the new default for future members, but do not turn existing false values into true without a reliable way to distinguish the old default from a member's explicit choice. Preserve explicit opt-outs and add migration coverage for that case.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

3. An empty board says no stars exist ✓ Resolved
Description
lb_empty now says “No stars yet,” although Leaderboard displays it for every empty result in
both the all-time and 30-day tabs. A member can have older stars but no stars in the selected
period, or starred members can all opt out, and either case produces that same message.
Code

data/locales/boards.en.yml[564]

+      lb_empty: 'No stars yet. A linked camera earns its first 5 stars 30 days after it was linked, once it has sent live pictures on at least 20 of those days; a report earns stars when a maintainer publishes it.'
Evidence
The component renders one translation for any zero-row response; the query filters stars by period,
excludes opted-out members, and omits members without positive points in that period.

frontend/apps/site/src/components/club/Leaderboard.tsx[15-24]
frontend/apps/site/src/components/club/Leaderboard.tsx[39-42]
service/internal/club/wall.go[136-158]
service/internal/wallstars/wallstars.go[556-574]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The empty-state message asserts that no stars exist even when only the selected period or listed members have no stars.
## Fix Focus Areas
- data/locales/boards.en.yml[564-564]
- frontend/apps/site/src/components/club/Leaderboard.tsx[15-42]
## Recommended Fix
Use wording that describes no leaderboard entries for the selected period, or distinguish the relevant empty states. Update the Russian and Chinese translations and generated frontend translation bundles as well.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


4. Camera owners get a false star timeline ✓ Resolved
Description
lb_empty says a linked camera earns its first five stars 30 days after linking if it sent live
pictures on 20 days, but the award requires 20 days marked both lit and varied and measures age from
when counting starts. A camera whose pictures do not qualify, or whose counting starts after its
link date because it lacked an available slot, does not earn those stars on the timeline the message
promises.
Code

data/locales/boards.en.yml[564]

+      lb_empty: 'No stars yet. A linked camera earns its first 5 stars 30 days after it was linked, once it has sent live pictures on at least 20 of those days; a report earns stars when a maintainer publishes it.'
Evidence
The star query counts only lit-and-varied days since counted_since; settlement may set that
timestamp after linking, and the award requires both the qualifying-day and age thresholds.

service/internal/wallstars/wallstars.go[304-313]
service/internal/wallstars/wallstars.go[338-346]
service/internal/wallstars/wallstars.go[418-446]
service/internal/wallstars/settle.go[61-87]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The new empty-state text promises camera stars based on upload days and link age, while settlement uses qualifying days and a potentially later counting start.
## Fix Focus Areas
- data/locales/boards.en.yml[564-564]
- service/internal/wallstars/wallstars.go[304-346]
- service/internal/wallstars/settle.go[61-87]
## Recommended Fix
Describe the 20 days as qualifying days with lit, changing pictures, and avoid promising an award exactly 30 days after linking when counting can start later. Keep all three locale sources and frontend translation bundles consistent.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Tip of the day
💡 Did you know, you can add REVIEW.md to your repo root and Qodo follows it on every PR

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment thread service/internal/db/migrations/027_leaderboard_opt_out.sql
Comment thread service/internal/db/migrations/027_leaderboard_opt_out.sql
Comment thread data/locales/boards.en.yml Outdated
Comment thread data/locales/boards.en.yml Outdated
…han stars come

The empty board showed one message for both tabs, and it said there were no
stars at all -- wrong on the 30-day tab, and wrong when everyone with stars
has opted out. The all-time message now says nobody on the leaderboard has
stars yet; the 30-day tab says nobody earned any in the month.

The camera timeline was a promise the settlement does not keep: counting
starts at the link only when a slot is free, and a day counts only with lit,
changing pictures. It now says no sooner than 30 days after the link, once
there have been 20 days of lit, changing pictures.
@openipc-ai
openipc-ai merged commit cfeb472 into master Oct 6, 2026
2 checks passed
@openipc-ai
openipc-ai deleted the leaderboard-opt-out branch October 6, 2026 09:46
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