Crowd favorite voting app for Nova
  • TypeScript 63.3%
  • Svelte 34.1%
  • JavaScript 0.8%
  • CSS 0.7%
  • Nix 0.6%
  • Other 0.5%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Krit Dass ab97c7c89d
All checks were successful
nixbot/nix-eval evaluation succeeded in 0s (1 warning)
nixbot/nix-build 0 attributes built
kennel/build build succeeded
kennel/deploy deployment healthy
CI / check (push) Successful in 3m1s
feat(organizer): add live judging leaderboard
2026-10-31 19:07:18 -04:00
.forgejo/workflows fix: restore CI checks and enable PR preview seeding 2026-10-25 17:40:13 -04:00
.vscode feat(api): add collection and ballot endpoints 2026-10-21 22:47:19 -04:00
scripts feat(sync): link voters and team members to hacker records 2026-10-31 19:06:47 -04:00
src feat(organizer): add live judging leaderboard 2026-10-31 19:07:18 -04:00
static feat(voter): add Nova theme tokens and logo 2026-10-24 10:33:18 -04:00
.gitignore chore: align with current kennel patterns 2026-10-13 13:11:41 -04:00
.oxlintrc.json chore: configure Oxc editor linting 2026-10-20 11:43:49 -04:00
BUG_TESTING.md feat: add organizer dashboard 2026-10-31 01:24:35 +00:00
deno.json chore: configure kennel 2026-10-13 16:52:36 +00:00
deno.lock feat: add organizer dashboard 2026-10-31 01:24:35 +00:00
devenv.lock fix: revert bot lockfile updates 2026-10-26 15:17:18 -04:00
devenv.nix chore: set crowd favorite custom domain 2026-10-25 18:20:31 -04:00
devenv.yaml chore: align with current kennel patterns 2026-10-13 13:11:41 -04:00
flake.lock fix: revert bot lockfile updates 2026-10-26 15:17:18 -04:00
flake.nix chore: align with current kennel patterns 2026-10-13 13:11:41 -04:00
package.json feat: add organizer dashboard 2026-10-31 01:24:35 +00:00
README.md feat(organizer): add live judging leaderboard 2026-10-31 19:07:18 -04:00
secretspec.toml feat: add organizer dashboard 2026-10-31 01:24:35 +00:00
server.js feat: add organizer dashboard 2026-10-31 01:24:35 +00:00
tsconfig.json feat(voter): save drafts offline and sync on reconnect 2026-10-31 12:32:31 -04:00
vite.config.ts chore: configure kennel 2026-10-13 16:52:36 +00:00

Nova Crowd Favorite

https://crowd-favorite.nova.scottylabs.org

Organizers

Open /organizer. From this repository, fetch the production password:

deno task password --profile prod

deno task password uses your current SecretSpec profile, normally dev. Passwords are stored in OpenBao. To rotate one, run collar secrets set ORGANIZER_PASSWORD --profile prod --provider openbao. Rotation signs everyone out. A missing password keeps the dashboard locked.

  • Voters: add emails and copy personal links. Search by email, team, or ranked team; filter by ballot status. Manage shows the ballot and offers Reset ballot and Replace voter link.
  • Teams: search names and members. Edit changes the name or eligibility.
  • QR codes: create codes, print one per page, or release a team's claim.
  • Results: calculate ranks from submitted ballots. Shared ranks are ties.
  • Settings: set or clear the closing time, close or reopen voting, and back up or restore the database.

Before the event, check teams, add voters, print QR codes, and set the closing time. After closing, calculate results and download the ballots and database. Use Refresh to reload lists and voting status.

Live leaderboard opens a screen for the event board. It shows the top 10 named voters by unique teams collected and updates every 10 seconds. Ties share a rank. It uses the organizer sign-in; keep that browser signed in on the display.

Send each voter their own link; the app does not send emails. Replacing a link invalidates the old one. A reset archives the ballot and preserves the voter's collection and link. Voting must be open for them to submit again.

