tenseleyflow/shithub / e3413e8

Browse files

Document social feed operations

Authored by mfwolffe <wolffemf@dukes.jmu.edu>
SHA
e3413e89094d77d41c650a10327778bb43778b69
Parents
86fb145
Tree
4a66e05

4 changed files

StatusFile+-
M README.md 1 2
A docs/internal/social.md 92 0
M docs/internal/stars-watchers.md 6 6
M docs/internal/worker.md 1 0
README.mdmodified
@@ -27,7 +27,7 @@ The core forge loop works end-to-end against the codebase you're reading:
2727
 - **Git** — bare repos on disk; HTTPS smart-HTTP push/pull (SSH service is planned but not shipped yet); pre/post-receive hook integration for size accounting and event emission.
2828
 - **Code browsing** — tree, blob (chroma syntax highlighting with light/dark themes), raw, blame, commit history, individual commit views, branch/tag listings, compare views, file finder.
2929
 - **Issues & PRs** — full CRUD on issues + comments + labels + milestones + assignees; pull requests with diff rendering, file-by-file review, line comments, reviews (approve/request-changes/comment), required-reviewer enforcement, status-check gates, three merge methods (merge / squash / rebase), conflict detection.
30
-- **Social** — stars, watches with notification level, forks (clone-on-create), `/explore`, stargazer/watcher lists.
30
+- **Social** — stars, watches with notification level, forks (clone-on-create), follows for users/orgs, Home/Explore feeds, trending repositories/developers, stargazer/watcher/follower lists.
3131
 - **Search** — code search (path + content, tantivy-equivalent indexing in Postgres), repo search, user search, quick-search.
3232
 - **Notifications** — per-user inbox, email fan-out, watch-level routing, one-click HMAC-signed unsubscribe links, auto-subscribe on participation.
3333
 - **Organizations & teams** — create, member roles (member/owner), invitations, one-level team nesting, team grants on repos with max-of-sources policy aggregation.
@@ -42,7 +42,6 @@ Pulled directly from the sprint plan we're working through:
4242
 - **Actions / CI** — there is no CI runner. Status checks are wired into PR gates so a future runner can publish into them.
4343
 - **Packages, Pages, Projects, Releases, Gists** — none of these surfaces exist yet.
4444
 - **GraphQL API** — only the internal HTTP surface exists. There is no public REST or GraphQL API.
45
-- **Activity feed** — domain events are recorded but the activity-feed view isn't built.
4645
 - **Admin / site-admin surface** — there is no `/admin` UI. Operator tooling is via `shithubd` subcommands and SQL.
4746
 - **Visual polish** — most pages render correctly but do not look like GitHub yet. Spacing, typography, header/footer chrome, color tokens, octicon coverage, empty-state illustrations, focus states — all still drifting from Primer. Closing this gap is ongoing; expect rough edges page-to-page.
4847
 - **Mobile / responsive** — the current CSS is desktop-first. Small viewports work but aren't tuned.
