Skip to content
Merged
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
2 changes: 1 addition & 1 deletion DESIGN.md
Original file line number Diff line number Diff line change
Expand Up @@ -339,7 +339,7 @@ A deleted commit that still has visible replies keeps its place so the conversat
A quote is a regular commit row with the quoted commit embedded under its body: a hairline-bordered, 8px-rounded block (like a link preview, no lane color) with a 20px avatar, name (600), mono handle, and mono relative time on one line, then the body at 15px, pre-wrapped and clamped to six lines. The whole block is one link to the quoted commit's page and tints Accent on hover/focus; it embeds one level only. A deleted quoted commit is a dashed block with a mono `[deleted]` token and "This commit was deleted."; one the viewer cannot see reads `[unavailable]` and "This commit isn't available.", with no author, time, or body. "Quote" sits in the action row after the star (icon only on phones) and opens a dialog titled "Quote @handle": the viewer's avatar, a 17px textarea ("Add your take") with its own stash, the quoted block as a static preview, a counter, Cancel, and Quote (⌘/Ctrl+Enter). It is hidden wherever the star is.

### Commit Page
Every commit has a page at `/@username/commits/{id}`, reached from its relative time or short hash (both links), the "↳ replying to @handle" label, or Enter on a focused commit. A sticky header reads "Commit" with the mono short hash and a copy-link icon on the right. Visible ancestors (up to 20) stack above as regular commit rows on one continuous rail, without delete, with deleted ones as tombstones; the focused commit sits last with a 17px body, a mono "time · date · commit hash" line, and its replies already expanded. A deleted commit's own page is a 404.
Every commit has a page at `/@handle/commits/{id}`, reached from its relative time or short hash (both links), the "↳ replying to @handle" label, or Enter on a focused commit. A sticky header reads "Commit" with the mono short hash and a copy-link icon on the right. Visible ancestors (up to 20) stack above as regular commit rows on one continuous rail, without delete, with deleted ones as tombstones; the focused commit sits last with a 17px body, a mono "time · date · commit hash" line, and its replies already expanded. A deleted commit's own page is a 404.

