From ed99525ef14e84760f79c99e8cdf07ab72a4208f Mon Sep 17 00:00:00 2001 From: M Waleed Kadous Date: Wed, 1 Apr 2026 15:16:57 -0700 Subject: [PATCH 1/6] [Spec 650][Phase: backend-url-fix] Add url field to recent activity GraphQL data --- packages/codev/dashboard/src/lib/api.ts | 4 ++-- packages/codev/src/__tests__/team-github.test.ts | 4 ++-- packages/codev/src/lib/team-github.ts | 16 ++++++++-------- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/packages/codev/dashboard/src/lib/api.ts b/packages/codev/dashboard/src/lib/api.ts index bbf7e146..b192413a 100644 --- a/packages/codev/dashboard/src/lib/api.ts +++ b/packages/codev/dashboard/src/lib/api.ts @@ -57,8 +57,8 @@ export interface TeamMemberGitHubData { assignedIssues: { number: number; title: string; url: string }[]; openPRs: { number: number; title: string; url: string }[]; recentActivity: { - mergedPRs: { number: number; title: string; mergedAt: string }[]; - closedIssues: { number: number; title: string; closedAt: string }[]; + mergedPRs: { number: number; title: string; url: string; mergedAt: string }[]; + closedIssues: { number: number; title: string; url: string; closedAt: string }[]; }; } diff --git a/packages/codev/src/__tests__/team-github.test.ts b/packages/codev/src/__tests__/team-github.test.ts index 019a537e..f56a5e61 100644 --- a/packages/codev/src/__tests__/team-github.test.ts +++ b/packages/codev/src/__tests__/team-github.test.ts @@ -101,10 +101,10 @@ describe('parseTeamGraphQLResponse', () => { nodes: [{ number: 10, title: 'Feature PR', url: 'https://github.com/org/repo/pull/10' }], }, u_alice_merged: { - nodes: [{ number: 5, title: 'Old PR', mergedAt: '2026-03-07T10:00:00Z' }], + nodes: [{ number: 5, title: 'Old PR', url: 'https://github.com/org/repo/pull/5', mergedAt: '2026-03-07T10:00:00Z' }], }, u_alice_closed: { - nodes: [{ number: 2, title: 'Done issue', closedAt: '2026-03-06T15:00:00Z' }], + nodes: [{ number: 2, title: 'Done issue', url: 'https://github.com/org/repo/issues/2', closedAt: '2026-03-06T15:00:00Z' }], }, }; diff --git a/packages/codev/src/lib/team-github.ts b/packages/codev/src/lib/team-github.ts index 72e1c9a7..daf907c9 100644 --- a/packages/codev/src/lib/team-github.ts +++ b/packages/codev/src/lib/team-github.ts @@ -23,8 +23,8 @@ export interface TeamMemberGitHubData { assignedIssues: { number: number; title: string; url: string }[]; openPRs: { number: number; title: string; url: string }[]; recentActivity: { - mergedPRs: { number: number; title: string; mergedAt: string }[]; - closedIssues: { number: number; title: string; closedAt: string }[]; + mergedPRs: { number: number; title: string; url: string; mergedAt: string }[]; + closedIssues: { number: number; title: string; url: string; closedAt: string }[]; }; } @@ -92,10 +92,10 @@ export function buildTeamGraphQLQuery(members: TeamMember[], owner: string, name nodes { ... on PullRequest { number title url } } } ${alias}_merged: search(query: "repo:${repo} author:${m.github} is:pr is:merged merged:>=${since}", type: ISSUE, first: 20) { - nodes { ... on PullRequest { number title mergedAt } } + nodes { ... on PullRequest { number title url mergedAt } } } ${alias}_closed: search(query: "repo:${repo} assignee:${m.github} is:issue is:closed closed:>=${since}", type: ISSUE, first: 20) { - nodes { ... on Issue { number title closedAt } } + nodes { ... on Issue { number title url closedAt } } }`; }) .join('\n'); @@ -120,15 +120,15 @@ export function parseTeamGraphQLResponse( const alias = toAlias(member.github); const assigned = data[`${alias}_assigned`] as { nodes?: Array<{ number: number; title: string; url: string }> } | undefined; const prs = data[`${alias}_prs`] as { nodes?: Array<{ number: number; title: string; url: string }> } | undefined; - const merged = data[`${alias}_merged`] as { nodes?: Array<{ number: number; title: string; mergedAt: string }> } | undefined; - const closed = data[`${alias}_closed`] as { nodes?: Array<{ number: number; title: string; closedAt: string }> } | undefined; + const merged = data[`${alias}_merged`] as { nodes?: Array<{ number: number; title: string; url: string; mergedAt: string }> } | undefined; + const closed = data[`${alias}_closed`] as { nodes?: Array<{ number: number; title: string; url: string; closedAt: string }> } | undefined; result.set(member.github, { assignedIssues: (assigned?.nodes ?? []).map(n => ({ number: n.number, title: n.title, url: n.url })), openPRs: (prs?.nodes ?? []).map(n => ({ number: n.number, title: n.title, url: n.url })), recentActivity: { - mergedPRs: (merged?.nodes ?? []).map(n => ({ number: n.number, title: n.title, mergedAt: n.mergedAt })), - closedIssues: (closed?.nodes ?? []).map(n => ({ number: n.number, title: n.title, closedAt: n.closedAt })), + mergedPRs: (merged?.nodes ?? []).map(n => ({ number: n.number, title: n.title, url: n.url, mergedAt: n.mergedAt })), + closedIssues: (closed?.nodes ?? []).map(n => ({ number: n.number, title: n.title, url: n.url, closedAt: n.closedAt })), }, }); } From 90e69212afbe463f0279e11df752993bc5ccabe8 Mon Sep 17 00:00:00 2001 From: M Waleed Kadous Date: Wed, 1 Apr 2026 15:19:31 -0700 Subject: [PATCH 2/6] [Spec 650][Phase: backend-url-fix] Add explicit url assertions for recent activity --- packages/codev/src/__tests__/team-github.test.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/codev/src/__tests__/team-github.test.ts b/packages/codev/src/__tests__/team-github.test.ts index f56a5e61..a056edde 100644 --- a/packages/codev/src/__tests__/team-github.test.ts +++ b/packages/codev/src/__tests__/team-github.test.ts @@ -36,6 +36,9 @@ describe('buildTeamGraphQLQuery', () => { expect(query).toContain('u_alice_prs: search('); expect(query).toContain('u_alice_merged: search('); expect(query).toContain('u_alice_closed: search('); + // Verify merged/closed fragments request url + expect(query).toMatch(/on PullRequest \{[^}]*url[^}]*mergedAt/); + expect(query).toMatch(/on Issue \{[^}]*url[^}]*closedAt/); expect(query).toContain('u_bob_assigned: search('); expect(query).toContain('u_bob_prs: search('); }); @@ -116,7 +119,9 @@ describe('parseTeamGraphQLResponse', () => { expect(alice.assignedIssues[0]).toEqual({ number: 1, title: 'Bug fix', url: 'https://github.com/org/repo/issues/1' }); expect(alice.openPRs).toHaveLength(1); expect(alice.recentActivity.mergedPRs).toHaveLength(1); + expect(alice.recentActivity.mergedPRs[0]).toEqual({ number: 5, title: 'Old PR', url: 'https://github.com/org/repo/pull/5', mergedAt: '2026-03-07T10:00:00Z' }); expect(alice.recentActivity.closedIssues).toHaveLength(1); + expect(alice.recentActivity.closedIssues[0]).toEqual({ number: 2, title: 'Done issue', url: 'https://github.com/org/repo/issues/2', closedAt: '2026-03-06T15:00:00Z' }); }); it('handles missing data keys gracefully (empty arrays)', () => { From 9eaee84a92c1086b47aa6ded5cbf283077d8bab8 Mon Sep 17 00:00:00 2001 From: M Waleed Kadous Date: Wed, 1 Apr 2026 15:20:47 -0700 Subject: [PATCH 3/6] [Spec 650][Phase: expanded-member-cards] Show issue/PR titles with links in member cards --- .../dashboard/src/components/TeamView.tsx | 50 ++++++++++++++++--- packages/codev/dashboard/src/index.css | 39 +++++++++++++-- 2 files changed, 80 insertions(+), 9 deletions(-) diff --git a/packages/codev/dashboard/src/components/TeamView.tsx b/packages/codev/dashboard/src/components/TeamView.tsx index 3a16d608..dff4e17c 100644 --- a/packages/codev/dashboard/src/components/TeamView.tsx +++ b/packages/codev/dashboard/src/components/TeamView.tsx @@ -7,8 +7,6 @@ interface TeamViewProps { function MemberCard({ member }: { member: TeamApiMember }) { const gh = member.github_data; - const issueCount = gh?.assignedIssues.length ?? 0; - const prCount = gh?.openPRs.length ?? 0; const mergedCount = gh?.recentActivity.mergedPRs.length ?? 0; const closedCount = gh?.recentActivity.closedIssues.length ?? 0; @@ -26,10 +24,50 @@ function MemberCard({ member }: { member: TeamApiMember }) { > @{member.github} -
- {issueCount} issues - {prCount} PRs -
+ {gh && ( + <> +
+ Working on + {gh.assignedIssues.length > 0 ? ( +
+ {gh.assignedIssues.map(issue => ( + + #{issue.number} {issue.title} + + ))} +
+ ) : ( + No assigned issues + )} +
+
+ Open PRs + {gh.openPRs.length > 0 ? ( +
+ {gh.openPRs.map(pr => ( + + #{pr.number} {pr.title} + + ))} +
+ ) : ( + No open PRs + )} +
+ + )} {(mergedCount > 0 || closedCount > 0) && (
{mergedCount > 0 && {mergedCount} merged} diff --git a/packages/codev/dashboard/src/index.css b/packages/codev/dashboard/src/index.css index 237d89f5..a83b7dcb 100644 --- a/packages/codev/dashboard/src/index.css +++ b/packages/codev/dashboard/src/index.css @@ -1871,11 +1871,44 @@ a.attention-row { text-decoration: underline; } -.team-member-stats { +.team-member-section { + margin-top: 6px; +} + +.team-section-label { + font-size: 10px; + color: var(--text-muted); + text-transform: uppercase; + letter-spacing: 0.3px; + display: block; + margin-bottom: 2px; +} + +.team-item-list { display: flex; - gap: 10px; + flex-direction: column; +} + +.team-item-link { + display: block; font-size: 11px; - color: var(--text-secondary); + color: var(--accent); + text-decoration: none; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + min-width: 0; + padding: 1px 0; +} + +.team-item-link:hover { + text-decoration: underline; +} + +.team-item-empty { + font-size: 11px; + color: var(--text-muted); + font-style: italic; } .team-member-activity { From 664c22e82255632d80dffc32a022ee6dec520e14 Mon Sep 17 00:00:00 2001 From: M Waleed Kadous Date: Wed, 1 Apr 2026 15:24:00 -0700 Subject: [PATCH 4/6] [Spec 650][Phase: activity-feed] Add combined activity feed with relative dates --- .../dashboard/src/components/TeamView.tsx | 67 +++++++++++++++++++ packages/codev/dashboard/src/index.css | 51 ++++++++++++++ 2 files changed, 118 insertions(+) diff --git a/packages/codev/dashboard/src/components/TeamView.tsx b/packages/codev/dashboard/src/components/TeamView.tsx index dff4e17c..c5457ba7 100644 --- a/packages/codev/dashboard/src/components/TeamView.tsx +++ b/packages/codev/dashboard/src/components/TeamView.tsx @@ -91,6 +91,68 @@ function MessageItem({ message }: { message: TeamApiMessage }) { ); } +interface ActivityEntry { + type: 'merged' | 'closed'; + number: number; + title: string; + url: string; + timestamp: string; + author: string; +} + +function relativeDate(isoString: string): string { + const diff = Date.now() - new Date(isoString).getTime(); + const hours = Math.floor(diff / (1000 * 60 * 60)); + if (hours < 1) return 'just now'; + if (hours < 24) return `${hours}h ago`; + const days = Math.floor(hours / 24); + return `${days}d ago`; +} + +function buildActivityFeed(members: TeamApiMember[]): ActivityEntry[] { + const entries: ActivityEntry[] = []; + for (const member of members) { + const gh = member.github_data; + if (!gh) continue; + for (const pr of gh.recentActivity.mergedPRs) { + entries.push({ type: 'merged', number: pr.number, title: pr.title, url: pr.url, timestamp: pr.mergedAt, author: member.github }); + } + for (const issue of gh.recentActivity.closedIssues) { + entries.push({ type: 'closed', number: issue.number, title: issue.title, url: issue.url, timestamp: issue.closedAt, author: member.github }); + } + } + entries.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime()); + return entries; +} + +function ActivityFeed({ members }: { members: TeamApiMember[] }) { + const entries = buildActivityFeed(members); + + if (entries.length === 0) { + return
No recent activity
; + } + + return ( + + ); +} + export function TeamView({ isActive }: TeamViewProps) { const { data, error, loading, refresh } = useTeam(isActive); @@ -146,6 +208,11 @@ export function TeamView({ isActive }: TeamViewProps) {
)} + +
+

Recent Activity

+ +
); diff --git a/packages/codev/dashboard/src/index.css b/packages/codev/dashboard/src/index.css index a83b7dcb..232578a6 100644 --- a/packages/codev/dashboard/src/index.css +++ b/packages/codev/dashboard/src/index.css @@ -1968,6 +1968,57 @@ a.attention-row { padding: 8px 0; } +.team-activity-feed { + display: flex; + flex-direction: column; + gap: 2px; +} + +.team-activity-entry { + display: flex; + align-items: baseline; + gap: 6px; + font-size: 12px; + color: var(--text-secondary); + text-decoration: none; + padding: 4px 8px; + border-radius: 3px; +} + +.team-activity-entry:hover { + background: var(--bg-secondary); +} + +.team-activity-date { + font-size: 10px; + color: var(--text-muted); + min-width: 50px; + flex-shrink: 0; +} + +.team-activity-author { + color: var(--accent); + flex-shrink: 0; +} + +.team-activity-action { + color: var(--text-muted); + flex-shrink: 0; +} + +.team-activity-ref { + color: var(--text-secondary); + flex-shrink: 0; +} + +.team-activity-title { + color: var(--text-secondary); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + min-width: 0; +} + /* Open Files & Shells (Spec 467) */ .ofs-rows { display: flex; From 739d061166e9ea87e2393edb4834f9ff7a1cc687 Mon Sep 17 00:00:00 2001 From: M Waleed Kadous Date: Wed, 1 Apr 2026 15:26:49 -0700 Subject: [PATCH 5/6] [Spec 650][Phase: activity-feed] Add tests for relativeDate and buildActivityFeed --- .../dashboard/__tests__/activityFeed.test.ts | 110 ++++++++++++++++++ .../dashboard/src/components/TeamView.tsx | 6 +- 2 files changed, 113 insertions(+), 3 deletions(-) create mode 100644 packages/codev/dashboard/__tests__/activityFeed.test.ts diff --git a/packages/codev/dashboard/__tests__/activityFeed.test.ts b/packages/codev/dashboard/__tests__/activityFeed.test.ts new file mode 100644 index 00000000..63e3ff7c --- /dev/null +++ b/packages/codev/dashboard/__tests__/activityFeed.test.ts @@ -0,0 +1,110 @@ +/** + * Unit tests for activity feed logic — relativeDate and buildActivityFeed. + */ +import { describe, it, expect, vi, afterEach } from 'vitest'; +import { relativeDate, buildActivityFeed } from '../src/components/TeamView.js'; +import type { TeamApiMember } from '../src/lib/api.js'; + +function makeMember(github: string, data: TeamApiMember['github_data'] = null): TeamApiMember { + return { name: github, github, role: 'developer', filePath: '', github_data: data }; +} + +describe('relativeDate', () => { + afterEach(() => { vi.useRealTimers(); }); + + it('returns "just now" for timestamps less than 1 hour ago', () => { + vi.useFakeTimers(); + vi.setSystemTime(new Date('2026-04-01T12:00:00Z')); + expect(relativeDate('2026-04-01T11:30:00Z')).toBe('just now'); + expect(relativeDate('2026-04-01T11:59:59Z')).toBe('just now'); + }); + + it('returns "Xh ago" for timestamps 1-23 hours ago', () => { + vi.useFakeTimers(); + vi.setSystemTime(new Date('2026-04-01T12:00:00Z')); + expect(relativeDate('2026-04-01T11:00:00Z')).toBe('1h ago'); + expect(relativeDate('2026-04-01T06:00:00Z')).toBe('6h ago'); + expect(relativeDate('2026-03-31T13:00:00Z')).toBe('23h ago'); + }); + + it('returns "Xd ago" for timestamps 24+ hours ago', () => { + vi.useFakeTimers(); + vi.setSystemTime(new Date('2026-04-01T12:00:00Z')); + expect(relativeDate('2026-03-31T12:00:00Z')).toBe('1d ago'); + expect(relativeDate('2026-03-25T12:00:00Z')).toBe('7d ago'); + }); +}); + +describe('buildActivityFeed', () => { + it('returns empty array when no members have activity', () => { + const members = [makeMember('alice', { + assignedIssues: [], openPRs: [], + recentActivity: { mergedPRs: [], closedIssues: [] }, + })]; + expect(buildActivityFeed(members)).toEqual([]); + }); + + it('returns empty array when github_data is null', () => { + expect(buildActivityFeed([makeMember('alice')])).toEqual([]); + }); + + it('aggregates merged PRs and closed issues from multiple members', () => { + const members = [ + makeMember('alice', { + assignedIssues: [], openPRs: [], + recentActivity: { + mergedPRs: [{ number: 10, title: 'PR A', url: 'https://github.com/org/repo/pull/10', mergedAt: '2026-04-01T10:00:00Z' }], + closedIssues: [], + }, + }), + makeMember('bob', { + assignedIssues: [], openPRs: [], + recentActivity: { + mergedPRs: [], + closedIssues: [{ number: 5, title: 'Issue B', url: 'https://github.com/org/repo/issues/5', closedAt: '2026-04-01T08:00:00Z' }], + }, + }), + ]; + const entries = buildActivityFeed(members); + expect(entries).toHaveLength(2); + expect(entries[0].author).toBe('alice'); + expect(entries[0].type).toBe('merged'); + expect(entries[1].author).toBe('bob'); + expect(entries[1].type).toBe('closed'); + }); + + it('sorts entries reverse chronologically', () => { + const members = [ + makeMember('alice', { + assignedIssues: [], openPRs: [], + recentActivity: { + mergedPRs: [ + { number: 1, title: 'Old', url: 'u1', mergedAt: '2026-03-30T10:00:00Z' }, + { number: 2, title: 'New', url: 'u2', mergedAt: '2026-04-01T10:00:00Z' }, + ], + closedIssues: [ + { number: 3, title: 'Mid', url: 'u3', closedAt: '2026-03-31T10:00:00Z' }, + ], + }, + }), + ]; + const entries = buildActivityFeed(members); + expect(entries.map(e => e.number)).toEqual([2, 3, 1]); + }); + + it('correctly attributes entries to their member', () => { + const members = [ + makeMember('alice', { + assignedIssues: [], openPRs: [], + recentActivity: { + mergedPRs: [{ number: 1, title: 'X', url: 'u', mergedAt: '2026-04-01T10:00:00Z' }], + closedIssues: [], + }, + }), + ]; + const entries = buildActivityFeed(members); + expect(entries[0]).toMatchObject({ + type: 'merged', number: 1, title: 'X', url: 'u', author: 'alice', + }); + }); +}); diff --git a/packages/codev/dashboard/src/components/TeamView.tsx b/packages/codev/dashboard/src/components/TeamView.tsx index c5457ba7..83681713 100644 --- a/packages/codev/dashboard/src/components/TeamView.tsx +++ b/packages/codev/dashboard/src/components/TeamView.tsx @@ -91,7 +91,7 @@ function MessageItem({ message }: { message: TeamApiMessage }) { ); } -interface ActivityEntry { +export interface ActivityEntry { type: 'merged' | 'closed'; number: number; title: string; @@ -100,7 +100,7 @@ interface ActivityEntry { author: string; } -function relativeDate(isoString: string): string { +export function relativeDate(isoString: string): string { const diff = Date.now() - new Date(isoString).getTime(); const hours = Math.floor(diff / (1000 * 60 * 60)); if (hours < 1) return 'just now'; @@ -109,7 +109,7 @@ function relativeDate(isoString: string): string { return `${days}d ago`; } -function buildActivityFeed(members: TeamApiMember[]): ActivityEntry[] { +export function buildActivityFeed(members: TeamApiMember[]): ActivityEntry[] { const entries: ActivityEntry[] = []; for (const member of members) { const gh = member.github_data; From 4c0c6a14073ab05a230114d472ec0a2ff614db51 Mon Sep 17 00:00:00 2001 From: M Waleed Kadous Date: Wed, 1 Apr 2026 15:28:39 -0700 Subject: [PATCH 6/6] [Spec 650] Add spec, plan, review, and arch.md update --- .../650-team-page-show-issue-pr-detail.md | 164 ++++++++++++++++++ codev/resources/arch.md | 3 +- .../650-team-page-show-issue-pr-detail.md | 106 +++++++++++ .../650-team-page-show-issue-pr-detail.md | 146 ++++++++++++++++ 4 files changed, 418 insertions(+), 1 deletion(-) create mode 100644 codev/plans/650-team-page-show-issue-pr-detail.md create mode 100644 codev/reviews/650-team-page-show-issue-pr-detail.md create mode 100644 codev/specs/650-team-page-show-issue-pr-detail.md diff --git a/codev/plans/650-team-page-show-issue-pr-detail.md b/codev/plans/650-team-page-show-issue-pr-detail.md new file mode 100644 index 00000000..1089865a --- /dev/null +++ b/codev/plans/650-team-page-show-issue-pr-detail.md @@ -0,0 +1,164 @@ +# Plan: Team Page — Show Issue/PR Details and Activity Feed + +## Metadata +- **ID**: plan-2026-04-01-team-page-detail +- **Status**: draft +- **Specification**: codev/specs/650-team-page-show-issue-pr-detail.md +- **Created**: 2026-04-01 + +## Executive Summary +Expand the team page to show individual issue/PR titles with GitHub links (instead of just counts) and add a combined activity feed. Requires a minimal backend fix to add `url` to recent activity GraphQL fragments, then frontend changes to `TeamView.tsx` and `index.css`. + +## Success Metrics +- [ ] Member cards show issue/PR titles with clickable GitHub links +- [ ] Combined activity feed below Messages section +- [ ] Activity feed sorted reverse chronologically with correct attribution +- [ ] Empty states handled (`github_data: null` hides sections; empty arrays show text) +- [ ] Long titles truncated with ellipsis +- [ ] Build passes, no regressions + +## Phases (Machine Readable) + +```json +{ + "phases": [ + {"id": "backend-url-fix", "title": "Add URL to recent activity data"}, + {"id": "expanded-member-cards", "title": "Expand member cards with issue/PR lists"}, + {"id": "activity-feed", "title": "Add combined activity feed"} + ] +} +``` + +## Phase Breakdown + +### Phase 1: Add URL to recent activity data +**Dependencies**: None + +#### Objectives +- Add `url` field to merged PRs and closed issues in the GraphQL query and types + +#### Deliverables +- [ ] Updated GraphQL query fragments in `team-github.ts` +- [ ] Updated TypeScript types in `team-github.ts` and `api.ts` +- [ ] Updated response parsing to include `url` + +#### Implementation Details + +**File: `packages/codev/src/lib/team-github.ts`** +- `TeamMemberGitHubData.recentActivity.mergedPRs`: add `url: string` to type (line 26) +- `TeamMemberGitHubData.recentActivity.closedIssues`: add `url: string` to type (line 27) +- GraphQL query `_merged` fragment (line 94): add `url` → `nodes { ... on PullRequest { number title url mergedAt } }` +- GraphQL query `_closed` fragment (line 97): add `url` → `nodes { ... on Issue { number title url closedAt } }` +- `parseTeamGraphQLResponse` (lines 123-124): update type casts and map to include `url` + +**File: `packages/codev/dashboard/src/lib/api.ts`** +- `TeamMemberGitHubData.recentActivity.mergedPRs`: add `url: string` (line 60) +- `TeamMemberGitHubData.recentActivity.closedIssues`: add `url: string` (line 61) + +#### Acceptance Criteria +- [ ] `url` field present on all recent activity items +- [ ] Update existing unit test mocks in `team-github.test.ts` to include `url` field +- [ ] Existing unit tests still pass +- [ ] Build succeeds + +--- + +### Phase 2: Expand member cards with issue/PR lists +**Dependencies**: Phase 1 + +#### Objectives +- Replace count-only display with individual issue/PR title lists in `MemberCard` + +#### Deliverables +- [ ] Updated `MemberCard` component showing issue/PR titles +- [ ] CSS styles for issue/PR lists +- [ ] Empty state handling + +#### Implementation Details + +**File: `packages/codev/dashboard/src/components/TeamView.tsx`** +- Replace the `team-member-stats` div (lines 29-32) with two sections: + - "Working on" section: render `gh.assignedIssues` as a list of `` tags with `#{number} {title}` + - "Open PRs" section: render `gh.openPRs` as a list of `` tags with `#{number} {title}` +- When `github_data` is `null`: hide both sections entirely +- When arrays are empty: show "No assigned issues" / "No open PRs" text +- Keep existing recent activity summary (merged/closed counts with "last 7d") + +**File: `packages/codev/dashboard/src/index.css`** +- Add styles for `.team-member-issues` and `.team-member-prs` list containers +- Style `.team-item-link`: `display: block`, `text-overflow: ellipsis`, `overflow: hidden`, `white-space: nowrap` (block-level with constrained width for ellipsis to work) +- Style section labels (small, muted headers for "Working on" / "Open PRs") + +#### Acceptance Criteria +- [ ] Each issue/PR title displayed as clickable link +- [ ] Links open in new tab with `noopener noreferrer` +- [ ] Long titles truncated with ellipsis +- [ ] `github_data: null` hides sections +- [ ] Empty arrays show placeholder text + +--- + +### Phase 3: Add combined activity feed +**Dependencies**: Phase 1, Phase 2 + +#### Objectives +- Add a unified activity timeline below the Messages section + +#### Deliverables +- [ ] New `ActivityFeed` component +- [ ] CSS styles for activity feed +- [ ] Relative date formatting helper + +#### Implementation Details + +**File: `packages/codev/dashboard/src/components/TeamView.tsx`** +- Add `ActivityFeed` component that: + 1. Aggregates `recentActivity.mergedPRs` and `recentActivity.closedIssues` across all members + 2. Tags each item with the member's `name` and `github` handle + 3. Sorts by timestamp (mergedAt/closedAt) in reverse chronological order + 4. Renders each entry as: `{relativeDate} @{github} {merged|closed} #{number} {title}` + 5. Each entry rendered as a single `` anchor wrapping the entire row (entire row clickable), with `target="_blank"` and `rel="noopener noreferrer"`, linking to GitHub via `url` field +- Add inline `relativeDate(isoString: string)` helper: returns "just now" (<1h), "Xh ago" (1-23h), "Xd ago" (1d+) +- Add the feed as a third section in `TeamView` after Messages +- Show "No recent activity" when feed is empty + +**File: `packages/codev/dashboard/src/index.css`** +- Style `.team-activity-feed` container +- Style `.team-activity-entry`: row layout, muted date, action text +- Style `.team-activity-author`: `@handle` display + +#### Acceptance Criteria +- [ ] Activity feed shows entries from all members +- [ ] Entries sorted reverse chronologically +- [ ] Relative dates display correctly +- [ ] Links open in new tab +- [ ] Empty state shows "No recent activity" + +## Dependency Map +``` +Phase 1 (backend-url-fix) ──→ Phase 2 (expanded-member-cards) ──→ Phase 3 (activity-feed) +``` + +## Risk Analysis +| Risk | Probability | Impact | Mitigation | +|------|------------|--------|------------| +| GraphQL url field not available | Very Low | Medium | Verified: GitHub API exposes `url` on Issue and PullRequest | +| CSS layout breakage on narrow widths | Low | Low | Existing `minmax(200px, 1fr)` grid handles this; add `text-overflow: ellipsis` | + +## Validation Checkpoints +1. **After Phase 1**: `npm run build` passes, types are consistent +2. **After Phase 2**: Member cards render titles in dashboard +3. **After Phase 3**: Full feature complete, activity feed renders correctly + +## Expert Review +**Date**: 2026-04-01 +**Models Consulted**: Gemini, Codex, Claude +**Key changes from consultation**: +- Added explicit unit test update task for Phase 1 (Gemini, Codex) +- Specified "entire row clickable" as single `` anchor for activity feed (Codex) +- Added "just now" fallback for sub-1h relative dates (Codex) +- Clarified CSS ellipsis requires block-level display (Gemini) + +## Approval +- [ ] Technical Lead Review +- [x] Expert AI Consultation Complete diff --git a/codev/resources/arch.md b/codev/resources/arch.md index 2f5a1f14..c153e1ac 100644 --- a/codev/resources/arch.md +++ b/codev/resources/arch.md @@ -645,7 +645,8 @@ packages/codev/dashboard/ **Team View** (Spec 587): - Conditional tab — only appears when `codev/team/people/` has 2+ valid member files - `teamEnabled` boolean in `DashboardState` controls tab visibility (set by `hasTeam()` in `/api/state`) -- Member cards: name, role badge, GitHub handle link, assigned issues, open PRs, recent activity (last 7 days) +- Member cards: name, role badge, GitHub handle link, clickable issue/PR title lists, recent activity counts (last 7 days) +- Combined activity feed: unified reverse-chronological timeline of merged PRs and closed issues across all members - Message log from `codev/team/messages.md` displayed in reverse chronological order - Data from `/api/team` endpoint — members enriched with batched GraphQL GitHub data - Fetch-on-activation pattern (like Statistics), manual refresh button, no polling diff --git a/codev/reviews/650-team-page-show-issue-pr-detail.md b/codev/reviews/650-team-page-show-issue-pr-detail.md new file mode 100644 index 00000000..2f557e3f --- /dev/null +++ b/codev/reviews/650-team-page-show-issue-pr-detail.md @@ -0,0 +1,106 @@ +# Review: Team Page — Show Issue/PR Details and Activity Feed + +## Summary +Expanded the team page to show individual issue/PR titles with clickable GitHub links (replacing count-only display) and added a combined activity feed showing recent merged PRs and closed issues across all members. Included a minimal backend fix to add `url` to the GraphQL query for recent activity items. + +**Stats**: 6 files changed, 325 insertions, 21 deletions across 5 commits. + +## Spec Compliance +- [x] Member cards show individual issue titles with clickable GitHub links +- [x] Member cards show individual PR titles with clickable GitHub links +- [x] Combined activity feed renders below Messages section +- [x] Activity feed entries sorted reverse chronologically with correct attribution +- [x] Activity feed entries link to GitHub (entire row clickable) +- [x] Empty states handled (`github_data: null` hides sections; empty arrays show text) +- [x] Long titles truncated with CSS ellipsis +- [x] Existing member card info preserved (name, role, GitHub handle) +- [x] Relative dates: "just now" (<1h), "Xh ago", "Xd ago" +- [x] All links open in new tab with `noopener noreferrer` + +## Deviations from Plan +- **Backend change added**: Original issue described this as "frontend-only," but all 3 spec consultants identified that `recentActivity` items lacked `url` fields. Added a minimal backend fix (GraphQL query + type updates) — 4 lines of query change. +- **Tests added for dashboard logic**: Plan didn't originally include frontend unit tests. Codex reviewer requested them. Added `activityFeed.test.ts` with 8 tests covering `relativeDate` and `buildActivityFeed` pure functions. + +## Lessons Learned + +### What Went Well +- 3-way consultation caught a real data gap (missing `url` on activity items) before implementation started — saved a rework cycle +- Clean separation of pure functions (`relativeDate`, `buildActivityFeed`) from React components made testing straightforward +- Existing `TeamMemberGitHubData` interface and `useTeam` hook required zero modifications to the data flow + +### Challenges Encountered +- **GraphQL URL gap**: The `_merged` and `_closed` query fragments omitted `url` even though the GitHub API supports it. Caught during spec consultation, not during code review — consultation was load-bearing here. +- **Dashboard test config**: Dashboard tests run with a separate vitest config (`dashboard/vitest.config.ts`) with jsdom environment, excluded from the main test suite. Required running from the `dashboard/` subdirectory. + +### What Would Be Done Differently +- Check data completeness (all fields available) during spec writing rather than assuming "all data is already available" + +## Technical Debt +- No Playwright E2E tests for the new UI — existing team tab E2E tests only verify API contract and tab visibility, not rendered content + +## Consultation Feedback + +### Specify Phase (Round 1) + +#### Gemini (REQUEST_CHANGES) +- **Concern**: `recentActivity` items lack `url` — spec claims frontend-only but can't link activity items + - **Addressed**: Updated spec to include minimal backend fix + +#### Codex (REQUEST_CHANGES) +- **Concern**: Relative date format under-specified; empty state for `github_data: null` unclear; CSS truncation needs bounds; testing should include Playwright + - **Addressed**: Added relative date format spec, null vs empty distinction, CSS truncation requirement + - **Rebutted**: Playwright coverage deferred as out of scope for ~250 LOC change + +#### Claude (COMMENT) +- **Concern**: Same URL gap; suggested truncation guidance and date formatting utility + - **Addressed**: All points incorporated into spec revision + +### Plan Phase (Round 1) + +#### Gemini (APPROVE) +- No concerns. Provided helpful CSS tip (block-level for ellipsis). + +#### Codex (REQUEST_CHANGES) +- **Concern**: Missing test tasks; "entire row clickable" not explicit; sub-1h date undefined + - **Addressed**: Added test update task, explicit anchor requirement, "just now" fallback + +#### Claude (APPROVE) +- No concerns. + +### Implement: backend-url-fix (Round 1) + +#### Gemini (APPROVE), Claude (APPROVE) +- No concerns. + +#### Codex (REQUEST_CHANGES) +- **Concern**: Missing explicit `url` assertions in tests + - **Addressed**: Added `toEqual` assertions and query-level regex checks + +### Implement: expanded-member-cards (Round 1) + +#### All three (APPROVE) +- No concerns raised. + +### Implement: activity-feed (Round 1) + +#### Gemini (APPROVE), Claude (APPROVE) +- No concerns. + +#### Codex (REQUEST_CHANGES) +- **Concern**: No frontend test coverage for activity feed logic + - **Addressed**: Added `activityFeed.test.ts` with 8 tests for pure functions + +## Flaky Tests +No flaky tests encountered. + +## Architecture Updates +Updated `codev/resources/arch.md` Team View section to reflect: +- Member cards now show clickable issue/PR title lists (not just counts) +- Added combined activity feed description + +## Lessons Learned Updates +No lessons learned updates needed — straightforward frontend enhancement with no novel insights beyond existing entries. + +## Follow-up Items +- Add Playwright E2E tests for team page rendered content (issue/PR links, activity feed ordering) +- Consider item count cap if teams have many open issues/PRs (currently shows all) diff --git a/codev/specs/650-team-page-show-issue-pr-detail.md b/codev/specs/650-team-page-show-issue-pr-detail.md new file mode 100644 index 00000000..2ea6a96f --- /dev/null +++ b/codev/specs/650-team-page-show-issue-pr-detail.md @@ -0,0 +1,146 @@ +# Specification: Team Page — Show Issue/PR Details and Activity Feed + +## Metadata +- **ID**: spec-2026-04-01-team-page-detail +- **Status**: draft +- **Created**: 2026-04-01 + +## Clarifying Questions Asked +No clarifying questions needed — the issue description (#650) is detailed and the existing data structures are well-defined. + +## Problem Statement +The team page currently renders only aggregate counts ("3 issues", "2 PRs") for each member. The backend fetches issue/PR details via GraphQL, but the frontend discards this data and shows only counts. Users must leave the dashboard and go to GitHub to see what each team member is actually working on. + +Additionally, the GraphQL query fetches `url` for assigned issues and open PRs, but omits `url` for recent activity items (merged PRs and closed issues). This gap needs to be fixed in the backend query so the activity feed can link to GitHub. + +## Current State +- `MemberCard` displays counts: `{issueCount} issues`, `{prCount} PRs` +- Recent activity shows only `{mergedCount} merged`, `{closedCount} closed` with a "last 7d" label +- No clickable links to individual issues or PRs +- No combined activity feed across members +- The `TeamMemberGitHubData` interface already provides full objects: + - `assignedIssues`: `{ number, title, url }[]` + - `openPRs`: `{ number, title, url }[]` + - `recentActivity.mergedPRs`: `{ number, title, mergedAt }[]` (missing `url` — needs backend fix) + - `recentActivity.closedIssues`: `{ number, title, closedAt }[]` (missing `url` — needs backend fix) + +## Desired State + +### 1. Expanded Member Cards +Each member card shows actual issue/PR titles instead of just counts: + +- **Working on** section: Lists assigned issue titles, each clickable and linking to GitHub. Shows issue number and title (e.g., "#42 Fix login timeout"). Falls back to "No assigned issues" when empty. +- **Open PRs** section: Lists open PR titles, each clickable. Shows PR number and title (e.g., "#45 Add retry logic"). Falls back to "No open PRs" when empty. +- **Recent activity** summary: Keeps existing merged/closed counts with "last 7d" label (unchanged). + +### 2. Combined Activity Feed +A new section below Messages showing a unified timeline of recent activity across all members: + +- Shows merged PRs and closed issues from the last 7 days (data already filtered by backend) +- Each entry displays: relative date (e.g., "2d ago"), @author (from parent member), action verb (merged/closed), #number, title +- Sorted reverse chronologically by timestamp +- Entire entry row is clickable, linking to GitHub (using `url` field added to backend) +- Relative dates: use simple "Xd ago" / "Xh ago" format, computed from ISO 8601 timestamps +- Shows "No recent activity" when empty + +### 3. Empty States and Edge Cases +- Member with `github_data: null`: hide Working on / Open PRs sections entirely (show only name, role, GitHub handle). Do not show empty-state text — absence of GitHub data means it wasn't fetched. +- Member with `github_data` but empty arrays: show "No assigned issues" / "No open PRs" text. +- Long titles: truncate with CSS `text-overflow: ellipsis` to prevent card layout breakage. +- No item count cap — show all items. Truncation can be added later if needed. + +### 4. Preserved Behavior +- Existing member card info (name, role, GitHub handle) remains unchanged +- Refresh button continues to work +- Loading/error states unchanged +- Messages section unchanged + +## Stakeholders +- **Primary Users**: Architects using the Codev dashboard to monitor team activity +- **Technical Team**: Codev maintainers + +## Success Criteria +- [ ] Member cards show individual issue titles with clickable GitHub links +- [ ] Member cards show individual PR titles with clickable GitHub links +- [ ] Combined activity feed renders below Messages section +- [ ] Activity feed entries are sorted reverse chronologically +- [ ] Activity feed entries link to GitHub +- [ ] Empty states handled gracefully (no blank sections) +- [ ] Existing member card info preserved +- [ ] Visual design consistent with existing team page styles + +## Constraints +### Technical Constraints +- Primarily frontend change with a minimal backend fix (add `url` to GraphQL query for recent activity) +- Files to modify: + - `team-github.ts`: Add `url` to merged PR and closed issue GraphQL fragments + types + - `api.ts`: Update `TeamMemberGitHubData` type to include `url` on recent activity items + - `TeamView.tsx`: Expand member cards and add activity feed + - `index.css`: Styles for new UI elements +- Must work with existing `useTeam` hook + +### Business Constraints +- ~250 LOC change — ASPIR-appropriate scope + +## Assumptions +- Issue/PR URLs from the backend are valid GitHub URLs +- `recentActivity.mergedPRs[].mergedAt` and `recentActivity.closedIssues[].closedAt` are ISO 8601 date strings +- GitHub GraphQL API supports `url` field on both `Issue` and `PullRequest` types (verified) + +## Solution Approaches + +### Approach 1: Inline Expansion (Recommended) +Expand the existing `MemberCard` component to render issue/PR lists inline. Add a new `ActivityFeed` component below the Messages section that aggregates activity across all members. + +**Pros**: +- Minimal structural change — enhances existing component +- All data available without additional API calls +- Simple, flat UI consistent with current design + +**Cons**: +- Cards will be taller with many issues/PRs (acceptable trade-off) + +**Estimated Complexity**: Low +**Risk Level**: Low + +## Open Questions +None — all consultation feedback has been incorporated. + +## Performance Requirements +- No additional API calls — the `url` field is added to the existing GraphQL query (no extra round trips) +- Activity feed sort is O(n log n) on a small dataset (< 100 items typically) + +## Security Considerations +- URLs come from GitHub via the backend — no user-controlled input +- Links open in new tab with `rel="noopener noreferrer"` (existing pattern) + +## Test Scenarios +### Functional Tests +1. Member with assigned issues: card shows issue titles with clickable links +2. Member with open PRs: card shows PR titles with clickable links +3. Member with `github_data: null`: card hides Working on / Open PRs sections +4. Member with empty arrays: card shows "No assigned issues" / "No open PRs" +5. Activity feed with mixed merged PRs and closed issues: sorted reverse chronologically +6. Activity feed empty: shows "No recent activity" +7. Multiple members contribute to activity feed: all entries present and attributed correctly +8. Long titles: truncated with ellipsis, no layout overflow +9. Activity feed links open in new tab with `noopener noreferrer` + +## Risks and Mitigation +| Risk | Probability | Impact | Mitigation Strategy | +|------|------------|--------|-------------------| +| Cards too tall with many items | Low | Low | Natural scrolling; could truncate later if needed | + +## Expert Consultation +**Date**: 2026-04-01 +**Models Consulted**: Gemini, Codex, Claude +**Key changes from consultation**: +- Added `url` field to recent activity backend data (all 3 reviewers flagged this gap) +- Clarified relative date format ("Xd ago" / "Xh ago") +- Added explicit `github_data: null` vs empty array behavior +- Added CSS truncation requirement for long titles +- Updated constraints from "frontend-only" to include minimal backend fix + +## Approval +- [ ] Technical Lead Review +- [x] Expert AI Consultation Complete