Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions KNOWLEDGE_BASE.md
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ Sources: [badge definitions](app/Enums/Achievement.php), [unlock conditions and

Follow or unfollow from a developer's profile or a follow control elsewhere in the app. Following puts their commits in your Following log. Following or unfollowing from a log updates the controls, counts, and live-following sidebar without navigating away: Latest and Trending keep their loaded commits and ranking, while Following updates its commits and empty-state suggestions. You cannot follow yourself or follow across a block. GitHub follows are imported once: connections to developers already on /dev/null are added in both directions, with up to 1,000 GitHub connections checked each way. This is not continuous synchronization, so unfollowing someone on /dev/null sticks.

**Who to follow** suggests developers in the side column or within the log on smaller screens, each with the first line of their bio and why they are suggested (how many people you follow follow them, or their follower count). Follow from the icon next to each suggestion; for someone who already follows you, it follows back. The X icon marks a suggestion **Not interested**: that developer is never suggested to you again, without muting them, and the next suggestion takes their place. Refresh for different suggestions, or use **… → Don't show again** to hide the block, with an immediate **Undo** option. Show or hide these suggestions in Settings → Appearance. Suggested developers must have a visible top-level commit. On profiles, suggestions prioritize the people that developer follows; on commit pages, they prioritize visible thread participants. Regular suggestions fill any remaining places. You can also follow back from the notifications list.
**Who to follow** suggests developers in the side column or within the log on smaller screens, each with the first line of their bio and why they are suggested (how many people you follow follow them, or their follower count). Follow from the icon next to each suggestion; for someone who already follows you, it follows back. The X icon marks a suggestion **Not interested**: that developer is never suggested to you again, without muting them, and the next suggestion takes their place. Refresh for different suggestions. They also move on to other people every minute, but not while they're out of view, you're pointing at them or moving through them with the keyboard, or the tab is in the background; turn that off with the **… → Refresh automatically** switch. Use **… → Don't show again** to hide the block, with an immediate **Undo** option. Choose 1, 3 (the default) or 5 suggestions, or hide them, in Settings → Appearance. Suggested developers must have a visible top-level commit. On profiles, suggestions prioritize the people that developer follows; on commit pages, they prioritize visible thread participants. Regular suggestions fill any remaining places. You can also follow back from the notifications list.

Sources: [follow import](app/Actions/CreateUserGitHubFollows.php), [suggestions](resources/js/components/who-to-follow.tsx), [suggestion eligibility](app/Queries/SuggestedUsersQuery.php), [appearance settings](resources/js/pages/appearance/update.tsx), [notifications page](resources/js/pages/notification/index.tsx).

Expand Down Expand Up @@ -410,7 +410,7 @@ Settings → Appearance offers:
- **Navigation:** expanded or collapsed, on wide screens. The navigation also has its own collapse button.
- **Lane dimming:** on by default; other commits fade while the pointer rests on a commit or its author.
- **Replies:** how many replies show under a commit before "Show more": 1, 3, 5, 10, 15, 20, or 25 (3 by default).
- **Who to follow:** show or hide the suggestions.
- **Who to follow:** show 1, 3, or 5 suggestions, or hide them.

These appearance choices are saved in the browser, not on the account, so each browser keeps its own. Settings → Profile lets you choose one of six lane colors (Orange, Green, Violet, Teal, Rose, Ochre), a custom color, or the automatic color used around your identity and activity graph.

Expand Down
22 changes: 22 additions & 0 deletions app/Enums/WhoToFollowCount.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

declare(strict_types=1);

namespace App\Enums;

use Illuminate\Http\Request;

enum WhoToFollowCount: int
{
case One = 1;
case Three = 3;
case Five = 5;

/**
* Kept in a cookie, like whether to show the suggestions at all, so each browser keeps its own.
*/
public static function of(Request $request): self
{
return array_find(self::cases(), fn (self $count): bool => (string) $count->value === $request->cookie('who_to_follow_count')) ?? self::Three;
}
}
4 changes: 4 additions & 0 deletions app/Http/Middleware/HandleInertiaRequests.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use App\Dto\ReaderClock;
use App\Enums\Log;
use App\Enums\RepliesPerPage;
use App\Enums\WhoToFollowCount;
use App\Http\Requests\ListNotificationsRequest;
use App\Models\Commit;
use App\Models\User;
Expand Down Expand Up @@ -79,6 +80,7 @@ public function share(Request $request): array
$suggestedUsers = $user instanceof User && $showsWhoToFollow && $hasAside
? Inertia::defer(fn (): Collection => resolve(SuggestedUsersQuery::class)->get(
$user,
limit: WhoToFollowCount::of($request)->value,
round: max(0, (int) $request->cookie('who_to_follow_round')),
spares: 3,
context: $context,
Expand Down Expand Up @@ -113,6 +115,8 @@ public function share(Request $request): array
'hasUnmoderatedCommits' => fn (): bool => $isAdmin && Commit::query()->pending()->exists(),
'sidebarOpen' => ! $request->hasCookie('sidebar_state') || $request->cookie('sidebar_state') === 'true',
'showsWhoToFollow' => $showsWhoToFollow,
'whoToFollowCount' => WhoToFollowCount::of($request)->value,
'refreshesWhoToFollow' => $request->cookie('who_to_follow_auto_refresh') !== 'false',
'liveFollowing' => fn (): Collection => $user instanceof User && $hasAside
? resolve(LiveFollowingQuery::class)->get($user)
: collect(),
Expand Down
3 changes: 2 additions & 1 deletion app/Queries/SuggestedUsersQuery.php
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,8 @@ private function pages(Randomizer $randomizer, Collection $network, Collection $
$pages = [];

do {
$networkSlots = $prioritizeNetwork ? $limit : $limit - $randomizer->getInt(1, max(1, $limit - 1));
// A single slot goes to either pool, so one suggestion isn't always someone popular.
$networkSlots = $prioritizeNetwork ? $limit : $limit - $randomizer->getInt($limit > 1 ? 1 : 0, max(1, $limit - 1));
$popular ??= new Collection($randomizer->shuffleArray($popularPool->all()));

$pageNetwork = $network->take(max($networkSlots, $limit - $popular->count()));
Expand Down
2 changes: 1 addition & 1 deletion bootstrap/app.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
$middleware->prepend(StripNullBytes::class);
$middleware->append(PreventFraming::class);

$middleware->encryptCookies(except: ['appearance', 'sidebar_state', 'who_to_follow', 'who_to_follow_round', 'lane_dimming', 'replies_per_page', 'time_zone', 'hour_cycle']);
$middleware->encryptCookies(except: ['appearance', 'sidebar_state', 'who_to_follow', 'who_to_follow_round', 'who_to_follow_count', 'who_to_follow_auto_refresh', 'lane_dimming', 'replies_per_page', 'time_zone', 'hour_cycle']);

$middleware->redirectGuestsTo(fn (): string => route('home'));
$middleware->redirectUsersTo(fn (Request $request): string => route('log.show', $request->user()->log ?? Log::Following));
Expand Down
37 changes: 29 additions & 8 deletions resources/js/components/who-to-follow-tabs.tsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,46 @@
import type { LucideIcon } from 'lucide-react';
import { Eye, EyeOff } from 'lucide-react';
import { EyeOff } from 'lucide-react';
import { useState } from 'react';
import { SegmentedControl } from '@/components/segmented-control';
import { useWhoToFollow } from '@/hooks/use-who-to-follow';
import { withViewTransition } from '@/lib/utils';

const counts = [1, 3, 5];

/**
* Hide stands for 0, so hiding keeps the count chosen before for when the suggestions come back. Hiding or bringing
* them back from the block itself moves the selection along.
*/
export default function WhoToFollowTabs() {
const { showsWhoToFollow, setShowsWhoToFollow } = useWhoToFollow();
const [selected, setSelected] = useState(showsWhoToFollow);
const {
showsWhoToFollow,
setShowsWhoToFollow,
whoToFollowCount,
setWhoToFollowCount,
} = useWhoToFollow();
const shown = showsWhoToFollow ? whoToFollowCount : 0;
const [selected, setSelected] = useState(shown);
const [lastShown, setLastShown] = useState(shown);

if (shown !== lastShown) {
setLastShown(shown);
setSelected(shown);
}

const tabs: { value: boolean; icon: LucideIcon; label: string }[] = [
{ value: true, icon: Eye, label: 'Show' },
{ value: false, icon: EyeOff, label: 'Hide' },
const tabs: { value: number; icon?: LucideIcon; label: string }[] = [
{ value: 0, icon: EyeOff, label: 'Hide' },
...counts.map((count) => ({ value: count, label: String(count) })),
];

const select = (value: boolean): void => {
const select = (value: number): void => {
if (value === selected) {
return;
}

void withViewTransition(() => setSelected(value)).then(() =>
setShowsWhoToFollow(value),
value === 0
? setShowsWhoToFollow(false)
: setWhoToFollowCount(value),
);
};

Expand All @@ -31,6 +51,7 @@ export default function WhoToFollowTabs() {
options={tabs}
value={selected}
onChange={select}
className="tabular-nums"
/>
);
}
Loading