Skip to content

Feature/about page update#946

Merged
dan13ram merged 14 commits into
BuilderOSS:stagingfrom
xSatori:feature/about-page-update
May 9, 2026
Merged

Feature/about page update#946
dan13ram merged 14 commits into
BuilderOSS:stagingfrom
xSatori:feature/about-page-update

Conversation

@xSatori
Copy link
Copy Markdown
Collaborator

@xSatori xSatori commented May 6, 2026

Description

Rebuilds the About page with app-consistent styling, responsive layouts, and data-backed ecosystem sections. This PR replaces the older page implementation with a modular About experience that better matches the rest of the site and improves the accuracy of the DAO, proposal, and showcase content surfaced on the page.

Motivation & context

The previous About page looked visually out of place relative to the rest of the app, was not behaving well on mobile, and had inaccurate governance status labels in the proposal showcase. This change addresses those issues by:

  • moving the page to a dedicated modules/about implementation
  • aligning the visual system with the broader app design language
  • improving mobile responsiveness and text/card wrapping
  • adding structured About-specific API endpoints for DAO tabs, showcase content, and snapshot stats
  • correcting proposal status labels so governance states are represented more accurately

This also includes related build/lint support changes needed to keep the branch deployable.

Code review

Primary review areas:

  • apps/web/src/modules/about/*
    • page structure
    • component composition
    • styling and responsive behavior
    • seeded fallback data and hooks
  • apps/web/src/pages/api/about/*
    • DAO tab aggregation
    • showcase aggregation
    • snapshot stat responses
    • proposal status derivation in showcase.ts
  • smaller integration updates in:
    • apps/web/src/pages/about.tsx
    • apps/web/src/modules/dashboard/*
    • supporting API/util changes required for build support

Specific logic to check:

  • proposal status mapping now distinguishes Active, Succeeded, Queued, Executed, and Defeated
  • About page borders, spacing, and card treatments now match the rest of the app more closely
  • mobile layouts no longer overflow or collapse awkwardly in the major About sections
  • DAO cards should use an LLM API to summarize the description

Type of change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)

Checklist

  • I have done a self-review of my own code
  • Any new and existing tests pass locally with my changes
  • My changes generate no new warnings (lint warnings, console warnings, etc)

Local verification

  • pnpm --dir apps/web lint

Summary by CodeRabbit

  • New Features

    • New About page with hero, ecosystem/DAO highlights, stats, showcases, how-it-works, use-cases, and final CTA.
    • New interactive sections: featured DAOs, coining/drops/proposal highlights, activity feed, and stat grids.
    • New button variant: secondaryOutline for UI.
  • API

    • New endpoints powering About content and snapshot statistics.
  • Chores

    • Added runtime deps for GraphQL and Markdown handling; added environment vars.
  • Style

    • Various UI styling refinements across comments, panels, and mint widget.

xSatori added 4 commits April 13, 2026 17:07
Improve responsive behavior and fix overflow/wrapping issues across the AboutPage styles. Add overflowX: 'clip' and multiple minWidth: 0 rules to prevent horizontal overflow, enable text wrapping via whiteSpace/overflowWrap changes, and introduce media queries to restore larger sizes at desktop breakpoints. Adjust typography (font sizes and line-heights) for hero, titles, and various cards, reduce paddings/min-heights for mobile and reapply previous values at wider viewports, and tweak layout grids (montage -> single column on mobile, two-column on wider screens) and scroll/marquee sizing to use vw-based columns on small screens. Misc updates include reducing gaps/padding for compact view, updating logo marquee and tile sizes, and general polish for consistent responsive presentation.
Aligns this styling with the broader design system on the app
@vercel
Copy link
Copy Markdown

vercel Bot commented May 6, 2026

@xSatori is attempting to deploy a commit to the Nouns Builder Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 6, 2026

Warning

Rate limit exceeded

@dan13ram has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 53 minutes and 7 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c3bbe1a5-d6bf-4cc6-9d9c-c0345ffc7d5c

📥 Commits

Reviewing files that changed from the base of the PR and between 2949a26 and a7523c5.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (14)
  • apps/web/src/modules/about/components/AboutFinalCta.tsx
  • apps/web/src/modules/about/components/DroposalHighlightsSection.tsx
  • apps/web/src/modules/about/components/SectionIntro.tsx
  • apps/web/src/modules/about/useAboutDaoTabs.ts
  • apps/web/src/modules/about/useAboutSnapshotStats.ts
  • apps/web/src/modules/about/utils.ts
  • apps/web/src/modules/dashboard/CreateActions.tsx
  • apps/web/src/pages/api/about/dao-tabs.ts
  • apps/web/src/pages/api/about/showcase.ts
  • apps/web/src/pages/api/about/snapshot.ts
  • apps/web/src/pages/dao/[network]/[token]/index.tsx
  • apps/web/src/pages/dao/[network]/[token]/proposal/create.tsx
  • apps/web/src/utils/api/ai/summaries.ts
  • packages/sdk/src/subgraph/sdk.generated.ts
📝 Walkthrough

Walkthrough

This pull request implements a comprehensive About page feature and infra: new About types and seeded data, a large Vanilla Extract stylesheet, 15+ React components and sections, three GET API endpoints (dao-tabs, showcase, snapshot), SWR hooks and utilities, DropMintWidget and call-site updates, a new button variant, Redis-backed AI text caching utilities, and several minor UI/type fixes.

Changes

About Page Feature Module

Layer / File(s) Summary
Type Definitions
apps/web/src/modules/about/types.ts
Adds About-related types for stats, DAOs, highlights, builder UI, activity items, and API response shapes.
Hardcoded Content
apps/web/src/modules/about/data.ts
Adds seeded arrays for coining highlights, drops/proposals, builder steps/features/value props, ecosystem activity, and hero highlights.
API Routes
apps/web/src/pages/api/about/dao-tabs.ts, apps/web/src/pages/api/about/showcase.ts, apps/web/src/pages/api/about/snapshot.ts
GET-only endpoints that query subgraphs across chains to build DAO tabs, showcase highlights, and ecosystem snapshot stats; wrapped with CORS, rate limiting, and Cache-Control.
Custom Hooks
apps/web/src/modules/about/useAboutDaoTabs.ts, useAboutShowcase.ts, useAboutSnapshotStats.ts
SWR hooks for fetching the new about endpoints with typed fetchers, error handling, and derived isLoading flags.
Utility Functions
apps/web/src/modules/about/utils.ts
Chain logo lookup and Markdown-to-plain-text helper used by components.
CSS Styling
apps/web/src/modules/about/AboutPage.css.ts
Large Vanilla Extract stylesheet covering page layout, hero, montage/marquee, DAO cards, tabs, badges, grids, final CTA, and dark-mode overrides.
Simple Components
apps/web/src/modules/about/components/SectionIntro.tsx, CoiningCard.tsx, DaoCard.tsx, EcosystemStatGrid.tsx, EcosystemActivitySection.tsx
Foundational presentational components for section headers, cards, and stat grids.
Section Components
apps/web/src/modules/about/components/*.tsx
Higher-level sections (AboutHero, FeaturedDaoSection, WhyBuilder, WhatIsBuilder, WhatYouCanBuild, HowItWorks, Coining/Droposal/Proposal highlights, AboutFinalCta) that assemble the page.
Page Component and Export
apps/web/src/modules/about/AboutPage.tsx, apps/web/src/modules/about/index.ts, apps/web/src/pages/about.tsx
Top-level AboutPageView orchestrates sections; pages/about.tsx delegates rendering to AboutPageView; barrel export added.
DropMintWidget & Integrations
packages/ui/src/DropMintWidget/*, packages/dao-ui/*, packages/feed-ui/*, apps/web/src/modules/drop/*
DropMintWidget gains optional unstyledContainer prop, quantity control behavior changes, themed CSS updates; Gallery/MintModal/DropDetail updated to pass new prop.
Button Variant & Dashboard
packages/zord/src/elements/Button.css.ts, packages/zord/src/elements/Button.tsx, apps/web/src/modules/dashboard/CreateActions.css.ts, CreateActions.tsx
Adds secondaryOutline variant, adjusts primary/ghost hover behavior, extends ButtonProps union, updates CreateActions to use the new variant and removes custom CSS exports.
AI Caching Utilities
apps/web/src/utils/api/ai/summaries.ts, apps/web/src/pages/api/ai/generateTxSummary.ts
Adds Redis-backed generateCachedAiText, getAiCacheKey, AI_MODEL export, and summarizeDaoDescription; generateTxSummary refactored to use the cached helper.
Proposal Status Styling
packages/proposal-ui/src/components/ProposalStatus/ProposalStatus.helper.ts, index.ts, packages/proposal-ui/src/index.ts
Extracts and exports ProposalStateColorStyle, proposalStateColorStyles, and getProposalStateColorStyle for reuse in DroposalHighlightsSection.
Small UI / Type Safety Tweaks
apps/web/src/modules/coin/*, apps/web/src/modules/drop/*, apps/web/src/modules/dashboard/*, apps/web/src/pages/dao/*, apps/web/src/pages/api/migrated.ts, proposal/create.tsx
Minor style token updates (borders/backgrounds, timestamp color) and type-safety refactors (typed vote object, MemberDao alias, chainIdKey casts, consolidated proposal-store selectors).

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly Related PRs

Poem

🐰 I hopped to stitch a page of light,

DAOs and marquees in joyous flight.
Data, styles, and cached AI rhyme,
About Builder blooms — one hop at a time.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

@dan13ram dan13ram changed the base branch from main to staging May 9, 2026 08:11
@dan13ram
Copy link
Copy Markdown
Collaborator

dan13ram commented May 9, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 9, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@vercel
Copy link
Copy Markdown

vercel Bot commented May 9, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
testnet-nouns-builder Ready Ready Preview May 9, 2026 9:29am

Request Review

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 14

🧹 Nitpick comments (6)
apps/web/src/pages/dao/[network]/[token]/index.tsx (1)

46-74: ⚡ Quick win

Strengthen minter address handling with explicit guards

The cast on line 46 to keyof typeof MERKLE_RESERVE_MINTER passes TypeScript checks but doesn't prevent accessing unmapped keys at runtime. While all current CHAIN_ID values are covered in both maps, the pattern could break if new chains are added without updating the maps. Extract minter values explicitly and guard the read query to ensure undefined values are never passed to useReadContracts.

Suggested fix
-  const chainIdKey = chain.id as keyof typeof MERKLE_RESERVE_MINTER
+  const merkleReserveMinter = MERKLE_RESERVE_MINTER[chain.id]
+  const erc721RedeemMinter = ERC721_REDEEM_MINTER[chain.id]
+  const hasKnownMinterAddresses = Boolean(merkleReserveMinter && erc721RedeemMinter)

   const { data: contractData } = useReadContracts({
     allowFailure: false,
+    query: { enabled: hasKnownMinterAddresses },
     contracts: [
       { ...auctionContractParams, functionName: 'owner' },
       { ...tokenContractParams, functionName: 'remainingTokensInReserve' },
       {
         ...tokenContractParams,
         functionName: 'minter',
-        args: [MERKLE_RESERVE_MINTER[chainIdKey]],
+        args: [merkleReserveMinter!],
       },
       {
         ...tokenContractParams,
         functionName: 'minter',
-        args: [ERC721_REDEEM_MINTER[chainIdKey]],
+        args: [erc721RedeemMinter!],
       },
     ] as const,
   })

   const handleMinterEnabled = React.useCallback(
     async (minterAddress: AddressType) => {
-      if (minterAddress === MERKLE_RESERVE_MINTER[chainIdKey]) {
+      if (minterAddress === merkleReserveMinter) {
         await openTab('merkle-reserve')
-      } else if (minterAddress === ERC721_REDEEM_MINTER[chainIdKey]) {
+      } else if (minterAddress === erc721RedeemMinter) {
         await openTab('erc721-redeem')
       }
     },
-    [chainIdKey, openTab]
+    [merkleReserveMinter, erc721RedeemMinter, openTab]
   )

Also applies to: 116-123

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/src/pages/dao/`[network]/[token]/index.tsx around lines 46 - 74, The
code casts chain.id to keyof typeof MERKLE_RESERVE_MINTER and then directly
indexes MERKLE_RESERVE_MINTER and ERC721_REDEEM_MINTER when building the
useReadContracts call; instead, explicitly read const merkleMinter =
MERKLE_RESERVE_MINTER[chain.id as number] and const redeemMinter =
ERC721_REDEEM_MINTER[chain.id as number], check each for undefined, and only
include the corresponding contract entries in the contracts array passed to
useReadContracts (or omit the minter function calls) so undefined values are
never passed into useReadContracts; update the logic around chainIdKey, the
contracts array construction, and the second occurrence at lines ~116-123 to use
these guarded variables.
apps/web/src/utils/api/ai/summaries.ts (3)

36-43: ⚡ Quick win

Silent failure when Redis is unavailable.

The function uses optional chaining (redisConnection?.get()) to gracefully handle missing Redis connections. While this prevents crashes, it silently disables caching, potentially leading to:

  • Repeated expensive AI API calls
  • Increased latency
  • Higher costs

Consider logging a warning when Redis operations are skipped, or explicitly validating that Redis is available in critical paths.

Proposed logging enhancement
  const redisConnection = getRedisConnection()
+ if (!redisConnection) {
+   console.warn('[AI Cache] Redis unavailable; proceeding without cache')
+ }
+
  const cacheKey = getAiCacheKey(namespace, data, model)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/src/utils/api/ai/summaries.ts` around lines 36 - 43, The code
currently swallows Redis availability by using optional chaining on
redisConnection?.get(cacheKey), silently disabling caching; update the flow in
the function that calls getRedisConnection and getAiCacheKey so that if
redisConnection is falsy you log a warning (e.g., using the module's existing
logger or console.warn) that Redis is unavailable and caching will be skipped,
then proceed without calling redisConnection.get; specifically check
redisConnection before calling redisConnection.get(cacheKey) and add a clear log
message referencing the namespace/cacheKey context so operators can diagnose
missing Redis rather than silently falling back.

79-84: 💤 Low value

Consider input validation for DAO descriptions.

The summarizeDaoDescription function doesn't validate the input description before processing. While trimDescription handles empty strings, consider adding explicit validation to:

  • Reject empty/whitespace-only input early
  • Handle unexpectedly long descriptions
  • Prevent potential prompt injection (though server-side context mitigates this)
Proposed validation
 export const summarizeDaoDescription = async (description: string) => {
+  if (!description?.trim()) {
+    throw new Error('Description cannot be empty')
+  }
+  
   return generateCachedAiText({
     namespace: 'ai:daoDescription',
     data: { description },
     prompt: buildDaoDescriptionPrompt(description),
   })
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/src/utils/api/ai/summaries.ts` around lines 79 - 84,
summarizeDaoDescription lacks input validation: before calling
generateCachedAiText (and buildDaoDescriptionPrompt) trim the description (use
existing trimDescription if available), reject or throw for
empty/whitespace-only inputs, enforce a MAX_DAO_DESCRIPTION_LENGTH (either
truncate with a warning or return an error) to handle overly long texts, and
perform simple sanitization to reduce prompt-injection vectors (e.g., strip or
escape special system-like tokens/markers such as "###", "Assistant:", or other
control sequences) so only validated/sanitized text is passed into
buildDaoDescriptionPrompt and generateCachedAiText.

19-20: 💤 Low value

Consider simpler hashing for cache keys.

Using keccak256 (a cryptographic hash) for cache key generation is cryptographically secure but computationally expensive for this use case. A simpler, faster hash function (e.g., a built-in hash or even a deterministic JSON stringification) would suffice for cache key uniqueness without the performance overhead.

Alternative approach
-  const hash = keccak256(toHex(`${safeStringify(data)}:${model}`))
+  const hash = Buffer.from(`${safeStringify(data)}:${model}`).toString('base64')
   return `${namespace}:${hash}`
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/src/utils/api/ai/summaries.ts` around lines 19 - 20, The cache key
generation currently uses keccak256(toHex(...)) which is overkill and slow for
this use case; update the hash creation in the block that defines hash (and the
return `${namespace}:${hash}`) to use a much faster deterministic
approach—either use a stable/deterministic JSON string of the payload
(safeStringify(data)) concatenated with model and namespace as the key, or plug
in a fast non-crypto hash (e.g., murmurhash/fnv1a) instead of keccak256 and drop
toHex; replace the keccak256 and toHex calls around safeStringify(data) and
model accordingly so the resulting cache key remains unique but cheaper to
compute.
apps/web/src/modules/about/utils.ts (1)

4-10: 💤 Low value

Consider documenting normalization behavior.

The normalizeChainLabel function performs aggressive normalization (lowercasing, removing all non-alphanumeric characters). This can cause identical labels for chains with similar names (e.g., "Base-Sepolia" and "BaseSepolia" both become "base sepolia"). While this appears intentional for fuzzy matching, consider adding a brief comment explaining the normalization strategy to prevent future confusion.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/src/modules/about/utils.ts` around lines 4 - 10, The
normalizeChainLabel function aggressively normalizes input (lowercases,
collapses non-alphanumerics to spaces, trims) which can map distinct labels like
"Base-Sepolia" and "BaseSepolia" to the same result; add a concise inline
comment above normalizeChainLabel describing the normalization rules (lowercase,
remove/replace non-alphanumerics with spaces, collapse/trim) and the intended
purpose (fuzzy matching/deduplication) so future maintainers understand this
behavior and its trade-offs.
apps/web/src/modules/about/components/DroposalHighlightsSection.tsx (1)

26-62: ⚡ Quick win

Consider moving statusStyleByType outside the component.

The statusStyleByType object is currently recreated on every render. If getProposalStateColorStyle returns static values (not dependent on runtime theme/context), this constant should be defined outside the component to avoid unnecessary allocations.

♻️ Suggested refactor
+const STATUS_STYLE_BY_TYPE: Record<
+  DroposalHighlight['status'],
+  { borderColor: string; color: string; backgroundColor: string }
+> = {
+  Active: {
+    ...getProposalStateColorStyle(ProposalState.Active),
+    backgroundColor: `color-mix(in srgb, ${getProposalStateColorStyle(ProposalState.Active).borderColor} 12%, transparent)`,
+  },
+  // ... rest of mappings
+}
+
 export const DroposalHighlightsSection: React.FC<DroposalHighlightsSectionProps> = ({
   items,
   eyebrowText = 'Drops',
   title = 'Drops turn releases into onchain distribution',
   copy = 'Launch collectible drops that turn media, editions, and releases into distribution, ownership, and treasury growth for decentralized communities.',
   linkLabel = 'View drop',
   showStatusBadge = true,
 }) => {
-  const statusStyleByType: Record<
-    DroposalHighlight['status'],
-    { borderColor: string; color: string; backgroundColor: string }
-  > = {
-    Active: { /* ... */ },
-    // ...
-  }
-
   const highlights = items?.length ? items : dropHighlights

