···11+# punch
22+33+who's pushing on [tangled.org](https://tangled.org) — push-count leaderboard across every knot. one row per `committerDid`, ranked by `sh.tangled.git.refUpdate` events.
44+55+**[sites.wisp.place/zzstoatzz.io/punch](https://sites.wisp.place/zzstoatzz.io/punch/)**
66+77+## stack
88+99+```
1010+relay.waow.tech (listReposByCollection)
1111+ ↓
1212+ knot /events (WS, per-host)
1313+ ↓
1414+ CF Worker (*/10 min cron) → D1 (cursors, pushes) + KV (handles)
1515+ ↓
1616+ place.wisp.fs record on pds.zzstoatzz.io → sites.wisp.place
1717+```
1818+1919+- **indexer**: [Cloudflare Workers](https://workers.cloudflare.com) + D1 + KV
2020+- **site**: [wisp.place](https://wisp.place) (atproto static hosting)
2121+- **handles**: [typeahead.waow.tech](https://typeahead.waow.tech) + bsky AppView + plc.directory
2222+2323+## dev
2424+2525+```sh
2626+bun install && bun run dev # static site at :4747
2727+2828+cd worker && bun install
2929+wrangler dev # worker; GET /__run fires one cycle
3030+wrangler deploy
3131+wrangler tail
3232+```
3333+3434+secrets: `PUNCH_HANDLE`, `PUNCH_APP_PASSWORD` (atproto app password for the wisp-publishing account).
3535+3636+## publish
3737+3838+the worker only rewrites `leaderboard.json` in the wisp manifest each tick. html/css/js changes ship with a manual wispctl:
3939+4040+```sh
4141+wispctl deploy zzstoatzz.io --path public --site punch --password "$PUNCH_APP_PASSWORD" --yes
4242+```
4343+4444+<details>
4545+<summary>why pushes, not commits</summary>
4646+4747+tangled's own PUNCHCARD filters `meta.commitCount.byEmail[]` by the committer's private verified-emails table (`core/appview/state/knotstream.go:219` in tangled). without that table we'd massively overcount merges/rebases. one refUpdate = one push is the cleanest metric we can compute from public firehose data — and it surfaces accounts tangled's counter hides (e.g. prolific pushers who haven't run email verification).
4848+4949+</details>