a love letter to tangled (android, iOS, and a search API)

feat: domain models & mock data

+561 -11
+11 -11
docs/tasks/phase-1.md
··· 18 18 19 19 ## Domain Models 20 20 21 - - [ ] Create `domain/models/user.ts` — `UserSummary` type 22 - - [ ] Create `domain/models/repo.ts` — `RepoSummary`, `RepoDetail`, `RepoFile` types 23 - - [ ] Create `domain/models/pull-request.ts` — `PullRequestSummary` type 24 - - [ ] Create `domain/models/issue.ts` — `IssueSummary` type 25 - - [ ] Create `domain/models/activity.ts` — `ActivityItem` type 21 + - [x] Create `domain/models/user.ts` — `UserSummary` type 22 + - [x] Create `domain/models/repo.ts` — `RepoSummary`, `RepoDetail`, `RepoFile` types 23 + - [x] Create `domain/models/pull-request.ts` — `PullRequestSummary` type 24 + - [x] Create `domain/models/issue.ts` — `IssueSummary` type 25 + - [x] Create `domain/models/activity.ts` — `ActivityItem` type 26 26 27 27 ## Mock Data 28 28 29 - - [ ] Use realistic data: fetch `desertthunder.dev` to create mock data for repo names, timestamps within last 30 days 30 - - [ ] Create `src/mocks/users.ts` — factory for `UserSummary` instances 31 - - [ ] Create `src/mocks/repos.ts` — factory for `RepoSummary` and `RepoDetail` instances 32 - - [ ] Create `src/mocks/pull-requests.ts` — factory for `PullRequestSummary` instances 33 - - [ ] Create `src/mocks/issues.ts` — factory for `IssueSummary` instances 34 - - [ ] Create `src/mocks/activity.ts` — factory for `ActivityItem` instances 29 + - [x] Use realistic data: fetch `desertthunder.dev` to create mock data for repo names, timestamps within last 30 days 30 + - [x] Create `src/mocks/users.ts` — factory for `UserSummary` instances 31 + - [x] Create `src/mocks/repos.ts` — factory for `RepoSummary` and `RepoDetail` instances 32 + - [x] Create `src/mocks/pull-requests.ts` — factory for `PullRequestSummary` instances 33 + - [x] Create `src/mocks/issues.ts` — factory for `IssueSummary` instances 34 + - [x] Create `src/mocks/activity.ts` — factory for `ActivityItem` instances 35 35 36 36 ## Design System Components 37 37
src/domain/models/.gitkeep

This is a binary file and will not be displayed.

