diff --git a/docs/USER_GUIDE.md b/docs/USER_GUIDE.md index 6f1c80d4..949b4ea6 100644 --- a/docs/USER_GUIDE.md +++ b/docs/USER_GUIDE.md @@ -110,8 +110,8 @@ When a group is collapsed, a brief preview of any status change detected by the The **Scope** filter chip appears on the Issues and Pull Requests tabs when you have tracked users configured or monitor-all repos enabled. It has two options: -- **Involves me** (default) — shows only items where you (the signed-in user) are the author, assignee, reviewer, or mentioned. For monitored repos, all activity in that repo is always shown regardless of scope. -- **All activity** — shows every open item across your selected repos. Items that involve you are highlighted with a blue left border. +- **Involves me** (default) — shows items where you or any of your tracked users are involved (author, assignee, reviewer, or mentioned). For monitored repos, only items where you are the author, assignee, or reviewer are shown. +- **All activity** — shows every open item across your selected repos. Items involving you or your tracked users are highlighted with a colored left border. The scope filter is hidden (and always set to "Involves me") when you have no tracked users and no monitor-all repos, because in that configuration all fetched data already involves you. @@ -251,7 +251,7 @@ Normally, the dashboard shows only issues and PRs that involve you (or a tracked **How to enable:** In **Settings > Repositories**, expand the repo panel. Each repo has an eye icon toggle. Enabling it adds the repo to the monitored list (maximum 10 monitored repos). -**Effect on display:** Repo groups for monitored repos show a **Monitoring all** badge in their header. Items from monitored repos are always visible even when the Scope filter is set to "Involves me", and they bypass the User filter. +**Effect on display:** Repo groups for monitored repos show a **Monitoring all** badge in their header. In "Involves me" scope, monitored repo items are shown when you are the author, assignee, or reviewer; switch to "All activity" to see all monitored repo items. Monitored repo items bypass the User filter. Upstream repos cannot be monitored (only selected repos are eligible). @@ -447,7 +447,7 @@ These are UI preferences that persist across sessions but are not included in th **Items I expect to see are not showing up.** -- Check that the Scope filter is set correctly. "Involves me" hides items where you have no direct involvement. Switch to "All activity" to see everything. +- Check that the Scope filter is set correctly. "Involves me" shows items involving you or your tracked users; items from monitored repos where you are not directly involved (author, assignee, or reviewer) are hidden. Switch to "All activity" to see everything. - Verify the repo is in your selected repo list (Settings > Repositories). - Check if the item was accidentally ignored (toolbar Ignored badge). - If you recently added the repo, wait for the next full refresh or click the manual refresh button. diff --git a/src/app/lib/grouping.ts b/src/app/lib/grouping.ts index f0f49cf6..3e771a8e 100644 --- a/src/app/lib/grouping.ts +++ b/src/app/lib/grouping.ts @@ -77,7 +77,8 @@ export function orderRepoGroups( * Three-tier involvement check for scope filtering. * Shared by IssuesTab and PullRequestsTab — keep both call sites in sync. * - * Tier 1: surfacedBy annotation present → check if user is included + * Tier 1: surfacedBy annotation present → pass (item was found via an involves: + * search for the main user or a tracked user — always relevant) * Tier 2: monitored repo (no surfacedBy) → field-based fallback (author/assignee) * Pass reviewerLogins for PRs (only when enriched — unenriched PRs have []) * Tier 3: non-monitored, no surfacedBy → pass (fetched via involves:{user}) @@ -89,7 +90,7 @@ export function isUserInvolved( reviewerLogins?: string[], ): boolean { const surfacedBy = item.surfacedBy ?? []; - if (surfacedBy.length > 0) return surfacedBy.includes(login); + if (surfacedBy.length > 0) return true; if (monitoredRepos.has(item.repoFullName)) { return item.userLogin.toLowerCase() === login || item.assigneeLogins.some(a => a.toLowerCase() === login) || diff --git a/tests/components/dashboard/IssuesTab.test.tsx b/tests/components/dashboard/IssuesTab.test.tsx index c10acd25..310cfca3 100644 --- a/tests/components/dashboard/IssuesTab.test.tsx +++ b/tests/components/dashboard/IssuesTab.test.tsx @@ -322,10 +322,10 @@ describe("IssuesTab — monitored repos filter bypass", () => { // ── IssuesTab — scope filter ─────────────────────────────────────────────────── describe("IssuesTab — scope filter", () => { - it("default scope shows only items involving the user (surfacedBy includes userLogin)", () => { + it("default scope shows items surfaced by tracked users (surfacedBy present)", () => { const issues = [ makeIssue({ id: 1, title: "My issue", repoFullName: "org/repo", surfacedBy: ["me"] }), - makeIssue({ id: 2, title: "Community issue", repoFullName: "org/repo", surfacedBy: ["other"] }), + makeIssue({ id: 2, title: "Tracked User issue", repoFullName: "org/repo", surfacedBy: ["other"] }), ]; setAllExpanded("issues", ["org/repo"], true); @@ -339,7 +339,7 @@ describe("IssuesTab — scope filter", () => { )); screen.getByText("My issue"); - expect(screen.queryByText("Community issue")).toBeNull(); + screen.getByText("Tracked User issue"); }); it("scope 'all' shows all items including community items", () => { @@ -437,9 +437,9 @@ describe("IssuesTab — left border accent in 'all' scope", () => { expect(listitem?.className).toContain("border-l-primary"); }); - it("does not add border-l-2 to community items in 'all' scope", () => { + it("does not add border-l-2 to untracked monitored repo items in 'all' scope", () => { const issues = [ - makeIssue({ id: 1, title: "Community issue", repoFullName: "org/monitored", surfacedBy: ["other"], userLogin: "other", assigneeLogins: [] }), + makeIssue({ id: 1, title: "Community issue", repoFullName: "org/monitored", userLogin: "other", assigneeLogins: [] }), ]; setTabFilter("issues", "scope", "all"); setAllExpanded("issues", ["org/monitored"], true); @@ -456,6 +456,25 @@ describe("IssuesTab — left border accent in 'all' scope", () => { expect(listitem?.className).not.toContain("border-l-primary"); }); + it("adds border-l-primary to tracked user issue in monitored repo in 'all' scope", () => { + const issues = [ + makeIssue({ id: 1, title: "Bot issue", repoFullName: "org/monitored", surfacedBy: ["tracked-bot[bot]"] }), + ]; + setTabFilter("issues", "scope", "all"); + setAllExpanded("issues", ["org/monitored"], true); + + const { container } = render(() => ( + + )); + + const listitem = container.querySelector('[role="listitem"]'); + expect(listitem?.className).toContain("border-l-primary"); + }); + it("does not add border-l-2 in default 'involves_me' scope", () => { const issues = [ makeIssue({ id: 1, title: "My issue", repoFullName: "org/repo", surfacedBy: ["me"] }), diff --git a/tests/components/dashboard/PullRequestsTab.test.tsx b/tests/components/dashboard/PullRequestsTab.test.tsx index 4ef55da8..7007fe9c 100644 --- a/tests/components/dashboard/PullRequestsTab.test.tsx +++ b/tests/components/dashboard/PullRequestsTab.test.tsx @@ -272,10 +272,10 @@ describe("PullRequestsTab — monitored repos filter bypass", () => { // ── PullRequestsTab — scope filter ──────────────────────────────────────────── describe("PullRequestsTab — scope filter", () => { - it("default scope shows only items involving the user (surfacedBy includes userLogin)", () => { + it("default scope shows items surfaced by tracked users (surfacedBy present)", () => { const prs = [ makePullRequest({ id: 1, title: "My PR", repoFullName: "org/repo", surfacedBy: ["me"] }), - makePullRequest({ id: 2, title: "Community PR", repoFullName: "org/repo", surfacedBy: ["other"] }), + makePullRequest({ id: 2, title: "Tracked User PR", repoFullName: "org/repo", surfacedBy: ["other"] }), ]; setAllExpanded("pullRequests", ["org/repo"], true); @@ -289,7 +289,7 @@ describe("PullRequestsTab — scope filter", () => { )); screen.getByText("My PR"); - expect(screen.queryByText("Community PR")).toBeNull(); + screen.getByText("Tracked User PR"); }); it("scope 'all' shows all PRs including community items", () => { @@ -387,9 +387,9 @@ describe("PullRequestsTab — left border accent in 'all' scope", () => { expect(listitem?.className).toContain("border-l-primary"); }); - it("does not add border-l-primary to community PRs in 'all' scope", () => { + it("does not add border-l-primary to untracked monitored repo PRs in 'all' scope", () => { const prs = [ - makePullRequest({ id: 1, title: "Community PR", repoFullName: "org/monitored", surfacedBy: ["other"], userLogin: "other", assigneeLogins: [], reviewerLogins: [] }), + makePullRequest({ id: 1, title: "Community PR", repoFullName: "org/monitored", userLogin: "other", assigneeLogins: [], reviewerLogins: [] }), ]; setTabFilter("pullRequests", "scope", "all"); setAllExpanded("pullRequests", ["org/monitored"], true); @@ -406,6 +406,25 @@ describe("PullRequestsTab — left border accent in 'all' scope", () => { expect(listitem?.className).not.toContain("border-l-primary"); }); + it("adds border-l-primary to tracked user PR in monitored repo in 'all' scope", () => { + const prs = [ + makePullRequest({ id: 1, title: "Bot PR", repoFullName: "org/monitored", surfacedBy: ["tracked-bot[bot]"] }), + ]; + setTabFilter("pullRequests", "scope", "all"); + setAllExpanded("pullRequests", ["org/monitored"], true); + + const { container } = render(() => ( + + )); + + const listitem = container.querySelector('[role="listitem"]'); + expect(listitem?.className).toContain("border-l-primary"); + }); + it("does not add border-l-primary in default 'involves_me' scope", () => { const prs = [ makePullRequest({ id: 1, title: "My PR", repoFullName: "org/repo", surfacedBy: ["me"] }), diff --git a/tests/lib/grouping.test.ts b/tests/lib/grouping.test.ts index 08da07a7..79165c94 100644 --- a/tests/lib/grouping.test.ts +++ b/tests/lib/grouping.test.ts @@ -63,12 +63,20 @@ describe("isUserInvolved", () => { const base = { repoFullName: "org/repo", userLogin: "author", assigneeLogins: [] as string[] }; const monitored = new Set(["org/monitored"]); - it("returns true when surfacedBy includes user", () => { + it("returns true when surfacedBy is non-empty (main user)", () => { expect(isUserInvolved({ ...base, surfacedBy: ["me"] }, "me", monitored)).toBe(true); }); - it("returns false when surfacedBy excludes user", () => { - expect(isUserInvolved({ ...base, surfacedBy: ["other"] }, "me", monitored)).toBe(false); + it("returns true when surfacedBy contains only a tracked user (not the main user)", () => { + expect(isUserInvolved({ ...base, surfacedBy: ["tracked-bot[bot]"] }, "me", monitored)).toBe(true); + }); + + it("returns true when surfacedBy contains multiple tracked users but not the main user", () => { + expect(isUserInvolved({ ...base, surfacedBy: ["bot1[bot]", "bot2"] }, "me", monitored)).toBe(true); + }); + + it("returns true for monitored repo item with surfacedBy (tier 1 before tier 2)", () => { + expect(isUserInvolved({ ...base, repoFullName: "org/monitored", surfacedBy: ["tracked-bot[bot]"] }, "me", monitored)).toBe(true); }); it("returns true for non-monitored item with no surfacedBy (fetched via involves:{user})", () => {