- TypeScript 63.3%
- Svelte 34.1%
- JavaScript 0.8%
- CSS 0.7%
- Nix 0.6%
- Other 0.5%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
| .forgejo/workflows | ||
| .vscode | ||
| scripts | ||
| src | ||
| static | ||
| .gitignore | ||
| .oxlintrc.json | ||
| BUG_TESTING.md | ||
| deno.json | ||
| deno.lock | ||
| devenv.lock | ||
| devenv.nix | ||
| devenv.yaml | ||
| flake.lock | ||
| flake.nix | ||
| package.json | ||
| README.md | ||
| secretspec.toml | ||
| server.js | ||
| tsconfig.json | ||
| vite.config.ts | ||
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.