Note: Only apply this refactor if getProposalStateColorStyle returns static values. If it depends on theme or other runtime context, keep it inside the component.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/src/modules/about/components/DroposalHighlightsSection.tsx` around
lines 26 - 62, The statusStyleByType mapping is being recreated on each render;
move the const statusStyleByType declaration out of the
DroposalHighlightsSection component (to module scope) so it is allocated once
and reuse it across renders if getProposalStateColorStyle returns static values;
if getProposalStateColorStyle depends on runtime theme/context instead, keep it
inside the component but wrap the creation in useMemo (e.g., memoize the
statusStyleByType using useMemo with the theme/context deps) and reference the
same symbol names (statusStyleByType and getProposalStateColorStyle) so callers
inside DroposalHighlightsSection keep working.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/web/src/modules/about/components/AboutFinalCta.tsx`:
- Around line 24-36: The visible numeric markers inside the ordered list are
duplicated for screen readers; keep the semantic <ol>/<li> but hide the visual
markers from assistive tech by marking the marker elements (the Text elements
with class finalChecklistMarker) as presentational—add aria-hidden="true" (or
role="presentation") to those Text nodes so the browser's native ol numbering is
the only thing announced; ensure the Box components remain as="ol" and as="li"
so semantics are preserved.

In `@apps/web/src/modules/about/components/DroposalHighlightsSection.tsx`:
- Around line 50-53: The Trending highlight currently spreads styles from
getProposalStateColorStyle(ProposalState.Defeated); update the mapping so
Trending uses getProposalStateColorStyle(ProposalState.Succeeded) instead (keep
the existing backgroundColor expression) so the Trending status uses the
Succeeded color palette; locate the Trending object in
DroposalHighlightsSection.tsx and replace the ProposalState.Defeated reference
with ProposalState.Succeeded.

In `@apps/web/src/modules/about/components/SectionIntro.tsx`:
- Around line 4-24: The prop eyebrowText on the SectionIntro component is
declared but never rendered; update the JSX in SectionIntro to output
eyebrowText (e.g., render a Text element before the title) so callers' eyebrow
labels aren't dropped; use the existing sectionHeader wrapper and add the
eyebrowText inside it (choose/introduce an appropriate className such as
sectionEyebrow or a new prop if styling control is needed) and ensure the prop
name eyebrowText is used instead of being ignored.

In `@apps/web/src/modules/about/useAboutDaoTabs.ts`:
- Line 12: The request URL in useAboutDaoTabs builds the query string by
interpolating network directly (fetch(`/api/about/dao-tabs?network=${network}`,
...)), which can break for reserved characters; fix it by encoding the network
value with encodeURIComponent when constructing the URL (e.g., replace
${network} with ${encodeURIComponent(network)}) so the fetch call always sends a
well-formed query parameter.
- Around line 17-25: The code parses response.text() with JSON.parse before
checking response.ok, so a JSON.parse error can bypass the typed HttpError path;
update the parsing to try parsing JSON inside a try/catch (or attempt JSON.parse
and fall back to raw text) and ensure the !response.ok branch always runs even
if parsing fails by constructing the HttpError (with status and either parsed
body or the raw text) and throwing it; refer to the response, text, body
variables and the HttpError type in useAboutDaoTabs.ts when implementing this
change.

