Skip to content

feat: read a community's Node DAO proposals signed out (#413, part 1) - #474

Open
adenarc wants to merge 1 commit into
mainfrom
feat/public-node-dao-proposals-413
Open

adenarc wants to merge 1 commit into
mainfrom
feat/public-node-dao-proposals-413

Conversation

@adenarc

@adenarc adenarc commented Nov 1, 2026

Copy link
Copy Markdown
Collaborator

Anyone can now read a community's Node DAO proposals, signed out. This is the public reading part of #413. Member submission, the bond and the applications part still wait on decision B, #406 and #412.

The plan and the DAO-account source are recorded on #413: #413 (comment)

What's new

  • getNodeDao({ nodeId }) (public): the community's tenant owner when ownerKind = "dao", read live from City Node's database, with the network from the account suffix. It returns nulls for a community owned by a person or the platform. It doesn't use the session-only linked treasury (auth.getDao).
  • /n/$slug/proposals: proposals newest first, with status, kind, title, proposer and date. Pages are stable ID ranges, with "Show older" to load the next one.
  • /n/$slug/proposals/$proposalId: one proposal read by ID through get_proposal, so older proposals work too. It shows status, proposer, submitted time, vote tally and description. A missing, removed or malformed ID shows "not found".
  • New reads readDaoProposalPage and readDaoProposal throw on RPC failure, unlike fetchDaoProposals. The DAO lookup, the first page, an older page and a single proposal each show a retry instead of an empty list.
  • The community page shows a Proposals button only when the community has a Node DAO.
  • Kinds without their own summary show their kind name (e.g. "ChangePolicy proposal"). Trezu's <br> and * Title: description format is shown as lines, and the list uses the title line.
  • Testnet DAOs show "not shown here yet", because proposal reads are mainnet only today.
  • New strings in en/es/fr/zh. Changeset included.

Tested

  • API integration: getNodeDao signed out, owner account changed (the next read follows it), testnet suffix, and unknown community
  • UI lib: paging by ID (including gaps from removed proposals), an empty DAO, RPC failure, detail by ID, ERR_NO_PROPOSAL treated as not found, and other errors thrown
  • Components: list signed out with only the two public procedures called, Show older, a retryable first-page failure, an older-page failure that keeps the loaded page, empty state, transfer and unsupported kinds, no DAO, testnet, DAO lookup failure with retry, unknown community, older detail with votes, missing ID, malformed ID with no chain read, detail failure with retry. Also the community page's Proposals link with and without a DAO
  • pnpm run typecheck, pnpm run lint, ui 1149 and api 435 tests pass
  • Browser, local stack on Postgres with a seeded community owned by a real mainnet Sputnik DAO and one owned by a person, signed out:
    • the list loaded live proposals, and Show older went from 20 to 40
    • an older proposal's detail page loaded
    • a missing ID showed not found
    • the person-owned community said it has no Node DAO and its page has no Proposals button
    • at 375px there was no horizontal scroll on the list or the detail page
    • the only console errors were the signed-out sign-in restore messages that also show on main

No screenshots: the real DAO's proposals show other people's NEAR account names.

Part of #413

- public getNodeDao: the community's tenant owner when it's a DAO, read live, network from the account suffix
- /n/$slug/proposals lists proposals newest first with stable pages by ID and Show older
- /n/$slug/proposals/$proposalId reads one proposal by ID, older ones included; missing or removed IDs show not found
- every read fails with a retry instead of an empty list; testnet DAOs aren't shown yet
- community page links to Proposals when there is a Node DAO
@multi-review

multi-review Bot commented Nov 1, 2026

Copy link
Copy Markdown

Multi Review · 14f372f

The PR adds a public getNodeDao procedure and signed-out pages for listing and viewing a community's proposals. It also adds paged reads by ID range and by single ID (readDaoProposalPage, readDaoProposal) that throw on RPC failure, and a Proposals button on the community page. Overall risk is low: the API only shows the owner account, which the existing public resolveTenantById already returns. One edge case in the list UI can hide older proposals.

1 issue(s)

What was checked (10 areas)
  • getNodeDao handler: nodes.getById, then tenants.resolveTenantById, then resolveNetwork (whether a hint is needed, and the output enum)
  • Compared getNodeDao with getNodeMemberCount and the public resolveTenantById, to see if it exposes anything new
  • readDaoProposalPage: get_last_proposal_id semantics, the [from, end) range, nextBefore, and gaps from removed proposals
  • readDaoProposal: ERR_NO_PROPOSAL check, other errors rethrown
  • daoProposalPagesQueryOptions refetch: pages recomputed via getNextPageParam, so ID ranges stay disjoint
  • DaoProposalList rendering states (pending, error, empty, Show older guards)
  • DaoProposalDetail with null, malformed and valid IDs, and the error and not-found states
  • NodeDaoGate: node query states, then DAO query states, then the testnet guard
  • NodePage nodeDao query and the Proposals link params
  • Other tests that render the $slug route, in case the new getNodeDao call hits their fetch mocks

Findings

  • 🟡 Low ui/src/routes/_public/n/-node-dao-proposals.tsx:164 An empty first page shows "No proposals yet" and hides Show older, even when older proposals exist
    readDaoProposalPage reads fixed ID ranges, and the docs say removed proposals leave gaps. Suppose all of the newest 20 IDs were removed with Remove votes, which delete them from Sputnik storage (common when a DAO clears spam). Then the first page returns proposals: [] with a non-null nextBefore. The list then shows the dao-proposals.empty notice. The Show older button is gated on proposals.length > 0 && pages.hasNextPage, so it never renders. The user is told the DAO has no proposals, and there's no way to reach the older ones that exist.
    Suggested fix: Show the empty notice only when !pages.hasNextPage. Make the Show older button and the older-page error depend on hasNextPage / isFetchNextPageError rather than on proposals.length > 0. Or keep reading older ranges in readDaoProposalPage until a page is non-empty or ID 0 is reached.

Comment @multi-review to re-review new commits, or @multi-review full for a full review.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant