Skip to content

Whitelabel Governance#1801

Open
pikonha wants to merge 9 commits intodevfrom
codex/whitelabel-governance
Open

Whitelabel Governance#1801
pikonha wants to merge 9 commits intodevfrom
codex/whitelabel-governance

Conversation

@pikonha
Copy link
Copy Markdown
Member

@pikonha pikonha commented Apr 2, 2026

Summary

  • Adds whitelabel governance support for ENS, including a custom domain (governance.ens.domains), theme, and branding config
  • Introduces a new /whitelabel/[daoId] Next.js route with all governance sub-pages (overview, proposals, delegates, feed, service providers)
  • Adds whitelabel layout, shell, sidebar, header, and connect-wallet components
  • Adds middleware for hostname-based DAO resolution and theme injection
  • Enables offchainProposals flag for ENS, COMP, GTC, and UNI
  • Adds useConnectedWalletVotingPower hook and WhitelabelDelegateDetailPage component

Test plan

  • Visit governance.ens.domains locally (or with middleware override) and verify the whitelabel layout renders with ENS branding
  • Navigate all whitelabel sub-routes (proposals, delegates, governance, feed, service providers)
  • Verify offchain proposals appear for ENS, COMP, GTC, and UNI
  • Verify non-whitelabel DAOs are unaffected

🤖 Generated with Claude Code

@vercel
Copy link
Copy Markdown

vercel bot commented Apr 2, 2026

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

Project Deployment Actions Updated (UTC)
anticapture Ready Ready Preview, Comment Apr 9, 2026 6:51pm
anticapture-storybook Ready Ready Preview, Comment Apr 9, 2026 6:51pm

Request Review

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6356353ade

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +90 to +94
const proposalRoute = `${WHITELABEL_ROUTES.proposals}/${proposalId}`;

return isOffchain
? `${basePath}/${proposalRoute}?proposalType=offchain`
: `${basePath}/${proposalRoute}`;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Keep proposal detail links on the governance route

getDaoProposalPath always constructs .../proposals/{id} (or ?proposalType=offchain). On standard dashboard paths, basePath resolves to /{dao} (e.g. /aave/governance), so this now emits /aave/proposals/..., but non-whitelabel detail pages still live under /[daoId]/governance/proposal/[proposalId] and /[daoId]/governance/offchain-proposal/[proposalId]. That makes proposal links from governance/feed/delegate views 404 for normal (non-whitelabel-hostname) users.

Useful? React with 👍 / 👎.

return basePath || "/";
}

return `${basePath}/${page}`;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve governance list path for non-whitelabel pages

getDaoPagePath appends the page directly to the DAO base path (/{dao}/{page}), so callers requesting page: "proposals" now navigate to /{dao}/proposals. In regular routes, proposal lists are still served from /{dao}/governance, so controls like the proposal-detail breadcrumb now send users to an unmapped URL and fail with 404 outside whitelabel rewrites.

Useful? React with 👍 / 👎.

Comment on lines +19 to +44
const NAV_ITEMS = [
{
label: "Proposals",
page: WHITELABEL_ROUTES.proposals,
icon: Landmark,
enabled: (daoId: DaoIdEnum) => !!daoConfigByDaoId[daoId].governancePage,
},
{
label: "Delegates",
page: WHITELABEL_ROUTES.delegates,
icon: UserCheck,
enabled: (daoId: DaoIdEnum) => !!daoConfigByDaoId[daoId].dataTables,
},
{
label: "Activity Feed",
page: WHITELABEL_ROUTES.activityFeed,
icon: Newspaper,
enabled: (daoId: DaoIdEnum) => !!daoConfigByDaoId[daoId].activityFeed,
},
{
label: "SPP Accountability",
page: WHITELABEL_ROUTES.sppAccountability,
icon: Building2,
enabled: (daoId: DaoIdEnum) => daoId === "ENS",
},
] as const;
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Nav Items are duplicated here and in WhitelabelSidebar, extract it in a const

page: item.page,
}),
})).filter((item) => item.enabled(daoId)),
[daoConfig, daoId, pathname],
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Suggested change
[daoConfig, daoId, pathname],
[daoId, pathname],

Comment on lines +162 to +167
const { votes } = useVoterInfo({
address: address ?? "",
daoId,
proposalId: proposal?.id ?? "",
decimals,
});
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This is firing graphql requests per proposal in the list

Image

label: "SPP Accountability",
page: WHITELABEL_ROUTES.sppAccountability,
icon: Building2,
enabled: (daoId: DaoIdEnum) => daoId === "ENS",
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

