Move sub-issues inline and remove sub-issues tab

- When no sub-issues exist, show "Add sub-issue" button alongside
  "Upload attachment" and "New document" in the action row
- When sub-issues exist, show them in a dedicated section above
  Documents with "Sub-issues" header and "Add sub-issue" button
- Remove the sub-issues tab from the comments/activity tabs

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
dotta 2026-04-06 11:59:37 -05:00
parent 1b55474a9b
commit c414790404

View file

@ -1487,6 +1487,50 @@ export function IssueDetail() {
missingBehavior="placeholder"
/>
{childIssues.length > 0 && (
<div className="space-y-3">
<div className="flex items-center justify-between gap-2">
<h3 className="text-sm font-medium text-muted-foreground">Sub-issues</h3>
<Button variant="outline" size="sm" onClick={openNewSubIssue} className="shadow-none">
<ListTree className="h-3.5 w-3.5 mr-1.5" />
<span className="hidden sm:inline">Add sub-issue</span>
<span className="sm:hidden">Sub-issue</span>
</Button>
</div>
<div className="border border-border rounded-lg divide-y divide-border">
{childIssues.map((child) => (
<Link
key={child.id}
to={createIssueDetailPath(child.identifier ?? child.id)}
state={resolvedIssueDetailState ?? location.state}
onClickCapture={() =>
rememberIssueDetailLocationState(
child.identifier ?? child.id,
resolvedIssueDetailState ?? location.state,
location.search,
)}
className="flex items-center justify-between px-3 py-2 text-sm hover:bg-accent/20 transition-colors"
>
<div className="flex items-center gap-2 min-w-0">
<StatusIcon status={child.status} />
<PriorityIcon priority={child.priority} />
<span className="font-mono text-muted-foreground shrink-0">
{child.identifier ?? child.id.slice(0, 8)}
</span>
<span className="truncate">{child.title}</span>
</div>
{child.assigneeAgentId && (() => {
const name = agentMap.get(child.assigneeAgentId)?.name;
return name
? <Identity name={name} size="sm" />
: <span className="text-muted-foreground font-mono">{child.assigneeAgentId.slice(0, 8)}</span>;
})()}
</Link>
))}
</div>
</div>
)}
<IssueDocumentsSection
issue={issue}
canDeleteDocuments={Boolean(session?.user?.id)}
@ -1508,7 +1552,18 @@ export function IssueDetail() {
sharingPreferenceAtSubmit: feedbackDataSharingPreference,
});
}}
extraActions={!hasAttachments ? attachmentUploadButton : undefined}
extraActions={
<>
{!hasAttachments && attachmentUploadButton}
{childIssues.length === 0 && (
<Button variant="outline" size="sm" onClick={openNewSubIssue} className="shadow-none">
<ListTree className="h-3.5 w-3.5 mr-1.5" />
<span className="hidden sm:inline">Add sub-issue</span>
<span className="sm:hidden">Sub-issue</span>
</Button>
)}
</>
}
/>
{hasAttachments ? (
@ -1662,10 +1717,6 @@ export function IssueDetail() {
<MessageSquare className="h-3.5 w-3.5" />
Comments
</TabsTrigger>
<TabsTrigger value="subissues" className="gap-1.5">
<ListTree className="h-3.5 w-3.5" />
Sub-issues
</TabsTrigger>
<TabsTrigger value="activity" className="gap-1.5">
<ActivityIcon className="h-3.5 w-3.5" />
Activity
@ -1738,49 +1789,6 @@ export function IssueDetail() {
/>
</TabsContent>
<TabsContent value="subissues">
<div className="mb-3 flex items-center justify-end">
<Button variant="outline" size="sm" onClick={openNewSubIssue}>
Add sub-issue
</Button>
</div>
{childIssues.length === 0 ? (
<p className="text-xs text-muted-foreground">No sub-issues.</p>
) : (
<div className="border border-border rounded-lg divide-y divide-border">
{childIssues.map((child) => (
<Link
key={child.id}
to={createIssueDetailPath(child.identifier ?? child.id)}
state={resolvedIssueDetailState ?? location.state}
onClickCapture={() =>
rememberIssueDetailLocationState(
child.identifier ?? child.id,
resolvedIssueDetailState ?? location.state,
location.search,
)}
className="flex items-center justify-between px-3 py-2 text-sm hover:bg-accent/20 transition-colors"
>
<div className="flex items-center gap-2 min-w-0">
<StatusIcon status={child.status} />
<PriorityIcon priority={child.priority} />
<span className="font-mono text-muted-foreground shrink-0">
{child.identifier ?? child.id.slice(0, 8)}
</span>
<span className="truncate">{child.title}</span>
</div>
{child.assigneeAgentId && (() => {
const name = agentMap.get(child.assigneeAgentId)?.name;
return name
? <Identity name={name} size="sm" />
: <span className="text-muted-foreground font-mono">{child.assigneeAgentId.slice(0, 8)}</span>;
})()}
</Link>
))}
</div>
)}
</TabsContent>
<TabsContent value="activity">
{linkedApprovals && linkedApprovals.length > 0 && (
<div className="mb-3 space-y-3">