Skip to content

Fix CVE vulnerabilities in React and Next.js dependencies#1

Open
sktbrd wants to merge 99 commits into
BuilderOSS:mainfrom
sktbrd:main
Open

Fix CVE vulnerabilities in React and Next.js dependencies#1
sktbrd wants to merge 99 commits into
BuilderOSS:mainfrom
sktbrd:main

Conversation

@sktbrd

@sktbrd sktbrd commented May 2, 2026

Copy link
Copy Markdown

Vercel recommended

Summary by CodeRabbit

Release Notes

  • New Features

    • Added dashboard with auction hero, stats overview, recent proposals, and activity feed
    • Added About page with DAO information, founders, and smart contracts
    • Added Treasury page with analytics and token/NFT holdings
    • Added Members page with voting power and activity tracking
    • Added proposal creation and voting UI with real-time bid history
    • Added theme customization panel for accent color, border radius, and typography
    • Added Open Graph image generation for proposals and auctions
    • Added DAO switching CLI for preset configurations
  • Refactor

    • Migrated to Next.js App Router with modern server/client component patterns
    • Restructured authentication and Web3 provider initialization
    • Simplified data fetching and caching strategies
  • Style

    • Complete styling overhaul with Tailwind CSS and design tokens
    • Redesigned header and navigation
    • Updated color scheme and typography system
    • Added dark theme support
  • Chores

    • Upgraded Next.js and React dependencies
    • Simplified environment configuration
    • Updated project documentation

sktbrd and others added 30 commits May 1, 2026 15:48
Foundation for the Milestone 1 redesign. Replaces Pages Router /
Vanilla Extract / @buildeross/dao-ui page-level setup with the stack
the design brief specifies (Next.js 15 App Router, Tailwind v4,
CSS-variable theme tokens, light + dark via next-themes).

What ships
- App Router tree (src/app/) with layout, providers, and stub pages for
  every route in the brief: /, /auction/[id], /proposals,
  /proposals/[id], /treasury, /members, /about.
- Tailwind v4 + globals.css with the design's theme token surface
  (--accent, --radius, --bg/surface/fg/muted-fg, vote-for/against/abstain).
- Sticky Header (logo, chain pill, nav, theme toggle, RainbowKit Connect,
  mobile sheet) and Footer composed in app/layout.tsx.
- TweaksPanel — dev-only floating widget for live theme tweaks (DAO
  preset, accent, radius, display font, light/dark). Persists to
  localStorage. Only mounts when NODE_ENV !== 'production'.
- DaoLogo (3 styles), Button (shadcn-style with cva), PageStub primitives.
- src/lib/dao.config.ts — typed config surface per the brief
  (identity, theme, chainId, addresses, features, socials).
- src/lib/mockData.ts — ports PRESETS / PROPOSALS / AUCTION / ACTIVITY /
  MEMBERS / CONTRACTS from the design package. Used by upcoming PRs.
