Move to threads based system.

This commit is contained in:
Gabriel Brown
2026-06-22 10:37:26 -04:00
parent 8ae6c4b533
commit 206b64176b
82 changed files with 6169 additions and 1930 deletions
@@ -1,11 +1,11 @@
'use client';
import Link from 'next/link';
import { useParams } from 'next/navigation';
import { AgentJobList } from '@/components/agents/agent-job-list';
import { AgentRequestForm } from '@/components/agents/agent-request-form';
import { SpoonActivityTimeline } from '@/components/spoons/spoon-activity-timeline';
import { SpoonAgentSettingsForm } from '@/components/spoons/spoon-agent-settings-form';
import { SpoonAiReviewPanel } from '@/components/spoons/spoon-ai-review-panel';
import { SpoonClonePanel } from '@/components/spoons/spoon-clone-panel';
import { SpoonCommitList } from '@/components/spoons/spoon-commit-list';
import { SpoonDetailHeader } from '@/components/spoons/spoon-detail-header';
@@ -46,12 +46,10 @@ const SpoonDetailPage = () => {
}) ?? [];
const pullRequests =
useQuery(api.spoonPullRequests.listForSpoon, { spoonId, limit: 100 }) ?? [];
const reviews =
useQuery(api.aiReviews.listForSpoon, { spoonId, limit: 25 }) ?? [];
const syncRuns =
useQuery(api.syncRuns.listForSpoon, { spoonId, limit: 25 }) ?? [];
const agentRequests =
useQuery(api.agentRequests.listForSpoon, { spoonId, limit: 25 }) ?? [];
const threads =
useQuery(api.threads.listForSpoon, { spoonId, limit: 25 }) ?? [];
const agentSettings = useQuery(api.spoonAgentSettings.getForSpoon, {
spoonId,
});
@@ -68,7 +66,7 @@ const SpoonDetailPage = () => {
<SpoonMetrics
spoon={details.spoon}
state={details.state}
latestReview={details.latestReview}
latestThread={threads[0]}
/>
{details.spoon.lastError ? (
<Card className='border-destructive shadow-none'>
@@ -95,11 +93,8 @@ const SpoonDetailPage = () => {
<TabsTrigger className='h-9 flex-none px-3' value='pulls'>
Pull requests
</TabsTrigger>
<TabsTrigger className='h-9 flex-none px-3' value='ai'>
AI review
</TabsTrigger>
<TabsTrigger className='h-9 flex-none px-3' value='agent'>
Agent work
<TabsTrigger className='h-9 flex-none px-3' value='threads'>
Threads
</TabsTrigger>
<TabsTrigger className='h-9 flex-none px-3' value='activity'>
Activity
@@ -125,6 +120,17 @@ const SpoonDetailPage = () => {
'unknown'
).replaceAll('_', ' ')}
</p>
{details.effectiveUpstreamAheadBy === 0 &&
(details.state?.upstreamAheadBy ??
details.spoon.upstreamAheadBy ??
0) > 0 ? (
<p className='text-muted-foreground mt-1 text-xs'>
Up to date after ignored upstream changes. Raw upstream
ahead:{' '}
{details.state?.upstreamAheadBy ??
details.spoon.upstreamAheadBy}
</p>
) : null}
</div>
<div>
<p className='text-muted-foreground'>Default branches</p>
@@ -155,37 +161,34 @@ const SpoonDetailPage = () => {
</Card>
<Card className='shadow-none'>
<CardHeader className='pb-3'>
<CardTitle className='text-base'>Latest AI review</CardTitle>
<CardTitle className='text-base'>Latest thread</CardTitle>
</CardHeader>
<CardContent className='space-y-3 text-sm'>
{details.latestReview ? (
{threads[0] ? (
<>
<div className='grid grid-cols-2 gap-3'>
<div>
<p className='text-muted-foreground'>Risk</p>
<p className='text-muted-foreground'>Status</p>
<p className='mt-1 font-semibold capitalize'>
{details.latestReview.risk}
{threads[0].status.replaceAll('_', ' ')}
</p>
</div>
<div>
<p className='text-muted-foreground'>Action</p>
<p className='text-muted-foreground'>Source</p>
<p className='mt-1 font-semibold capitalize'>
{details.latestReview.recommendedAction.replaceAll(
'_',
' ',
)}
{threads[0].source.replaceAll('_', ' ')}
</p>
</div>
</div>
<p className='text-muted-foreground'>
{details.latestReview.outputSummary ??
details.latestReview.inputSummary}
{threads[0].summary ??
'Open the thread to continue maintenance work.'}
</p>
</>
) : (
<p className='text-muted-foreground'>
Run a refresh and AI review to get a compatibility summary
for upstream changes.
Refresh GitHub state or create a thread to start maintenance
work for this Spoon.
</p>
)}
</CardContent>
@@ -239,26 +242,45 @@ const SpoonDetailPage = () => {
<SpoonPrList pullRequests={pullRequests} />
</TabsContent>
<TabsContent value='ai' className='space-y-4'>
<SpoonAiReviewPanel
latestReview={details.latestReview}
reviews={reviews}
/>
</TabsContent>
<TabsContent value='agent' className='space-y-4'>
<TabsContent value='threads' className='space-y-4'>
<AgentRequestForm
spoon={details.spoon}
agentSettings={agentSettings}
/>
<Card className='shadow-none'>
<CardHeader>
<CardTitle className='text-base'>Spoon threads</CardTitle>
</CardHeader>
<CardContent className='space-y-3'>
{threads.length ? (
threads.map((thread) => (
<Link
key={thread._id}
href={`/threads/${thread._id}`}
className='border-border hover:border-primary/50 block rounded-md border p-3 transition-colors'
>
<p className='font-medium'>{thread.title}</p>
<p className='text-muted-foreground mt-1 text-sm'>
{thread.status.replaceAll('_', ' ')} ·{' '}
{thread.source.replaceAll('_', ' ')}
</p>
</Link>
))
) : (
<p className='text-muted-foreground text-sm'>
No threads exist for this Spoon yet.
</p>
)}
</CardContent>
</Card>
<AgentJobList jobs={agentJobs} />
</TabsContent>
<TabsContent value='activity'>
<SpoonActivityTimeline
syncRuns={syncRuns}
reviews={reviews}
requests={agentRequests}
threads={threads}
jobs={agentJobs}
/>
</TabsContent>