import type { CommitSummary } from "$lib/api/repo"; // MOCK. sh.tangled.repo.log takes a `path` param but no knot filters on it // yet, so there is no real file history to read: asking for one makes bobbin // skip the mirror and the knot answers RepoNotFound. Until the filter lands, // the history page shows a history invented from the repo's own authors. // // to drop the mock: delete this file and have the loader call // `log(git, { ref, path, limit, cursor })` again, through a target that reaches // the knot. export const MOCKED = true; export interface MockAuthor { authorName: string; authorEmail: string; authorDid?: string; authorHandle?: string; } const SUBJECTS = [ "pull the loader out of the component", "stop the header from reflowing on hover", "drop the dead compatibility branch", "name the magic number", "let the caller pass its own fetch", "fix the off-by-one in the page cursor", "reuse the existing chip instead of a new one", "move the guard up to the boundary", "keep the empty state on one line", "tighten the types around the response", "cache the resolved handle for the session", "handle the ref with a slash in it", "split the view out of the route", "stop swallowing the parse error", "round the sizes to the token scale", "make the toggle keyboard reachable", "read the default from the config", "collapse the two nearly identical helpers", "defer the icon import", "treat a missing body as empty, not null", "walk the parents once, not twice", "put the separator back between the rows", "trim the trailing newline before hashing", "spell the aria label out loud" ]; const BODIES = [ "the old path only ever ran on the first render, so nothing depended on it.", "measured before and after: the reflow is gone and the header holds its height.", "this keeps the call site honest about what it owns.\n\nno behaviour change.", "turns out the knot already sorts these, so the second pass was pure cost.", "reported in chat: a branch called release/2.0 lost everything after the slash." ]; const FALLBACK_AUTHORS: MockAuthor[] = [ { authorName: "dawn", authorEmail: "dawn@tangled.org" }, { authorName: "oppi", authorEmail: "oppi@tangled.sh" }, { authorName: "icy", authorEmail: "icy@tangled.sh" } ]; const seedOf = (key: string): number => { let hash = 0x811c9dc5; for (let index = 0; index < key.length; index += 1) { hash ^= key.charCodeAt(index); hash = Math.imul(hash, 0x01000193); } return hash >>> 0; }; const random = (seed: number) => { let state = seed; return () => { state = (state + 0x6d2b79f5) >>> 0; let drawn = Math.imul(state ^ (state >>> 15), 1 | state); drawn = (drawn + Math.imul(drawn ^ (drawn >>> 7), 61 | drawn)) ^ drawn; return ((drawn ^ (drawn >>> 14)) >>> 0) / 4294967296; }; }; const DAY_MS = 86_400_000; export const mockFileHistory = ( repo: string, path: string, authors: MockAuthor[], now: Date = new Date() ): CommitSummary[] => { const next = random(seedOf(`${repo}:${path}`)); const pool = authors.length > 0 ? authors : FALLBACK_AUTHORS; const count = 8 + Math.floor(next() * 160); const hex = () => Array.from({ length: 40 }, () => "0123456789abcdef"[Math.floor(next() * 16)]).join(""); let when = now.getTime() - Math.floor(next() * 4 * DAY_MS); return Array.from({ length: count }, () => { const hash = hex(); const body = next() < 0.25 ? BODIES[Math.floor(next() * BODIES.length)] : ""; const commit: CommitSummary = { hash, shortHash: hash.slice(0, 8), subject: SUBJECTS[Math.floor(next() * SUBJECTS.length)], body, when: new Date(when).toISOString(), ...pool[Math.floor(next() * pool.length)] }; if (next() < 0.5) commit.changeId = hex().slice(0, 32); when -= Math.floor(next() * 7 * DAY_MS); return commit; }); }; export const authorsOf = (commits: CommitSummary[]): MockAuthor[] => { const seen = new Set(); return commits.flatMap((commit) => { const key = commit.authorDid ?? commit.authorEmail; if (!key || seen.has(key)) return []; seen.add(key); return [ { authorName: commit.authorName, authorEmail: commit.authorEmail, authorDid: commit.authorDid, authorHandle: commit.authorHandle } ]; }); };