we should use serviceProviders boolean from daoConfig

Comment on lines +69 to +82
const filteredOnchainProposals = useMemo(
() =>
proposals.filter((proposal) =>
proposal.title.toLowerCase().includes(normalizedSearch),
),
[normalizedSearch, proposals],
);
const filteredOffchainProposals = useMemo(
() =>
offchainProposals.filter((proposal) =>
proposal.title.toLowerCase().includes(normalizedSearch),
),
[normalizedSearch, offchainProposals],
);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

this filter are only client side so I dont know if its too useful because if a proposal isnt fetched yet it will not appear on search

"/",
`/${WHITELABEL_ROUTES.proposals}`,
`/${WHITELABEL_ROUTES.delegates}`,
"/activity-feed",
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Suggested change
"/activity-feed",
"/${WHITELABEL_ROUTES.activityFeed}",

`/${WHITELABEL_ROUTES.sppAccountability}`,
`/${daoSlug}/${WHITELABEL_ROUTES.proposals}`,
`/${daoSlug}/${WHITELABEL_ROUTES.delegates}`,
`/${daoSlug}/activity-feed`,
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Suggested change
`/${daoSlug}/activity-feed`,
`/${daoSlug}/${WHITELABEL_ROUTES.activityFeed}`,

Comment on lines +51 to +58
return normalizedPathname === `/${daoSlug}` ||
normalizedPathname.startsWith(`/${daoSlug}/`)
? `/${daoSlug}`
: normalizedPathname === internalBasePath ||
normalizedPathname.startsWith(`${internalBasePath}/`)
? internalBasePath
: "";
};
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

avoid nested ternaries

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Suggested change
return normalizedPathname === `/${daoSlug}` ||
normalizedPathname.startsWith(`/${daoSlug}/`)
? `/${daoSlug}`
: normalizedPathname === internalBasePath ||
normalizedPathname.startsWith(`${internalBasePath}/`)
? internalBasePath
: "";
};
if (normalizedPathname === `/${daoSlug}` || normalizedPathname.startsWith(`/${daoSlug}/`))
return `/${daoSlug}`;
if (normalizedPathname === internalBasePath ||
normalizedPathname.startsWith(`${internalBasePath}/`))
return internalBasePath;
return "";
};

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3edefc49bf

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +162 to +167
const { votes } = useVoterInfo({
address: address ?? "",
daoId,
proposalId: proposal?.id ?? "",
decimals,
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Avoid per-card voter queries in proposal lists

Calling useVoterInfo inside each ProposalItem introduces an N+1 query pattern for connected users: rendering a page of proposals now fires one GetAccountPower request per row (plus repeats on pagination/re-renders), which can significantly increase latency and backend load on governance/overview screens. This regression is introduced here because proposalId varies per card, so Apollo cannot collapse these into a single request.

Useful? React with 👍 / 👎.

const { daoId } = await params;
const daoIdEnum = daoId.toUpperCase() as DaoIdEnum;

return <HoldersAndDelegatesSection daoId={daoIdEnum} />;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Show delegates tab on the delegates route

This page renders HoldersAndDelegatesSection without forcing the active tab, but that component defaults to tokenHolders; as a result /whitelabel/[daoId]/delegates shows token holders instead of delegates despite the route and metadata saying “Delegates”. Users returning from delegate detail to this route are also dropped into the wrong view.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7bccd429c2

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

<ProposalListSection
loading={onchainLoading}
hasItems={proposals.length > 0}
hasItems={filteredOnchainProposals.length > 0}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Keep pagination active when filtering proposal results

Using filteredOnchainProposals.length > 0 (and the offchain equivalent) as hasItems makes the list enter the empty-state branch whenever the current page has no title matches, and that branch does not render the loadMoreRef sentinel that triggers fetchNextPage. In practice, a search term that only appears on later pages will incorrectly show “No proposals match your search” and never load additional pages, so search results can be silently incomplete.

Useful? React with 👍 / 👎.

}: WhitelabelIndexPageProps) {
const { daoId } = await params;

redirect(`/whitelabel/${daoId}/proposals`);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Redirect whitelabel index to external proposals route

This redirect always sends users to /whitelabel/{daoId}/proposals; combined with middleware rewriting / to /whitelabel/{daoId}, visiting a whitelabel hostname root (e.g. governance.ens.domains/) will expose and lock users onto internal implementation paths instead of the public /proposals URL scheme. That breaks the clean custom-domain routing behavior introduced by hostname rewrites.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e7f00e1976

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +18 to +19
return ALL_DAOS.includes(forcedDao as DaoIdEnum)
? (forcedDao as DaoIdEnum)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Restrict FORCE_DAO to whitelabel-enabled DAOs

resolveForcedDao currently accepts any value in ALL_DAOS, so setting FORCE_DAO to a non-whitelabel DAO (for example AAVE, which is shown in .env.example) makes middleware rewrite requests to /whitelabel/<dao>/.... Those routes immediately 404 because app/whitelabel/[daoId]/layout.tsx rejects DAOs without whitelabel config, so local forced-domain development breaks for most DAO values.

Useful? React with 👍 / 👎.

Comment on lines +147 to +151
`/${WHITELABEL_ROUTES.proposals}`,
`/${WHITELABEL_ROUTES.delegates}`,
`/${WHITELABEL_ROUTES.holdersAndDelegates}`,
`/${WHITELABEL_ROUTES.activityFeed}`,
`/${WHITELABEL_ROUTES.serviceProviders}`,
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Add governance slug to whitelabel rewrite candidates

The rewrite candidate list excludes the governance path, so requests like /governance or /governance/proposal/<id> on a whitelabel hostname are not rewritten to /whitelabel/[daoId]/... and fall through to 404s, even though internal whitelabel governance pages exist. This breaks legacy/bookmarked governance URLs on custom domains.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: afc7959ab7

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

appName: "Shutter Governance",
logo: ShutterIcon,
},
hostnames: ["governance.ens.domains"], // change later
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Assign a unique hostname to Shutter whitelabel config

