From a0abad1ff831a2fa941f1ab5641e276994cf32b3 Mon Sep 17 00:00:00 2001 From: Adam Spitz Date: Tue, 8 Sep 2026 15:14:53 -0400 Subject: [PATCH 1/2] Keep unmaterialized prospective rounds off the cause-board post path. Cause boards already listed attested content contracts. Exclude open prospective rounds from that path so they appear only via a project-level vouch, and link the post-level content board from the cause page. --- causestarter/TODO.md | 2 +- .../hooks/useCauseProjects.test.tsx | 1 + ui/src/causestarter/hooks/useCauseProjects.ts | 10 +++- ui/src/causestarter/pages/CauseDetailPage.tsx | 7 ++- ...erializedProspectiveRoundAddresses.test.ts | 52 +++++++++++++++++++ ...UnmaterializedProspectiveRoundAddresses.ts | 31 +++++++++++ ui/src/content-funding/index.ts | 1 + .../selectAlignedContent.test.ts | 19 +++++++ .../content-funding/selectAlignedContent.ts | 5 ++ .../components/AlignedProjectsList.test.tsx | 1 + .../components/AlignedProjectsList.tsx | 10 +++- .../components/CauseBoard.test.tsx | 1 + .../fundingportals/components/CauseBoard.tsx | 10 +++- 13 files changed, 145 insertions(+), 5 deletions(-) create mode 100644 ui/src/content-funding/hooks/useUnmaterializedProspectiveRoundAddresses.test.ts create mode 100644 ui/src/content-funding/hooks/useUnmaterializedProspectiveRoundAddresses.ts diff --git a/causestarter/TODO.md b/causestarter/TODO.md index 1d0899fc..e739bad8 100644 --- a/causestarter/TODO.md +++ b/causestarter/TODO.md @@ -11,7 +11,7 @@ open **if they stay listed here**. - [x] **Cluster-page mediator opt-in** and **statement-level triples** (`/bridge/triple`) — [ADR 0012](/specs/decisions/0012-mediator-is-an-address.md). -- [ ] **Content contracts on the cause board — leftover after first slice.** +- [x] **Content contracts on the cause board — leftover after first slice.** Product rule (settled): list the *contract* (not individual posts) on the cause project list when any post in that contract has a current positive content attestation to a published plank. Dedup by address with vouched diff --git a/ui/src/causestarter/hooks/useCauseProjects.test.tsx b/ui/src/causestarter/hooks/useCauseProjects.test.tsx index 040d710c..b6381f39 100644 --- a/ui/src/causestarter/hooks/useCauseProjects.test.tsx +++ b/ui/src/causestarter/hooks/useCauseProjects.test.tsx @@ -18,6 +18,7 @@ vi.mock('@ui/content-funding', async () => { return { ...actual, useContentFundingState: () => contentState, + useUnmaterializedProspectiveRoundAddresses: () => [], } }) diff --git a/ui/src/causestarter/hooks/useCauseProjects.ts b/ui/src/causestarter/hooks/useCauseProjects.ts index 55325499..ef030587 100644 --- a/ui/src/causestarter/hooks/useCauseProjects.ts +++ b/ui/src/causestarter/hooks/useCauseProjects.ts @@ -20,7 +20,11 @@ import { type AlignedProjectFundingTotals, } from '@commonality/sdk/fundingportals' import { ETH_CURRENCY, type Currency, type IpfsCidV1 } from '@commonality/sdk/utils' -import { selectAlignedContentContracts, useContentFundingState } from '@ui/content-funding' +import { + selectAlignedContentContracts, + useContentFundingState, + useUnmaterializedProspectiveRoundAddresses, +} from '@ui/content-funding' import { useTrustedContentAttesters } from '@ui/shared' import { mapWithConcurrency, PLANK_QUERY_CONCURRENCY } from '../lib/concurrency' import { useMachinery } from '../../shared' @@ -78,6 +82,8 @@ export function useCauseProjects( loading: contentLoading, } = useContentFundingState() const trustedContentAttesters = useTrustedContentAttesters() + const unmaterializedProspective = useUnmaterializedProspectiveRoundAddresses() + const unmaterializedKey = unmaterializedProspective.slice().sort().join('\0') const contentTrustKey = trustedContentAttesters .map((entry) => entry.address.toLowerCase()) .sort() @@ -171,6 +177,7 @@ export function useCauseProjects( contentAttestations, cids, contentTrustKey ? contentTrustKey.split('\0') : undefined, + unmaterializedKey ? unmaterializedKey.split('\0') : undefined, ) for (const contract of contentContracts) { const key = contract.contractAddress.toLowerCase() @@ -234,6 +241,7 @@ export function useCauseProjects( contentAttestationsKey, contentTrustKey, contentLoading, + unmaterializedKey, ]) const countByPlankCid = useMemo(() => { diff --git a/ui/src/causestarter/pages/CauseDetailPage.tsx b/ui/src/causestarter/pages/CauseDetailPage.tsx index 3b144fd0..12e6a07b 100644 --- a/ui/src/causestarter/pages/CauseDetailPage.tsx +++ b/ui/src/causestarter/pages/CauseDetailPage.tsx @@ -31,7 +31,7 @@ import { RosterHistory } from '../components/RosterHistory' import { RosterPublishPanel } from '../components/RosterPublishPanel' import { SafetyRejectionDialog } from '../components/SafetyRejectionDialog' import { - bookmarkCause, causeEditPath, causeFundingPath, causeLeaderboardPath, causeMediatorPath, + bookmarkCause, causeContentBoardPath, causeEditPath, causeFundingPath, causeLeaderboardPath, causeMediatorPath, causePath, causeTitle, findCauseByStable, getCause, isCauseBookmarked, isLive, markPlankPublished, markRosterPublished, newPlank, publishedPlanks, realPlanks, @@ -1190,6 +1190,11 @@ export function CauseDetailPage({ editMode = false }: { editMode?: boolean }) { to: '/content/new', variant: 'outlined', }, + { + label: 'Attested posts', + to: causeContentBoardPath(cause), + variant: 'outlined', + }, ]} projectsHelp={ diff --git a/ui/src/content-funding/hooks/useUnmaterializedProspectiveRoundAddresses.test.ts b/ui/src/content-funding/hooks/useUnmaterializedProspectiveRoundAddresses.test.ts new file mode 100644 index 00000000..6544e56b --- /dev/null +++ b/ui/src/content-funding/hooks/useUnmaterializedProspectiveRoundAddresses.test.ts @@ -0,0 +1,52 @@ +import { renderHook, waitFor } from '@testing-library/react' +import { beforeEach, describe, expect, it, vi } from 'vitest' +import { useUnmaterializedProspectiveRoundAddresses } from './useUnmaterializedProspectiveRoundAddresses' + +vi.mock('@commonality/sdk/content-funding', async () => { + const actual = await vi.importActual( + '@commonality/sdk/content-funding', + ) + return { + ...actual, + getProspectiveRounds: vi.fn(), + } +}) + +vi.mock('../../shared', async () => { + const actual = await vi.importActual('../../shared') + return { ...actual, useMachinery: () => ({}) } +}) + +import { getProspectiveRounds } from '@commonality/sdk/content-funding' + +describe('useUnmaterializedProspectiveRoundAddresses', () => { + beforeEach(() => { + vi.mocked(getProspectiveRounds).mockResolvedValue([ + { + round: '0x1111111111111111111111111111111111111111', + channelIdHash: '0x00', + receiptToken: '0x2222222222222222222222222222222222222222', + receiptTokenId: 0n, + condition: '0x3333333333333333333333333333333333333333', + materializedToken: null, + content: [], + }, + { + round: '0x4444444444444444444444444444444444444444', + channelIdHash: '0x00', + receiptToken: '0x5555555555555555555555555555555555555555', + receiptTokenId: 0n, + condition: '0x6666666666666666666666666666666666666666', + materializedToken: '0x7777777777777777777777777777777777777777', + content: [], + }, + ] as Awaited>) + }) + + it('returns only rounds that have not materialized', async () => { + const { result } = renderHook(() => useUnmaterializedProspectiveRoundAddresses()) + await waitFor(() => { + expect(result.current).toEqual(['0x1111111111111111111111111111111111111111']) + }) + }) +}) diff --git a/ui/src/content-funding/hooks/useUnmaterializedProspectiveRoundAddresses.ts b/ui/src/content-funding/hooks/useUnmaterializedProspectiveRoundAddresses.ts new file mode 100644 index 00000000..315298c7 --- /dev/null +++ b/ui/src/content-funding/hooks/useUnmaterializedProspectiveRoundAddresses.ts @@ -0,0 +1,31 @@ +import { useEffect, useState } from 'react' +import { getProspectiveRounds } from '@commonality/sdk/content-funding' +import { useMachinery } from '../../shared' + +/** Addresses of prospective rounds that have not materialized. Cause boards + * list those only via a project-level alignment vouch, not post attestation. */ +export function useUnmaterializedProspectiveRoundAddresses(): string[] { + const machinery = useMachinery() + const [addresses, setAddresses] = useState([]) + + useEffect(() => { + let cancelled = false + void getProspectiveRounds(machinery) + .then((rounds) => { + if (cancelled) return + setAddresses( + rounds + .filter((round) => !round.materializedToken) + .map((round) => round.round.toLowerCase()), + ) + }) + .catch(() => { + if (!cancelled) setAddresses([]) + }) + return () => { + cancelled = true + } + }, [machinery]) + + return addresses +} diff --git a/ui/src/content-funding/index.ts b/ui/src/content-funding/index.ts index 5d9557ca..9344f3dd 100644 --- a/ui/src/content-funding/index.ts +++ b/ui/src/content-funding/index.ts @@ -18,6 +18,7 @@ export { ContentFundingProjectSection } from './components/ContentFundingProject export { useClaimFlow } from './hooks/useClaimFlow' export { useContentFundingState } from './hooks/useContentFundingState' +export { useUnmaterializedProspectiveRoundAddresses } from './hooks/useUnmaterializedProspectiveRoundAddresses' export type { ContentAttestationInfo } from './hooks/useContentFundingState' export { selectAlignedContentContracts, diff --git a/ui/src/content-funding/selectAlignedContent.test.ts b/ui/src/content-funding/selectAlignedContent.test.ts index abae4aef..35ec8389 100644 --- a/ui/src/content-funding/selectAlignedContent.test.ts +++ b/ui/src/content-funding/selectAlignedContent.test.ts @@ -82,6 +82,25 @@ describe('selectAlignedContentContracts', () => { expect(rows).toHaveLength(1) }) + it('omits excluded addresses so unmaterialized prospective rounds stay off the post-attestation path', () => { + const rows = selectAlignedContentContracts( + [channel()], + new Map([ + ['twitter:uid:1:111', [{ + canonicalId: 'twitter:uid:1:111', + subjectId: 'x', + attested: true, + attester: '0x1', + statementCid: STATEMENT, + }]], + ]), + [STATEMENT], + undefined, + ['0xABC'], + ) + expect(rows).toEqual([]) + }) + it('treats an empty trust set as unfiltered', () => { const rows = selectAlignedContentContracts( [channel()], diff --git a/ui/src/content-funding/selectAlignedContent.ts b/ui/src/content-funding/selectAlignedContent.ts index af02f818..2a4965d8 100644 --- a/ui/src/content-funding/selectAlignedContent.ts +++ b/ui/src/content-funding/selectAlignedContent.ts @@ -105,12 +105,17 @@ export function selectAlignedContentContracts( attestations: Map, statementCids: readonly string[], trustedAttesters?: Iterable, + excludeAddresses?: Iterable, ): AlignedContentContract[] { + const excluded = new Set( + [...(excludeAddresses ?? [])].map((address) => address.toLowerCase()).filter(Boolean), + ) const items = alignedItemsForStatements(channels, attestations, statementCids, trustedAttesters) const byAddress = new Map() for (const item of items) { const key = item.contractAddress.toLowerCase() + if (excluded.has(key)) continue const existing = byAddress.get(key) if (existing) { existing.alignedItemCount += 1 diff --git a/ui/src/fundingportals/components/AlignedProjectsList.test.tsx b/ui/src/fundingportals/components/AlignedProjectsList.test.tsx index 7bf51581..22cdb233 100644 --- a/ui/src/fundingportals/components/AlignedProjectsList.test.tsx +++ b/ui/src/fundingportals/components/AlignedProjectsList.test.tsx @@ -98,6 +98,7 @@ vi.mock('../../content-funding', async () => { contentAttestations: new Map(), loading: false, })), + useUnmaterializedProspectiveRoundAddresses: () => [], } }) diff --git a/ui/src/fundingportals/components/AlignedProjectsList.tsx b/ui/src/fundingportals/components/AlignedProjectsList.tsx index ad029838..65683b14 100644 --- a/ui/src/fundingportals/components/AlignedProjectsList.tsx +++ b/ui/src/fundingportals/components/AlignedProjectsList.tsx @@ -29,7 +29,11 @@ import { useTrustedSet, TrustNetworkRefreshIndicator, } from '../../shared' -import { selectAlignedContentContracts, useContentFundingState } from '../../content-funding' +import { + selectAlignedContentContracts, + useContentFundingState, + useUnmaterializedProspectiveRoundAddresses, +} from '../../content-funding' import { getProjectStatus } from '../../lazy-giving' import { AlignedProjectCard, @@ -103,6 +107,8 @@ export function AlignedProjectsList({ const machinery = useMachinery() const { address } = useAccount() const { channels, contentAttestations } = useContentFundingState() + const unmaterializedProspective = useUnmaterializedProspectiveRoundAddresses() + const unmaterializedKey = unmaterializedProspective.slice().sort().join('\0') const trustedContentAttesters = useTrustedContentAttesters() const contentTrustKey = trustedContentAttesters .map((entry) => entry.address.toLowerCase()) @@ -194,6 +200,7 @@ export function AlignedProjectsList({ contentAttestations, loadCids, contentTrustKey ? contentTrustKey.split('\0') : undefined, + unmaterializedKey ? unmaterializedKey.split('\0') : undefined, ).map((contract) => ({ projectAddress: contract.contractAddress, alignmentType: 'direct' as const, @@ -259,6 +266,7 @@ export function AlignedProjectsList({ contentAttestationsKey, contentTrustKey, inclusionRules, + unmaterializedKey, ]) const effectiveStatus = statusFilterLock ?? statusFilter diff --git a/ui/src/fundingportals/components/CauseBoard.test.tsx b/ui/src/fundingportals/components/CauseBoard.test.tsx index 538d5676..1b491c46 100644 --- a/ui/src/fundingportals/components/CauseBoard.test.tsx +++ b/ui/src/fundingportals/components/CauseBoard.test.tsx @@ -75,6 +75,7 @@ vi.mock('../../content-funding', async () => { contentAttestations: new Map(), loading: false, })), + useUnmaterializedProspectiveRoundAddresses: () => [], } }) diff --git a/ui/src/fundingportals/components/CauseBoard.tsx b/ui/src/fundingportals/components/CauseBoard.tsx index edccad1a..14425dd6 100644 --- a/ui/src/fundingportals/components/CauseBoard.tsx +++ b/ui/src/fundingportals/components/CauseBoard.tsx @@ -38,7 +38,11 @@ import { useTrustedContentAttesters, TrustNetworkRefreshIndicator, } from '../../shared' -import { selectAlignedContentContracts, useContentFundingState } from '../../content-funding' +import { + selectAlignedContentContracts, + useContentFundingState, + useUnmaterializedProspectiveRoundAddresses, +} from '../../content-funding' import { AlignedProjectsList } from './AlignedProjectsList' import { SuccessfulProjectsTab } from './SuccessfulProjectsTab' import { AttestAlignmentForm } from './AttestAlignmentForm' @@ -208,6 +212,8 @@ export function CauseBoard({ const [title, setTitle] = useState(null) const [summary, setSummary] = useState(null) const { channels, contentAttestations } = useContentFundingState() + const unmaterializedProspective = useUnmaterializedProspectiveRoundAddresses() + const unmaterializedKey = unmaterializedProspective.slice().sort().join('\0') const trustedContentAttesters = useTrustedContentAttesters() const contentTrustKey = trustedContentAttesters .map((entry) => entry.address.toLowerCase()) @@ -316,6 +322,7 @@ export function CauseBoard({ contentAttestations, loadCids, contentTrustKey ? contentTrustKey.split('\0') : undefined, + unmaterializedKey ? unmaterializedKey.split('\0') : undefined, ) const union = unionAlignedFundingProjects([...byAddress.values()], contentContracts) const included = rulesForLoad?.geographic @@ -414,6 +421,7 @@ export function CauseBoard({ contentAttestationsKey, contentTrustKey, inclusionRulesKey, + unmaterializedKey, ]) if (preview) { From 2205ae9548f08ba68768d19092b36ced2fb9a351 Mon Sep 17 00:00:00 2001 From: Adam Spitz Date: Tue, 8 Sep 2026 15:24:32 -0400 Subject: [PATCH 2/2] Fail closed until unmaterialized prospective rounds are known. Boards skip post-attestation content-contract rows while the exclude list is unloaded or the query failed, instead of treating that as an empty exclude. --- ui/src/causestarter/hooks/useCauseProjects.ts | 22 ++++++---- ...erializedProspectiveRoundAddresses.test.ts | 11 +++++ ...UnmaterializedProspectiveRoundAddresses.ts | 40 +++++++++++++------ .../components/AlignedProjectsList.tsx | 22 ++++++---- .../fundingportals/components/CauseBoard.tsx | 22 ++++++---- 5 files changed, 80 insertions(+), 37 deletions(-) diff --git a/ui/src/causestarter/hooks/useCauseProjects.ts b/ui/src/causestarter/hooks/useCauseProjects.ts index ef030587..11ecf09c 100644 --- a/ui/src/causestarter/hooks/useCauseProjects.ts +++ b/ui/src/causestarter/hooks/useCauseProjects.ts @@ -83,7 +83,10 @@ export function useCauseProjects( } = useContentFundingState() const trustedContentAttesters = useTrustedContentAttesters() const unmaterializedProspective = useUnmaterializedProspectiveRoundAddresses() - const unmaterializedKey = unmaterializedProspective.slice().sort().join('\0') + const unmaterializedReady = unmaterializedProspective !== undefined + const unmaterializedKey = unmaterializedReady + ? unmaterializedProspective.slice().sort().join('\0') + : null const contentTrustKey = trustedContentAttesters .map((entry) => entry.address.toLowerCase()) .sort() @@ -172,13 +175,15 @@ export function useCauseProjects( } } - const contentContracts = selectAlignedContentContracts( - channels, - contentAttestations, - cids, - contentTrustKey ? contentTrustKey.split('\0') : undefined, - unmaterializedKey ? unmaterializedKey.split('\0') : undefined, - ) + const contentContracts = unmaterializedReady + ? selectAlignedContentContracts( + channels, + contentAttestations, + cids, + contentTrustKey ? contentTrustKey.split('\0') : undefined, + unmaterializedKey ? unmaterializedKey.split('\0') : [], + ) + : [] for (const contract of contentContracts) { const key = contract.contractAddress.toLowerCase() const existing = byAddress.get(key) @@ -241,6 +246,7 @@ export function useCauseProjects( contentAttestationsKey, contentTrustKey, contentLoading, + unmaterializedReady, unmaterializedKey, ]) diff --git a/ui/src/content-funding/hooks/useUnmaterializedProspectiveRoundAddresses.test.ts b/ui/src/content-funding/hooks/useUnmaterializedProspectiveRoundAddresses.test.ts index 6544e56b..754ca74b 100644 --- a/ui/src/content-funding/hooks/useUnmaterializedProspectiveRoundAddresses.test.ts +++ b/ui/src/content-funding/hooks/useUnmaterializedProspectiveRoundAddresses.test.ts @@ -45,8 +45,19 @@ describe('useUnmaterializedProspectiveRoundAddresses', () => { it('returns only rounds that have not materialized', async () => { const { result } = renderHook(() => useUnmaterializedProspectiveRoundAddresses()) + expect(result.current).toBeUndefined() await waitFor(() => { expect(result.current).toEqual(['0x1111111111111111111111111111111111111111']) }) }) + + it('stays undefined when the query fails so consumers fail closed', async () => { + vi.mocked(getProspectiveRounds).mockRejectedValueOnce(new Error('rpc down')) + const { result } = renderHook(() => useUnmaterializedProspectiveRoundAddresses()) + expect(result.current).toBeUndefined() + await waitFor(() => { + expect(getProspectiveRounds).toHaveBeenCalled() + }) + expect(result.current).toBeUndefined() + }) }) diff --git a/ui/src/content-funding/hooks/useUnmaterializedProspectiveRoundAddresses.ts b/ui/src/content-funding/hooks/useUnmaterializedProspectiveRoundAddresses.ts index 315298c7..5e30f814 100644 --- a/ui/src/content-funding/hooks/useUnmaterializedProspectiveRoundAddresses.ts +++ b/ui/src/content-funding/hooks/useUnmaterializedProspectiveRoundAddresses.ts @@ -1,26 +1,21 @@ import { useEffect, useState } from 'react' import { getProspectiveRounds } from '@commonality/sdk/content-funding' +import type { SDKMachinery } from '@commonality/sdk/machinery' import { useMachinery } from '../../shared' -/** Addresses of prospective rounds that have not materialized. Cause boards - * list those only via a project-level alignment vouch, not post attestation. */ -export function useUnmaterializedProspectiveRoundAddresses(): string[] { +/** Unmaterialized prospective-round addresses, or `undefined` until the fetch succeeds. */ +export function useUnmaterializedProspectiveRoundAddresses(): string[] | undefined { const machinery = useMachinery() - const [addresses, setAddresses] = useState([]) + const [addresses, setAddresses] = useState(undefined) useEffect(() => { let cancelled = false - void getProspectiveRounds(machinery) - .then((rounds) => { - if (cancelled) return - setAddresses( - rounds - .filter((round) => !round.materializedToken) - .map((round) => round.round.toLowerCase()), - ) + void loadUnmaterializedProspectiveRoundAddresses(machinery) + .then((next) => { + if (!cancelled) setAddresses(next) }) .catch(() => { - if (!cancelled) setAddresses([]) + // Leave undefined so consumers fail closed (no post-attestation rows). }) return () => { cancelled = true @@ -29,3 +24,22 @@ export function useUnmaterializedProspectiveRoundAddresses(): string[] { return addresses } + +const inflight = new WeakMap>() + +function loadUnmaterializedProspectiveRoundAddresses(machinery: SDKMachinery): Promise { + const existing = inflight.get(machinery) + if (existing) return existing + const pending = getProspectiveRounds(machinery).then((rounds) => + rounds + .filter((round) => !round.materializedToken) + .map((round) => round.round.toLowerCase()), + ) + inflight.set(machinery, pending) + void pending + .catch(() => undefined) + .finally(() => { + if (inflight.get(machinery) === pending) inflight.delete(machinery) + }) + return pending +} diff --git a/ui/src/fundingportals/components/AlignedProjectsList.tsx b/ui/src/fundingportals/components/AlignedProjectsList.tsx index 65683b14..39ea0cc4 100644 --- a/ui/src/fundingportals/components/AlignedProjectsList.tsx +++ b/ui/src/fundingportals/components/AlignedProjectsList.tsx @@ -108,7 +108,10 @@ export function AlignedProjectsList({ const { address } = useAccount() const { channels, contentAttestations } = useContentFundingState() const unmaterializedProspective = useUnmaterializedProspectiveRoundAddresses() - const unmaterializedKey = unmaterializedProspective.slice().sort().join('\0') + const unmaterializedReady = unmaterializedProspective !== undefined + const unmaterializedKey = unmaterializedReady + ? unmaterializedProspective.slice().sort().join('\0') + : null const trustedContentAttesters = useTrustedContentAttesters() const contentTrustKey = trustedContentAttesters .map((entry) => entry.address.toLowerCase()) @@ -195,13 +198,15 @@ export function AlignedProjectsList({ const aligned = perPlank.flat() if (cancelled) return - const contentRows = selectAlignedContentContracts( - channels, - contentAttestations, - loadCids, - contentTrustKey ? contentTrustKey.split('\0') : undefined, - unmaterializedKey ? unmaterializedKey.split('\0') : undefined, - ).map((contract) => ({ + const contentRows = (unmaterializedReady + ? selectAlignedContentContracts( + channels, + contentAttestations, + loadCids, + contentTrustKey ? contentTrustKey.split('\0') : undefined, + unmaterializedKey ? unmaterializedKey.split('\0') : [], + ) + : []).map((contract) => ({ projectAddress: contract.contractAddress, alignmentType: 'direct' as const, fundingCurrency: contract.fundingCurrency ?? ETH_CURRENCY, @@ -266,6 +271,7 @@ export function AlignedProjectsList({ contentAttestationsKey, contentTrustKey, inclusionRules, + unmaterializedReady, unmaterializedKey, ]) diff --git a/ui/src/fundingportals/components/CauseBoard.tsx b/ui/src/fundingportals/components/CauseBoard.tsx index 14425dd6..17eb4328 100644 --- a/ui/src/fundingportals/components/CauseBoard.tsx +++ b/ui/src/fundingportals/components/CauseBoard.tsx @@ -213,7 +213,10 @@ export function CauseBoard({ const [summary, setSummary] = useState(null) const { channels, contentAttestations } = useContentFundingState() const unmaterializedProspective = useUnmaterializedProspectiveRoundAddresses() - const unmaterializedKey = unmaterializedProspective.slice().sort().join('\0') + const unmaterializedReady = unmaterializedProspective !== undefined + const unmaterializedKey = unmaterializedReady + ? unmaterializedProspective.slice().sort().join('\0') + : null const trustedContentAttesters = useTrustedContentAttesters() const contentTrustKey = trustedContentAttesters .map((entry) => entry.address.toLowerCase()) @@ -317,13 +320,15 @@ export function CauseBoard({ } } } - const contentContracts = selectAlignedContentContracts( - channels, - contentAttestations, - loadCids, - contentTrustKey ? contentTrustKey.split('\0') : undefined, - unmaterializedKey ? unmaterializedKey.split('\0') : undefined, - ) + const contentContracts = unmaterializedReady + ? selectAlignedContentContracts( + channels, + contentAttestations, + loadCids, + contentTrustKey ? contentTrustKey.split('\0') : undefined, + unmaterializedKey ? unmaterializedKey.split('\0') : [], + ) + : [] const union = unionAlignedFundingProjects([...byAddress.values()], contentContracts) const included = rulesForLoad?.geographic ? (await Promise.all(union.map(async (project) => { @@ -421,6 +426,7 @@ export function CauseBoard({ contentAttestationsKey, contentTrustKey, inclusionRulesKey, + unmaterializedReady, unmaterializedKey, ])