Since #2099, sidebar follow-list items belonging to an organization link to the org page instead of the space. resolveSpaceItem (apps/ui/src/helpers/organizations/config.ts) hardcodes active_proposals: 0 in the org branch:
if (org) {
return {
link: { name: 'org', params: { org: org.id } },
title: org.name,
avatarSpace: { ...org.spaceIds[0], avatar: '', active_proposals: 0 }
};
}
SpaceAvatar only renders the badge when active_proposals is truthy, so org sidebar items never show the active proposals indicator, even when org spaces have live proposals. Non-org spaces are unaffected.
Expected: org sidebar item shows the active proposals count, summed across all of the org's spaces (for consistency — the badge should reflect the whole org, not just the followed space).
Investigation findings
Data availability
- Offchain hub:
activeProposals is returned by space queries (apps/ui/src/networks/offchain/api/queries.ts) — real counts available today.
- Onchain networks (eth, arb1, sn):
active_proposals is currently hardcoded to null in formatSpace (apps/ui/src/networks/common/graphqlApi/index.ts:360). The branch feat/active-proposals-explore-ui fixes this: it adds active_proposal_count to SPACE_FRAGMENT and maps active_proposals: space.active_proposal_count. Once merged, batched loadSpaces returns true counts for all networks.
- The fix should sum all org spaces without skipping onchain ones: today they contribute
null (treated as 0), and it becomes fully correct once the branch above merges — no further changes needed.
Org configs (ORGANIZATIONS in apps/ui/src/helpers/organizations/config.ts)
| Org |
Spaces |
Networks |
| starknet |
2 |
sn, s |
| snapshot |
1 |
s |
| arbitrum |
3 |
arb1 ×2, s |
| ens |
2 |
eth, s |
Every org has exactly one offchain (s:) space.
Query cost analysis
Approaches considered, extra HTTP requests to populate org-wide counts:
| Approach |
Extra requests |
Notes |
A. Per-org loadSpace for each org space (like useOrganization) |
up to 8 |
wasteful |
B. Fold org sibling ids into the existing followed-spaces batch (getSpaces id_in in useFollowedSpacesQuery) |
0–1 |
muddies followed-query semantics |
C. One shared batched query for all followed orgs' sibling ids, split per network via getSpaces |
≤4 (one per network: s, arb1, eth, sn) |
clean, cached by vue-query |
| D. Like C, but split per endpoint instead of per network (recommended) |
2 flat |
see below |
Recommended approach (D)
All mainnet onchain networks (eth, arb1, sn) share one sx-api endpoint (API_URL in apps/ui/src/networks/evm/metadata.ts and starknet/metadata.ts). loadSpaces (apps/ui/src/networks/common/graphqlApi/index.ts:775) only sets the indexer variable when filter.network is provided — omitting it queries across all indexers in a single request. id_in passes straight into where, and returned spaces carry _indexer so the network field resolves correctly.
So one shared query fn, splitting all followed orgs' sibling space ids into two buckets:
- onchain ids → single
loadSpaces({ id_in }, no network filter) against the shared sx-api endpoint → 1 request covers arb1 + eth + sn
- offchain ids → hub
loadSpaces({ id_in }) → 1 request
2 requests total, regardless of how many orgs are followed. The existing getSpaces helper (apps/ui/src/queries/spaces.ts) splits per network, so this needs a small custom fetch (~15 lines) instead of reusing it. Result should be a single vue-query entry (keyed on the set of followed org ids) shared by all sidebar items; each org item sums active_proposals over its spaces (null → 0).
Caveat: testnet networks use API_TESTNET_URL (different endpoint). No org config uses testnets today; group by endpoint if that ever changes.
Implementation sketch
- Add a shared query (e.g.
useOrgsSpacesQuery in apps/ui/src/queries/spaces.ts) fetching all sibling spaces for a given set of org configs, using the two-bucket fetch above.
- In
AppSidebarItem (apps/ui/src/components/App/SidebarItem.vue), when resolveSpaceItem resolves to an org, override avatarSpace.active_proposals with the summed count from the shared query.
resolveSpaceItem itself stays pure/unchanged (existing unit tests in apps/ui/src/helpers/organizations.test.ts keep passing); only the component layer supplies the real count.
- Note:
SpacesListItem.vue also uses resolveSpaceItem; check whether the explore list should get the same treatment for consistency.
Since #2099, sidebar follow-list items belonging to an organization link to the org page instead of the space.
resolveSpaceItem(apps/ui/src/helpers/organizations/config.ts) hardcodesactive_proposals: 0in the org branch:SpaceAvataronly renders the badge whenactive_proposalsis truthy, so org sidebar items never show the active proposals indicator, even when org spaces have live proposals. Non-org spaces are unaffected.Expected: org sidebar item shows the active proposals count, summed across all of the org's spaces (for consistency — the badge should reflect the whole org, not just the followed space).
Investigation findings
Data availability
activeProposalsis returned by space queries (apps/ui/src/networks/offchain/api/queries.ts) — real counts available today.active_proposalsis currently hardcoded tonullinformatSpace(apps/ui/src/networks/common/graphqlApi/index.ts:360). The branchfeat/active-proposals-explore-uifixes this: it addsactive_proposal_counttoSPACE_FRAGMENTand mapsactive_proposals: space.active_proposal_count. Once merged, batchedloadSpacesreturns true counts for all networks.null(treated as 0), and it becomes fully correct once the branch above merges — no further changes needed.Org configs (
ORGANIZATIONSinapps/ui/src/helpers/organizations/config.ts)Every org has exactly one offchain (
s:) space.Query cost analysis
Approaches considered, extra HTTP requests to populate org-wide counts:
loadSpacefor each org space (likeuseOrganization)getSpacesid_ininuseFollowedSpacesQuery)getSpacesRecommended approach (D)
All mainnet onchain networks (eth, arb1, sn) share one sx-api endpoint (
API_URLinapps/ui/src/networks/evm/metadata.tsandstarknet/metadata.ts).loadSpaces(apps/ui/src/networks/common/graphqlApi/index.ts:775) only sets theindexervariable whenfilter.networkis provided — omitting it queries across all indexers in a single request.id_inpasses straight intowhere, and returned spaces carry_indexerso thenetworkfield resolves correctly.So one shared query fn, splitting all followed orgs' sibling space ids into two buckets:
loadSpaces({ id_in }, no network filter)against the shared sx-api endpoint → 1 request covers arb1 + eth + snloadSpaces({ id_in })→ 1 request2 requests total, regardless of how many orgs are followed. The existing
getSpaceshelper (apps/ui/src/queries/spaces.ts) splits per network, so this needs a small custom fetch (~15 lines) instead of reusing it. Result should be a single vue-query entry (keyed on the set of followed org ids) shared by all sidebar items; each org item sumsactive_proposalsover its spaces (null → 0).Caveat: testnet networks use
API_TESTNET_URL(different endpoint). No org config uses testnets today; group by endpoint if that ever changes.Implementation sketch
useOrgsSpacesQueryinapps/ui/src/queries/spaces.ts) fetching all sibling spaces for a given set of org configs, using the two-bucket fetch above.AppSidebarItem(apps/ui/src/components/App/SidebarItem.vue), whenresolveSpaceItemresolves to an org, overrideavatarSpace.active_proposalswith the summed count from the shared query.resolveSpaceItemitself stays pure/unchanged (existing unit tests inapps/ui/src/helpers/organizations.test.tskeep passing); only the component layer supplies the real count.SpacesListItem.vuealso usesresolveSpaceItem; check whether the explore list should get the same treatment for consistency.