Refine touched issue unread indicators in inbox
This commit is contained in:
@@ -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)}`
|
||||||
|
|||||||
Reference in New Issue
Block a user