### People Search
A command palette opened by ⌘K / Ctrl+K or `/` anywhere, the "Search developers ⌘K" control at the top of the right column, a Search nav item where that column is hidden, or the search icon in the mobile header. Popover surface, 576px max, 12vh from the top: a borderless 17px input with a search icon and an `esc` cap, results as rows (36px avatar, name in 600, mono handle with lane dot, truncated bio) with the active row on Accent and a `↵` hint, and a footer of key hints (hidden on phones). Combobox semantics: arrow keys move, Enter opens the profile, Escape closes. Empty: "Type a name or @handle."; pending: "Searching…"; no match: mono `fatal: no developers match '…'`.
Expand Down
8 changes: 4 additions & 4 deletions KNOWLEDGE_BASE.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ Sources: [onboarding page](resources/js/pages/onboarding/show.tsx), [sign-in tes

### Profiles and GitHub sync

Every developer has a profile at `/@{username}`, with their GitHub identity, bio and other public profile information, commit/following/follower counts, a **Follows you** badge when they follow you, and an activity graph. Across a block in either direction, the profile is shown as unavailable. The Commits tab lists top-level commits; Replies lists that developer's replies with parent context; README shows the README of their GitHub profile repository (the one named after their username), as GitHub renders it, refreshed daily. It keeps only plain formatting, links, and images hosted by GitHub: no scripts, styles, or classes. When GitHub doesn't answer, the tab says so rather than showing no README. Follow and follower counts open the corresponding people lists. The profile links to GitHub and, when available, GitHub Sponsors. Share uses the device's share sheet when supported, or copies the profile link.
Every developer has a profile at `/@{handle}`, with their GitHub identity, bio and other public profile information, commit/following/follower counts, a **Follows you** badge when they follow you, and an activity graph. Across a block in either direction, the profile is shown as unavailable. The Commits tab lists top-level commits; Replies lists that developer's replies with parent context; README shows the README of their GitHub profile repository (the one named after their username), as GitHub renders it, refreshed daily. It keeps only plain formatting, links, and images hosted by GitHub: no scripts, styles, or classes. When GitHub doesn't answer, the tab says so rather than showing no README. Follow and follower counts open the corresponding people lists. The profile links to GitHub and, when available, GitHub Sponsors. Share uses the device's share sheet when supported, or copies the profile link.

Change your name, avatar, bio, company, location, or website on GitHub, not on /dev/null. Settings → Profile and the **Edit profile** menu on your own profile offer **Edit on GitHub** and **Sync from GitHub**. Profile details also refresh on sign-in. Manual sync is limited to once a minute. The GitHub Sponsors link is refreshed on sign-in only. A GitHub username rename is picked up on sign-in or sync; if a stale account still holds the new username because its owner also renamed, that account moves to a `_renamed-<github id>` placeholder until its owner signs in or syncs again. Accounts are matched by GitHub id and never merged.

Expand Down Expand Up @@ -420,7 +420,7 @@ Sources: [web app manifest](public/site.webmanifest), [service worker](public/sw

### Activity graphs and README embeds

Profiles display commit activity with current and longest streaks. The profile graph adapts to how long someone has been committing: the last 24 hours, 7 days, 30 days, or a year. Hovering a cell highlights that period's commits. Settings → Profile includes a live preview and copyable Markdown for embedding your /dev/null activity graph in a GitHub profile README. The graph is public at `/@{username}/graph.svg`, uses your lane color, and supports `?theme=light`, `dark`, or `auto` (light by default). The SVG is cached for an hour, so a new commit may not appear in an external embed immediately.
Profiles display commit activity with current and longest streaks. The profile graph adapts to how long someone has been committing: the last 24 hours, 7 days, 30 days, or a year. Hovering a cell highlights that period's commits. Settings → Profile includes a live preview and copyable Markdown for embedding your /dev/null activity graph in a GitHub profile README. The graph is public at `/@{handle}/graph.svg`, uses your lane color, and supports `?theme=light`, `dark`, or `auto` (light by default). The SVG is cached for an hour, so a new commit may not appear in an external embed immediately.

Sources: [graph embed settings](resources/js/components/activity-graph-embed.tsx), [graph controller](app/Http/Controllers/UserActivityGraphController.php), [graph route and cache](routes/web.php).

Expand Down Expand Up @@ -462,9 +462,9 @@ Sources: [workflow instructions](resources/js/pages/terminal/show.tsx), [GitHub

### Public read API and Atom feeds

The read-only JSON API at `/api/v1` requires no authentication or token. Read a public profile at `/api/v1/users/{username}`, that user's commits at `/api/v1/users/{username}/commits`, or one commit at `/api/v1/commits/{commit}`. `/api/v1/users/{username}/followers` and `/following` list people the way the profile's people lists do, newest follow first; a signed request also gets each person's lane and whether you follow each other. Add `?replies=1` to the user-commits endpoint to read their replies instead of top-level commits. Lists use cursor pagination: 20 commits or 30 people per page. Only public approved, non-deleted content is returned, not private profile data such as email. Unsigned requests are limited to **60 requests per minute per IP** and cannot create posts, follow, or star; terminal posting and the [signed API](#signed-api-and-devices) are separate signed interfaces.
The read-only JSON API at `/api/v1` requires no authentication or token. Read a public profile at `/api/v1/users/{handle}`, that user's commits at `/api/v1/users/{handle}/commits`, or one commit at `/api/v1/commits/{commit}`. `/api/v1/users/{handle}/followers` and `/following` list people the way the profile's people lists do, newest follow first; a signed request also gets each person's lane and whether you follow each other. Add `?replies=1` to the user-commits endpoint to read their replies instead of top-level commits. Lists use cursor pagination: 20 commits or 30 people per page. Only public approved, non-deleted content is returned, not private profile data such as email. Unsigned requests are limited to **60 requests per minute per IP** and cannot create posts, follow, or star; terminal posting and the [signed API](#signed-api-and-devices) are separate signed interfaces.

Subscribe in a feed reader to `/@{username}/feed.atom` for a profile's latest **50 approved top-level commits**, including image enclosures. Feeds are public and can be cached for five minutes.
Subscribe in a feed reader to `/@{handle}/feed.atom` for a profile's latest **50 approved top-level commits**, including image enclosures. Feeds are public and can be cached for five minutes.

Sources: [API routes](routes/api.php), [API controllers](app/Http/Controllers/Api), [feed generation](app/Actions/CreateUserFeed.php), [rate limits](app/Providers/AppServiceProvider.php).

Expand Down
2 changes: 1 addition & 1 deletion PRODUCT.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ Deliberately excluded: sign-up or identity outside GitHub, editing commits, and
- Voice: dry, terminal-literate humor. Understated wit; copy may wink at Unix and `/dev/null`, but stays clear and never obscures what an action does.
- Lore is part of the product: Tom (everyone's first friend, after MySpace Tom), pastéis (a tribute to the pastéis de nata brought to the livestream), @world, the Unix-named achievements. Extend it with restraint, never at the expense of clarity. The stories behind it are in the knowledge base.
- Current tagline in use: "A social network for developers."
- Vocabulary: a post is a **commit** and a timeline is a **log**, in the UI and in code (`Commit` model, `LogController`, `/log/{log}`). A GitHub account mirrored at its `/@handle` is a **profile** (`Profile` model, as GitHub's `ProfileOwner`), and the unique name GitHub calls its login is its **handle** in code; the API also sends it as `username` for older clients. The git metaphor stops at those two nouns, except inside Remotes, which carries its own small vocabulary: a conversation is a **remote**, sending is a **push**, a read receipt is **pulled**, a request is **fetched** until **merged** or **dropped**, archiving is **stash** and **pop**, a group is an **org** of **maintainers** and **contributors**, and **HEAD** marks where you left off. No branches, forks, diffs, or PRs. **Star** is the engagement verb, not a git ref.
- Vocabulary: a post is a **commit** and a timeline is a **log**, in the UI and in code (`Commit` model, `LogController`, `/log/{log}`). A GitHub account mirrored at its `/@handle` is a **profile** (`Profile` model, as GitHub's `ProfileOwner`), and the unique name GitHub calls its login is its **handle** in code and in the API. The git metaphor stops at those two nouns, except inside Remotes, which carries its own small vocabulary: a conversation is a **remote**, sending is a **push**, a read receipt is **pulled**, a request is **fetched** until **merged** or **dropped**, archiving is **stash** and **pop**, a group is an **org** of **maintainers** and **contributors**, and **HEAD** marks where you left off. No branches, forks, diffs, or PRs. **Star** is the engagement verb, not a git ref.

## Evidence on Hand

Expand Down
8 changes: 4 additions & 4 deletions app/Http/Requests/ListApiSuggestedUsersRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@

/**
* Who to follow may be asked for the page it shows on, as the web asks for it beside a profile or a commit. The commit
* has to be one the user can see, as its page requires. The API calls a handle a username, as in its user resources.
* has to be one the user can see, as its page requires.
*/
final class ListApiSuggestedUsersRequest extends FormRequest
{
Expand All @@ -29,8 +29,8 @@ public function rules(): array

return [
'round' => ['nullable', 'integer', 'min:0'],
'username' => ['nullable', 'string', Rule::exists('profiles', 'handle')->where('type', ProfileType::User->value)],
'commit_id' => ['bail', 'nullable', 'uuid', 'prohibits:username', new VisibleCommit($user, __('That commit is no longer available.'))],
'handle' => ['nullable', 'string', Rule::exists('profiles', 'handle')->where('type', ProfileType::User->value)],
'commit_id' => ['bail', 'nullable', 'uuid', 'prohibits:handle', new VisibleCommit($user, __('That commit is no longer available.'))],
];
}

Expand All @@ -40,7 +40,7 @@ public function context(): User|Commit|null
assert($user instanceof User);

return match (true) {
$this->filled('username') => User::query()->whereRelation('profile', 'handle', $this->string('username')->value())->firstOrFail(),
$this->filled('handle') => User::query()->whereRelation('profile', 'handle', $this->string('handle')->value())->firstOrFail(),
$this->filled('commit_id') => Commit::query()->visibleTo($user)->findOrFail($this->string('commit_id')->value()),
default => null,
};
Expand Down
1 change: 0 additions & 1 deletion app/Http/Resources/UserResource.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ public function toArray(Request $request): array
return [
'id' => $this->id,
'handle' => $this->handle,
'username' => $this->handle,
'name' => $this->name,
'avatar_url' => $this->avatar_url,
'bio' => $this->bio,
Expand Down
5 changes: 2 additions & 3 deletions app/Services/Mentions.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,9 @@ public function handles(string $text): array
/**
* Finds where each "@handle" is in the text, by the same rules and also skipping code, so a client can link it
* to the profile like the web does. The handle keeps its case, and the offset and length count characters in
* the whole text and include the "@". The handle is also the username, which clients read before handles.
* the whole text and include the "@".
*
* @return list<array{handle: string, username: string, offset: int, length: int}>
* @return list<array{handle: string, offset: int, length: int}>
*/
public function positions(string $text): array
{
Expand All @@ -52,7 +52,6 @@ public function positions(string $text): array
foreach ($matches as $match) {
$positions[] = [
'handle' => $match[1][0],
'username' => $match[1][0],
'offset' => $offset + mb_strlen(mb_strcut($part, 0, $match[0][1])),
'length' => mb_strlen($match[0][0]),
];
Expand Down
4 changes: 1 addition & 3 deletions tests/Feature/Controllers/Api/CommitControllerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@
'author' => [
'id' => $user->id,
'handle' => 'octocat',
'username' => 'octocat',
'name' => 'The Octocat',
'avatar_url' => 'https://avatars.githubusercontent.com/u/583231',
'bio' => $user->bio,
Expand Down Expand Up @@ -222,7 +221,6 @@
$author = fn (User $author): array => [
'id' => $author->id,
'handle' => $author->handle,
'username' => $author->handle,
'name' => $author->name,
'avatar_url' => $author->avatar_url,
'bio' => $author->bio,
Expand Down Expand Up @@ -257,7 +255,7 @@
['type' => 'code', 'code' => '@Override'],
],
'preview_segments' => null,
'mentions' => [['handle' => 'nunomaduro', 'username' => 'nunomaduro', 'offset' => 4, 'length' => 11]],
'mentions' => [['handle' => 'nunomaduro', 'offset' => 4, 'length' => 11]],
'status' => 'approved',
'is_pinned' => false,
'starred' => true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
['type' => 'code', 'code' => 'the PR'],
['type' => 'text', 'text' => ', @ghost? https://laravel.com'],
])
->assertJsonPath('data.messages.0.mentions', [['handle' => 'octocat', 'username' => 'octocat', 'offset' => 4, 'length' => 8]])
->assertJsonPath('data.messages.0.mentions', [['handle' => 'octocat', 'offset' => 4, 'length' => 8]])
->assertJsonPath('data.messages.0.link_preview.url', 'https://laravel.com')
->assertJsonPath('data.messages.0.link_preview.is_pending', true)
->assertJsonPath('data.messages.0.reply_to', null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@
'data' => [
'id' => $user->id,
'handle' => 'octocat',
'username' => 'octocat',
'name' => 'The Octocat',
'avatar_url' => 'https://avatars.githubusercontent.com/u/583231',
'bio' => 'I am the Octocat.',
Expand Down
3 changes: 1 addition & 2 deletions tests/Feature/Controllers/Api/LogControllerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,6 @@
'author' => [
'id' => $author->id,
'handle' => 'octocat',
'username' => 'octocat',
'name' => 'The Octocat',
'avatar_url' => 'https://avatars.githubusercontent.com/u/583231',
'bio' => $author->bio,
Expand Down Expand Up @@ -119,7 +118,7 @@
->assertJsonPath('data.0.stars_count', 2)
->assertJsonPath('data.0.starred', true)
->assertJsonPath('data.0.is_pinned', true)
->assertJsonPath('data.0.mentions', [['handle' => 'NunoMaduro', 'username' => 'NunoMaduro', 'offset' => 4, 'length' => 11]])
->assertJsonPath('data.0.mentions', [['handle' => 'NunoMaduro', 'offset' => 4, 'length' => 11]])
->assertJsonPath('data.0.link_preview', [
'url' => 'https://laravel.com',
'title' => $linkPreview->title,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@
$user = fn (User $user): array => [
'id' => $user->id,
'handle' => $user->handle,
'username' => $user->handle,
'name' => $user->name,
'avatar_url' => $user->avatar_url,
'bio' => $user->bio,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@
'user' => [
'id' => $nuno->id,
'handle' => 'nunomaduro',
'username' => 'nunomaduro',
'name' => $nuno->name,
'avatar_url' => $nuno->avatar_url,
'bio' => 'Pest and Pint.',
Expand Down
Loading
Loading