Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
78 commits
Select commit Hold shift + click to select a range
1c8f1a1
feat(dashboard): replace the panel header with the v2.1 hero
brunod-e Aug 13, 2026
8a21e30
feat(dashboard): add the latest finding ticker to the panel
brunod-e Aug 13, 2026
2c87e1e
feat(dashboard): show every monitored DAO on the panel
brunod-e Aug 13, 2026
388e6b2
feat(dashboard): add the track record section to the panel
brunod-e Aug 13, 2026
02a9a0a
feat(dashboard): add the use it now section to the panel
brunod-e Aug 13, 2026
c24a289
feat(dashboard): add the services row to the panel
brunod-e Aug 13, 2026
7ebec01
feat(dashboard): use the audited testimonials on the panel
brunod-e Aug 13, 2026
b919491
Merge branch 'feat/panel-v2.1-case-studies' into feat/panel-v2.1-use-…
brunod-e Aug 13, 2026
5fc5286
Merge branch 'feat/panel-v2.1-use-it-now' into feat/panel-v2.1-servic…
brunod-e Aug 13, 2026
6797922
fix(dashboard): align the panel hero with the Figma spec
brunod-e Aug 13, 2026
67aa01a
Merge branch 'feat/panel-v2.1-hero' into feat/panel-v2.1-ticker
brunod-e Aug 13, 2026
f94be3f
fix(dashboard): track the ticker label at the design's 0.78px
brunod-e Aug 13, 2026
4397b2f
Merge branch 'feat/panel-v2.1-ticker' into feat/panel-v2.1-table-scroll
brunod-e Aug 13, 2026
c8f2e21
Merge branch 'feat/panel-v2.1-table-scroll' into feat/panel-v2.1-case…
brunod-e Aug 13, 2026
69ebeaf
fix(dashboard): give the testimonial card its border back
brunod-e Aug 13, 2026
0793b27
Merge branch 'feat/panel-v2.1-case-studies' into feat/panel-v2.1-use-…
brunod-e Aug 13, 2026
fd7e9e0
refactor(dashboard): name the 13px mono tracking instead of hard-codi…
brunod-e Aug 13, 2026
9948976
Merge branch 'feat/panel-v2.1-hero' into feat/panel-v2.1-ticker
brunod-e Aug 13, 2026
2b22a2e
refactor(dashboard): use the named tracking token on the ticker label
brunod-e Aug 13, 2026
9377588
Merge branch 'feat/panel-v2.1-ticker' into feat/panel-v2.1-table-scroll
brunod-e Aug 13, 2026
de7a71a
Merge branch 'feat/panel-v2.1-table-scroll' into feat/panel-v2.1-case…
brunod-e Aug 13, 2026
7fb5087
refactor(dashboard): use the named tracking token on the track record…
brunod-e Aug 13, 2026
1442bd3
Merge branch 'feat/panel-v2.1-case-studies' into feat/panel-v2.1-use-…
brunod-e Aug 13, 2026
15f784f
fix(dashboard): draw the use it now badge icons in the dimmed colour
brunod-e Aug 13, 2026
c12e7b7
Merge branch 'feat/panel-v2.1-use-it-now' into feat/panel-v2.1-servic…
brunod-e Aug 13, 2026
42e23df
fix(dashboard): draw the services badge icon in the dimmed colour
brunod-e Aug 13, 2026
6cd7c92
refactor(dashboard): let the stage card own its own hero sizing
brunod-e Aug 13, 2026
6b2810f
fix(dashboard): sit the stage count next to its bar, as the design dr…
brunod-e Aug 13, 2026
6f275fe
feat(dashboard): read the panel ticker from the live Paragraph feed
brunod-e Aug 14, 2026
2d60b64
fix(dashboard): finish the track record cards with real assets and links
brunod-e Aug 14, 2026
f989d94
fix(dashboard): send Connect your agent to the in-app API keys page
brunod-e Aug 14, 2026
feefc70
fix(dashboard): space the panel sections the way the design draws them
brunod-e Aug 14, 2026
4401037
Merge branch 'feat/panel-v2.1-ticker' into feat/panel-v2.1-table-scroll
brunod-e Aug 14, 2026
4de56ca
Merge branch 'feat/panel-v2.1-table-scroll' into feat/panel-v2.1-case…
brunod-e Aug 14, 2026
0452245
Merge branch 'feat/panel-v2.1-case-studies' into feat/panel-v2.1-use-…
brunod-e Aug 14, 2026
cc11b48
Merge branch 'feat/panel-v2.1-use-it-now' into feat/panel-v2.1-servic…
brunod-e Aug 14, 2026
78393eb
fix(dashboard): let keyboard reach the governance risk stage bars
brunod-e Aug 14, 2026
1f4119f
fix(dashboard): put a deadline on the Paragraph feed request
brunod-e Aug 14, 2026
a5f5833
fix(dashboard): hold the latest finding strip to one line on desktop
brunod-e Aug 14, 2026
3eb59ee
test(dashboard): assert the panel scrolls as a page, not as a table
brunod-e Aug 14, 2026
92d7808
fix(dashboard): stop disabled clickable cards from navigating
brunod-e Aug 14, 2026
53b470e
refactor(dashboard): keep the track record copy inside the panel feature
brunod-e Aug 14, 2026
c54f581
Merge branch 'feat/panel-v2.1-hero' into feat/panel-v2.1-ticker
brunod-e Aug 14, 2026
56f36d5
Merge branch 'feat/panel-v2.1-ticker' into feat/panel-v2.1-table-scroll
brunod-e Aug 14, 2026
30a7fdd
Merge branch 'feat/panel-v2.1-table-scroll' into feat/panel-v2.1-case…
brunod-e Aug 14, 2026
65a53e9
Merge branch 'feat/panel-v2.1-case-studies' into feat/panel-v2.1-use-…
brunod-e Aug 14, 2026
d2e152f
Merge branch 'feat/panel-v2.1-use-it-now' into feat/panel-v2.1-servic…
brunod-e Aug 14, 2026
4da7c3b
docs(dashboard): describe the live Paragraph feed in the ticker chang…
brunod-e Aug 14, 2026
4254247
fix(dashboard): announce the testimonial the carousel arrows select
brunod-e Aug 14, 2026
0dbd0f7
Merge branch 'feat/panel-v2.1-ticker' into feat/panel-v2.1-table-scroll
brunod-e Aug 14, 2026
dc16fac
Merge branch 'feat/panel-v2.1-table-scroll' into feat/panel-v2.1-case…
brunod-e Aug 14, 2026
987de53
Merge branch 'feat/panel-v2.1-case-studies' into feat/panel-v2.1-use-…
brunod-e Aug 14, 2026
dd33254
Merge branch 'feat/panel-v2.1-use-it-now' into feat/panel-v2.1-servic…
brunod-e Aug 14, 2026
366a6e2
fix(dashboard): pin the monitored DAOs header while the page scrolls
brunod-e Aug 17, 2026
5e6db49
test(dashboard): point the panel smoke test at the v2.1 hero heading
brunod-e Aug 17, 2026
605e954
Merge branch 'feat/panel-v2.1-hero' into feat/panel-v2.1-ticker
brunod-e Aug 17, 2026
e7da851
Merge branch 'feat/panel-v2.1-ticker' into feat/panel-v2.1-table-scroll
brunod-e Aug 17, 2026
27e85e1
Merge branch 'feat/panel-v2.1-table-scroll' into feat/panel-v2.1-case…
brunod-e Aug 17, 2026
b0a2a57
Merge branch 'feat/panel-v2.1-case-studies' into feat/panel-v2.1-use-…
brunod-e Aug 17, 2026
725bdd6
fix(dashboard): back every use it now claim with what actually ships
brunod-e Aug 17, 2026
7968dd9
fix(dashboard): scope the header pin to xl and harden its e2e test
brunod-e Aug 17, 2026
d5cbca3
Merge branch 'feat/panel-v2.1-table-scroll' into feat/panel-v2.1-case…
brunod-e Aug 17, 2026
ac8fe65
Merge branch 'feat/panel-v2.1-case-studies' into feat/panel-v2.1-use-…
brunod-e Aug 17, 2026
ba5d1cd
Merge branch 'feat/panel-v2.1-use-it-now' into feat/panel-v2.1-servic…
brunod-e Aug 17, 2026
d24ce8f
fix(dashboard): pin the monitored DAOs header across the whole deskto…
brunod-e Aug 17, 2026
28f31f7
Merge branch 'feat/panel-v2.1-table-scroll' into feat/panel-v2.1-case…
brunod-e Aug 17, 2026
64b48c1
fix(dashboard): stop the testimonial avatar repeating the author to s…
brunod-e Aug 17, 2026
80f22bd
Merge branch 'feat/panel-v2.1-case-studies' into feat/panel-v2.1-use-…
brunod-e Aug 17, 2026
881dd44
Merge branch 'feat/panel-v2.1-use-it-now' into feat/panel-v2.1-servic…
brunod-e Aug 17, 2026
9a5948f
fix(dashboard): keep service dividers off wrapped line boundaries
brunod-e Aug 17, 2026
a53c9c7
fix(dashboard): pin the monitored DAOs header across the whole deskto…
brunod-e Aug 17, 2026
47e309b
Merge branch 'feat/panel-v2.1-table-scroll' into feat/panel-v2.1-case…
brunod-e Aug 17, 2026
e5bb40f
Merge branch 'feat/panel-v2.1-case-studies' into feat/panel-v2.1-use-…
brunod-e Aug 17, 2026
5db013d
Merge branch 'feat/panel-v2.1-use-it-now' into feat/panel-v2.1-servic…
brunod-e Aug 17, 2026
e388432
Merge remote-tracking branch 'origin/dev' into feat/panel-v2.1-servic…
brunod-e Aug 19, 2026
5afa811
Merge branch 'dev' into feat/panel-v2.1
brunod-e Aug 21, 2026
e76a9c5
fix(dashboard): break panel hero headline and standardize testimonial…
brunod-e Aug 24, 2026
f9b1da9
test(dashboard): update mobile panel smoke test for panel v2.1 hero h…
brunod-e Aug 24, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/panel-v21-hero.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@anticapture/dashboard": minor
---

