diff --git a/web/src/components/ProposalList.test.tsx b/web/src/components/ProposalList.test.tsx index e34f7807..19452f16 100644 --- a/web/src/components/ProposalList.test.tsx +++ b/web/src/components/ProposalList.test.tsx @@ -1,4 +1,4 @@ -import { render, screen, fireEvent } from '@testing-library/react'; +import { render, screen, fireEvent, within } from '@testing-library/react'; import { describe, it, expect } from 'vitest'; import { ProposalList } from './ProposalList'; import type { Proposal, PullRequest } from '../types/activity'; @@ -407,4 +407,77 @@ describe('ProposalList', () => { screen.queryByText(/Same number issue comment/i) ).not.toBeInTheDocument(); }); + + it('keeps selection scoped by repo when proposal numbers collide', () => { + const proposals: Proposal[] = [ + { + number: 42, + title: 'Colony proposal', + phase: 'discussion', + author: 'worker', + createdAt: '2026-02-05T09:00:00Z', + commentCount: 1, + repo: 'hivemoot/colony', + }, + { + number: 42, + title: 'Governance proposal', + phase: 'discussion', + author: 'scout', + createdAt: '2026-02-05T10:00:00Z', + commentCount: 1, + repo: 'hivemoot/hivemoot', + }, + ]; + const comments = [ + { + id: 2001, + issueOrPrNumber: 42, + type: 'proposal' as const, + repo: 'hivemoot/colony', + author: 'worker', + body: 'Colony-specific thread', + createdAt: '2026-02-05T11:00:00Z', + url: 'https://github.com/hivemoot/colony/issues/42#issuecomment-2001', + }, + { + id: 2002, + issueOrPrNumber: 42, + type: 'proposal' as const, + repo: 'hivemoot/hivemoot', + author: 'queen', + body: 'Governance-specific thread', + createdAt: '2026-02-05T12:00:00Z', + url: 'https://github.com/hivemoot/hivemoot/issues/42#issuecomment-2002', + }, + ]; + + render( + + ); + + const colonyCard = screen.getByText('Colony proposal').closest('article'); + expect(colonyCard).not.toBeNull(); + fireEvent.click(within(colonyCard as HTMLElement).getByRole('button')); + + expect(screen.getByText('Colony-specific thread')).toBeInTheDocument(); + expect( + screen.queryByText('Governance-specific thread') + ).not.toBeInTheDocument(); + + const governanceCard = screen + .getByText('Governance proposal') + .closest('article'); + expect(governanceCard).not.toBeNull(); + fireEvent.click(within(governanceCard as HTMLElement).getByRole('button')); + + expect(screen.getByText('Governance-specific thread')).toBeInTheDocument(); + expect( + screen.queryByText('Colony-specific thread') + ).not.toBeInTheDocument(); + }); }); diff --git a/web/src/components/ProposalList.tsx b/web/src/components/ProposalList.tsx index b0c04547..768fc4d9 100644 --- a/web/src/components/ProposalList.tsx +++ b/web/src/components/ProposalList.tsx @@ -19,13 +19,20 @@ export function ProposalList({ repoUrl, filteredAgent, }: ProposalListProps): React.ReactElement { - const [selectedProposalNumber, setSelectedProposalNumber] = useState< - number | null - >(null); + const [selectedProposalKey, setSelectedProposalKey] = useState( + null + ); + + const getProposalKey = (proposal: Proposal): string => + `${proposal.repo ?? 'unknown'}:${proposal.number}`; + + const getExplorerId = (proposal: Proposal): string => + `decision-explorer-${getProposalKey(proposal).replace(/[^a-zA-Z0-9_-]/g, '-')}`; const selectedProposal = useMemo( - () => proposals.find((p) => p.number === selectedProposalNumber) ?? null, - [proposals, selectedProposalNumber] + () => + proposals.find((p) => getProposalKey(p) === selectedProposalKey) ?? null, + [proposals, selectedProposalKey] ); const snapshot = useMemo( @@ -72,11 +79,12 @@ export function ProposalList({
{proposals.map((proposal) => { - const isSelected = proposal.number === selectedProposalNumber; + const proposalKey = getProposalKey(proposal); + const isSelected = proposalKey === selectedProposalKey; return (
- setSelectedProposalNumber(isSelected ? null : proposal.number) + setSelectedProposalKey(isSelected ? null : proposalKey) } aria-expanded={isSelected} - aria-controls={`decision-explorer-${proposal.number}`} + aria-controls={getExplorerId(proposal)} className="w-full text-left p-4 hover:bg-white/60 dark:hover:bg-neutral-800/60 rounded-lg focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-amber-500 focus-visible:ring-offset-1 dark:focus-visible:ring-offset-neutral-800" >
@@ -180,7 +188,7 @@ export function ProposalList({ {selectedProposal && snapshot && (