Refine touched issue unread indicators in inbox

This commit is contained in:
Dotta
2026-03-06 09:35:36 -06:00
parent a57732f7dd
commit c0c64fe682

View File

@@ -35,7 +35,6 @@ import {
ArrowUpRight, ArrowUpRight,
XCircle, XCircle,
X, X,
UserCheck,
RotateCcw, RotateCcw,
} from "lucide-react"; } from "lucide-react";
import { Identity } from "../components/Identity"; import { Identity } from "../components/Identity";
@@ -137,6 +136,19 @@ function runFailureMessage(run: HeartbeatRun): string {
return firstNonEmptyLine(run.error) ?? firstNonEmptyLine(run.stderrExcerpt) ?? "Run exited with an error."; return firstNonEmptyLine(run.error) ?? firstNonEmptyLine(run.stderrExcerpt) ?? "Run exited with an error.";
} }
function normalizeTimestamp(value: string | Date | null | undefined): number {
if (!value) return 0;
const timestamp = new Date(value).getTime();
return Number.isFinite(timestamp) ? timestamp : 0;
}
function issueLastActivityTimestamp(issue: Issue): number {
return Math.max(
normalizeTimestamp(issue.updatedAt),
normalizeTimestamp(issue.lastExternalCommentAt),
);
}
function readIssueIdFromRun(run: HeartbeatRun): string | null { function readIssueIdFromRun(run: HeartbeatRun): string | null {
const context = run.contextSnapshot; const context = run.contextSnapshot;
if (!context) return null; if (!context) return null;
@@ -357,7 +369,7 @@ export function Inbox() {
queryFn: () => queryFn: () =>
issuesApi.list(selectedCompanyId!, { issuesApi.list(selectedCompanyId!, {
touchedByUserId: "me", touchedByUserId: "me",
status: "backlog,todo,in_progress,in_review,blocked", status: "backlog,todo,in_progress,in_review,blocked,done",
}), }),
enabled: !!selectedCompanyId, enabled: !!selectedCompanyId,
}); });
@@ -372,16 +384,14 @@ export function Inbox() {
() => (issues ? getStaleIssues(issues) : []).filter((i) => !dismissed.has(`stale:${i.id}`)), () => (issues ? getStaleIssues(issues) : []).filter((i) => !dismissed.has(`stale:${i.id}`)),
[issues, dismissed], [issues, dismissed],
); );
const sortByRecentExternalComment = useCallback((a: Issue, b: Issue) => { const sortByMostRecentActivity = useCallback(
const aExternal = a.lastExternalCommentAt ? new Date(a.lastExternalCommentAt).getTime() : 0; (a: Issue, b: Issue) => issueLastActivityTimestamp(b) - issueLastActivityTimestamp(a),
const bExternal = b.lastExternalCommentAt ? new Date(b.lastExternalCommentAt).getTime() : 0; [],
if (aExternal !== bExternal) return bExternal - aExternal; );
return new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime();
}, []);
const touchedIssues = useMemo( const touchedIssues = useMemo(
() => [...touchedIssuesRaw].sort(sortByRecentExternalComment), () => [...touchedIssuesRaw].sort(sortByMostRecentActivity),
[sortByRecentExternalComment, touchedIssuesRaw], [sortByMostRecentActivity, touchedIssuesRaw],
); );
const agentById = useMemo(() => { const agentById = useMemo(() => {
@@ -642,22 +652,22 @@ export function Inbox() {
to={`/issues/${issue.identifier ?? issue.id}`} to={`/issues/${issue.identifier ?? issue.id}`}
className="flex cursor-pointer items-center gap-3 px-4 py-3 transition-colors hover:bg-accent/50 no-underline text-inherit" className="flex cursor-pointer items-center gap-3 px-4 py-3 transition-colors hover:bg-accent/50 no-underline text-inherit"
> >
<UserCheck className="h-4 w-4 shrink-0 text-blue-600 dark:text-blue-400" /> <span className="flex w-4 shrink-0 justify-center">
<span
className={`h-2.5 w-2.5 rounded-full ${
issue.isUnreadForMe
? "bg-blue-600 dark:bg-blue-400"
: "border border-muted-foreground/40 bg-transparent"
}`}
aria-label={issue.isUnreadForMe ? "Unread" : "Read"}
/>
</span>
<PriorityIcon priority={issue.priority} /> <PriorityIcon priority={issue.priority} />
<StatusIcon status={issue.status} /> <StatusIcon status={issue.status} />
<span className="text-xs font-mono text-muted-foreground"> <span className="text-xs font-mono text-muted-foreground">
{issue.identifier ?? issue.id.slice(0, 8)} {issue.identifier ?? issue.id.slice(0, 8)}
</span> </span>
<span className="flex-1 truncate text-sm">{issue.title}</span> <span className="flex-1 truncate text-sm">{issue.title}</span>
<span
className={`shrink-0 rounded-full px-2 py-0.5 text-[10px] font-medium ${
issue.isUnreadForMe
? "bg-blue-500/20 text-blue-600 dark:text-blue-400"
: "bg-muted text-muted-foreground"
}`}
>
{issue.isUnreadForMe ? "Unread" : "Read"}
</span>
<span className="shrink-0 text-xs text-muted-foreground"> <span className="shrink-0 text-xs text-muted-foreground">
{issue.lastExternalCommentAt {issue.lastExternalCommentAt
? `commented ${timeAgo(issue.lastExternalCommentAt)}` ? `commented ${timeAgo(issue.lastExternalCommentAt)}`