'use client'; import Link from 'next/link'; import { useParams } from 'next/navigation'; import { SpoonActivityTimeline } from '@/components/spoons/spoon-activity-timeline'; import { SpoonAgentSettingsForm } from '@/components/spoons/spoon-agent-settings-form'; import { SpoonClonePanel } from '@/components/spoons/spoon-clone-panel'; import { SpoonCommitList } from '@/components/spoons/spoon-commit-list'; import { SpoonDetailHeader } from '@/components/spoons/spoon-detail-header'; import { SpoonMetrics } from '@/components/spoons/spoon-metrics'; import { SpoonPrList } from '@/components/spoons/spoon-pr-list'; import { SpoonSecretsForm } from '@/components/spoons/spoon-secrets-form'; import { SpoonSettingsForm } from '@/components/spoons/spoon-settings-form'; import { DeleteThreadButton } from '@/components/threads/delete-thread-button'; import { ThreadWorkspaceForm } from '@/components/threads/thread-workspace-form'; import { useQuery } from 'convex/react'; import type { Id } from '@spoon/backend/convex/_generated/dataModel.js'; import { api } from '@spoon/backend/convex/_generated/api.js'; import { Card, CardContent, CardHeader, CardTitle, Tabs, TabsContent, TabsList, TabsTrigger, } from '@spoon/ui'; const SpoonDetailPage = () => { const params = useParams<{ spoonId: string }>(); const spoonId = params.spoonId as Id<'spoons'>; const details = useQuery(api.spoons.getDetails, { spoonId }); const upstreamCommits = useQuery(api.spoonCommits.listForSpoon, { spoonId, side: 'upstream', limit: 100, }) ?? []; const forkCommits = useQuery(api.spoonCommits.listForSpoon, { spoonId, side: 'fork', limit: 100, }) ?? []; const pullRequests = useQuery(api.spoonPullRequests.listForSpoon, { spoonId, limit: 100 }) ?? []; const syncRuns = useQuery(api.syncRuns.listForSpoon, { spoonId, limit: 25 }) ?? []; const threads = useQuery(api.threads.listForSpoon, { spoonId, limit: 25 }) ?? []; const agentSettings = useQuery(api.spoonAgentSettings.getForSpoon, { spoonId, }); const agentJobs = useQuery(api.agentJobs.listForSpoon, { spoonId, limit: 25 }) ?? []; const canDeleteThread = (thread: (typeof threads)[number]) => { const latestJobStatus = thread.latestJobStatus; const latestWorkspaceStatus = thread.latestJobWorkspaceStatus; if (!latestJobStatus && !latestWorkspaceStatus) return true; return ( ['failed', 'cancelled', 'timed_out', 'draft_pr_opened'].includes( latestJobStatus ?? '', ) || ['stopped', 'expired', 'failed'].includes(latestWorkspaceStatus ?? '') ); }; if (details === undefined) { return
Loading Spoon...
; } return (
{details.spoon.lastError ? ( {details.spoon.lastError} ) : null} Overview Upstream Fork changes Pull requests Threads Activity Settings
Repository health

Drift state

{( details.state?.status ?? details.spoon.syncStatus ?? 'unknown' ).replaceAll('_', ' ')}

{details.effectiveUpstreamAheadBy === 0 && (details.state?.upstreamAheadBy ?? details.spoon.upstreamAheadBy ?? 0) > 0 ? (

Up to date after ignored upstream changes. Raw upstream ahead:{' '} {details.state?.upstreamAheadBy ?? details.spoon.upstreamAheadBy}

) : null}

Default branches

{details.state?.upstreamDefaultBranch ?? details.spoon.upstreamDefaultBranch}{' '} →{' '} {details.state?.forkDefaultBranch ?? details.spoon.forkDefaultBranch ?? details.spoon.upstreamDefaultBranch}

Merge base

{details.state?.mergeBaseSha ?? details.spoon.lastMergeBaseCommit ?? 'Unknown'}

Cadence

{details.spoon.syncCadence}

Latest thread {threads[0] ? ( <>

Status

{threads[0].status.replaceAll('_', ' ')}

Source

{threads[0].source.replaceAll('_', ' ')}

{threads[0].summary ?? 'Open the thread to continue maintenance work.'}

) : (

Refresh GitHub state or create a thread to start maintenance work for this Spoon.

)}

Upstream waiting

Commits upstream has that your fork does not.

Fork changes

Custom commits Spoon should preserve during maintenance.

Spoon threads {threads.length ? ( threads.map((thread) => (

{thread.title}

{thread.status.replaceAll('_', ' ')} ·{' '} {thread.source.replaceAll('_', ' ')} {thread.latestJobWorkspaceStatus ? ` · workspace ${thread.latestJobWorkspaceStatus.replaceAll('_', ' ')}` : ''}

)) ) : (

No threads exist for this Spoon yet.

)}
); }; export default SpoonDetailPage;