Hacktoberfest 2026:维护者为十月标记出来的 issue,仍然开放、适合新手。 浏览 Hacktoberfest issue

[Bug]: Sidebar "Failed" badge does not clear when the session is opened (unlike "Done")

未关闭 适合新手
#25,470 1 条评论 0 个 reaction 已指派 0 人 在 GitHub 查看

维护者通常 1 天内回复

@BilalIrfanContact 已经在做这个了。

开始于 2026年10月26日。

  • #25663 来自 @BilalIrfanContact —— 未关闭

评估

难度
2/5
预计耗时
1-3 小时
新手友好度
86/100
Issue 类型
缺陷
描述清晰度
描述清楚
活跃度
活跃
技术栈
react, typescript
领域
frontend

调研方向

阅读 apps/web/src/components/sidebar/SidebarThreadRow.tsx 第 684–754 行附近,以及 apps/web/src/components/Sidebar.logic.ts 第 689–708 行附近的 helper hasUnseenCompletion 和 hasUnseenFailure。追踪打开 thread 时 lastVisitedAt 的更新方式,然后检查相关的 sidebar 测试,再让失败徽标采用与 Done 相同的未查看状态行为。Done 表示打开失败的 session 会清除其徽标,包括 completedAt 为 null 的情况。

由索引模型根据 Issue 内容生成。

描述

Before submitting
  • I searched existing issues and did not find a duplicate.
  • I included enough detail to reproduce or investigate the problem.
Area

apps/web

Summary

In the sidebar thread list, a session card shows a status badge. A "Done" badge clears as soon as you open that session, but a "Failed" badge does not — it stays on the card even after the session has been opened/selected in the sidebar.

The two badges should behave the same way: opening the session should acknowledge (clear) its terminal status badge, whether it finished successfully or failed.

Steps to reproduce
  1. Have a session run to completion successfully → its sidebar card shows a green Done badge.
  2. Click/open that session → the Done badge clears.
  3. Have another session end in failure → its sidebar card shows a red Failed badge (e.g. "Audit private file for public exposure" → (!) Failed).
  4. Click/open that session.
  5. Observe: the Failed badge is still on the card.
Expected behavior

Opening a failed session clears (acknowledges) its Failed badge, mirroring how opening a completed session clears the Done badge.

Actual behavior

The Failed badge persists on the sidebar card after the session is opened. Only Done clears on open.

Impact

Minor bug or occasional failure

Root cause (investigated)

The two badges are gated by two independent mechanisms. "Done" is gated by a visited-watermark comparison (so opening clears it); "Failed" is gated by the raw runtime status (which opening never touches).

The card and its two inputs — apps/web/src/components/sidebar/SidebarThreadRow.tsx:684-685

const isUnread = hasUnseenCompletion({ ...thread, lastVisitedAt });
const status = resolveSidebarThreadStatus(thread);

The badge ternary — SidebarThreadRow.tsx:736-754. failed is tested before isUnread, and the two come from different sources:

: status === "failed"
  ? { label: "Failed", icon: "failed", className: "text-error-foreground" }
  : isWoke
    ? { label: "Woke", ... }
    : isUnread
      ? { label: "Done", icon: "done", className: "text-success-foreground" }
      : null;
  • Done ⇐ isUnread ⇐ hasUnseenCompletion(...) — a watermark comparison.
  • Failed ⇐ status === "failed" ⇐ resolveSidebarThreadStatus(...) — raw runtime status, no watermark.

Why Done clears on open — hasUnseenCompletion compares against lastVisitedAt (apps/web/src/components/Sidebar.logic.ts:689-698):

export function hasUnseenCompletion(thread: ThreadStatusInput): boolean {
  if (!thread.latestRun?.completedAt) return false;
  const completedAt = Date.parse(thread.latestRun.completedAt);
  if (Number.isNaN(completedAt)) return false;
  if (!thread.lastVisitedAt) return false;
  const lastVisitedAt = Date.parse(thread.lastVisitedAt);
  if (Number.isNaN(lastVisitedAt)) return true;
  return completedAt > lastVisitedAt;   // false once visited after completion
}

