Problem
Admins can't tell which organisers are still active. Outreach to dormant organisers is guesswork — is someone quiet because they've drifted away, or because their chapter simply hasn't run a workshop lately? The same gap hides dying chapters and makes trustee reporting harder.
The app has no sign-in tracking, and most organiser and member actions leave no attributable trace. What history exists is scattered across tables with different shapes (invitations, invitation_logs since March 2026, member notes), and some actions — feedback submissions, waiting-list promotions — have no actor attribution at all.
What we're building
Two things, delivered as one data layer plus one view:
- An append-only activity log. Every member, organiser, and admin action that we can attribute lands as one row in the existing
activities table (public_activity, already installed and used for sponsor audits): owner = the acting member, key = a dotted action name (member.login, workshop_invitation.rsvp, organiser_role.granted, …), trackable = the affected object, recipient = the affected member for actions on others. A single MemberActivityRecorder funnel with a fixed key vocabulary keeps call sites consistent; recording failures are logged, never raised, so activity tracking can't break a user flow.
- A weekly activity strip on the admin member profile (
/admin/members/:id, organisers only): 52 ISO-week cells, GitHub-contribution style. Cells have three states — empty, dim (logins/logouts only), solid (any other action) — so an admin can see at a glance whether a member is active, merely present, or gone quiet, with per-week tooltips for detail.
Key decisions
- Reuse the
activities table rather than add a new member_actions table — the gem's schema (owner/key/trackable/recipient) is exactly the event log we need, and one migration covers everything.
- Log-only history. The log starts at deploy; earlier weeks read as empty and self-heal. No backfill, no union with legacy tables.
- All member activity counts, not just organiser actions — RSVPs, subscriptions, and check-ins light cells too. Profile edits are included deliberately: that signal helps reveal misuse and account takeovers.
- Admin entity CRUD is excluded (chapters, sponsors, announcements…): it says more about entity management than member activity. Known gap with a built-in check — if the
/admin/organisers overview shows admins drifting to "quiet" while clearly working, we revisit.
- Feedback submissions and waiting-list auto-promotions stay invisible — no actor attribution exists today.
PR stack
| PR |
Scope |
| #2850 |
Recorder + session lifecycle (member.login/logout, provider-unlink site) |
| #2851 |
Subscriptions, mailing lists, profile edits, TOC acceptance |
| #2852 |
Event/meeting RSVPs, token-based workshop RSVPs, waiting lists, check-ins |
| #2853 |
Admin actions: organiser roles, bans, RSVP overrides, verification, notes, admin subscription/meeting-invitation changes |
| #2854 |
Workshop creation + created_by_id stamp, invitation batch sends |
| #2855 |
Weekly strip on the admin member profile |
Merge bottom-up — each PR builds on the previous.
Out of scope
The /admin/organisers overview table, chapter health quantification, per-action-type detail views, anything member-facing, and GDPR erasure of activities.
Problem
Admins can't tell which organisers are still active. Outreach to dormant organisers is guesswork — is someone quiet because they've drifted away, or because their chapter simply hasn't run a workshop lately? The same gap hides dying chapters and makes trustee reporting harder.
The app has no sign-in tracking, and most organiser and member actions leave no attributable trace. What history exists is scattered across tables with different shapes (invitations,
invitation_logssince March 2026, member notes), and some actions — feedback submissions, waiting-list promotions — have no actor attribution at all.What we're building
Two things, delivered as one data layer plus one view:
activitiestable (public_activity, already installed and used for sponsor audits):owner= the acting member,key= a dotted action name (member.login,workshop_invitation.rsvp,organiser_role.granted, …),trackable= the affected object,recipient= the affected member for actions on others. A singleMemberActivityRecorderfunnel with a fixed key vocabulary keeps call sites consistent; recording failures are logged, never raised, so activity tracking can't break a user flow./admin/members/:id, organisers only): 52 ISO-week cells, GitHub-contribution style. Cells have three states — empty, dim (logins/logouts only), solid (any other action) — so an admin can see at a glance whether a member is active, merely present, or gone quiet, with per-week tooltips for detail.Key decisions
activitiestable rather than add a newmember_actionstable — the gem's schema (owner/key/trackable/recipient) is exactly the event log we need, and one migration covers everything./admin/organisersoverview shows admins drifting to "quiet" while clearly working, we revisit.PR stack
member.login/logout, provider-unlink site)created_by_idstamp, invitation batch sendsMerge bottom-up — each PR builds on the previous.
Out of scope
The
/admin/organisersoverview table, chapter health quantification, per-action-type detail views, anything member-facing, and GDPR erasure of activities.