fix: MR detail 404, wire up fork buttons on import page - Fix listPullComments to pass pull AT-URI instead of separate params - Add xrpcProcedure helper for POST endpoints - Wire up Fork buttons to call dev.cospan.repo.fork and navigate

Author: Aaron Steven White
Commit 602218228d6e1c5dab9d4a7964d625a3886a28ea
Parent: a99c1a6dea
Structural diff unavailable

These commits were pushed via plain git push, so no pre-parsed schemas are available. Install git-remote-cospan and re-push via panproto:// to see scope-level changes, breaking change detection, and semantic diffs.

brew install panproto/tap/git-remote-cospan
3 files changed +62 -5
@@ -54,3 +54,31 @@ export async function xrpcQuery<T>(
5454 
5555 	return response.json() as Promise<T>;
5656 }
57+
58+export async function xrpcProcedure<T>(
59+	nsid: string,
60+	body: Record<string, unknown>
61+): Promise<T> {
62+	const base = getAppviewUrl();
63+	const url = `${base || (typeof window !== 'undefined' ? window.location.origin : 'http://localhost:3000')}/xrpc/${nsid}`;
64+
65+	const response = await fetch(url, {
66+		method: 'POST',
67+		headers: { 'Content-Type': 'application/json' },
68+		body: JSON.stringify(body),
69+		credentials: 'include',
70+	});
71+
72+	if (!response.ok) {
73+		let error = 'Unknown';
74+		let message = response.statusText;
75+		try {
76+			const b = await response.json();
77+			error = b.error ?? error;
78+			message = b.message ?? message;
79+		} catch {}
80+		throw new XRPCError(response.status, error, message);
81+	}
82+
83+	return response.json() as Promise<T>;
84+}
@@ -55,9 +55,10 @@ export async function listPullComments(params: {
5555 	limit?: number;
5656 	cursor?: string;
5757 }): Promise<PullCommentListResponse> {
58+	const pullUri = `at://${params.did}/dev.cospan.repo.pull/${params.rkey}`;
5859 	const raw = await xrpcQuery<RawPullCommentListResponse>(
5960 		'dev.cospan.repo.pull.comment.list',
60-		params
61+		{ pull: pullUri, limit: params.limit, cursor: params.cursor }
6162 	);
6263 	return { items: raw.comments ?? [], cursor: raw.cursor ?? null };
6364 }
@@ -5,11 +5,31 @@
55 	import type { Repo } from '$lib/api/repo.js';
66 	import { listRepos } from '$lib/api/repo.js';
77 	import { resolveHandle } from '$lib/api/handle.js';
8+	import { xrpcProcedure } from '$lib/api/client.js';
89 	import { getAuth } from '$lib/stores/auth.svelte';
910 
1011 	let { data } = $props();
1112 
1213 	let auth = $derived(getAuth());
14+	let forkingRepo = $state<string | null>(null);
15+	let forkError = $state<string | null>(null);
16+
17+	async function forkRepo(repo: Repo) {
18+		if (!auth.authenticated || !auth.did) return;
19+		forkingRepo = `${repo.did}/${repo.name}`;
20+		forkError = null;
21+		try {
22+			const sourceUri = `at://${repo.did}/dev.cospan.repo/${repo.rkey ?? repo.name}`;
23+			await xrpcProcedure('dev.cospan.repo.fork', {
24+				sourceRepo: sourceUri,
25+				did: auth.did,
26+			});
27+			goto(`/${auth.did}/${repo.name}`);
28+		} catch (e: any) {
29+			forkError = e.message ?? 'Fork failed';
30+			forkingRepo = null;
31+		}
32+	}
1333 	let searchQuery = $state('');
1434 	let activeTab = $state<'all' | 'mine'>('all');
1535 
@@ -216,8 +236,12 @@
216236 								<p class="mt-0.5 truncate text-[12px] text-ghost">{repo.description}</p>
217237 							{/if}
218238 						</div>
219-						<button class="shrink-0 rounded-md bg-focus px-3 py-1 text-[12px] font-medium text-white transition-all hover:bg-focus-bright">
220-							Fork
239+						<button
240+							onclick={() => forkRepo(repo)}
241+							disabled={forkingRepo === `${repo.did}/${repo.name}`}
242+							class="shrink-0 rounded-md bg-focus px-3 py-1 text-[12px] font-medium text-white transition-all hover:bg-focus-bright disabled:opacity-50"
243+						>
244+							{forkingRepo === `${repo.did}/${repo.name}` ? 'Forking…' : 'Fork'}
221245 						</button>
222246 					</div>
223247 				{/each}
@@ -269,8 +293,12 @@
269293 								View on Tangled ↗
270294 							</a>
271295 						</div>
272-						<button class="shrink-0 rounded-md border border-line px-2.5 py-1 text-[11px] font-medium text-caption transition-all hover:border-line-bright hover:text-ink">
273-							Fork
296+						<button
297+							onclick={() => forkRepo(repo)}
298+							disabled={!auth.authenticated || forkingRepo === `${repo.did}/${repo.name}`}
299+							class="shrink-0 rounded-md border border-line px-2.5 py-1 text-[11px] font-medium text-caption transition-all hover:border-line-bright hover:text-ink disabled:opacity-50"
300+						>
301+							{forkingRepo === `${repo.did}/${repo.name}` ? 'Forking…' : 'Fork'}
274302 						</button>
275303 					</div>
276304 				{/each}
cospan · schematic version control on atproto built on AT Protocol