docs/internal/social.mdadded
@@ -0,0 +1,92 @@
1
+# Social Feed
2
+
3
+S42 turns the S26 social primitives into a GitHub-like network surface:
4
+follow graph, authenticated Home feed, public Explore feed, and cached
5
+trending rankings.
6
+
7
+## Follow Graph
8
+
9
+`follows` stores one follower user and exactly one target:
10
+
11
+- `followee_user_id` for user profiles.
12
+- `followee_org_id` for organization profiles.
13
+
14
+The schema enforces target XOR, blocks user self-follows, cascades on
15
+deleted users/orgs, and uses partial unique indexes so follow/unfollow is
16
+idempotent. State changes go through `internal/social` and record audit
17
+rows when an audit recorder is supplied.
18
+
19
+Follow actions emit public user-scoped `domain_events`:
20
+
21
+- `followed_user`, `source_kind = "user"`, `source_id = target_user_id`
22
+- `followed_org`, `source_kind = "org"`, `source_id = target_org_id`
23
+
24
+The web layer exposes profile/org Follow buttons and follower/following
25
+tabs. Suspended actors are rejected by middleware/policy before mutation.
26
+
27
+## Feeds
28
+
29
+Feeds read from `domain_events`; handlers never hand-roll visibility
30
+logic. Public feeds require `domain_events.public = true`, non-deleted
31
+actors, non-suspended actors, and a current public repo if the event is
32
+repo-scoped. This second repo visibility check is load-bearing: an event
33
+emitted while a repo was public must not leak after the repo becomes
34
+private.
35
+
36
+The authenticated Home feed includes:
37
+
38
+- the viewer's own public activity,
39
+- public activity from followed users,
40
+- public activity from repos the viewer watches,
41
+- public activity from repos owned by followed orgs,
42
+- public org-scoped activity for followed orgs.
43
+
44
+Explore uses the global public feed. Both feeds page with a keyset
45
+cursor over `(created_at, id)`.
46
+
47
+## Event Kinds
48
+
49
+Current feed sources include:
50
+
51
+- `repo_created`
52
+- `push`
53
+- `star` / `unstar`
54
+- `forked`
55
+- `issue_created`, comments, close/reopen, assignment events
56
+- `pr_opened` and pull-request comment events
57
+- `followed_user` / `followed_org`
58
+
59
+The `kind` and `source_kind` columns remain text. New product surfaces
60
+can add events without a schema migration as long as their payload is
61
+small JSON and the public flag is set conservatively.
62
+
63
+## Trending
64
+
65
+`trending_snapshots` stores denormalized rankings for day/week/month
66
+windows and two kinds:
67
+
68
+- `repos`
69
+- `users`
70
+
71
+The `trending:compute` worker job captures all six snapshots. A job with
72
+an empty payload schedules its next run one hour later; pass
73
+`{"schedule_next":false}` for a one-off recompute. Explore reads the
74
+latest weekly snapshot and falls back to live computation before the
75
+first worker run.
76
+
77
+The repo score weights recent public stars, forks, and unique push
78
+actors. The user score weights recent followers plus recent public event
79
+activity.
80
+
81
+## Operational Notes
82
+
83
+Seed the recurring job once after deploy:
84
+
85
+```sql
86
+INSERT INTO jobs (kind, payload) VALUES ('trending:compute', '{}');
87
+SELECT pg_notify('shithub_jobs', '');
88
+```
89
+
90
+The job is safe to re-run. Multiple recurring seeds produce multiple
91
+hourly refresh jobs, so operators should keep one scheduled chain per
92
+instance unless they intentionally want a shorter effective interval.
docs/internal/stars-watchers.mdmodified
@@ -84,12 +84,12 @@ incrementally; we don't want a migration per kind.
8484
 
8585
 ### Public-flag policy
8686
 
87
-* Public-repo events → `public = true`. Activity feed (post-MVP)
88
-  surfaces these in public timelines.
87
+* Public-repo events → `public = true`. S42's Home and Explore feeds
88
+  surface these in authenticated and public timelines.
8989
 * Private-repo events → `public = false`. Visible only to repo
9090
   collaborators via per-row visibility checks at read time.
91
-* User-scoped events (e.g. profile-edit, post-MVP) → follows the
92
-  user's profile-visibility setting.
91
+* User-scoped events (e.g. follow events) → public only when the action
92
+  is safe to expose on the public timeline.
9393
 
9494
 The handler/orchestrator is the source of truth for the public flag
9595
 — `social.Star` reads `repo.visibility` and passes the bool through.
@@ -128,8 +128,8 @@ manipulation.
128128
   both.
129129
 * **S32 (settings UI):** wire `AutoWatchOnCollab` into the
130130
   collaborator-add path.
131
-* **Activity feed (post-MVP):** read public events sliced by repo
132
-  or by actor.
131
+* **S42 (social feed):** read public events for Home/Explore and cached
132
+  trending rankings.
133133
 
134134
 ## What S11 promised but didn't ship
135135
 
docs/internal/worker.mdmodified
@@ -35,6 +35,7 @@ backstop poll (every 5s by default) covers dropped notifications.
3535
 | `push:process`         | post-receive hook per ref            | `push_events.processed_at` |
3636
 | `repo:size_recalc`     | enqueued by `push:process`           | overwrite-last-wins        |
3737
 | `jobs:purge_completed` | future cron / manual ad-hoc          | always safe to re-run      |
38
+| `trending:compute`     | recurring self-scheduled S42 job     | append-only snapshots      |
3839
 
3940
 Adding a new kind: write the handler in `internal/worker/jobs/<kind>.go`,
4041
 add the `Kind` constant to `internal/worker/types.go`, register it in