Replace the Panel section header with the v2.1 hero: a judgment headline, a subhead explaining the Stage framework, a link to the framework docs, and a "Governance risk, right now" card showing Stage 0/1/2 as horizontal bars with per-stage hover detail. Retires the Treasury Monitoring and Delegated Supply History cards.
5 changes: 5 additions & 0 deletions .changeset/panel-v21-services-row.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@anticapture/dashboard": minor
---

Add the Blockful services row at the foot of the panel, listing the service lines with a link to the contact page.
5 changes: 5 additions & 0 deletions .changeset/panel-v21-table-scroll.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@anticapture/dashboard": minor
---

Show every monitored DAO on the panel: the homepage itself now scrolls and the table header stays pinned on desktop.
5 changes: 5 additions & 0 deletions .changeset/panel-v21-ticker.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@anticapture/dashboard": minor
---

Add the "Latest finding" ticker between the panel hero and the Monitored DAOs table. The strip shows the newest post on the blockful Paragraph publication, read server-side from its RSS feed and revalidated hourly, and links out to that post. If the feed is unreachable it falls back to the publication index, so the ticker always renders.
5 changes: 5 additions & 0 deletions .changeset/panel-v21-track-record.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@anticapture/dashboard": minor
---

Add the "Track record" section to the panel: three outcome-framed case cards linking to the case write-ups, plus a testimonial carousel carrying the verified quotes from the X mentions audit. `ClickableCard` now accepts an `href` so cards can render as links.
5 changes: 5 additions & 0 deletions .changeset/panel-v21-use-it-now.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@anticapture/dashboard": minor
---