Why Failed does NOT clear on open — resolveSidebarThreadStatus returns "failed" purely from live runtime status, with no lastVisitedAt/seen input (Sidebar.logic.ts:868-895):

if (thread.runtime?.status === "failed") {
  return thread.runtime.lastErrorClass === "usage_limit" ? "limited" : "failed";
}

Opening a thread advances lastVisitedAt (local via apps/web/src/components/chat/ThreadVisitWatermark.tsx → markThreadVisited in apps/web/src/uiStateStore.ts:244; server via packages/client-runtime/src/state/threadVisits.ts). That flips hasUnseenCompletion to false (Done clears) but leaves thread.runtime.status === "failed" untouched (Failed stays).

Note: an unused hasUnseenFailure already exists

Sidebar.logic.ts:700-708 already defines a hasUnseenFailure(...) helper, but the active sidebar card (SidebarThreadRow.tsx) does not use it — it uses the raw resolveSidebarThreadStatus status instead. hasUnseenFailure is also asymmetric with hasUnseenCompletion:

function hasUnseenFailure(thread: ThreadStatusInput): boolean {
  if (thread.latestRun?.status !== "failed") return false;
  if (!thread.lastVisitedAt) return true;
  const completedAt = Date.parse(thread.latestRun.completedAt ?? "");
  const lastVisitedAt = Date.parse(thread.lastVisitedAt);
  if (Number.isNaN(completedAt) || Number.isNaN(lastVisitedAt)) return true; // sticky forever when completedAt is null
  return completedAt > lastVisitedAt;
}

A failed run can legitimately have completedAt === null (see packages/client-runtime/src/state/threadSettled.ts:100-108 and packages/client-runtime/src/state/models.ts:260-275, which fall back to runtime.updatedAt). In that case hasUnseenFailure returns true permanently, so even wiring it in as-is would keep the badge sticky for those runs.

Divergence
Badge Source Watermark-gated? Clears on open?
Done hasUnseenCompletion yes (vs lastVisitedAt) ✅
Failed resolveSidebarThreadStatus (runtime.status === "failed") no ❌
Suggested fix direction

Gate the Failed badge the same way as Done in SidebarThreadRow.tsx — surface "failed" only when there is an unseen failure (use/wire in hasUnseenFailure(..., lastVisitedAt)), positioned consistently relative to the isUnread check. While doing so, make hasUnseenFailure symmetric with hasUnseenCompletion: fall back to runtime.updatedAt when latestRun.completedAt is null instead of returning true, so failures with no completedAt can still be acknowledged on open.

Relevant files
  • apps/web/src/components/sidebar/SidebarThreadRow.tsx:684-685, 699-754 — active card badge
  • apps/web/src/components/Sidebar.logic.ts:689-708 (hasUnseenCompletion / unused hasUnseenFailure), 868-895 (resolveSidebarThreadStatus)
  • apps/web/src/components/chat/ThreadVisitWatermark.tsx, apps/web/src/uiStateStore.ts:244, packages/client-runtime/src/state/threadVisits.ts — what advances lastVisitedAt on open
  • packages/client-runtime/src/state/threadSettled.ts:100-108, packages/client-runtime/src/state/models.ts:260-275 — failed runs can have completedAt === null
Version or commit

main @ 0712398

Environment

apps/web (web app). Observed on macOS.


Contributions welcome

This is open for anyone who wants to pick it up — the root cause and a suggested fix direction are laid out above. If you'd like to take it, leave a comment so we don't double up, and feel free to ask questions on the issue.

主要语言
TypeScript
星标
24.8k
派生
6.4k
平均合并
3 小时 21 分钟
30 天内合并 PR
297

环境准备

在 Codespaces 中打开

在浏览器里用你自己的 GitHub 账号启动这个项目的开发容器。

从这里开始

  1. 先读完整个 Issue,再读项目的贡献指南。
  2. 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
  3. Fork 仓库,在一个分支上完成修改。
  4. 提交 Pull Request,并在描述里引用这个 Issue 编号。

pingdotgg/t3code 的其他 Issue

查看 pingdotgg/t3code 的全部 Issue

相似的 Issue

更多 TypeScript Issue

把新 issue 发到你的邮箱

精选适合新手参与的 GitHub issue 摘要。