+18
src/domain/models/activity.ts
··· 1 + type ItemKind = 2 + | "repo_created" 3 + | "repo_starred" 4 + | "user_followed" 5 + | "pr_opened" 6 + | "pr_merged" 7 + | "issue_opened" 8 + | "issue_closed"; 9 + 10 + export type ActivityItem = { 11 + id: string; 12 + kind: ItemKind; 13 + actorDid: string; 14 + actorHandle: string; 15 + targetUri?: string; 16 + targetName?: string; 17 + createdAt: string; 18 + };
+11
src/domain/models/issue.ts
··· 1 + type IssueState = "open" | "closed"; 2 + 3 + export type IssueSummary = { 4 + atUri: string; 5 + title: string; 6 + authorDid: string; 7 + authorHandle: string; 8 + state: IssueState; 9 + createdAt: string; 10 + commentCount?: number; 11 + };
+14
src/domain/models/pull-request.ts
··· 1 + type PRStatus = "open" | "merged" | "closed"; 2 + 3 + export type PullRequestSummary = { 4 + atUri: string; 5 + title: string; 6 + authorDid: string; 7 + authorHandle: string; 8 + status: PRStatus; 9 + createdAt: string; 10 + updatedAt?: string; 11 + sourceBranch: string; 12 + targetBranch: string; 13 + roundCount?: number; 14 + };
+26
src/domain/models/repo.ts
··· 1 + import type { UserSummary } from "./user"; 2 + 3 + export type RepoSummary = { 4 + atUri: string; 5 + ownerDid: string; 6 + ownerHandle: string; 7 + name: string; 8 + description?: string; 9 + primaryLanguage?: string; 10 + stars?: number; 11 + forks?: number; 12 + updatedAt?: string; 13 + knot: string; 14 + }; 15 + 16 + export type RepoDetail = RepoSummary & { 17 + readme?: string; 18 + defaultBranch?: string; 19 + languages?: Record<string, number>; 20 + collaborators?: UserSummary[]; 21 + topics?: string[]; 22 + }; 23 + 24 + type FileKind = "file" | "dir" | "submodule"; 25 + 26 + export type RepoFile = { path: string; name: string; type: FileKind; size?: number; lastCommitMessage?: string };
+9
src/domain/models/user.ts
··· 1 + export type UserSummary = { 2 + did: string; 3 + handle: string; 4 + displayName?: string; 5 + avatar?: string; 6 + bio?: string; 7 + followerCount?: number; 8 + followingCount?: number; 9 + };
+98
src/mocks/activity.ts
··· 1 + import type { ActivityItem } from "@/domain/models/activity"; 2 + 3 + const MOCK_ACTIVITY: ActivityItem[] = [ 4 + { 5 + id: "act-001", 6 + kind: "repo_created", 7 + actorDid: "did:plc:p2cp5gopk7mgjegy9waligxd", 8 + actorHandle: "desertthunder.dev", 9 + targetUri: "at://did:plc:p2cp5gopk7mgjegy9waligxd/sh.tangled.repo/twisted", 10 + targetName: "twisted", 11 + createdAt: "2026-03-22T09:15:00Z", 12 + }, 13 + { 14 + id: "act-002", 15 + kind: "pr_opened", 16 + actorDid: "did:plc:c3d4e5f6g7h8i9j0k1l2m3n4", 17 + actorHandle: "clara.bsky.social", 18 + targetUri: "at://did:plc:a1b2c3d4e5f6g7h8i9j0k1l2/sh.tangled.pr/3", 19 + targetName: "atproto-explorer#3", 20 + createdAt: "2026-03-20T10:12:00Z", 21 + }, 22 + { 23 + id: "act-003", 24 + kind: "repo_starred", 25 + actorDid: "did:plc:b2c3d4e5f6g7h8i9j0k1l2m3", 26 + actorHandle: "bob.tngl.sh", 27 + targetUri: "at://did:plc:c3d4e5f6g7h8i9j0k1l2m3n4/sh.tangled.repo/iris-ui", 28 + targetName: "iris-ui", 29 + createdAt: "2026-03-21T14:05:00Z", 30 + }, 31 + { 32 + id: "act-004", 33 + kind: "issue_opened", 34 + actorDid: "did:plc:p2cp5gopk7mgjegy9waligxd", 35 + actorHandle: "desertthunder.dev", 36 + targetUri: "at://did:plc:p2cp5gopk7mgjegy9waligxd/sh.tangled.issue/12", 37 + targetName: "twisted#12", 38 + createdAt: "2026-03-22T08:40:00Z", 39 + }, 40 + { 41 + id: "act-005", 42 + kind: "pr_merged", 43 + actorDid: "did:plc:a1b2c3d4e5f6g7h8i9j0k1l2", 44 + actorHandle: "alice.tngl.sh", 45 + targetUri: "at://did:plc:a1b2c3d4e5f6g7h8i9j0k1l2/sh.tangled.pr/2", 46 + targetName: "atproto-explorer#2", 47 + createdAt: "2026-03-16T11:00:00Z", 48 + }, 49 + { 50 + id: "act-006", 51 + kind: "user_followed", 52 + actorDid: "did:plc:p2cp5gopk7mgjegy9waligxd", 53 + actorHandle: "desertthunder.dev", 54 + targetUri: "at://did:plc:a1b2c3d4e5f6g7h8i9j0k1l2", 55 + targetName: "alice.tngl.sh", 56 + createdAt: "2026-03-19T20:30:00Z", 57 + }, 58 + { 59 + id: "act-007", 60 + kind: "repo_starred", 61 + actorDid: "did:plc:e5f6g7h8i9j0k1l2m3n4o5p6", 62 + actorHandle: "riku.tngl.sh", 63 + targetUri: "at://did:plc:d4e5f6g7h8i9j0k1l2m3n4o5/sh.tangled.repo/tangled-cli", 64 + targetName: "tangled-cli", 65 + createdAt: "2026-03-22T06:10:00Z", 66 + }, 67 + { 68 + id: "act-008", 69 + kind: "issue_closed", 70 + actorDid: "did:plc:a1b2c3d4e5f6g7h8i9j0k1l2", 71 + actorHandle: "alice.tngl.sh", 72 + targetUri: "at://did:plc:a1b2c3d4e5f6g7h8i9j0k1l2/sh.tangled.issue/5", 73 + targetName: "atproto-explorer#5", 74 + createdAt: "2026-03-13T15:20:00Z", 75 + }, 76 + { 77 + id: "act-009", 78 + kind: "repo_created", 79 + actorDid: "did:plc:e5f6g7h8i9j0k1l2m3n4o5p6", 80 + actorHandle: "riku.tngl.sh", 81 + targetUri: "at://did:plc:e5f6g7h8i9j0k1l2m3n4o5p6/sh.tangled.repo/nix-atproto", 82 + targetName: "nix-atproto", 83 + createdAt: "2026-03-17T08:30:00Z", 84 + }, 85 + { 86 + id: "act-010", 87 + kind: "user_followed", 88 + actorDid: "did:plc:c3d4e5f6g7h8i9j0k1l2m3n4", 89 + actorHandle: "clara.bsky.social", 90 + targetUri: "at://did:plc:d4e5f6g7h8i9j0k1l2m3n4o5", 91 + targetName: "dev.tangled.sh", 92 + createdAt: "2026-03-20T09:00:00Z", 93 + }, 94 + ]; 95 + 96 + export function getMockActivity(): ActivityItem[] { 97 + return [...MOCK_ACTIVITY].sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()); 98 + }
+75
src/mocks/issues.ts
··· 1 + import type { IssueSummary } from "@/domain/models/issue"; 2 + 3 + const MOCK_ISSUES: IssueSummary[] = [ 4 + { 5 + atUri: "at://did:plc:a1b2c3d4e5f6g7h8i9j0k1l2/sh.tangled.issue/8", 6 + title: "Support streaming responses from XRPC subscriptions", 7 + authorDid: "did:plc:p2cp5gopk7mgjegy9waligxd", 8 + authorHandle: "desertthunder.dev", 9 + state: "open", 10 + createdAt: "2026-03-21T12:00:00Z", 11 + commentCount: 4, 12 + }, 13 + { 14 + atUri: "at://did:plc:a1b2c3d4e5f6g7h8i9j0k1l2/sh.tangled.issue/7", 15 + title: "Keyboard navigation broken in record browser", 16 + authorDid: "did:plc:c3d4e5f6g7h8i9j0k1l2m3n4", 17 + authorHandle: "clara.bsky.social", 18 + state: "open", 19 + createdAt: "2026-03-19T17:30:00Z", 20 + commentCount: 2, 21 + }, 22 + { 23 + atUri: "at://did:plc:a1b2c3d4e5f6g7h8i9j0k1l2/sh.tangled.issue/6", 24 + title: "Add pagination to lexicon list endpoint", 25 + authorDid: "did:plc:b2c3d4e5f6g7h8i9j0k1l2m3", 26 + authorHandle: "bob.tngl.sh", 27 + state: "open", 28 + createdAt: "2026-03-17T09:15:00Z", 29 + commentCount: 7, 30 + }, 31 + { 32 + atUri: "at://did:plc:a1b2c3d4e5f6g7h8i9j0k1l2/sh.tangled.issue/5", 33 + title: "DID resolution fails when PLC directory is unreachable", 34 + authorDid: "did:plc:e5f6g7h8i9j0k1l2m3n4o5p6", 35 + authorHandle: "riku.tngl.sh", 36 + state: "closed", 37 + createdAt: "2026-03-10T11:00:00Z", 38 + commentCount: 5, 39 + }, 40 + { 41 + atUri: "at://did:plc:a1b2c3d4e5f6g7h8i9j0k1l2/sh.tangled.issue/4", 42 + title: "Export TypeScript types for all lexicon schemas", 43 + authorDid: "did:plc:a1b2c3d4e5f6g7h8i9j0k1l2", 44 + authorHandle: "alice.tngl.sh", 45 + state: "closed", 46 + createdAt: "2026-03-06T14:20:00Z", 47 + commentCount: 3, 48 + }, 49 + { 50 + atUri: "at://did:plc:p2cp5gopk7mgjegy9waligxd/sh.tangled.issue/12", 51 + title: "Offline mode: cache last-viewed repos to IndexedDB", 52 + authorDid: "did:plc:p2cp5gopk7mgjegy9waligxd", 53 + authorHandle: "desertthunder.dev", 54 + state: "open", 55 + createdAt: "2026-03-22T08:40:00Z", 56 + commentCount: 0, 57 + }, 58 + { 59 + atUri: "at://did:plc:p2cp5gopk7mgjegy9waligxd/sh.tangled.issue/11", 60 + title: "Skeleton loaders flicker on fast connections", 61 + authorDid: "did:plc:c3d4e5f6g7h8i9j0k1l2m3n4", 62 + authorHandle: "clara.bsky.social", 63 + state: "open", 64 + createdAt: "2026-03-18T16:05:00Z", 65 + commentCount: 1, 66 + }, 67 + ]; 68 + 69 + export function getMockIssues(): IssueSummary[] { 70 + return MOCK_ISSUES; 71 + } 72 + 73 + export function getMockOpenIssues(): IssueSummary[] { 74 + return MOCK_ISSUES.filter((i) => i.state === "open"); 75 + }
+74
src/mocks/pull-requests.ts
··· 1 + import type { PullRequestSummary } from "@/domain/models/pull-request"; 2 + 3 + const MOCK_PRS: PullRequestSummary[] = [ 4 + { 5 + atUri: "at://did:plc:a1b2c3d4e5f6g7h8i9j0k1l2/sh.tangled.pr/3", 6 + title: "feat: add dark mode support to token system", 7 + authorDid: "did:plc:c3d4e5f6g7h8i9j0k1l2m3n4", 8 + authorHandle: "clara.bsky.social", 9 + status: "open", 10 + createdAt: "2026-03-20T10:12:00Z", 11 + updatedAt: "2026-03-21T15:44:00Z", 12 + sourceBranch: "feat/dark-mode-tokens", 13 + targetBranch: "main", 14 + roundCount: 2, 15 + }, 16 + { 17 + atUri: "at://did:plc:a1b2c3d4e5f6g7h8i9j0k1l2/sh.tangled.pr/2", 18 + title: "fix: resolve CORS issue with lexicon fetch", 19 + authorDid: "did:plc:b2c3d4e5f6g7h8i9j0k1l2m3", 20 + authorHandle: "bob.tngl.sh", 21 + status: "merged", 22 + createdAt: "2026-03-15T08:30:00Z", 23 + updatedAt: "2026-03-16T11:00:00Z", 24 + sourceBranch: "fix/cors-lexicon", 25 + targetBranch: "main", 26 + roundCount: 1, 27 + }, 28 + { 29 + atUri: "at://did:plc:a1b2c3d4e5f6g7h8i9j0k1l2/sh.tangled.pr/1", 30 + title: "chore: upgrade @atcute/client to v4", 31 + authorDid: "did:plc:a1b2c3d4e5f6g7h8i9j0k1l2", 32 + authorHandle: "alice.tngl.sh", 33 + status: "merged", 34 + createdAt: "2026-03-08T14:00:00Z", 35 + updatedAt: "2026-03-09T09:20:00Z", 36 + sourceBranch: "chore/atcute-v4", 37 + targetBranch: "main", 38 + roundCount: 1, 39 + }, 40 + { 41 + atUri: "at://did:plc:p2cp5gopk7mgjegy9waligxd/sh.tangled.pr/5", 42 + title: "feat: tab routing with per-tab navigation stacks", 43 + authorDid: "did:plc:p2cp5gopk7mgjegy9waligxd", 44 + authorHandle: "desertthunder.dev", 45 + status: "open", 46 + createdAt: "2026-03-21T18:05:00Z", 47 + sourceBranch: "feat/tab-routing", 48 + targetBranch: "main", 49 + roundCount: 0, 50 + }, 51 + { 52 + atUri: "at://did:plc:b2c3d4e5f6g7h8i9j0k1l2m3/sh.tangled.pr/7", 53 + title: "refactor: replace manual flag parsing with cobra", 54 + authorDid: "did:plc:e5f6g7h8i9j0k1l2m3n4o5p6", 55 + authorHandle: "riku.tngl.sh", 56 + status: "closed", 57 + createdAt: "2026-03-12T09:45:00Z", 58 + updatedAt: "2026-03-13T12:30:00Z", 59 + sourceBranch: "refactor/cobra-cli", 60 + targetBranch: "main", 61 + roundCount: 3, 62 + }, 63 + ]; 64 + 65 + export function getMockPullRequests(repoAtUri?: string): PullRequestSummary[] { 66 + if (repoAtUri) { 67 + return MOCK_PRS.filter((pr) => pr.atUri.startsWith(repoAtUri.replace("/sh.tangled.repo/", "/sh.tangled.pr/"))); 68 + } 69 + return MOCK_PRS; 70 + } 71 + 72 + export function getMockOpenPRs(): PullRequestSummary[] { 73 + return MOCK_PRS.filter((pr) => pr.status === "open"); 74 + }
+165
src/mocks/repos.ts
··· 1 + import type { RepoSummary, RepoDetail, RepoFile } from '@/domain/models/repo'; 2 + 3 + // Timestamps within the last 30 days (relative to 2026-03-22) 4 + const MOCK_REPOS: RepoSummary[] = [ 5 + { 6 + atUri: 'at://did:plc:a1b2c3d4e5f6g7h8i9j0k1l2/sh.tangled.repo/atproto-explorer', 7 + ownerDid: 'did:plc:a1b2c3d4e5f6g7h8i9j0k1l2', 8 + ownerHandle: 'alice.tngl.sh', 9 + name: 'atproto-explorer', 10 + description: 'Interactive explorer for AT Protocol lexicons and records.', 11 + primaryLanguage: 'TypeScript', 12 + stars: 312, 13 + forks: 28, 14 + updatedAt: '2026-03-21T14:32:00Z', 15 + knot: 'tangled.sh', 16 + }, 17 + { 18 + atUri: 'at://did:plc:p2cp5gopk7mgjegy9waligxd/sh.tangled.repo/twisted', 19 + ownerDid: 'did:plc:p2cp5gopk7mgjegy9waligxd', 20 + ownerHandle: 'desertthunder.dev', 21 + name: 'twisted', 22 + description: 'A mobile companion reader for Tangled, built with Ionic Vue.', 23 + primaryLanguage: 'TypeScript', 24 + stars: 47, 25 + forks: 3, 26 + updatedAt: '2026-03-22T09:15:00Z', 27 + knot: 'tangled.sh', 28 + }, 29 + { 30 + atUri: 'at://did:plc:b2c3d4e5f6g7h8i9j0k1l2m3/sh.tangled.repo/git-log-pretty', 31 + ownerDid: 'did:plc:b2c3d4e5f6g7h8i9j0k1l2m3', 32 + ownerHandle: 'bob.tngl.sh', 33 + name: 'git-log-pretty', 34 + description: 'Opinionated git log formatter with colour themes and TUI.', 35 + primaryLanguage: 'Go', 36 + stars: 189, 37 + forks: 14, 38 + updatedAt: '2026-03-19T22:08:00Z', 39 + knot: 'tangled.sh', 40 + }, 41 + { 42 + atUri: 'at://did:plc:c3d4e5f6g7h8i9j0k1l2m3n4/sh.tangled.repo/iris-ui', 43 + ownerDid: 'did:plc:c3d4e5f6g7h8i9j0k1l2m3n4', 44 + ownerHandle: 'clara.bsky.social', 45 + name: 'iris-ui', 46 + description: 'Accessible component library for AT Protocol apps.', 47 + primaryLanguage: 'TypeScript', 48 + stars: 631, 49 + forks: 72, 50 + updatedAt: '2026-03-20T11:45:00Z', 51 + knot: 'tangled.sh', 52 + }, 53 + { 54 + atUri: 'at://did:plc:e5f6g7h8i9j0k1l2m3n4o5p6/sh.tangled.repo/nix-atproto', 55 + ownerDid: 'did:plc:e5f6g7h8i9j0k1l2m3n4o5p6', 56 + ownerHandle: 'riku.tngl.sh', 57 + name: 'nix-atproto', 58 + description: 'Nix flakes and modules for self-hosting AT Protocol services.', 59 + primaryLanguage: 'Nix', 60 + stars: 94, 61 + forks: 11, 62 + updatedAt: '2026-03-17T08:30:00Z', 63 + knot: 'tangled.sh', 64 + }, 65 + { 66 + atUri: 'at://did:plc:d4e5f6g7h8i9j0k1l2m3n4o5/sh.tangled.repo/tangled-cli', 67 + ownerDid: 'did:plc:d4e5f6g7h8i9j0k1l2m3n4o5', 68 + ownerHandle: 'dev.tangled.sh', 69 + name: 'tangled-cli', 70 + description: 'Official command-line tool for interacting with the Tangled platform.', 71 + primaryLanguage: 'Go', 72 + stars: 1842, 73 + forks: 203, 74 + updatedAt: '2026-03-22T07:00:00Z', 75 + knot: 'tangled.sh', 76 + }, 77 + { 78 + atUri: 'at://did:plc:a1b2c3d4e5f6g7h8i9j0k1l2/sh.tangled.repo/lexicon-validator', 79 + ownerDid: 'did:plc:a1b2c3d4e5f6g7h8i9j0k1l2', 80 + ownerHandle: 'alice.tngl.sh', 81 + name: 'lexicon-validator', 82 + description: 'Runtime validation for AT Protocol lexicon schemas.', 83 + primaryLanguage: 'TypeScript', 84 + stars: 77, 85 + forks: 9, 86 + updatedAt: '2026-03-14T16:20:00Z', 87 + knot: 'tangled.sh', 88 + }, 89 + { 90 + atUri: 'at://did:plc:p2cp5gopk7mgjegy9waligxd/sh.tangled.repo/bsky-feeds', 91 + ownerDid: 'did:plc:p2cp5gopk7mgjegy9waligxd', 92 + ownerHandle: 'desertthunder.dev', 93 + name: 'bsky-feeds', 94 + description: 'Custom Bluesky feed generators with a simple declarative API.', 95 + primaryLanguage: 'Python', 96 + stars: 203, 97 + forks: 31, 98 + updatedAt: '2026-03-10T19:55:00Z', 99 + knot: 'tangled.sh', 100 + }, 101 + ]; 102 + 103 + const MOCK_REPO_FILES: RepoFile[] = [ 104 + { path: '', name: 'src', type: 'dir', lastCommitMessage: 'feat: add skeleton loaders' }, 105 + { path: '', name: 'docs', type: 'dir', lastCommitMessage: 'docs: update phase-1 spec' }, 106 + { path: '', name: 'public', type: 'dir', lastCommitMessage: 'chore: add favicon' }, 107 + { path: '', name: '.gitignore', type: 'file', size: 412, lastCommitMessage: 'chore: initial scaffold' }, 108 + { path: '', name: 'package.json', type: 'file', size: 1840, lastCommitMessage: 'chore: add tanstack query' }, 109 + { path: '', name: 'README.md', type: 'file', size: 2310, lastCommitMessage: 'docs: update readme' }, 110 + { path: '', name: 'tsconfig.json', type: 'file', size: 688, lastCommitMessage: 'chore: initial scaffold' }, 111 + { path: '', name: 'vite.config.ts', type: 'file', size: 520, lastCommitMessage: 'chore: path aliases' }, 112 + ]; 113 + 114 + const README_CONTENT = `# twisted 115 + 116 + A mobile companion reader for [Tangled](https://tangled.sh), built with Ionic Vue and Capacitor. 117 + 118 + ## Features 119 + 120 + - Browse repositories, files, and READMEs 121 + - View issues and pull requests 122 + - Activity feed (global and personalized) 123 + - Sign in via AT Protocol OAuth 124 + 125 + ## Getting Started 126 + 127 + \`\`\`bash 128 + pnpm install 129 + pnpm dev 130 + \`\`\` 131 + 132 + ## Tech Stack 133 + 134 + - Vue 3 + TypeScript 135 + - Ionic Vue 136 + - Capacitor (iOS / Android) 137 + - Pinia + TanStack Query 138 + `; 139 + 140 + export function getMockRepos(): RepoSummary[] { 141 + return MOCK_REPOS; 142 + } 143 + 144 + export function getMockRepoDetail(ownerHandle: string, name: string): RepoDetail | undefined { 145 + const summary = MOCK_REPOS.find((r) => r.ownerHandle === ownerHandle && r.name === name); 146 + if (!summary) return undefined; 147 + 148 + return { 149 + ...summary, 150 + readme: README_CONTENT, 151 + defaultBranch: 'main', 152 + languages: summary.primaryLanguage 153 + ? { [summary.primaryLanguage]: 85, Other: 15 } 154 + : {}, 155 + topics: ['atproto', 'tangled', 'open-source'], 156 + }; 157 + } 158 + 159 + export function getMockRepoFiles(): RepoFile[] { 160 + return MOCK_REPO_FILES; 161 + } 162 + 163 + export function getTrendingRepos(): RepoSummary[] { 164 + return [...MOCK_REPOS].sort((a, b) => (b.stars ?? 0) - (a.stars ?? 0)).slice(0, 5); 165 + }
+60
src/mocks/users.ts
··· 1 + import type { UserSummary } from '@/domain/models/user'; 2 + 3 + const MOCK_USERS: UserSummary[] = [ 4 + { 5 + did: 'did:plc:p2cp5gopk7mgjegy9waligxd', 6 + handle: 'desertthunder.dev', 7 + displayName: 'Desert Thunder', 8 + bio: 'Building things on the AT Protocol. Open source enthusiast.', 9 + followerCount: 142, 10 + followingCount: 87, 11 + }, 12 + { 13 + did: 'did:plc:a1b2c3d4e5f6g7h8i9j0k1l2', 14 + handle: 'alice.tngl.sh', 15 + displayName: 'Alice Chen', 16 + bio: 'Distributed systems @ Tangled. TypeScript, Go, Rust.', 17 + followerCount: 891, 18 + followingCount: 234, 19 + }, 20 + { 21 + did: 'did:plc:b2c3d4e5f6g7h8i9j0k1l2m3', 22 + handle: 'bob.tngl.sh', 23 + displayName: 'Bob Nakamura', 24 + bio: 'Open source contributor. Loves compilers and weird edge cases.', 25 + followerCount: 307, 26 + followingCount: 412, 27 + }, 28 + { 29 + did: 'did:plc:c3d4e5f6g7h8i9j0k1l2m3n4', 30 + handle: 'clara.bsky.social', 31 + displayName: 'Clara Osei', 32 + bio: 'Frontend dev. Making the decentralized web feel fast.', 33 + followerCount: 554, 34 + followingCount: 198, 35 + }, 36 + { 37 + did: 'did:plc:d4e5f6g7h8i9j0k1l2m3n4o5', 38 + handle: 'dev.tangled.sh', 39 + displayName: 'Tangled Dev', 40 + bio: 'Official Tangled development account.', 41 + followerCount: 4210, 42 + followingCount: 12, 43 + }, 44 + { 45 + did: 'did:plc:e5f6g7h8i9j0k1l2m3n4o5p6', 46 + handle: 'riku.tngl.sh', 47 + displayName: 'Riku Mäkinen', 48 + bio: 'Systems programmer. NixOS, Git internals, coffee.', 49 + followerCount: 228, 50 + followingCount: 315, 51 + }, 52 + ]; 53 + 54 + export function getMockUsers(): UserSummary[] { 55 + return MOCK_USERS; 56 + } 57 + 58 + export function getMockUser(handle: string): UserSummary | undefined { 59 + return MOCK_USERS.find((u) => u.handle === handle); 60 + }