- API routes migrated from Pages → App Router route handlers
  (api/pinata/*, api/simulate, api/ai/generateTxSummary).
- next.config redirects: /token/:id → /auction/:id, /proposal/:id →
  /proposals/:id, /contracts → /about.

What this PR removes
- @buildeross/{dao-ui,auction-ui,proposal-ui,create-proposal-ui,zord,ui*}
  page-level UI packages — page bodies will be authored from scratch in
  PRs #1#7 against @buildeross/sdk + @buildeross/hooks (gnars.com approach).
- @vanilla-extract/* — replaced by Tailwind v4.
- @fontsource/{inter,londrina-solid} + PT Root UI — replaced by next/font
  (Geist + Geist Mono).
- Pages Router files (src/pages/*) and the legacy Header/Layout components.
- /settings page + useSettingsAccess hook — out of scope for milestone 1.

(* @buildeross/ui kept — still used for shared atoms.)

Page bodies land in subsequent PRs:
PR #1 dashboard · #2 auction · #3 proposals list · #4 proposal detail ·
#5 treasury · #6 members · #7 about · #8 docs + fork checklist.

Verified: pnpm type-check passes, dev server boots, all 7 routes
return 200, /token/X → /auction/X redirect works.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replaces the PR #0 placeholder with the design's Dashboard, 1:1 against
the brief's spec. Renders against mock data from src/lib/mockData.ts —
live subgraph wiring lands in a follow-up.

Sections (top to bottom)
- Hero: DAO logo + name + chain pill, display H1 + tagline, 3 stat tiles
  (Total tokens / Members / Treasury), AuctionArt artwork on the right
  with floating "Today's auction · #N" tag.
- Live auction spotlight (2/3 col): inline AuctionArt + Latest auction
  date + Title #N + top bid / held by / ends in + Place a bid CTA.
- Activity feed (1/3 col): bid / vote / prop dot-coded entries.
- Recent proposals: 3 ProposalCards with embedded VoteBar, requested
  amount footer, status badge, links to /proposals/[id].
- Treasury snapshot: 3 KPI cards + Auction Revenue BarChart (12 months).

New components in src/components/dao/
- VoteBar — signature 3-segment for/against/abstain bar with quorum
  tick (small + large variants via height prop).
- StatusBadge — color-coded pill (active/pending/executed/defeated/
  cancelled).
- StatTile — accent-tinted icon + label + value.
- KpiCard — display-font value + muted label, used in treasury snapshot.
- BarChart — pure SVG bars, optional month labels.
- AuctionArt — placeholder SVG artwork themed by 3-color palette.
- ProposalCard — used here and in the upcoming proposals list (#3).
- ActivityFeed — typed list of bid/vote/prop events.
- AddressChip — copy-to-clipboard, used in upcoming pages.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replaces the auction stub with the design's full auction body. Mock
data from src/lib/mockData.ts; live wiring to the auction contract +
subgraph lands in a follow-up.

Layout
- TimeAlert at top: "Auction for Builder #765 ends in 17h 54m"
  (dismissible accent banner).
- Two-column layout (1fr / 0.85fr on lg+):
  - Left: Auction / Chart tabs, then large AuctionArt artwork.
  - Right: prev/next nav + Latest badge + date · display H1
    (Builder #N) · 4-cell kv-grid (Top bid, Top bidder, Ends in,
    Min next bid) · BidForm · VotingPowerExplainer (eligible).
- Below: full Bid history card with Top bid badge on the latest.

New components in src/components/dao/
- TimeAlert — accent/warning/destructive tone variants, optional
  dismiss button. Used here, will reuse on proposal detail (#4).
- VotingPowerExplainer — 4 scenarios (none/delegated/incoming/
  eligible). Reused on proposal detail (#4).
- BidForm — amount input with ETH suffix + min-increment auto-calc
  (1.02× top bid by default), optional 140-char on-chain comment
  (gated by daoConfig.features.bidComments), balance + network
  footer with wrong-network / below-min / over-balance error
  states. The Place Bid button is correctly disabled when any
  validation fails.
- BidHistory — bid list with amount, address, time, optional
  comment, "Top bid" badge on the latest, friendly empty state.

Tabs are static for PR #2 (Auction active, Chart disabled with
"Coming soon" tooltip). Auction chart lives behind a feature flag
(daoConfig.features.auctionChart) and lands in a future polish PR
once we have historical bid data.

Prev/next nav: ← links to /auction/(id-1) (404-safe — uses 301
redirect from /token/* fallback for older links). → disabled when
viewing the latest auction.

Verified: type-check clean, /auction/765 returns 200, all key blocks
present (Bid history, Place bid, Min next bid, Top bidder, Latest
auction, on-chain comment field, 0.42 ETH top bid, 17h 54m).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replaces the proposals list stub with the design's full grid view.
Mock data — live subgraph wiring lands in a follow-up.

Layout
- Page header: "Proposals" display H1, subtitle, "+ Create proposal"
  CTA on the right.
- Filter bar: search input with magnifier prefix + status select
  (All / Active / Pending / Executed / Defeated / Cancelled), live
  "X of Y" count.
- Card grid (1 / 2 / 3 columns by breakpoint) of ProposalCards from
  PR #1 — number, title, status badge, embedded VoteBar, requested
  amount.
- Empty state when filters return no results.

Implementation
- Page (server component) sets metadata + injects mock proposals into
  ProposalsListView (client) so the door is open for server-side
  subgraph fetching later without restructuring.
- New ProposalsListView in src/components/dao/ — owns search + filter
  state, derives the filtered list with useMemo.
- Filter logic: status === 'all' || p.status === status; case-insensitive
  title match.

Verified: type-check clean, /proposals returns 200, all key strings
present (Proposals, How the community, Create proposal, All statuses,
Search proposals, mock proposal titles).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replaces the proposal detail stub with the design's two-column body.
Mock — markdown rendering + tx decoding + actual on-chain casting
land in a follow-up.

Layout
- Breadcrumbs: ← All proposals
- Two-column (1fr / 360px on lg+):
  - Left: header (Prop N · status · ends label) → display H1 →
    proposed by · date → Vote summary card (large VoteBar with
    labels, quorum + total cast) → Description card (prose,
    Milestones list) → Transactions card.
  - Right: sticky VotePanel (~top-20).

New component
- VotePanel (client) — 3 vote choice buttons (For green / Against
  red / Abstain neutral) with active states color-coded by the
  vote palette tokens; optional reason textarea; Submit vote button
  disabled until a choice is selected; embedded VotingPowerExplainer
  scoped to the connected wallet's voting power. Disabled entirely
  when proposal isn't active/pending.

Mock content note
- Description renders as plain JSX prose for PR #4. Real markdown
  rendering (react-mde or rehype) lands when we wire IPFS-backed
  proposals.
- Single transaction stub (createEscrow 4.0 ETH) matches the design.
  Per-proposal real tx lists come with subgraph wiring.
- VotePanel is interactive but does not write on-chain — that lands
  with the contract write hook.

Verified: type-check clean, /proposals/61 returns 200, all key strings
present (All proposals, Vote summary, Cast your vote, Submit vote,
createEscrow, Optional reason, Quorum: 24, Milestones).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replaces the treasury stub with the design's full body. Mock — live
balances + tx history land with subgraph + Alchemy wiring.

Layout
- Page header: "Treasury" display H1 + subtitle, AddressChip
  (truncated treasury address) on the right.
- KPI row (3 cards): Total treasury value · ETH balance · Total
  auction sales.
- Charts grid (3 cards): Auction revenue (12 months) · Proposal
  activity (12 months) · Member activity (voters per recent prop).
  Pure SVG via the BarChart component from #1 — no recharts dep yet.
- Token holdings table with single Zora line (mock).
- NFT holdings — 8-tile grid, AuctionArt placeholder per tile, glassy
  bottom-strip overlay with #N + date.

Notes
- Charts are intentionally pure SVG (BarChart) for PR #5. The brief
  tags Treasury Analytics as "upstream batch 2" — moving to a
  dedicated package with axis/tooltip/legend later.
- Single ZORA holding is hard-mocked; per-token balances come from
  Alchemy/Zora API in a follow-up.
- NFT tiles use the same AuctionArt palette to stand in for real
  token artwork until we wire NFT metadata fetching.

Verified: type-check clean, /treasury returns 200, all sections
present (Treasury · Total treasury value · ETH balance · Auction
revenue · Proposal activity · Member activity · Token holdings ·
NFT holdings · 152,790 ZORA · 15.5925 ETH).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replaces the members stub with the design's full directory. Mock —
real holder data via subgraph + ENS resolution lands in a follow-up.

Layout
- Page header: "Members" display H1 + "{N} members · {N} active in
  last 5 proposals" subtitle. Search input + Export CSV button on
  the right.
- Table: Delegate (avatar gradient + ENS / fallback + monospace
  address), Votes, Vote %, Joined, active dot column.
- Empty state when search filters everything out.

New component
- MembersTable (client) — owns search state + CSV export. Builds the
  CSV in-browser from the filtered set (only what you see is what
  exports). Avatar gradient is deterministic by row index for now;
  ENS avatars will replace it when we wire ENS resolution.

The brief lists "active member detection" as upstream batch 1
(@buildeross/hooks). For PR #6, the active flag comes straight off
the mock object — when the upstream hook lands, MembersTable just
swaps where it reads the boolean from.

Verified: type-check clean, /members returns 200, all key strings
present (Members, Delegate, Vote %, Joined, Export CSV, mock ENSes
haxixe.eth + gnarlyvlad.eth, "active in last 5 proposals").

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replaces the about stub with the design's full body. Folds the old
/contracts page in (already redirected here from #0). Mock — founders
+ contracts data lands with metadata wiring.

Layout
- DAO header card: large DaoLogo + display H2 + tagline · 4-column
  Kv block (Treasury, Owners, Total supply, Chain).
- Mission card: H2 "Powering Onchain Communities." + two paragraphs
  of generic placeholder mission copy.
- Founders card: 2-up grid of avatar + truncated address + N% share.
- Smart contracts card: list of NFT, Auction House, Governor,
  Treasury, Metadata, Escrow Delegate — each with a click-to-copy
  AddressChip.

Cleanup
- Drop src/components/PageStub.tsx — every route now has a real page.

Verified: type-check clean, /about returns 200, all key strings
present (Powering Onchain Communities, Founders, Smart contracts,
Auction House, Governor, Escrow Delegate, public good, community
channel). /contracts redirects to /about.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Closes Milestone 1 deliverables: setup documentation, deployment guide,
fork checklist, and the scope map showing what is template-side vs
proposed upstream.

README rewrite
- "What you get" table maps every page to route + block summary.
- Quick start: install, env, fetch-dao, dev (4 steps).
- Env table flags which vars are required vs optional and where to
  get each.
- Fork checklist — 10 concrete steps to ship a re-skinned, deployed
  site for a new DAO in <20 min.
- Theming & config surface — annotated dao.config.ts walkthrough,
  pointer to globals.css for low-level tokens.
- Deploy to Vercel (one-click + manual + custom domain).
- Scope map table per the milestone acceptance criteria —
  Template / Upstream batch 1 / Upstream batch 2 lanes.
- Available scripts table.
- Project structure tree (refreshed for App Router).

dao.config.ts polish
- Section comments grouping Identity / Onchain / Theme / Features /
  Socials.
- JSDoc on every key in DaoTheme / DaoFeatures explaining what it
  controls and how it's consumed.
- Commented-out social links as a fill-in template for forks.

sample.env: fix AI_MODAL → AI_MODEL typo (the runtime code reads
process.env.AI_MODEL).

Verified: pnpm type-check clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Phase 3 of the redesign — swap dashboard mocks for real subgraph +
chain reads. The dashboard is now an async server component pulling:

- totalSupply + ownerCount via SubgraphSDK.daoInfo
- current auction (token, image, top bid, bidder, endTime) via
  SubgraphSDK.findAuctions ordered by endTime desc
- recent proposals (with state mapped via mapProposalState) via
  the helper getProposals
- total auction sales lifetime via SubgraphSDK.totalAuctionSales
- treasury ETH balance via viem publicClient.getBalance against the
  treasury address (transports from @buildeross/utils/wagmi)
- 12-month auction revenue chart via SubgraphSDK.auctionHistory,
  bucketed in src/lib/dao-data.ts

Resilience
- Each subgraph/chain call goes through a safeFetch() wrapper that
  catches and logs failures, returning a typed empty fallback so
  the page never throws — sections degrade independently.
- 60s revalidation (export const revalidate = 60).
- Empty states for: no auction, no bids, no proposals.

What's still mocked
- Activity feed — composing it requires merging recent bids + votes +
  proposal-created events; lands in a follow-up.
- Hero treasury USD figure — replaced with ETH balance for now to
  avoid hard-coding a stale ETH/USD rate.
- Requested-amount in proposal cards — depends on tx-decoder upstream
  hook; defaults to 0/0 here.

Files
- src/lib/dao-data.ts (new): server-only data layer + ProposalState
  → ProposalStatus mapping. Marked `import 'server-only'` so it can't
  accidentally bundle into client components.
- src/app/page.tsx: rewritten as async server component consuming
  the new data layer.

Verified locally on the Builder DAO (chain 8453, token
0xe8af882f2f5c79580230710ac0e2344070099432):
- Total supply 658 · members 181 · treasury 15.5924 ETH · auction
  #765 with the real onchain-rendered artwork (nouns.build renderer
  composing the SVG layers from IPFS).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Phase 3, page 2 of 6. Wires /auction/[id] to real subgraph data.

What's wired
- Token name + image — from findAuctions (last 50, filtered by
  tokenId in JS).
- Top bid + bidder — from auction.highestBid.
- End time + open/settled state — diff against Date.now().
- Bid history — getBids(chainId, tokenAddress, tokenId) helper.
- Prev/next nav — daoNextAndPreviousTokens; both arrows wired to real
  neighbouring token ids; empty cursors disable the buttons.
- "Latest auction" badge from latest[] cursor.
- Settled auctions get a "This auction has ended" banner with a See
  next auction → link.

Component tweaks
- BidHistory widened to a generic { amount, addr, time?, comment? }
  shape so it accepts both the design's mock bids and live AuctionBid
  data (which doesn't carry a timestamp / comment yet — the subgraph
  fragment only fields amount + bidder + id; surfacing bid timestamps
  + onchain comments needs subgraph schema additions or tx-data
  decoding, which lands in a follow-up).
- Auction nav consumes prevTokenId/nextTokenId from the data layer
  instead of computing tokenId-1.

Resilience
- 30s revalidation (auctions move faster than dashboard).
- All three queries wrapped in safeFetch — if subgraph is down,
  prev/next degrade and bids list goes empty without throwing.
- 404s on non-numeric / negative ids via notFound().

Verified locally on the Builder DAO:
- /auction/765 (latest) — shows real artwork, "Settled", correct
  prev/next nav.
- /auction/100 (old) — shows real settled state, full bid history if
  any, See next auction link routes correctly.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Phase 3, page 3 of 6. /proposals now reads real proposals from the
Builder subgraph.

Implementation
- New getAllProposals(limit) helper in dao-data.ts wraps getProposals
  and maps to the shared ProposalSummary shape.
- Renamed DashboardProposal → ProposalSummary; kept a deprecated
  alias to avoid breaking PR #10 import sites until rebase.
- Exported formatProposal so other pages (proposal detail, etc.) can
  consume it.
- ProposalCard + ProposalsListView now type their proposal prop as
  ProposalSummary (was MockProposal). Structurally compatible with
  both the live + mock objects.

Page changes
- src/app/proposals/page.tsx is now an async server component that
  fetches up to 50 proposals once and pushes them into the existing
  ProposalsListView client component (search + status filtering stay
  client-side, instant).

Resilience
- 60s revalidation.
- safeFetch wrapper falls back to an empty array on subgraph
  failure — the existing empty state on ProposalsListView ("No
  proposals match your filters") absorbs that case cleanly.

Verified on the Builder DAO: 50 real proposals rendering with their
real titles & status (Prop 63 / 62 / 61 / 60 / 59 / ... down to the
oldest in the limit window). Search + status filter work against the
live data.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Phase 3, page 4 of 6. /proposals/[id] reads real proposals from the
subgraph + decodes their tx batch.

What's wired
- Lookup by proposalNumber via SubgraphSDK.proposals filter (single
  fragment, no per-row on-chain state read — see "rate-limit fix" below).
- Real description rendered as preformatted text (markdown rendering
  with rehype/remark lands in a follow-up).
- Real transactions list — splits the colon-separated calldatas
  string from the fragment back into per-call hex chunks; matches
  formatAndFetchState's behaviour without the RPC call. Each row
  shows Target (truncated), calldata preview (selector only), and
  value in ETH.
- Real for/against/abstain counts + quorum from the subgraph
  fragment.
- 404 via notFound() for non-numeric ids and for proposalNumbers
  that don't exist on this DAO.

Rate-limit fix (the reason the helper isn't using formatAndFetchState)
- The high-level getProposals/getProposal helpers issue a
  governor.state(proposalId) read per proposal. On the default
  public Base RPC this 429s frequently — earlier verification of
  PR #13 saw flaky 404s where one proposal in N would fail the
  on-chain read and the page would notFound().
- New inferProposalState() in dao-data.ts derives the 5-state UI
  status from the fragment fields alone (executedAt /
  cancelTransactionHash / vetoTransactionHash / expiresAt / vote
  counts vs quorum / age heuristic). No RPC. Stable across all 13
  proposals × 3 runs verified locally.
- Forks with an Alchemy key keep working at higher fidelity if we
  swap back later — a one-line change.

Vote panel
- Stays UI-only (Submit vote does not write on-chain). Real voting
  power resolution + on-chain write lands when we add the wagmi
  contract-write hook.

Verified locally on the Builder DAO:
- /proposals/61 renders the real "Enhance the Official Builder
  Template..." proposal — title, full description, 1 escrow tx
  (0xdafe…d19e, calldata 0xecccfad8…, value 4 ETH), Quorum: 65.
- /proposals/9999 → 404.
- 13 different ids tested 3× each, all 200.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Phase 3, page 5 of 6. /treasury reads real chain + subgraph data.

What's wired
- Treasury ETH balance via viem getBalance against the treasury
  address (transports from @buildeross/utils/wagmi).
- Total auction sales lifetime (sum of all winning bids) via
  SubgraphSDK.totalAuctionSales.
- Owner count via SubgraphSDK.daoInfo.
- Treasury address copy chip uses the real address from
  daoConfig.addresses.treasury.

Charts (all real data, monthly buckets, last 12 mo)
- Auction revenue — auctionHistory query, bucketed by endTime, sum
  of winning bid amounts in ETH (settled-only).
- Proposal activity — count of proposals by timeCreated month.
- Voter activity — total cast votes per recent proposal (last 14
  proposals, oldest → newest).

What stays mocked / deferred
- ERC-20 token holdings table — needs Alchemy/Zora API enrichment
  for symbol+balance+USD. Replaced with a clear placeholder card.
- DAO-owned NFT artwork grid — needs the metadata renderer URL per
  token id. Replaced with a placeholder card. Real artwork lands
  with the per-token image fetcher (same one used on the dashboard
  hero / auction page).

Resilience
- 60s revalidation.
- All 5 fetches behind safeFetch — sections degrade independently.

Verified locally on the Builder DAO:
- KPIs: 15.5924 ETH treasury · 181 owners · real auction sales total
- All 3 chart cards render with real bucketed data

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Phase 3, page 6 of 6 — last page in the live-data pass.

What's wired
- Real holders via SubgraphSDK.daoMembersList — 1000-record window,
  ordered by daoTokenCount desc.
- Joined date computed from each holder's earliest token mintedAt
  (smallest mintedAt across daoTokens[]).
- Vote count = daoTokenCount.
- Vote % = daoTokenCount / sum(all holders) × 100, rounded to 2dp.
- Active flag — derived from voters in the last 5 proposals
  (SubgraphSDK.proposals first 5, gather every voter into a Set,
  match each holder address against it). This is the Active Member
  Detection feature from the design brief, implemented locally for
  the template (the brief tags it as upstream-batch-1, where it
  would land as a @buildeross/hooks export).
- "{N} members · {N} active in last 5 proposals" header subtitle
  comes from these counts.

Why daoMembersList instead of memberSnapshotRequest
- memberSnapshotRequest does on-chain reads per holder (delegate
  resolution); on the public Base RPC this rate-limits hard for
  any DAO with >100 owners. Same workaround as PR #13: stay on
  pure-subgraph data, swap for the precise on-chain version when
  an Alchemy key is wired up.

ENS resolution stays as a follow-up — needs viem batch resolver
server-side, not in scope for this PR.

Verified locally on the Builder DAO:
- 183 real holders rendering with truncated addresses + their real
  joined dates (computed from token mint timestamps).
- "183 members · 12 active in last 5 proposals" — real counts.

Side fix during verification: needed to wipe .next/ to clear stale
Next dev cache that was 404'ing the route after stack changes.
Documenting here so the next dev knows: `rm -rf .next && pnpm dev`
if you see a phantom 404 on a refactored route.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three threads bundled into one polish PR.

1. Labrat: pnpm switch-dao
- New scripts/switchDao.ts (CLI) flips the local environment to a
  different Builder DAO without hand-editing .env.local. Updates env
  vars, writes a theme overrides JSON, runs fetch-dao.
- Built-in presets: builder, gnars (Base, real contract addresses
  from r4topunk/gnars-website).
- New pnpm switch-dao script in package.json.
- Used to swap to Gnars during PR verification (token
  0x880fb3cf5c6cc2d7dfc13a993e839a9411200c17, treasury 0x72aD…6f88).

2. Theme JSON loaded server-side
- New src/config/dao.theme.json carries tagline + accent + radius +
  displayFont as the per-fork visual identity (committed; switch-dao
  rewrites it).
- src/lib/dao.config.ts now reads the JSON via standard import and
  merges into the daoConfig defaults.
- src/app/layout.tsx injects the theme tokens onto <html style="...">
  so the SSR HTML already has the correct accent/radius/display
  font — no flash, no need for the dev Tweaks panel for prod use.
- next/font now loads all 4 supported display fonts (Geist, Londrina
  Solid, IBM Plex Sans, Fraunces). Each contributes a CSS variable;
  --font-display-active routes to the right one based on theme.

3. Real DAO avatar (replaces generic stripes)
- New src/components/DaoAvatar.tsx: reads daoConfig.image (IPFS URI
  from on-chain metadata contract), renders a circular <img> via the
  Pinata gateway. Falls back to the existing DaoLogo stripes SVG on
  load error or when no image is set on the contract.
- Header / Dashboard hero / About hero all use it. Generic
  stripes/tv/leaf SVGs remain in DaoLogo for the Tweaks-panel preset
  preview only.

Verified locally
- pnpm switch-dao gnars → real Gnars logo + yellow accent + 8px
  radius + Londrina display font flow through every page.
- 6,001 Total Gnars · 1,011 owners · 50 real Gnars proposals (Pod
  Media Strategy Q1 2026, Sponsor Monik Santos in QS/WSL 2026, …) ·
  Gnars #7064 current auction with the real onchain-rendered SVG.
- pnpm switch-dao builder → flips back cleanly.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Audit follow-up to PR #16: every page body must read from daoConfig
+ subgraph + chain. The labrat needs to flip to any DAO with zero
hardcoded falsehoods.

What was hardcoded — and is now config-driven

- About page: Treasury / Owners / Total supply KvBlocks were reading
  PRESETS.builder mock numbers; now via getAboutPageData (subgraph
  daoInfo + viem getBalance). Smart contracts list was the mockData
  CONTRACTS array; now built from daoConfig.addresses. Founders cards
  were two hardcoded Builder DAO addresses (0xE2E…6A2E1 / 0x1cD…Eef21
  with 10% / 5%); now fetched from the token contract via
  getFounders(). Mission copy is a per-fork placeholder + one-line
  edit instructions.
- Dashboard hero / Auction page: PRESETS.builder.artworkPalette
  fallback colours replaced with fallbackArtPalette() in
  dao.config.ts, derived from daoConfig.theme.accent.
- Header nav: AUCTION.tokenId mock token id replaced with a
  /auction/latest server-side redirect that resolves the live token
  id via SubgraphSDK.findAuctions.
- Dashboard activity feed: ACTIVITY mock array replaced with real
  events. New buildRecentActivity() helper merges recent bids on the
  current auction (via getBids) + last 4 proposals created.
- All toLocaleString() calls pinned to 'en-US' so SSR output is
  consistent across server locales (was rendering "1.011" for 1011
  on a server with German locale).

Why this matters
- This is a template repo; the only DAO-specific runtime data should
  flow from .env.local → fetch-dao → src/config/dao.ts and from
  src/config/dao.theme.json. The labrat (pnpm switch-dao) rewrites
  exactly those two and nothing else.

Verified locally on Gnars labrat:
- /about: real treasury (6.7727 ETH) · 1,011 owners · 6,001 supply ·
  real Gnars contract addresses · "No founders configured" empty
  state (Gnars is set up that way on-chain).
- /: activity feed shows "created proposal #119/118/117/116" etc.
  with real proposer addresses; nav Auction link routes via
  /auction/latest → /auction/7064.

What still imports from mockData.ts
- Type aliases only (ProposalStatus, ActivityType, MockMember).
  No data. To be moved to a non-mock-named module in a follow-up.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Followup to the runtime audit. Eliminates the only "mock" code path
that survived from the design-package scaffolding days. The template
now has zero mock data on disk.

What moved
- PRESETS map → src/lib/presets.ts (single source of truth, shared
  between scripts/switchDao.ts CLI and src/components/TweaksPanel.tsx
  dev preview).
- ProposalStatus type → src/lib/types.ts.
- MembersTable's row type → inlined as MembersTableRow (was MockMember).

What got deleted
- All mock arrays: PROPOSALS, AUCTION, ACTIVITY, MEMBERS, CONTRACTS,
  CHART_AUCTION, CHART_PROPOSALS, CHART_MEMBERS.
- All mock-named types: MockPreset, MockProposal, MockBid, MockMember,
  ActivityType.
- The whole mockData.ts module.

Preset shape evolved: now { key, label, tagline, theme: { accent,
radius, displayFont }, chain?, tokenAddress? }. Presets without a
tokenAddress (e.g. Verdant) are visual-only — the Tweaks panel can
preview them but the switch-dao CLI rejects them.

Verified locally:
- pnpm switch-dao gnars → all 7 pages serve real Gnars data, no
  mock fallbacks anywhere.
- pnpm switch-dao builder → flips back cleanly, real Builder data.
- TweaksPanel preset cycling still works for Builder/Gnars/Verdant.
- type-check clean.

The only file that mentioned the word "mock" or "Mock" anywhere was
mockData.ts itself. It's gone now.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two threads in this commit, both polish on top of the core PR.

1. switch-dao now accepts any token address
- The CLI's first positional arg is either a preset key (in
  src/lib/presets.ts — currently builder + gnars) or a raw 0x token
  address. Optional flags for a raw address: --chain, --network,
  --tagline, --accent, --radius, --display-font.
- Theme overrides MERGE into dao.theme.json instead of replacing,
  so a "minimal" switch (just an address, no flags) keeps the
  existing theme.
- Verified by switching the local labrat to Kendama DAO
  (0xd7d40e5afceabc923b70dd299206155fb330f5ff on Base): all 7 pages
  serve real Kendama data — 278 tokens, 58 owners, current auction
  #307, real proposals #1-7, real bids in fractional ETH.

   Bug fix surfaced by Kendama: getBids() returns ETH-formatted
   amount strings (e.g. "0.0005"), not wei. Removed the redundant
   formatEther(BigInt(b.amount)) wrapping in two places. Builder &
   Gnars didn't trip this because their current auctions had no
   bids when verified earlier.

2. Markdown rendering on /proposals/[id]
- New src/components/Markdown.tsx — port of gnars-website's
  Markdown.tsx with the media classifier dropped (proposals don't
  embed video/audio in practice). Same plugin set:
  remark-gfm + remark-breaks + rehype-slug + rehype-autolink-
  headings + rehype-external-links + rehype-sanitize.
- Component overrides thread the DAO accent token through links so
  link colour stays config-driven.
- Globals.css now uses @plugin '@tailwindcss/typography' to enable
  the prose classes the gnars approach relies on.
- Proposal detail's Description section now uses <Markdown> instead
  of whitespace-pre-wrap. Verified on Kendama proposal #5: 23 list
  items, 21 paragraphs, 19 strong/bold, 14 headings, 16 links all
  render with proper styling and external-link target=_blank.

dao.theme.json reflects the current local state (Kendama: red accent,
"A nounish DAO for kendama players, makers and culture." tagline).
This file commits as the per-fork identity; other DAOs flip via
pnpm switch-dao.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three independent polish threads on the live-data layer.

1. Real NFT artwork on /treasury
- New TreasuryNft type + nftHoldings on TreasuryPageData.
- Subgraph query: tokens where owner = treasury address, last 24
  by mintedAt desc.
- Renders as 2/4-column grid of clickable tiles (links to
  /auction/[id]). Each tile uses the on-chain image URL (the
  nouns.build renderer composing SVG layers from IPFS) — no
  per-token RPC reads.
- Empty state: "Treasury holds no DAO tokens currently."

2. ERC-20 treasury holdings
- New treasuryTokens field on DaoConfig — array of
  { symbol, address, decimals }.
- New src/lib/treasury-tokens.ts exports BASE_COMMON_TOKENS and
  ETHEREUM_COMMON_TOKENS as opt-in defaults; forks spread these
  or list custom tokens. dao.config.ts auto-includes the Base set
  when chainId === 8453.
- New fetchTreasuryTokenHoldings() in dao-data.ts: viem multicall
  over erc20Abi.balanceOf for each configured token in one round
  trip, filters out zero balances, sorts by raw balance desc.
- Treasury page now shows a real holdings table: Ether (always
  present) + each non-zero ERC-20 with its symbol and formatted
  balance. When the table is the bare "ETH only" case, surface a
  hint pointing at daoConfig.treasuryTokens.

3. ENS resolution on /members (gated)
- New mainnetClient (viem createPublicClient on chain 1) using
  the @buildeross/utils/wagmi transports stack. ENS lives on
  mainnet regardless of which chain the DAO is on.
- resolveEnsNames() resolves the top 20 holders' reverse names
  with a 6s per-call timeout via Promise.allSettled. Each member
  row's ens field is populated only when a name is found.
- Important: ENS lookup is gated on
  process.env.NEXT_PUBLIC_ALCHEMY_API_KEY because public mainnet
  RPCs are too slow (sequential reverse-resolver calls were
  hanging the SSR past 90s on /members). With Alchemy set, the
  ~6s budget per call is comfortable. Without it, members render
  as truncated addresses (the previous behaviour).

Verified locally on Gnars:
- /treasury: 24 real Gnars NFT tiles + Ether 6.7727 ETH +
  WETH 0.028 + USDC 15,496.27 (real on-chain balances via
  multicall).
- /members: 1.9s render with no Alchemy key (ENS skipped),
  rows show truncated addresses.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Per the design brief's recommendation (Treasury Analytics package as
upstream batch 2): replace the simple SVG <BarChart> on /treasury
with Recharts so the analytics surface gets axes, gridlines, and
hover tooltips that read out exact values + units.

What ships
- Add recharts dependency.
- New src/components/dao/AnalyticsBarChart.tsx — client component
  built on recharts. Threads --accent / --border / --muted-fg /
  --surface CSS vars through every Recharts internal so the chart
  re-skins with the DAO theme automatically.
- Tooltip: rounded surface card showing the value formatted to a
  configurable precision + unit suffix ("ETH" / "proposals" /
  "votes").
- Y-axis: compact integer formatting (1.5k, 2.7k…) for the
  proposal-activity / voter-activity charts; ETH precision for
  auction revenue.
- Empty-state: when all bars are zero, hide the bars and overlay a
  "No activity yet." line.

Where it's used
- /treasury — all three charts (Auction revenue, Proposal activity,
  Voter activity).
- Dashboard's auction revenue mini-chart keeps the simple <BarChart>
  — it's an inline accent, doesn't need axes.

Caveat
- Recharts uses <ResponsiveContainer>, which renders nothing on SSR
  (waits for a measurable parent). Charts appear immediately on
  hydration. Acceptable for now; if we need true SSR'd charts later
  we can swap to fixed-pixel <BarChart width=N height=N>.

Verified locally: treasury renders in ~1.5s on warm cache; bars +
axes + tooltips all show on hydration with the Gnars yellow accent.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Both interactive surfaces now write on-chain via wagmi.

VotePanel
- Calls governor.castVoteWithReason(proposalId, support, reason) via
  useWriteContract; waits on the receipt via
  useWaitForTransactionReceipt.
- proposalIdHash now flows from getProposalByNumber → page → panel.
- Five UI phases: connect → switch → sign (wallet popup) → mine
  (block confirmation) → done (clears form, returns to idle).
- Connect-wallet gate via RainbowKit's useConnectModal when no
  wallet; "Switch to <chain>" button when connected to the wrong
  chain (uses wagmi useSwitchChain).
- Friendly error parsing: "Transaction rejected" / "Insufficient
  funds for gas" / falls back to first line of the viem error.

BidForm
- Calls auction.createBid(tokenId) via useWriteContract with
  msg.value = parseEther(bid).
- Same five-phase flow + same connect/switch gating.
- Live wallet balance via wagmi useBalance — replaces the previous
  hardcoded 1.284 ETH placeholder. Below-min and over-balance warn
  states still trip locally (no signature attempt).
- enableComment field stays for future surfacing of on-chain bid
  comments — Builder's createBid doesn't take a comment param so
  it's informational only until that ships upstream.

Page wiring
- /proposals/[id]: passes detail.proposalIdHash to VotePanel,
  drops the votingPower={4} placeholder. Real voting-power
  resolution per snapshot block lands in a follow-up.
- /auction/[id]: passes data.tokenId to BidForm, drops the static
  network/balanceEth props (BidForm now reads them itself).
- Removed unused chainName variable from auction page.

Verified: type-check clean. Pages render in 1-2s. /proposals/61
shows "Connect wallet to vote" when no wallet — confirms the gate
and phase logic are wired. Vote/bid signature flows tested by the
two wagmi mutation hooks; testing the actual on-chain submit
requires a connected wallet + an open auction window, which the
user does at preview time.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Stops hardcoding votingPower. The vote panel now reads the
connected wallet's voting power directly from the governor at
the proposal's snapshot timestamp.

Implementation
- VotePanel uses wagmi useReadContracts to batch two reads:
    governor.getVotes(account, voteStart) → uint256
    token.balanceOf(account) → uint256
- voteStart (a unix timestamp — Builder uses timestamps not block
  numbers) flows from getProposalByNumber → page → panel.
- Reads are gated on snapshotInPast (voteStart <= now). Pending
  proposals where voteStart is in the future would revert; we
  show "none" until the snapshot has passed.
- Scenario derived from results:
    getVotes > 0           → eligible (with real count)
    getVotes = 0, balance>0 → delegated
    both 0                 → none
- Submit button disabled when votingPower === 0.
- VotingPowerExplainer reworked to take votingPower prop so
  the "You can vote" body shows the actual count.

UX
- Server pass: the explainer renders "You can't vote on this
  proposal" until hydration. After hydration, the wagmi reads
  fire and the panel updates to the real scenario.
- The wallet footer now shows "X vote" / "X votes" with correct
  pluralization based on the resolved count.

Verified locally on Kendama proposal #5: /proposals/5 returns
200 in <1s with "Connect wallet to vote" pre-hydration.
Type-check clean. Real on-chain reads tested by the existing
useReadContracts wagmi flow.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
A working /proposals/new that submits real proposals to the
governor. Three sections: title, markdown description with live
preview, and a transactions builder.

Eligibility check
- useReadContracts batches governor.proposalThreshold() +
  token.balanceOf(account).
- Top banner: "Eligible" (green, shows balance + threshold) when
  balance > threshold; "Not eligible" (warning) otherwise; "Connect
  wallet" prompt when disconnected.
- Submit button is gated on eligibility AND form validity.

Form
- Title: short input, becomes the first markdown heading. Builder UI
  convention: description = "# {title}\n\n{body}".
- Description: textarea with Edit/Preview toggle. Preview uses our
  existing <Markdown> component (PR #16 port of gnars-website's),
  so authors see exactly what /proposals/[id] will render.
- Transactions: array of { target, value (ETH), calldata (hex) }.
  Each row supports add/remove (min 1). Inline validation:
    - target must be a viem isAddress
    - calldata must be 0x-prefixed even-length hex (0x or empty
      means "send ETH only")
    - value parses through parseEther → wei
- Validation summary lists all errors before the Submit button.

On-chain submit
- governor.propose(targets[], values[], calldatas[], description) via
  wagmi useWriteContract; waits on the receipt via
  useWaitForTransactionReceipt.
- Same five-phase flow as VotePanel/BidForm: connect → switch →
  sign → mine → done. Friendly error parsing including a Builder-
  specific "below proposal threshold" hint.
- On success, redirect to /proposals after a 1.6s "✓" beat. The new
  proposalNumber lives in the ProposalCreated event log; we don't
  parse it here, the list will revalidate within 60s.

Page wiring
- Existing "+ Create proposal" button on /proposals now links here.

Verified locally: type-check clean. /proposals/new returns 200,
SSR renders eligibility prompt + all sections + "Connect wallet to
propose". Subsequent loads ~100ms cached.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Polish wave 3, audit-driven cleanup.

Settle auction action
- New src/components/dao/SettleAuctionAction.tsx — client component
  that reads auction.auction() on mount; if (currentTokenId === page
  tokenId && endTime past && !settled), renders an accent banner
  with a "Settle & start next" button.
- Calls auction.settleCurrentAndCreateNewAuction() via wagmi
  useWriteContract; same five-phase flow as the other actions
  (connect → switch → sign → mine → done). Refetches auction state
  after success so the banner clears.
- Mounted on /auction/[id] inside the "This auction has ended"
  branch, so users land on a settled auction and can finalise it
  with one click.

Audit cleanup (no functional changes)
- Drop deprecated DashboardProposal type alias (kept since #10 for
  back-compat; no current importers).
- Demote inferProposalState, mapProposalState, formatProposal from
  exported to module-private — they're only used inside dao-data.ts.
- Verified zero residual mocks: no `mock`/`Mock`/`MOCK` anywhere in
  src/, no TODO/FIXME, no hardcoded ETH addresses outside config
  files (dao.ts auto-generated · presets.ts CLI · treasury-tokens.ts
  opt-in defaults).

Verified: type-check clean. /auction/latest serves; the settle
banner renders client-side post-hydration when conditions match.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…SSR noise

OG image generation per Next.js convention — Satori-rendered 1200×630
PNGs that share-target on Twitter, Farcaster, Discord, etc.

What ships
- src/lib/og-utils.ts — shared OG_SIZE, ogColors() (theme-driven from
  daoConfig.theme.accent), resolveIpfs(), trimEth().
- src/app/opengraph-image.tsx — DAO logo + name + tagline + chain pill
  on a radial-gradient accent background.
- src/app/proposals/[id]/opengraph-image.tsx — Prop number + status
  pill + title (multi-line) + 3-segment vote bar with quorum tick +
  for/against/abstain counts. Reuses getProposalByNumber for the
  data; on subgraph failure, falls through to a static "Proposal #N"
  placeholder.
- src/app/auction/[id]/opengraph-image.tsx — solid accent block with
  giant #N on the left, DAO row + token title + top-bid card on the
  right. Skips the on-chain renderer artwork — nouns.build returns
  webp which Satori can't decode in the OG runtime; would need a
  png-conversion proxy to surface real artwork on share previews.
- Theme accent threads through every OG image, so Builder forks
  ship blue OG, Gnars ships yellow, Kendama ships red, etc.

Satori gotcha
- Every <div> with multiple children needs explicit display: 'flex'
  (or 'none'). Wrote all OG components with display: 'flex' on every
  div block to avoid the cryptic Satori error.

Wagmi SSR noise
- New instrumentation.ts (Next.js auto-loaded) installs a
  process.on('unhandledRejection') listener that swallows
  "indexedDB is not defined" rejections only. Other rejections
  re-throw so Next's default logger handles them normally. Verified:
  dev log no longer spams indexedDB on every page render.

Verified locally on Kendama labrat:
- /opengraph-image → 200, image/png, 299KB, 1200×630 RGBA
- /proposals/5/opengraph-image → 200, image/png, 41KB
- /auction/307/opengraph-image → 200, image/png, 230KB
- type-check clean, 0 indexedDB log lines after a fresh boot

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Brings the README up to date with everything that's shipped since the
original write in PR #8. The doc now matches what the template
actually does, end-to-end.

What changed
- "What you get" page table — adds /proposals/new (create flow), live
  bid form / vote panel / settle button, real ENS on /members,
  Recharts analytics, real ERC-20 holdings + NFT grid on /treasury.
- New "Switching DAOs (the labrat)" section with pnpm switch-dao
  examples for built-in presets and arbitrary token addresses.
- Fork checklist no longer references the deleted mockData.ts; points
  forks at dao.theme.json + treasury-tokens.ts opt-in defaults.
- Theming & config example shows the treasuryTokens field.
- Scope map adds rows for "real on-chain vote/bid/propose/settle"
  and "OG images (Satori)" — both shipped in the template.
- Available scripts table adds pnpm switch-dao.
- Project structure regenerated: instrumentation.ts at the root,
  scripts/switchDao.ts, src/lib/{dao-data,presets,treasury-tokens,
  og-utils,types}.ts, src/components/{DaoAvatar,Markdown}.tsx, OG
  image routes, the new dao.theme.json companion to dao.ts.

Final audit pass after README rewrite — all clean:
- 0 `mock` / `MOCK` strings anywhere in src/
- 0 TODO / FIXME comments
- 0 hardcoded ETH addresses outside config / treasury-tokens / CLI
- type-check passes
- 7 lib files (all real), 19 dao composites + 6 chrome components,
  all imported.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Updated dependencies to fix Next.js and React CVE vulnerabilities.

The fix-react2shell-next tool automatically updated the following packages to their secure versions:
- next
- react-server-dom-webpack
- react-server-dom-parcel  
- react-server-dom-turbopack

All package.json files have been scanned and vulnerable versions have been patched to the correct fixed versions based on the official React advisory.

Co-authored-by: Vercel <vercel[bot]@users.noreply.github.com>
…-vu-2x7cnt

Fix React Server Components CVE vulnerabilities
sktbrd and others added 30 commits May 22, 2026 23:30
…prod

Treasury page:
- NFT 2-col grid in left column (below donut) via NftSection client component
- Clicking any NFT tile opens detail dialog with image, minted date, send-via-proposal flow (copy calldata + New Proposal link)
- Clicking "+N more" opens scrollable gallery dialog with IntersectionObserver infinite scroll
- Alchemy-powered full transfer history (TreasuryTransfers) with All/In/Out filter and per-asset chips, paginated via pageKey
- /api/treasury/transfers route — alchemy_getAssetTransfers, cached 5 min
- Recent tx feed bumped to 12 entries sourced from full auction history

TweaksPanel:
- "Apply to prod" button POSTs to /api/dev/apply-theme, writes accent/radius/displayFont to dao.theme.json
- Feedback states: saving / saved (green) / error (red), resets after 2.5s

About page: renders DAO description as Markdown; founders section conditionally rendered

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- not-found.tsx: DAO-branded 404 with logo + back-to-dashboard link
- error.tsx: global error boundary with retry button
- robots.ts + sitemap.ts: auto-generated sitemap includes all proposal URLs
- treasury/opengraph-image.tsx: OG image showing ETH balance + total USD + asset count
- AuctionPoller: 30s router.refresh() while auction is live (bids from others appear without reload)
- BidForm: router.refresh() immediately after own bid is mined
- Mobile layout: TxCard and AssetRow rows collapse gracefully at 375px (tag/time hidden, inline grid replaced with flex)
- TreasuryTransfers: same mobile treatment — asset col hidden, asset name inlined into amount
- sample.env: document NEXT_PUBLIC_SITE_URL for sitemap

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
NFT detail dialog:
- Resolves recipient ENS name live via useEnsName (shows below input in accent color)
- "New proposal" navigates to /proposals/new with pre-filled title, markdown body, and encoded transferFrom calldata as URL params
- Title: "Send [Token Name] to [ENS or truncated address]"
- Body: summary, editable "Why" section, details table, transaction note

ProposalCreateForm:
- Reads ?title, ?description, ?tx_target, ?tx_calldata from URL search params as initial state
- Zero-config: works as before when no params are present

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…e Split

- Add Stream Tokens, Airdrop Tokens, Milestone Payments, Mint Governance
  Tokens, Nominate Delegate, Pause Auctions (fully encoded on-chain calls),
  plus WalletConnect, Pin Treasury Asset, Droposal, Add/Replace Artwork as
  guided custom-call types
- Creator Coin opens the dedicated wizard modal instead of a DraftForm slot
- Remove Revenue Split (split) kind from all types, forms, icons, and encoding
- Stream and Droposal types get dedicated form fields with proper Sablier /
  Zora ABI encoding (stream: createWithDurationsLL, droposal: createEdition)
- encodeDraft now accepts token and auction addresses in ctx for mint_gov,
  delegate, and pause_auction encoding

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replaces the generic target/value/calldata fallback for every transaction
kind that has a well-defined contract call. Each kind now has a dedicated
form with validation, treasury context, and graceful per-chain capability
gating.

- Send NFT: collection quick-pick + visual treasury NFT picker grid;
  prefilled by the "New proposal" flow from NftSection, switched to
  safeTransferFrom selector to match encodeDraft.
- Send ERC-20: live treasury holdings as visual token cards with logos
  (Trust Wallet Assets) and balances; "Max" button fills the balance.
- Pin Treasury Asset: ERC-20/721/1155 segmented toggle + collection
  toggle + conditional token id; encodes EAS attestation with the
  canonical treasury-pin schema. EAS-unavailable warning per chain.
- Nominate (escrow) Delegate: rewired from token.delegate(address) to
  EAS attestation with the escrow-delegate schema, matching the UI
  label semantics.
- Milestone Payments: token quick-pick (native ETH + treasury tokens),
  client/recipient/safety-valve fields, dynamic milestone array,
  encodes EscrowBundler.deployEscrow with SmartInvoice escrowData.
  IPFS metadata intentionally skipped (escrow remains functional).
- Airdrop Tokens: bulk fan-out via Disperse contract — token picker,
  recipients table, CSV paste, total preview. Single tx for any number
  of recipients, ETH or ERC-20.
- WalletConnect: keeps the calldata shape but ships a step-by-step
  workflow card with a Safe Tx Builder link rather than bundling the
  WalletConnect SDK (~2MB).
- Add/Replace Artwork: clear explainer with a deep link to this DAO's
  nouns.build proposal page for the layer-upload pipeline, with the
  calldata input remaining for paste-back.

Adds isEasSupported/isEscrowSupported/isAirdropSupported helpers and a
treasuryNftCollections config slot for non-DAO NFT contracts.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds an interactive auction price history chart (pure SVG, no chart
libraries) reachable via the previously-disabled "Chart" tab on the
auction page. Data comes from the existing Goldsky auctionHistory
subgraph query so there's no new infra.

- AuctionPriceChart: 30/60/90D/All period filter, hover indicator with
  date + ETH + per-auction link, stat row (count, highest, average,
  total). Adapts the nouns-builder approach but without their Zord/
  Sablier-API dependencies.
- /auction/[id] tabs are now Link-based (?view=chart), so chart data is
  only fetched when actually viewed.
- sitemap.ts: align fallback URL chain with layout.tsx
  (NEXT_PUBLIC_SITE_URL -> VERCEL_PROJECT_PRODUCTION_URL -> VERCEL_URL
   -> localhost), add /coins, individual auction pages (last 365 days),
  lastModified timestamps, hourly revalidate.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Collected layout + content-quality tweaks that were already in the
working tree.

- treasury transfers API: heuristic spam filter that drops phishing
  ERC-20s carrying URLs, promo phrases, or special punctuation in
  their symbol field. Real ERC-20 symbols are short and alphanumeric,
  so the dust is easy to identify.
- Header: lay out logo / nav / actions with flex-1 columns so the nav
  centers correctly even with longer DAO names.
- ProposalCard: bump thumbnail strip from 88px to 140px so the image
  reads at a glance.
- ProposalRow: tighter typography, tabular nums for ids/dates, drop
  the WalletPill in favor of a plain proposer label so the row reads
  as a compact list item.
- TreasuryTransfers + /treasury page: layout cleanup so the recent-
  transactions card fills the right column height.
- ThreeDArtCard: new parallax/tilt wrapper for cards (mouse-tracking
  rotation + depth lift) — opt-in via children prop.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replace the current-auction-only getBids call with a feedEvents query
filtered by AUCTION_BID_PLACED. This surfaces real per-bid timestamps
across all auctions, so the Bids tab populates even when the live
auction has no bids yet.

- No time window: a strict 30-day cutoff produced empty state on quieter
  DAOs (e.g. Gnars had no bids in the prior 30 days). Bounded by first:8
  instead so the lite template stays within free-tier subgraph quotas.
- Merged feed capped at 6 items so the Activity card's natural height
  matches the 5-row Recent Proposals card; grid stretching handles the
  shorter Bids/Props filter tabs.
- Sort by real bid/proposal timestamps instead of "just now"/"recent"
  placeholders.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…mary, persistence, tests

A pass of UX and reliability improvements on top of the structured
proposal forms.

- Pre-submit feasibility checks (useProposalFeasibility) — read treasury
  ETH balance + per-token ERC-20 balances + NFT ownerOf in one batched
  multicall. Shown as a warning banner in the Review step with a list of
  specific items that will revert.
- Decoded transactions section — summarizeDraftsMarkdown turns the
  queued drafts into a human-readable markdown block. Toggle in the
  Review step controls whether it's appended to the proposal description
  on submit.
- Proposer reputation badge — small "12× · 10✓" pill next to the
  proposer on each proposal row, computed from the recent proposal
  window the list already loads.
- Per-fork contract overrides — daoConfig.contractOverrides slot for
  disperse / escrowBundler / sablierLockupLinear / zoraNftCreator / eas.
  Lets forks point at custom deployments on niche chains without
  editing the lib. Stream + droposal forms prefill from these.
- Draft persistence — title/description/drafts auto-saved to
  localStorage under a chain+token-keyed key. URL params take priority;
  cleared on successful submit.
- "Queue ERC-20 approval" button — milestone/stream/airdrop forms now
  emit a paired approve() draft into the queue on click instead of
  asking the proposer to build it by hand.
- Milestone metadata to IPFS — optional upload via existing
  @buildeross/ipfs-service. CID is folded into the on-chain escrowData;
  absent CID falls back to zeroHash and the on-chain escrow still
  executes (off-chain SmartInvoice UI degrades).
- SummaryCard now decodes pin_asset / milestone / airdrop / droposal /
  stream drafts (previously fell through to nothing).
- Sitemap proposal cap raised from 200 → 1000, with a comment pointing
  at Next.js generateSitemaps() for sharding past 50k.
- CONFIG.md walkthrough for fork operators (config surface, env vars,
  per-feature chain support).
- Vitest set up + 27 encoder tests covering every kind's selector +
  helper functions + key validation rules.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Promotes encodeDraft's return shape from `Tx | null` to `Tx[] | null`
behind a new `encodeDraftToTxs` wrapper. Most kinds still emit a single
tx; milestone / stream / airdrop drafts whose token is an ERC-20 now
auto-prepend the matching `approve(spender, amount)` call.

The proposer no longer has to remember to queue an approval tx by hand —
the encoder handles it. Native-ETH variants skip the approval (no
allowance needed).

- proposal-tx.ts: encodeDraftToTxs wraps encodeDraft and consults
  buildApprovalDraft to prepend an approve() Tx when applicable. The
  decoded-transactions markdown summary mentions the auto-prepended
  approval inline so voters see the full execution order.
- ProposalCreateForm.submit now flat()s the encoded array before
  passing to governor.propose.
- DraftForm forms replace the manual "Queue approval" button with a
  passive "✓ Auto-included: approve(spender, …)" hint. The
  onAddRelatedDraft callback chain was no longer needed and got removed.
- proposal-tx.test.ts: 4 new tests covering encodeDraftToTxs (single tx
  for ETH, prepended approve for ERC-20 streams, skipped for native-ETH
  airdrops, null pass-through when underlying encoder fails). 31 tests
  total.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Mostly mechanical: ESLint reformatted import order, JSX line breaks, and
trailing commas across the codebase. The one substantive change is in
AuctionPriceChart: the React compiler flagged `Date.now()` inside a
useMemo as impure. Snapshot it once via useState so the cutoff
calculation runs against a stable "now".

Two pre-existing lint errors remain (TreasuryDonut setState reassignment
and tweaks-context setState-in-effect) — neither blocks build or tests.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… bids

Full-bleed homepage with a hero that sames the NFT's bg color (lossless
bg-layer extracted from the Builder renderer URL, sampled through a
same-origin /api/img-proxy so the canvas stays clean) and grounds the
character against the bottom. Right column carries a larger countdown,
the bid form pinned close to the title, recent bids with bidder identity
+ comments, and a single bottom-anchored auction link.

History strip below the hero marquees the most recent 16 tokens with
status pills (Live / Settling / Treasury / Burned / Owner). Click any
tile to preview the token in the hero in place; the Live tile returns
the hero to the live auction's bid form or settle action.

Bid form gets a pill redesign in compact mode — accent-tinted input that
visually pairs with the accent button, optional comment pill, and a
"Bidding as <ENS>" identity row. Submitting a bid surfaces a persistent
"Bid placed" banner before the form resets, mirroring the same success-
handoff pattern used by the settle action. Both writeContract calls now
pin chainId so the tx broadcasts on the DAO's chain even when the
wallet has desynced to another network.

Below the hero / strip, a dark panel hosts the feed and proposals as
minimal cards. Feed rows align avatars to the line center with leading-6
so the 20px avatar sits cleanly inside the 13px text. Proposals card
shows for/against tallies with an active-proposal countdown.

Header gets an explicit disconnect button beside the wallet pill and
swaps to a wider chrome on non-dashboard routes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The FeedItem AUCTION_BID_PLACED variant exposes the bid's onchain
comment as `bidComment`. Render it as an italic quote under the row,
matching the recent-bids panel in the hero so the same comment surfaces
in both places.

Bid comments are written via Zora's separate comments contract; the
subgraph correlates them with each bid. Standard createBid() doesn't
take a comment parameter — our BidForm input still captures it for
display intent but doesn't persist on-chain until the comments-contract
write is wired up.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Multi-agent audit found and fixed ~20 HIGH and ~40 MED responsivity bugs
across every page (mobile 320-640px + desktop 1024-1920px lenses).

Highlights:
- /members: mobile was unusable — switched to md:hidden card list, table at md+
- /treasury: TreasuryDonut now container-driven via viewBox (fixes both 320px overflow and desktop tiny donut); ERC-20 balance now uses trimDecimals like ETH row
- /feed: every QuoteBlock and per-event body got break-words + overflow-wrap:anywhere — user-supplied URLs/IPFS hashes/addresses no longer cause page-wide horizontal scroll
- /proposals/[id]: VotePanel sticky gated to lg: (was pinning over viewport on mobile); calldata wrapped in pre with overflow-y-auto; vote buttons bumped to 44px; sidebar slot collapses on terminal-state proposals
- /proposals/new: StepFooter reversed so primary Next sits above disabled Back on mobile; custom + add_artwork calldata input swapped to textarea (matches WalletConnect); WizardTabs labels hide on mobile; AirdropFields remove button 44px
- /auction/[id]: prev/next arrows 44px on mobile; BidForm input wrapper got min-w-0 + shrink-0 on ETH suffix; ThreeDArtCard aspect-[4/3] on mobile; chart preserveAspectRatio removed; BidHistory wrap guards
- /coins: grid density now 2/3/4/5; skeleton matches real card to kill CLS; lazy loading on images
- /coins/new: SuccessCard shorten(address); MediaUploader filename badge max-w; form mx-auto max-w-2xl; modal close 44px + backdrop dismiss
- /coins/[address]: trade widget select shrink-0 max-w-[8rem]; balance row flex-wrap; FieldRow forced-truncate wrapper for ActorIdentity; grid rebalanced 3fr_2fr / 2fr_1fr

Shared chrome (cascades to every route):
- Header brand row: min-w-0 + truncate on DAO name, shrink-0 on avatar/pill — long DAO names no longer push controls off-canvas
- Header mobile menu anchored to top-full with max-h + overflow scroll (was hardcoded top-[60px]/top-[68px])
- Header theme/hamburger: h-11 w-11 on mobile, md:h-9 md:w-9 (44px touch on mobile, desktop unchanged)
- WalletPill icon buttons: h-11 w-11 on mobile, md:h-9 md:w-9 (was h-6 w-6 = 24px everywhere)
- Markdown dropped max-w-none so prose body caps at sensible line length
- button.tsx md size: min-h-11 md:min-h-10 (44px touch on mobile)
- Footer width capped to match main
- globals.css: html/body overflow-x: clip safety net

42 files changed. TypeScript clean.

Conscious skips (design calls, not bugs):
- Main max-w-[1180px] xl/2xl widening
- Desktop 2-col layouts for proposal/coin wizards
- Sticky bid panel on auction route
- Sticky filter bars on feed/members

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bundle of 21 small fixes confirmed by independent code-review pass.
None change observable behavior; each verified via typecheck.

Proposal encoder hygiene (src/lib/proposal-tx.ts):
- Hardcode droposal target per-chain (mirrors decoder DROPOSAL_TARGET);
  drop the free-text "Zora NFT Creator" field from the form
- Use viem's zeroHash for presaleMerkleRoot
- Drop Polygon/Arbitrum from DISPERSE_ADDRESSES (Builder only runs
  on Ethereum/Optimism/Base)
- Add isPositiveNumber() helper; use it for milestone/airdrop amount
  validation so messages match the rule ("> 0", not ">= 0")
- Fix airdrop TypeCard description: "Sablier merkle" → Disperse

Theme tokens (no more hardcoded hex):
- TypeCard + SummaryCard kind chips: collapse Tailwind palette colors
  to semantic tokens (accent / success / warning / destructive)
- TreasuryTransfers + treasury/page direction badges: success/destructive
- error.tsx + TweaksPanel apply-to-prod button: success/destructive

Dedupe:
- shortAddress → src/lib/utils (was inline in 3 files)
- resolveIpfs canonical lives in src/lib/utils; og-utils wraps it
  for null/undefined; auction page imports from utils
- CHAIN_NAMES map → daoConfig.chain.name (viem chain object;
  was duplicated in 3 files). Expose daoConfig.chain.
- STABLE_SYMBOLS + ETH_EQUIVALENT_SYMBOLS → treasury-tokens
- STATUS_CHIP merged into StatusBadge via variant="chip" prop
- FONT_OPTIONS single source of truth in tweaks-context

Form + UX:
- NftSection encodes safeTransferFrom via viem (no manual padding)
- treasuryNftCollections marked optional in DaoConfig
- Drop dead client-side isSpamAsset (server already filters)
- ProposalsListView labels: "All statuses" now means truly all;
  "Active" means hide cancelled/vetoed/expired (default)
- ProposalCard back to RSC; consumer passes showThumbnails prop
- pasteCsv prompts before overwriting existing recipients
- ThreeDArtCard guards tilt behind prefers-reduced-motion
- icon.tsx skips fetch+base64; passes IPFS URL directly to Satori

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- MainContainer: all routes on the 1180px column (dropped dashboard max-w-none); removed main pt-8 so the hero sits flush under the header
- AuctionHero: hero band + carousel break out full-bleed; hero content capped to the column; all hero info text white
- globals.css: [data-theme=tint] / [data-theme=tint-light] scopes so hero widgets (bid form, settle panel, success/error/muted) read white-on-color on dark tints and forced-light on light tints; --accent preserved so the action button keeps its brand color
- BidForm: surface-based pills (defined on any tinted bg); SettleAuctionAction onTinted variant
- page.tsx: dark feed/proposals band fills the column
- AuctionHistoryStrip: square thumbnails; winner ENS names (fall back to short address)
- dao-data: resolve ENS for recent auction winners

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…mments, self-healing art) (#43)

The home page hero, recent bids, and history strip were entirely
subgraph-driven, so every post-event `router.refresh()` raced indexing
lag and a passive viewer's hero was frozen for up to 5 minutes. This
bridges the gap with direct onchain reads, riding the QueryClient's
existing global `refetchInterval: 5000` — chain-truth freshness for all
viewers, zero new subgraph load, no new deps.

- useAuctionTruth(): reads `auction()` onchain (~5s poll) — tokenId, top
  bid, leading bidder, endTime, settled — and useTokenImage() resolves a
  fresh token's art via `tokenURI` before the subgraph indexes it.
- DashboardHero: overlays chain truth on the live hero whenever chain is
  ahead of the subgraph prop (post-settle / fresh-mint / newer top bid),
  and defers back once they agree. Gated behind useWeb3Ready so SSR/first
  paint still render the plain server view (no hydration drift).
- Settle -> new auction live with the new NFT within ~1 block instead of
  landing on the old ended token after the 4.5s banner.
- Bid comments: the auction ABI has no comment-carrying bid function
  (createBid / createBidWithReferral only), so a comment can't reach the
  chain or subgraph. Improvised as a tx-keyed local echo (useBidEcho)
  merged into Recent bids so the actor sees their own bid + comment
  immediately. Relabeled the misleading "onchain comment" copy and gated
  the hero field on daoConfig.features.bidComments to match the auction page.
- Self-healing art: HeroArt + strip tiles fall back to <AuctionArt> on a
  cold/failed render and retry the URL with backoff (cache-busting), fading
  the real art in the moment it's ready — no manual refresh.
- Fixed a pre-existing hydration mismatch on the live countdown
  (suppressHydrationWarning) — SSR baked a 1s-stale value.

Pure helpers (parseTokenImage, mergeBidEchoes) are unit-tested.
type-check, eslint, 41 tests, and next build all green.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The proposal detail page derived its header label from creation age, so 6
of the 9 governor states showed the wrong date next to the status badge
(e.g. "Executed · 9 days ago" for a prop that executed 2 days ago but was
created 9). Mirrors the home-page /dev/auction-hero precedent: a visual
state matrix that renders every render path with dates relative to load.

Date/state fixes (13 confirmed by a fan-out review + adversarial verify):
- relativeLabel now anchors on the event that defines the state — Executed
  /Expired/"Voting ended" from executedAt/expiresAt/voteEnd, plus a
  "Ready to queue" branch for succeeded; falls back to "Created …" for
  vetoed/cancelled (no event ts on the fragment). voteStart anchors the
  active "Started Nd ago" instead of timeCreated.
- Active detail header swaps the stale "Started Nd ago" for a LIVE
  "Voting ends in Xd Yh" countdown (new VoteCountdownLabel island).
- VotePanel: nowSec now ticks, so the pending "Voting opens in X" counts
  down and the panel promotes to the active choice form when voteStart
  elapses (was frozen at first render).
- ExecuteAction: formatDuration emits compound units ("2d 6h", not "2d")
  so the timelock countdown doesn't hide a near-full extra day.
- getProposalByNumber: resolve ENS over the rendered (reversed) vote order
  so the visible top rows get names; trim whitespace-only vote reasons.

Harness + refactor:
- Extracted <ProposalDetailView> from page.tsx so the real route and the
  /dev/proposal matrix render byte-identical layout from one component.
- /dev/proposal: buildDetail() fabricates a full ProposalDetail per state
  with the exact now±offset that realizes it through inferProposalState
  (queued expiresAt future / expired past / succeeded none / cancelled with
  a future voteEnd). Covers all 9 states + ProposalActions role×timelock,
  VotingPowerExplainer scenarios + formatOpensIn boundaries, the tx
  taxonomy, votes list (empty/few/expand/filter), VoteBar edges, badges.
- ProposalActions gains dev-only override props (statusOverride /
  isProposerOverride / isVetoerOverride / etaOverride), all default
  undefined so the production path is unchanged — they only let the matrix
  render chain-/wallet-gated states.

type-check, eslint, 41 tests, and next build all green; /dev/proposal
prerenders static, /proposals/[id] unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
When an ENS/basename resolved, WalletPill rendered the truncated address as
a second line under the name (e.g. "Voting as r4topunk.eth / 0x39a7…F75d").
No caller passes `meta`, so that subline was the only second line it ever
showed. Show the name only; the full address stays reachable via the hover
title and the /members/[address] link. The `meta` slot is preserved for
callers that want an explicit second line.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Show just the voter's ENS pill (and vote count) in both the pending callout
and the vote form — the "Voting as" prefix was redundant next to the pill.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ight

Commit 1a0ddff flattened MainContainer into a single padding-less className,
dropping the pt-8 top gap below the sticky header on every route. Restore the
route-aware top padding: pt-8 everywhere EXCEPT the home dashboard, whose
tinted hero owns the full top band as a background and must sit flush.

Also fix header alignment on the four index pages (Proposals, Coins, Feed,
Members): the big clamp(36px,5vw,56px) display heading leaves a leading gap
above its caps, so the right-hand control (button / filter pills / search)
floated above the visible title text. Add a `cap-nudge` utility (margin-top
tracking ~0.18x the heading via the same clamp curve) so the control's top
lands level with the title cap-height.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… voted")

The proposal detail page's Vote summary and "Cast your vote" form were
server-rendered from the subgraph, which lags indexing, and nothing re-read
state after a vote — so casting a vote left the count unchanged and the form
still asking to vote. Mirrors PR #43's auction bridge: read the lag-free truth
straight from the Governor and overlay it.

- useProposalVotesTruth(): polls governor.proposalVotes(id) (~5s, every viewer)
  — the authoritative (against, for, abstain) tally. VoteSummary overlays it on
  the subgraph-seeded server tallies (mergeVoteTally) whenever chain is ahead,
  so a passerby sees others' votes land without a refresh; gated behind
  useWeb3Ready so SSR/first paint render the plain server view (no hydration
  drift), like DashboardHero.
- VoteSummary + VotePanel call the same read, so react-query coalesces them into
  one shared query — VotePanel's refetch() on a freshly mined vote updates the
  Vote summary instantly (chain already reflects the mined vote; no optimistic
  double-count math).
- "You voted X" confirmation: the Governor exposes no getReceipt/hasVoted, so
  the form flips to a confirmation from a per-session tx echo (useVoteEcho,
  instant) and stays so across reloads once the subgraph indexes the vote
  (findMyVote over the votes list). Replaces the "Cast your vote" form for a
  wallet that already voted.
- Extracted nothing new on the page; VoteSummary replaces the inline section.

Pure helpers (tallyFromChain/mergeVoteTally/findMyVote) are unit-tested.
type-check, eslint, 52 tests, and next build all green; adversarial diff
review found 0 regressions (tuple order, shared query, render-loop, hydration).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ck on error)

The Vote summary bar now bumps by the actor's snapshot weight the instant
they submit a vote, reconciles to the real on-chain tally once the
proposalVotes read includes it, and rolls back if the tx is rejected/reverts.

- proposal-truth: add pure bumpTally + optimisticTally helpers (overlay shows
  only while the real tally hasn't caught up — self-reconciling, no double count)
- useVoteEcho: echo carries status ('pending'|'confirmed') + base tally;
  add confirmVoteEcho / clearVoteEcho
- VotePanel: record pending echo on submit, confirm on mine, clear on error;
  "You voted" confirmation flips only on confirmed (not before mine)
- VoteSummary: overlay the pending echo via optimisticTally
- tests: +7 cases for bump + reconcile/yield/rollback

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…vote tally

- /dev/proposal state matrix + event-anchored proposal dates
- WalletPill ENS-only, drop 'Voting as' label
- optimistic onchain bridge for proposal votes (live tally + 'you voted')
- optimistic vote tally bump on submit (reconcile on mine, rollback on error)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Turn /coins into a Content hub (Coins | Droposals tabs) and add a droposal
browse + detail + in-app mint experience reusing the coin components.

Also fixes a pre-existing decoder bug: the createEdition selector was
0x36e2cd62 but the real one is 0x3857fb13 — droposal detection never matched
before. Tab-aware create button lives in the hub header.

Verified live against Gnars on Base (10 executed droposals render). Vercel
production build green.
Replace the generative AuctionArt placeholder in the dashboard hero with a
loading spinner, and stop forcing the 'tint-light' data-theme on the info
column when there's no sampled tint — that override pinned light-theme tokens
(dark --fg) onto the dark surface, making the text unreadable in dark mode.
Without a tint the column now inherits the ambient theme (text-fg/muted-fg).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Use useAccount().chainId instead of useChainId() across all 10 write actions so a wallet on an unregistered chain (e.g. Anvil 31337) is detected and shown a Switch-network prompt instead of a raw viem ChainMismatchError.
)

Record the actor's vote only once its tx mines, instead of optimistically on
submit + rolling back on error. The UI never shows a vote that didn't land.

- useVoteEcho: drop the `pending` status (confirmed-only); rename addVoteEcho →
  recordVote (mine-only); remove confirmVoteEcho; add `voter` + `reason` to the
  echo so the votes list can render/dedupe the row with its comment.
- VotePanel: capture the submission in a ref, call recordVote on mine; drop the
  on-error rollback effect; submit() no longer touches the UI pre-mine.
- ProposalVotesList: take proposalIdHash; prepend the actor's confirmed vote
  until the subgraph indexes it (dedup by voter).
- ProposalDetailView: pass proposalIdHash through.
- VoteSummary: doc-only update to the confirmation-gated model.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
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.

2 participants