The Leaderboard Showed Training but Let You Do Nothing About It
A few weeks ago I wrote about leaderboards: a podium for the top three, weekly stats, trend indicators, and race-scoped chat. It worked, but there was a gap.
You could see that someone training for the same marathon had logged a 30K long run. You could be impressed. You could not tell them. The leaderboard showed training without letting you react to it. Race chat existed, but it was general conversation, like "anyone doing the hilly route Saturday?" — disconnected from specific sessions.
The missing piece was seeing a workout and being able to say something about it.
Kudos Is a One-Tap Thumbs-Up
Kudos works in one tap. You tap the thumbs-up and it fills. Tap again and it unfills. The database does an insert-or-delete based on whether your kudos already exists. That is toggle semantics, so there is no state to manage and no separate "like" and "unlike" API calls.
The button shows a count and a tooltip listing who gave it. The list is capped at ten names before it truncates.
Both runners have to be on the same leaderboard, and the workout has to be completed — there is no kudos for a workout you haven't done yet. Display names are cached when the kudos is created, so rendering a page with thirty workouts doesn't fire thirty extra queries. Loading kudos for all thirty is one batch query.
Comments Are Capped at 280 Characters
Comments follow the same rules as kudos: the same leaderboard, and completed workouts only. Comments allow free text, so they carry a few extra limits.
The maximum length is 280 characters. A workout comment is meant to be something like "that negative split was clean" or "how'd the knee feel on the downhill?" — a short reaction, not a review.
The rate limit is 10 comments per day. Comments are soft-deleted with a timestamp, so you can remove what you said without breaking the thread. There is no dedicated sanitize layer for XSS. Askama auto-escapes everything on render, which handles it without the double-encoding problem of sanitizing and then escaping.
Every Interaction Runs the Same Access Check
The social layer is narrow on purpose. Every interaction runs through the same access check: are you on the leaderboard, are you the workout owner, is the workout completed, and are you both training for the same race?
That last check is a configurable gate. Right now it is on, so you only see workouts from people training for the same race. Proposed workouts, the ones you haven't done yet, are never visible to anyone else.
This is a training group, not a social network. It is meant to be small. You can see a teammate's session, give it a thumbs-up, and leave a short note, and that is the full extent of the interaction.