In `@apps/web/src/modules/about/useAboutSnapshotStats.ts`:
- Around line 16-23: The response handling in useAboutSnapshotStats.ts currently
unconditionally calls JSON.parse on response.text which will throw for non-JSON
(e.g. HTML) error pages; update the code around response.text and body so you
attempt to parse JSON safely (either check response.headers.get('content-type')
for application/json or wrap JSON.parse in a try/catch), and if parsing fails
set body to the raw text (or a minimal object) before constructing the
HttpError; ensure the HttpError created in the existing block (new Error(...),
err.status, err.body) receives the safe body value so callers get the structured
HttpError path even when the server returned HTML or other non-JSON content.

In `@apps/web/src/modules/dashboard/CreateActions.tsx`:
- Around line 38-41: The Link wrapping Button in CreateActions creates nested
interactive controls; remove the nesting by using a single interactive element:
either turn the Link into the visible button (render Link styled as a button and
remove the child Button) or make the Button perform navigation (keep Button and
remove Link, calling router.push('/create') or using useRouter/nav helper in the
Button's onClick). Update the JSX in CreateActions to reference the Link or
Button only (not both) and ensure accessibility attributes (aria-label/role) and
styles are preserved.

In `@apps/web/src/pages/api/about/dao-tabs.ts`:
- Around line 293-305: The fetchCrossChainDaoCandidates function currently uses
Promise.all which rejects on the first failed fetchDaoCandidates call; change
the fan-out to use Promise.allSettled over PUBLIC_DEFAULT_CHAINS, filter for
settled results with status "fulfilled", extract each fulfilled value (the daos
mapped to { chainId, dao }), skip or log any rejected results (including chain
id and error) so a single chain outage doesn't return a 500, and finally flat()
the fulfilled arrays to return CrossChainDaoCandidate[]; apply the same pattern
to other similar fan-outs in this file.

In `@apps/web/src/pages/api/about/showcase.ts`:
- Around line 292-300: The sort comparator currently converts totalSalesAmount
to Number causing precision loss; update the .sort(...) to compare BigInt values
instead by parsing each totalSalesAmount with BigInt(e.g. const aAmount =
BigInt(a.totalSalesAmount || '0'), bAmount = BigInt(b.totalSalesAmount || '0'))
and return bAmount > aAmount ? 1 : bAmount < aAmount ? -1 : 0 so sorting uses
exact bigint comparison (refer to drops, totalSalesAmount and the .sort
comparator).
- Around line 373-396: The catch path returns fallback payload without the same
Cache-Control header, causing origin hits on failures; move extraction of
CACHE_TIMES.EXPLORE ({ maxAge, swr }) out of the try so it's available on error,
and in the catch call res.setHeader('Cache-Control', `public,
s-maxage=${maxAge}, stale-while-revalidate=${swr}`) before returning the
fallback JSON (affecting code around buildShowcaseResponse, CACHE_TIMES.EXPLORE,
and the fallback* variables).

In `@apps/web/src/pages/api/about/snapshot.ts`:
- Around line 91-123: The current fetchUniqueOwnersForChain function performs a
full scan of daoTokenOwners via repeated ABOUT_SNAPSHOT_OWNERS_QUERY paging on
every cache miss which will make the public API latency unbounded; instead, stop
doing full scans on the request path by switching the API to read from a
precomputed/periodically-updated store or indexed query: implement a background
job or cron that uses fetchUniqueOwnersForChain (or an incremental sync using
subgraph events) to compute and persist the unique owner Set, and have the API
handler read that cached result (falling back to a small bounded/filtered query
only when absolutely necessary); ensure OWNER_PAGE_SIZE paging logic is moved
out of the request path and that ABOUT_SNAPSHOT_OWNERS_QUERY is only used by the
background job or an incremental sync process.
- Around line 167-179: The current logic uses the deduped union (uniqueOwners)
whenever it is non-empty, which undercounts if some chain snapshots failed;
change it to use the union only when all owner scans succeeded by checking that
every snapshot in chainSnapshots has snapshot.uniqueOwners present (e.g., via
Array.every), and if not, fall back to the coarse aggregate computed from
daos.reduce; update the totalMembers assignment (and any related variables) to
conditionally choose uniqueOwners.size only when that all-scans-succeeded check
passes, otherwise use the daos.reduce fallback.

In `@apps/web/src/pages/dao/`[network]/[token]/proposal/create.tsx:
- Around line 152-168: The page currently calls useProposalStore() with no
selector which subscribes the component to the entire proposal store and causes
unnecessary rerenders; update the hook call to select only the fields the
component uses (e.g. transactionType, setTransactionType, resetTransactionType,
transactions, title, summary, representedAddress, discussionUrl,
representedAddressEnabled, setTitle, setSummary, setRepresentedAddress,
setDiscussionUrl, setRepresentedAddressEnabled, clearProposal) and pass
useShallow from 'zustand/shallow' as the equality comparator so the component
only re-renders when those specific values change; replace the bare
useProposalStore() invocation with a selector function that returns an object
containing just those identifiers.

In `@package.json`:
- Around line 57-59: Remove the redundant root-level dependency entry
"remove-markdown" from the root package.json dependencies object; keep the
existing declaration in apps/web/package.json (which consumers like
apps/web/src/modules/about/utils.ts already use) so the dependency is only
declared in the consuming package and not at the monorepo root.

---

Nitpick comments:
In `@apps/web/src/modules/about/components/DroposalHighlightsSection.tsx`:
- Around line 26-62: The statusStyleByType mapping is being recreated on each
render; move the const statusStyleByType declaration out of the
DroposalHighlightsSection component (to module scope) so it is allocated once
and reuse it across renders if getProposalStateColorStyle returns static values;
if getProposalStateColorStyle depends on runtime theme/context instead, keep it
inside the component but wrap the creation in useMemo (e.g., memoize the
statusStyleByType using useMemo with the theme/context deps) and reference the
same symbol names (statusStyleByType and getProposalStateColorStyle) so callers
inside DroposalHighlightsSection keep working.

In `@apps/web/src/modules/about/utils.ts`:
- Around line 4-10: The normalizeChainLabel function aggressively normalizes
input (lowercases, collapses non-alphanumerics to spaces, trims) which can map
distinct labels like "Base-Sepolia" and "BaseSepolia" to the same result; add a
concise inline comment above normalizeChainLabel describing the normalization
rules (lowercase, remove/replace non-alphanumerics with spaces, collapse/trim)
and the intended purpose (fuzzy matching/deduplication) so future maintainers
understand this behavior and its trade-offs.

In `@apps/web/src/pages/dao/`[network]/[token]/index.tsx:
- Around line 46-74: The code casts chain.id to keyof typeof
MERKLE_RESERVE_MINTER and then directly indexes MERKLE_RESERVE_MINTER and
ERC721_REDEEM_MINTER when building the useReadContracts call; instead,
explicitly read const merkleMinter = MERKLE_RESERVE_MINTER[chain.id as number]
and const redeemMinter = ERC721_REDEEM_MINTER[chain.id as number], check each
for undefined, and only include the corresponding contract entries in the
contracts array passed to useReadContracts (or omit the minter function calls)
so undefined values are never passed into useReadContracts; update the logic
around chainIdKey, the contracts array construction, and the second occurrence
at lines ~116-123 to use these guarded variables.

In `@apps/web/src/utils/api/ai/summaries.ts`:
- Around line 36-43: The code currently swallows Redis availability by using
optional chaining on redisConnection?.get(cacheKey), silently disabling caching;
update the flow in the function that calls getRedisConnection and getAiCacheKey
so that if redisConnection is falsy you log a warning (e.g., using the module's
existing logger or console.warn) that Redis is unavailable and caching will be
skipped, then proceed without calling redisConnection.get; specifically check
redisConnection before calling redisConnection.get(cacheKey) and add a clear log
message referencing the namespace/cacheKey context so operators can diagnose
missing Redis rather than silently falling back.
- Around line 79-84: summarizeDaoDescription lacks input validation: before
calling generateCachedAiText (and buildDaoDescriptionPrompt) trim the
description (use existing trimDescription if available), reject or throw for
empty/whitespace-only inputs, enforce a MAX_DAO_DESCRIPTION_LENGTH (either
truncate with a warning or return an error) to handle overly long texts, and
perform simple sanitization to reduce prompt-injection vectors (e.g., strip or
escape special system-like tokens/markers such as "###", "Assistant:", or other
control sequences) so only validated/sanitized text is passed into
buildDaoDescriptionPrompt and generateCachedAiText.
- Around line 19-20: The cache key generation currently uses
keccak256(toHex(...)) which is overkill and slow for this use case; update the
hash creation in the block that defines hash (and the return
`${namespace}:${hash}`) to use a much faster deterministic approach—either use a
stable/deterministic JSON string of the payload (safeStringify(data))
concatenated with model and namespace as the key, or plug in a fast non-crypto
hash (e.g., murmurhash/fnv1a) instead of keccak256 and drop toHex; replace the
keccak256 and toHex calls around safeStringify(data) and model accordingly so
the resulting cache key remains unique but cheaper to compute.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 923112a5-20f0-4cc5-9efe-3b33d84a3750

📥 Commits

Reviewing files that changed from the base of the PR and between 4669d32 and ef90afc.

⛔ Files ignored due to path filters (4)
  • apps/web/public/builderlogo.png is excluded by !**/*.png
  • apps/web/public/noggles-square-dark.svg is excluded by !**/*.svg
  • apps/web/public/noggles-square.svg is excluded by !**/*.svg
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (45)
  • apps/web/package.json
  • apps/web/src/modules/about/AboutPage.css.ts
  • apps/web/src/modules/about/AboutPage.tsx
  • apps/web/src/modules/about/components/AboutFinalCta.tsx
  • apps/web/src/modules/about/components/AboutHero.tsx
  • apps/web/src/modules/about/components/CoiningCard.tsx
  • apps/web/src/modules/about/components/CoiningHighlightsSection.tsx
  • apps/web/src/modules/about/components/DaoCard.tsx
  • apps/web/src/modules/about/components/DroposalHighlightsSection.tsx
  • apps/web/src/modules/about/components/EcosystemActivitySection.tsx
  • apps/web/src/modules/about/components/EcosystemStatGrid.tsx
  • apps/web/src/modules/about/components/FeaturedDaoSection.tsx
  • apps/web/src/modules/about/components/HowItWorksSection.tsx
  • apps/web/src/modules/about/components/ProposalHighlightsSection.tsx
  • apps/web/src/modules/about/components/SectionIntro.tsx
  • apps/web/src/modules/about/components/WhatIsBuilderSection.tsx
  • apps/web/src/modules/about/components/WhatYouCanBuildSection.tsx
  • apps/web/src/modules/about/components/WhyBuilderSection.tsx
  • apps/web/src/modules/about/data.ts
  • apps/web/src/modules/about/index.ts
  • apps/web/src/modules/about/types.ts
  • apps/web/src/modules/about/useAboutDaoTabs.ts
  • apps/web/src/modules/about/useAboutShowcase.ts
  • apps/web/src/modules/about/useAboutSnapshotStats.ts
  • apps/web/src/modules/about/utils.ts
  • apps/web/src/modules/dashboard/CreateActions.css.ts
  • apps/web/src/modules/dashboard/CreateActions.tsx
  • apps/web/src/modules/dashboard/Dashboard.tsx
  • apps/web/src/modules/dashboard/SingleDaoSelector.tsx
  • apps/web/src/pages/about.tsx
  • apps/web/src/pages/api/about/dao-tabs.ts
  • apps/web/src/pages/api/about/showcase.ts
  • apps/web/src/pages/api/about/snapshot.ts
  • apps/web/src/pages/api/ai/generateTxSummary.ts
  • apps/web/src/pages/api/migrated.ts
  • apps/web/src/pages/dao/[network]/[token]/index.tsx
  • apps/web/src/pages/dao/[network]/[token]/proposal/create.tsx
  • apps/web/src/utils/api/ai/summaries.ts
  • package.json
  • packages/proposal-ui/src/components/ProposalStatus/ProposalStatus.helper.ts
  • packages/proposal-ui/src/components/ProposalStatus/index.ts
  • packages/proposal-ui/src/index.ts
  • packages/zord/src/elements/Button.css.ts
  • packages/zord/src/elements/Button.tsx
  • turbo.json
💤 Files with no reviewable changes (1)
  • apps/web/src/modules/dashboard/CreateActions.css.ts

Comment thread apps/web/src/modules/about/components/AboutFinalCta.tsx
Comment thread apps/web/src/modules/about/components/DroposalHighlightsSection.tsx
Comment thread apps/web/src/modules/about/components/SectionIntro.tsx Outdated
Comment thread apps/web/src/modules/about/useAboutDaoTabs.ts Outdated
Comment thread apps/web/src/modules/about/useAboutDaoTabs.ts
Comment thread apps/web/src/pages/api/about/showcase.ts
Comment on lines +91 to +123
const fetchUniqueOwnersForChain = async (subgraphUrl: string): Promise<Set<string>> => {
const client = new GraphQLClient(subgraphUrl, {
headers: { 'Content-Type': 'application/json' },
})

const owners = new Set<string>()
let skip = 0

while (true) {
const data = await client.request<SnapshotOwnersQueryResponse>(
ABOUT_SNAPSHOT_OWNERS_QUERY,
{
first: OWNER_PAGE_SIZE,
skip,
}
)

const page = data.daoTokenOwners ?? []

for (const item of page) {
if (item.owner) {
owners.add(item.owner.toLowerCase())
}
}

if (page.length < OWNER_PAGE_SIZE) {
break
}

skip += OWNER_PAGE_SIZE
}

return owners
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Avoid full owner-table scans on the request path.

fetchUniqueOwnersForChain walks every daoTokenOwners page for a chain on each cache miss. That makes this public endpoint's latency unbounded as the ecosystem grows and will eventually turn into subgraph timeout/quota pressure.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/src/pages/api/about/snapshot.ts` around lines 91 - 123, The current
fetchUniqueOwnersForChain function performs a full scan of daoTokenOwners via
repeated ABOUT_SNAPSHOT_OWNERS_QUERY paging on every cache miss which will make
the public API latency unbounded; instead, stop doing full scans on the request
path by switching the API to read from a precomputed/periodically-updated store
or indexed query: implement a background job or cron that uses
fetchUniqueOwnersForChain (or an incremental sync using subgraph events) to
compute and persist the unique owner Set, and have the API handler read that
cached result (falling back to a small bounded/filtered query only when
absolutely necessary); ensure OWNER_PAGE_SIZE paging logic is moved out of the
request path and that ABOUT_SNAPSHOT_OWNERS_QUERY is only used by the background
job or an incremental sync process.

Comment thread apps/web/src/pages/api/about/snapshot.ts Outdated
Comment thread apps/web/src/pages/dao/[network]/[token]/proposal/create.tsx Outdated
Comment thread package.json Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
packages/ui/src/DropMintWidget/DropMintWidget.tsx (1)

179-190: ⚡ Quick win

Reconsider the placeholder Box approach for semantic correctness.

When quantity is 1, the code renders <Box className={quantityButton} />, which applies button visual styles to a non-interactive element. This can confuse assistive technologies and creates a visual element that looks clickable but isn't.

Consider these alternatives:

  1. Always render the button but keep it disabled (consistent semantics)
  2. Render an invisible spacer with only layout dimensions (minWidth/height)
  3. Adjust the parent layout to handle the missing element gracefully
♻️ Option 1: Always render disabled button
-              {quantity > 1 ? (
-                <Button
-                  variant="primary"
-                  size="sm"
-                  onClick={decrementQuantity}
-                  className={quantityButton}
-                >
-                  -
-                </Button>
-              ) : (
-                <Box className={quantityButton} />
-              )}
+              <Button
+                variant="primary"
+                size="sm"
+                onClick={decrementQuantity}
+                disabled={quantity <= 1}
+                className={quantityButton}
+              >
+                -
+              </Button>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ui/src/DropMintWidget/DropMintWidget.tsx` around lines 179 - 190,
The placeholder Box in DropMintWidget makes a non-interactive element look like
a button; replace it in the render where quantity is checked so semantics match
visuals — always render the same Button element (the one using className
quantityButton and onClick decrementQuantity) but set its disabled prop when
quantity <= 1 so it remains visually consistent and accessible; update any
aria-label or title on that Button if needed to convey disabled state to
assistive tech.
packages/ui/src/DropMintWidget/DropMintWidget.css.ts (1)

4-20: 💤 Low value

Note: Different border radius between styled and unstyled variants.

widgetContainer uses borderRadius: 'phat' while widgetContainerUnstyled uses borderRadius: 'curved'. If this difference is intentional to match different contexts (standalone vs. embedded), this is fine. Otherwise, consider standardizing.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ui/src/DropMintWidget/DropMintWidget.css.ts` around lines 4 - 20,
The two style exports widgetContainer and widgetContainerUnstyled use different
borderRadius values ('phat' vs 'curved'), causing inconsistent corner radii; if
this is unintended, standardize them by updating either widgetContainerUnstyled
to use 'phat' or widgetContainer to use 'curved' so both share the same
borderRadius, and if the difference is intentional add a clarifying comment
above the relevant export(s) explaining the contextual reason for the mismatch.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@packages/ui/src/DropMintWidget/DropMintWidget.css.ts`:
- Around line 4-20: The two style exports widgetContainer and
widgetContainerUnstyled use different borderRadius values ('phat' vs 'curved'),
causing inconsistent corner radii; if this is unintended, standardize them by
updating either widgetContainerUnstyled to use 'phat' or widgetContainer to use
'curved' so both share the same borderRadius, and if the difference is
intentional add a clarifying comment above the relevant export(s) explaining the
contextual reason for the mismatch.

In `@packages/ui/src/DropMintWidget/DropMintWidget.tsx`:
- Around line 179-190: The placeholder Box in DropMintWidget makes a
non-interactive element look like a button; replace it in the render where
quantity is checked so semantics match visuals — always render the same Button
element (the one using className quantityButton and onClick decrementQuantity)
but set its disabled prop when quantity <= 1 so it remains visually consistent
and accessible; update any aria-label or title on that Button if needed to
convey disabled state to assistive tech.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 33894868-da29-4aa0-9ae0-afbe77cfbf59

📥 Commits

Reviewing files that changed from the base of the PR and between ef90afc and 2949a26.

📒 Files selected for processing (15)
  • apps/web/src/modules/about/AboutPage.css.ts
  • apps/web/src/modules/about/components/AboutFinalCta.tsx
  • apps/web/src/modules/about/components/AboutHero.tsx
  • apps/web/src/modules/about/components/CoiningCard.tsx
  • apps/web/src/modules/about/components/DroposalHighlightsSection.tsx
  • apps/web/src/modules/coin/CoinDetail/CoinCommentForm.css.ts
  • apps/web/src/modules/coin/CoinDetail/CoinComments.css.ts
  • apps/web/src/modules/coin/CoinDetail/CoinComments.tsx
  • apps/web/src/modules/coin/CoinDetail/CoinDetail.css.ts
  • apps/web/src/modules/drop/DropDetail/DropDetail.css.ts
  • apps/web/src/modules/drop/DropDetail/DropDetail.tsx
  • packages/dao-ui/src/components/Gallery/Gallery.tsx
  • packages/feed-ui/src/Modals/MintModal.tsx
  • packages/ui/src/DropMintWidget/DropMintWidget.css.ts
  • packages/ui/src/DropMintWidget/DropMintWidget.tsx
✅ Files skipped from review due to trivial changes (4)
  • apps/web/src/modules/coin/CoinDetail/CoinComments.css.ts
  • apps/web/src/modules/coin/CoinDetail/CoinComments.tsx
  • apps/web/src/modules/coin/CoinDetail/CoinDetail.css.ts
  • apps/web/src/modules/coin/CoinDetail/CoinCommentForm.css.ts
🚧 Files skipped from review as they are similar to previous changes (5)
  • apps/web/src/modules/about/components/AboutHero.tsx
  • apps/web/src/modules/about/components/DroposalHighlightsSection.tsx
  • apps/web/src/modules/about/components/AboutFinalCta.tsx
  • apps/web/src/modules/about/components/CoiningCard.tsx
  • apps/web/src/modules/about/AboutPage.css.ts

@dan13ram dan13ram merged commit e4fdc5f into BuilderOSS:staging May 9, 2026
3 of 4 checks passed
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