Team membership comes from the submission Sheet. A voter's email must match a member email to claim a QR code. Sticker numbers do not need to match voter links. Names edited in the dashboard survive Sheet sync.

For a local session with a disposable database, see BUG_TESTING.md. That script generates its own password instead of using OpenBao.

Offline use

After opening the app online, voters can edit notes and draft rankings offline. Changes stay in this browser and sync when the connection returns. The save status shows whether they are stored locally or confirmed by the server. Submission, scanning, and claiming QR codes require a connection.

Offline refresh works for cached pages in a production build. Keep the tab open if visiting a team for the first time offline. Clearing browser data removes unsynced changes. The organizer dashboard is not cached.

Database recovery

In Settings, download a backup or restore one from the same app version. Restores replace all event data and save the current database on the server first. Failed imports roll back. Backups contain access hashes and private notes; keep them private.

If the app cannot start, stop its service and run this on the server:

deno task restore nova-backup.db /path/to/nova.db --offline

The command validates the backup, keeps the old database and its WAL/SHM files in a private directory alongside it, and restores them if replacement fails. Restart the service afterward. Never run this while the app is running.

The existing organizer CLI also works while the app is running. It needs APP_URL and that deployment's SYNC_HMAC_SECRET:

deno task organizer export-db > nova-backup.db
deno task organizer reset-ballot person@example.com

Team sync

In the submission Sheet, open Extensions > Apps Script and paste scripts/apps-script.gs. Set these Script Properties:

Property Value
SYNC_URL https://crowd-favorite.nova.scottylabs.org/api/sync
SYNC_HMAC_SECRET Production secret from OpenBao
SHEET_NAME Submission tab name

Use headers Timestamp, Team Name, Team Members, and Team Emails. Run pushToApp, authorize it, and add a recurring time-driven trigger. Sync sends the whole Sheet. Missing teams become inactive. Changing a name or timestamp in the Sheet creates a new team identity; rename through the dashboard to keep the same team.

Hacker names

Use columns Name and Email, one person per row.

For a separate tab in the submission spreadsheet, add the Script Property HACKERS_SHEET_NAME with that tab's name. Keep the existing SHEET_NAME, SYNC_URL, and SYNC_HMAC_SECRET. The same pushToApp trigger syncs both tabs.

For a separate spreadsheet, paste the same Apps Script there. Set SYNC_URL to https://crowd-favorite.nova.scottylabs.org/api/sync/hackers, SYNC_HMAC_SECRET to the production secret, and SHEET_NAME to the hackers tab. Leave HACKERS_SHEET_NAME unset. Run pushToApp and add a recurring trigger.

Names match voter and team membership records by email, ignoring email case. Sync updates names without creating voter links or changing ballots. Missing rows keep their previous names. People without a name stay off the leaderboard. Existing voters and team memberships migrate automatically on app startup.

Results and exports

Each place runs a fresh IRV election. A majority of continuing ballots wins; otherwise the lowest team is eliminated and its votes transfer. Exhausted ballots leave the majority denominator. Tied eliminations use parallel-universe tiebreaking: a team shares a place if it can win under a possible tied elimination order. Remove the winning group, then repeat for the next place. Shared places use competition ranking: 1, 1, 3.

Only submitted ballots count. Disqualified teams are skipped; inactive teams still count. Drafts, reset ballots, and unranked teams do not participate. Results are provisional while voting is open. Recalculate after any changes. The tally stops with an error if its tie search exceeds 20,000 candidate sets or requires recursive tie handling with more than 128 remaining teams.

To save results from the CLI:

deno task organizer tally --json > results.json

Ballot CSV exports include drafts and submissions, rank IDs and names, and current eligibility flags. A database export includes every table and committed WAL writes; it needs no accompanying WAL/SHM files. Both exports are private. Organizer API requests use the same HMAC authentication as Sheet sync; the CLI handles signing. See the API routes for endpoints.