Add the "Use it now" section to the panel and an explainer strip above the channel cards on the alerts page.
4 changes: 4 additions & 0 deletions apps/dashboard/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,11 @@
--font-mono:
"Roboto Mono", ui-monospace, SFMono-Regular, "Menlo", "Monaco", "Consolas",
"Liberation Mono", "Courier New", monospace;
/* The alternative (mono) styles track at 6% of their size. CSS letter-spacing
* takes no percentages, so each size gets its resolved value: wider is the
* 12px step (--text-alternative-xs), alternative-sm the 13px one. */
--tracking-wider: 0.72px;
--tracking-alternative-sm: 0.78px;
--animate-accordion-down: accordion-down 0.2s ease-out;
--animate-accordion-up: accordion-up 0.2s ease-out;
--animate-scroll-left: scroll-left 20s linear infinite;
Expand Down
6 changes: 3 additions & 3 deletions apps/dashboard/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,12 +51,12 @@ export default function Home() {
<div className="bg-surface-background dark flex h-screen overflow-hidden">
<JsonLd data={softwareApplicationSchema} />
<HeaderSidebar />
<main className="flex-1 overflow-auto lg:flex lg:min-h-0 lg:flex-col lg:overflow-hidden">
<main className="flex-1 overflow-auto">
<div className="lg:hidden">
<HeaderMobile className="fixed! top-0" />
</div>
<div className="flex min-h-screen w-full flex-col items-center lg:h-full lg:min-h-0 lg:flex-1">
<div className="w-full flex-1 lg:flex lg:min-h-0 lg:flex-col">
<div className="flex min-h-screen w-full flex-col items-center">
<div className="w-full flex-1">
<PanelSection />
</div>
<Footer />
Expand Down
10 changes: 7 additions & 3 deletions apps/dashboard/e2e/mobile-smoke.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,13 @@ test.describe("Mobile smoke tests", () => {

test("Panel (/) renders heading on mobile", async ({ goto, page }) => {
await goto("/");
await expect(page.locator("h4").filter({ hasText: "Panel" })).toBeVisible({
timeout: 15_000,
});
// Panel v2.1 dropped the "Panel" section title; the hero h1 is the page
// heading on every viewport.
await expect(
page
.locator("h1")
.filter({ hasText: "See which DAOs could be captured" }),
).toBeVisible({ timeout: 15_000 });
});

test("DAO Overview (/ens) renders on mobile", async ({ goto, page }) => {
Expand Down
88 changes: 81 additions & 7 deletions apps/dashboard/e2e/panel.spec.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,22 @@
import { test, expect } from "./fixtures";

/* Desktop width, but shorter than the table: the layout-sensitive tests below
* all need the page to scroll vertically past the table. */
const SHORT_DESKTOP_VIEWPORT = { width: 1920, height: 640 };

test.describe("Panel page", () => {
test(
"renders Panel heading and description",
"renders the panel hero heading and description",
{ tag: "@smoke" },
async ({ goto, page }) => {
await goto("/");
await expect(
page.locator("h4").filter({ hasText: "Panel" }),
page
.locator("h1")
.filter({ hasText: "See which DAOs could be captured" }),
).toBeVisible();
await expect(
page.locator("text=Check governance security across DAOs"),
page.locator("text=Live governance-security risk for every DAO"),
).toBeVisible();
},
);
Expand Down Expand Up @@ -61,7 +67,7 @@ test.describe("Panel page", () => {
goto,
page,
}) => {
await page.setViewportSize({ width: 1920, height: 640 });
await page.setViewportSize(SHORT_DESKTOP_VIEWPORT);
await page.route("**/api/user/api/auth/get-session", (route) =>
route.fulfill({
status: 200,
Expand All @@ -82,15 +88,83 @@ test.describe("Panel page", () => {
expect(tableBox).not.toBeNull();
expect(footerBox).not.toBeNull();
expect(tableBox!.y + tableBox!.height).toBeLessThanOrEqual(footerBox!.y);

// The table is no longer an inner scroll box: every row lays out at full
// height, past the bottom of this viewport, and `main` is what scrolls.
await expect
.poll(() => tableContainer.evaluate((element) => element.clientHeight))
.toBeGreaterThan(SHORT_DESKTOP_VIEWPORT.height);
await expect
.poll(() =>
tableContainer.evaluate(
(element) => element.scrollHeight > element.clientHeight,
),
page
.locator("main")
.evaluate((element) => element.scrollHeight > element.clientHeight),
)
.toBe(true);
});

// 1024 exercises the lg band, where the sidebar is up but headers may
// wrap; 1920 exercises the roomy layout. The header must pin in both.
for (const width of [1024, SHORT_DESKTOP_VIEWPORT.width]) {
test(`keeps the table header pinned while the ${width}px desktop page scrolls`, async ({
goto,
page,
}) => {
await page.setViewportSize({
width,
height: SHORT_DESKTOP_VIEWPORT.height,
});
await page.route("**/api/user/api/auth/get-session", (route) =>
route.fulfill({
status: 200,
contentType: "application/json",
body: "null",
}),
);
await goto("/");

await expect(page.locator("table thead").first()).toBeVisible({
timeout: 15_000,
});

// Metric cells stream in after load and keep growing the page, so the
// scroll, its precondition, and the header measurement all happen inside
// one polled evaluation: park the table top 150px above the scrollport
// of `main` (the page scroller) β€” never "scroll to bottom", sections
// after the table would push it entirely off screen β€” then read where
// the sticky header rests.
const positions = () =>
page.locator("main").evaluate((main) => {
const table = main.querySelector("table");
const thead = table?.querySelector("thead");
if (!table || !thead)
return { tableTop: NaN, theadTop: NaN, mainOverflowX: NaN };
main.scrollTop +=
table.getBoundingClientRect().top -
main.getBoundingClientRect().top +
150;
return {
tableTop: table.getBoundingClientRect().top,
theadTop: thead.getBoundingClientRect().top,
mainOverflowX: main.scrollWidth - main.clientWidth,
};
});

await expect
.poll(async () => (await positions()).tableTop, { timeout: 15_000 })
.toBeLessThan(0);

const { tableTop, theadTop, mainOverflowX } = await positions();
expect(tableTop).toBeLessThan(0);
// Pinned means resting at the top of the scrollport (sticky -top-px).
expect(theadTop).toBeGreaterThanOrEqual(-2);
expect(theadTop).toBeLessThanOrEqual(2);
// The un-scrollported table must not leak a page-level horizontal
// scrollbar; column headers wrap below xl to keep this true.
expect(mainOverflowX).toBe(0);
});
}

test("sortable column headers respond to clicks", async ({ goto, page }) => {
await goto("/");
await expect(page.locator("table").first()).toBeVisible({
Expand Down
12 changes: 12 additions & 0 deletions apps/dashboard/features/alerts/AlertsSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,18 @@ export const AlertsSection = () => {
className="bg-surface-background! lg:mt-0! border-b-0!"
>
<div className="flex flex-col gap-2">
<div className="bg-surface-default flex flex-col gap-1 p-4">
{/* Not a heading: the section title above renders as h4, so any
* heading here would invert the document outline. */}
<p className="text-primary text-alternative-xs font-mono font-medium uppercase leading-4 tracking-wider">
What you get pinged about
</p>
<p className="text-secondary text-sm font-normal leading-5">
New proposals on-chain or Snapshot, vote reminders, delegation
shifts, and results for the DAOs you track. You choose the triggers;
pick a channel below to start.
</p>
</div>
<div className="grid grid-cols-1 gap-3 py-5 lg:grid-cols-3">
{ALERTS_ITEMS.map((alert: AlertItem) => (
<AlertCard
Expand Down
62 changes: 23 additions & 39 deletions apps/dashboard/features/panel/PanelSection.tsx
Original file line number Diff line number Diff line change
@@ -1,59 +1,43 @@
import { BarChart4 } from "lucide-react";

import {
PanelTable,
DelegatedSupplyHistory,
DaoProtectionLevels,
TreasuryMonitoring,
} from "@/features/panel/components";
import { TheSectionLayout } from "@/shared/components";
import { Carousel } from "@/shared/components/design-system/carousel/Carousel";
import { DividerDefault } from "@/shared/components/design-system/divider/DividerDefault";
import { LatestFindingTicker } from "@/features/panel/components/LatestFindingTicker";
import { PanelHero } from "@/features/panel/components/PanelHero";
import { PanelTable } from "@/features/panel/components/PanelTable";
import { ServicesRow } from "@/features/panel/components/ServicesRow";
import { TrackRecordSection } from "@/features/panel/components/TrackRecordSection";
import { UseItNowSection } from "@/features/panel/components/UseItNowSection";
import {
SubSection,
SubSectionsContainer,
} from "@/shared/components/design-system/section";
import { PAGES_CONSTANTS } from "@/shared/constants/pages-constants";

export const PanelSection = () => {
return (
<TheSectionLayout
title={PAGES_CONSTANTS.panel.title}
icon={<BarChart4 className="section-layout-icon" />}
description={PAGES_CONSTANTS.panel.description}
className="mt-12 lg:mt-0 lg:min-h-0"
>
<div className="flex flex-col gap-8 lg:h-full lg:min-h-0 lg:flex-1 lg:gap-2">
<div className="lg:hidden">
<Carousel
slides={[
<DaoProtectionLevels key="dao-protection-levels" />,
<TreasuryMonitoring key="treasury-monitoring" />,
<DelegatedSupplyHistory key="delegated-supply-history" />,
]}
/>
</div>
<div className="hidden gap-2 lg:grid lg:grid-cols-3">
<DaoProtectionLevels />
<TreasuryMonitoring />
<DelegatedSupplyHistory />
</div>
// Figma spaces the three blocks 32px apart and everything inside them 8px
// apart, so the inner wrappers hold the tight gap while the page holds the
// section rhythm.
<div className="mt-12 flex w-full flex-col gap-5 px-4 py-5 lg:mt-0 lg:gap-8 lg:p-5">
<div className="flex flex-col gap-5 lg:gap-2">
<PanelHero />

<div className="block lg:hidden">
<DividerDefault isHorizontal />
</div>
<LatestFindingTicker />

<SubSectionsContainer className="gap-3 lg:min-h-0 lg:flex-1">
<SubSectionsContainer className="gap-3">
<SubSection
className="gap-0"
subsectionTitle={"Monitored DAOs"}
dateRange=""
contentClassName="lg:flex lg:flex-col lg:flex-1 lg:min-h-0"
>
<PanelTable />
</SubSection>
</SubSectionsContainer>
</div>
</TheSectionLayout>

<TrackRecordSection />

<div className="flex flex-col gap-5 lg:gap-2">
<UseItNowSection />

<ServicesRow />
</div>
</div>
);
};
Loading
Loading