This sets Shutter’s hostnames to governance.ens.domains, which is the same hostname configured for ENS. Because resolveDaoIdFromHostname builds a single hostname→DAO map, the later SHU entry overwrites ENS and requests for governance.ens.domains resolve to SHU instead of ENS, breaking the primary whitelabel routing target introduced in this change.

Useful? React with 👍 / 👎.

Comment on lines +91 to +95
const isWhitelabelRoute = Boolean(
getWhitelabelBasePath({
daoId: daoId.toUpperCase() as DaoIdEnum,
pathname,
}),
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Detect whitelabel route without matching standard DAO paths

isWhitelabelRoute is derived from getWhitelabelBasePath, but that helper intentionally returns a base path for normal /{dao}/... routes too. As a result, hideConnectWallet becomes true on standard proposal detail pages and suppresses the proposal-header connect CTA for non-whitelabel users, which is a regression in the normal navigation flow.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 69f79a7804

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

return `/${WHITELABEL_ROUTES.notifications}`;
}

return `/whitelabel/${daoId.toLowerCase()}/${WHITELABEL_ROUTES.notifications}`;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Return a reachable notifications path for standard DAO routes

getDaoNotificationsPath falls back to /whitelabel/{dao}/notifications for non-whitelabel page contexts, and VoteSuccessContent uses this helper for the “Get Notifications” button after voting. On standard proposal routes (for example /aave/governance/proposal/...), this sends users to a whitelabel URL that is rejected by app/whitelabel/[daoId]/layout.tsx for DAOs without whitelabel config, so the new post-vote CTA becomes a 404 for most DAOs.

Useful? React with 👍 / 👎.


export const HoldersAndDelegatesSection = ({ daoId }: { daoId: DaoIdEnum }) => {
const pathname = usePathname();
const isWhitelabel = Boolean(getWhitelabelBasePath({ daoId, pathname }));
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Detect whitelabel mode without matching normal DAO paths

HoldersAndDelegatesSection treats any truthy getWhitelabelBasePath result as whitelabel, but that helper also returns /{dao} on normal routes like /[daoId]/holders-and-delegates. This makes isWhitelabel true on standard pages, so the default tab flips to delegates instead of tokenHolders, changing existing non-whitelabel behavior and initial data shown to users.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: fcea846874

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".


export const GlobalProviders = ({ children }: { children: ReactNode }) => {
const pathname = usePathname();
const isWhitelabelRoute = pathname?.startsWith("/whitelabel/");
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Detect whitelabel mode from hostname-aware state

The whitelabel check relies on pathname.startsWith("/whitelabel/"), but middleware rewrites custom-domain requests like /proposals to /whitelabel/{daoId}/... internally while the browser URL stays /proposals (apps/dashboard/middleware.ts). In that common custom-domain flow this condition stays false, so the dark class is never removed and whitelabel pages render with dark-mode utility variants enabled, which breaks the intended branded theme.

Useful? React with 👍 / 👎.

Comment on lines +113 to +114
{(snapshotLink !== undefined ? true : isOngoing) && (
<Button onClick={() => setIsVotingModalOpen(true)}>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Gate offchain vote CTA on proposal being ongoing

For offchain proposals, the CTA condition uses snapshotLink !== undefined ? true : isOngoing, which always evaluates to true on Snapshot proposals regardless of status. That means closed offchain proposals on whitelabel detail pages still show “Cast your vote” and open the voting modal, leading users into a vote flow that cannot succeed once voting has ended.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: fab8687b77

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

theme: "light",
requestFeatureUrl: "https://blockful.io/contact",
customDomain: "governance.ens.domains", // change later
forumBaseUrl: "https://discuss.ens.domains/", // change later
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Point Shutter forum links to the Shutter forum

SHU sets whitelabel.forumBaseUrl to the ENS forum, and this commit makes TitleSection render a "Forum discussion" link via getWhitelabelForumProposalUrl for DAOs that have whitelabel config. On SHU proposal pages, users are therefore sent to ENS search results instead of Shutter governance discussions, so the new CTA consistently navigates to the wrong community.

Useful? React with 👍 / 👎.

export const ShutterAttackBanner = () => {
const pathname = usePathname();

if (pathname.startsWith("/whitelabel/")) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Detect whitelabel hostnames before rendering Shutter banner

The middleware rewrites whitelabel hostnames to /whitelabel/{daoId}/... internally, but the browser pathname on custom domains stays /proposals, /delegates, etc. This guard only checks pathname.startsWith('/whitelabel/'), so the Shutter-specific global banner still renders on branded whitelabel domains where it should be hidden.

Useful? React with 👍 / 👎.

@isadorable-png
Copy link
Copy Markdown
Collaborator

🎨 UI Review — PR #1801 Whitelabel Governance

Automated review by Claude · Figma: mUgy2KpQ3gJ07yZaUaXu8l · Preview


Layout / Shell

  • Body wrapper: left and right borders are lighter than the borders used inside the content — should match border-border-default consistently
  • text-[#0080bc] hardcoded on "Powered by Blockful" in the sidebar — should use text-link or text-[var(--primary)] to respect the per-DAO theme (widgets/WhitelabelSidebar.tsx:207)

Proposals

  • When search returns no results, show the DS blank slate component
  • Missing "You voted" badge on proposal cards — should display the option the user voted for

Proposal Detail

Current results

  • Abstain icon is wrong — should be a minus ()
  • Empty progress bar background is too dark; abstain bar should be darker than the empty background
  • Remove contract parameters from this page

Status / timeline

  • Status card colors incorrect — all timeline dots look the same; each status needs its own color

Links

  • Forum link is not working

Votes tab

  • Tabs are rendering in column layout — should be horizontal pill tabs
  • Table needs horizontal scroll on overflow
  • Filter popover has no border — blends with the table header row, no contrast

Actions tab

  • Encode/decode button needs a small bottom margin (~4px) — it's flush against the code box (can apply to Anticapture too)

Holders & Delegates

  • Add search bar to the top nav (consistent with Proposals); optionally hide the column-level search once added — lower priority if it takes too long
  • Table row hover: button background needs an opacity variant to stay visible — same problem affects the progress bar background
    • Drawer
      • Badge style needs fixing
      • Table is missing background color
      • Sorting arrow turns white when active — should use ENS brand color
      • Delegation confirmation modal: loading spinner and icon are dark-mode colors
      • VP History: segmented control contrast broken
  • Timeframe dropdown uses dark theme when active

Service Providers

  • Alert icon is white — should be gray
  • Provider placeholder icon: letter is white — should be gray (needs contrast)

Notifications

  • Cards are missing border radius

Governance Settings

  • Cards should be full-width in a 4-column grid (top and bottom rows); the 3 bottom cards leave the 4th slot naturally empty
  • Contract addresses: font is Roboto Mono — should match the rest of the DS

Mobile

Menu

  • Menu is pushing content down — should be position: absolute with a smooth slide-in animation

Header

  • Remove the dashed border-bottom
  • Hamburger button is 36×36px — below the 44px minimum touch target; p-2 + size-5 icon = 36px total (widgets/WhitelabelHeaderMobile.tsx:60)
  • Security alerts page: large margin-top on mobile header; two consecutive dividers after the header

Proposal Detail

  • Sticky tabs have no background — content scrolls through them
  • "Cast your vote" should open a bottom sheet drawer on mobile, not a modal
    • Votes tab: tab items are not full-width on mobile — they should be

Holders & Delegates

  • First column is too wide

Service Providers

  • First tab content is broken

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants