Skip to content

feat: watch-to-post reciprocity pacing (replaces clout)#182

Merged
GraysonCAdams merged 11 commits into
mainfrom
feat/reciprocity-pacing
May 29, 2026
Merged

feat: watch-to-post reciprocity pacing (replaces clout)#182
GraysonCAdams merged 11 commits into
mainfrom
feat/reciprocity-pacing

Conversation

@GraysonCAdams
Copy link
Copy Markdown
Collaborator

What & why

The feed could be flooded by one prolific poster while everyone else fell behind. This replaces all prior pacing (clout reputation, daily-cap, and the burst/cooldown queue timer) with a single watch-to-post credit economy:

  • Watching another member's clip earns a credit; posting a clip spends one.
  • A clip posts straight to the feed only when you have a credit and an empty queue; otherwise it waits in an unlimited personal queue until you spend a credit to post it (multi-select "Post N" in the queue sheet).
  • Conservation law: group-wide, total posts can't exceed total watches-of-others — so a noisy poster's ceiling becomes everyone else's output.
  • Soft feed gate: at the credit cap with a non-empty queue, forward scroll is blocked until you post; an empty queue never gates.

Anti-flood guards

  • Launch cutoff (reciprocity_started_at): only clips created at/after rollout earn credits, so the existing backlog stays watchable but can't be farmed into a flood of credits. Backfilled to deploy time on first boot.
  • Daily post cap (daily_post_limit, default off): optional rolling-24h hard ceiling on top of credits; over-cap posts are held in the queue rather than rejected.

Host config

seed / cap / ratio / dailyPostLimit are tunable per group via Settings → Share Pacing and PATCH /api/group/reciprocity.

Removed

The clout system (module, /api/clout, tier modals/components, columns), the daily_cap/queue pacing modes and their endpoints/pickers, the burst/cooldown machinery, and the 30s auto-publish scheduler timer.

Test plan

  • npm run check — 0 errors
  • npm test — 478 unit/API tests pass
  • npm run test:e2e (desktop) — reciprocity credit-lifecycle, multi-select post, queue, group-host all green
  • Migrations 0033–0035 replay cleanly; cutoff backfill verified

groups gains reciprocity_seed/cap/ratio and drops daily_share_limit,
share_pacing_mode, share_burst, share_cooldown_minutes, clout_enabled.
users gains post_credits and drops the clout_* columns. clip_queue drops
position/scheduled_at (the queue is now an unscheduled holding area).
New credits.ts owns the atomic earn/spend primitives, group config lookup,
and ratio-gated watch crediting. New members are seeded with the group's
configured starting credits at all four user-creation sites.
Rewrites queue.ts into an unlimited holding area: enqueue, publishQueuedClip
(now async + notifies), postQueuedClips (multi-select, spends a credit each),
and setClipReady (direct-post spends at publish, degrades to queue if drained).
Adds POST /api/queue/post, paginates GET /api/queue, and removes the
reorder + move-to-top routes. Deletes the 30s auto-publish scheduler timer so
nothing leaves the queue except a deliberate credit spend. Failed downloads
clean up their orphan queue row.
POST /api/clips and /api/clips/share post directly when the uploader has a
credit and an empty queue, else enqueue (credit spent at publish). The watched
handler earns a credit on the first watch of another member's clip and returns
the new balance.
Adds groups.reciprocity_started_at: only clips created at/after the cutoff earn
watch credits. Pre-cutoff backlog clips stay fully watchable but pay out zero,
so rolling out reciprocity over an existing feed can't convert the backlog into
a flood of posting credits. The cutoff is stamped at group creation and
backfilled to deploy time for the existing group on first boot after migration.
grantWatchCredit gates on the clip's created_at and only advances the ratio
counter on eligible (post-cutoff, non-self) watches. Self-watches and rewatches
still earn nothing.
Deletes the clout module/API/components/tests, the share-limit module, and the
share-pacing + daily-share-limit endpoints and their pickers. Settings now uses
ReciprocityPicker; the root layout drops CloutChangeModal; the app layout drops
the clout-tier poll and seeds the credit store + queue count; the me header
shows credit dots instead of a rank badge. Adds the credit store and the
CreditDots/CreditInfoModal primitives.
markClipWatched syncs the returned credit balance into the store. The feed
blocks forward advance (scrollToIndex, wheel, keyboard, native momentum via a
scroll-lock class) once the user is at the credit cap with a non-empty queue,
opening the post sheet instead. An empty queue never gates.
QueueSheet becomes a multi-select post surface (sort/reverse, lazy-load,
tap-to-explain credits pill, Post N) using SvelteSet for selection state.
AddVideo/AddVideoModal show the credit dots and a posts-now-vs-queued hint and
sync the returned balance. The share landing page is simplified to
posted/queued states. UploadStatus drops the daily-limit dots and timed copy.
Adds the reciprocity settings endpoint and its API test; rewrites the queue
unit/API tests for the holding-queue model + post endpoint; adds clips
direct-vs-queue and watch-to-earn cases; drops the clout/pacing test blocks.
Updates api.md + architecture.md, e2e fixtures (reciprocity seed/cap/ratio),
the queue-interactions spec (multi-select post), and adds the reciprocity
credit-lifecycle e2e spec.
Adds groups.daily_post_limit (null = unlimited, default off): a rolling-24h
ceiling on posts per member, layered on top of credits. Enforced at the share
decision, at publish (setClipReady degrades over-cap posts to the queue), and
when posting from the queue. Configurable via the reciprocity settings picker
and PATCH /api/group/reciprocity.
The requestfailed filter checked for capital 'Cancelled' but WebKit reports
lowercase 'Load request cancelled', so navigation-cancelled video/font loads
leaked through and flaked back-gestures on mobile-ios only. Match
case-insensitively and on both spellings.
@GraysonCAdams GraysonCAdams merged commit 07713b6 into main May 29, 2026
20 of 23 checks passed
@GraysonCAdams GraysonCAdams deleted the feat/reciprocity-pacing branch May 29, 2026 19:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants