Skip to content

feat(dashboard): BRICKS analytics dashboard redesign#1

Merged
mattlgroff merged 3 commits into
masterfrom
feat/dashboard-redesign
Apr 17, 2026
Merged

feat(dashboard): BRICKS analytics dashboard redesign#1
mattlgroff merged 3 commits into
masterfrom
feat/dashboard-redesign

Conversation

@jdcampos1
Copy link
Copy Markdown
Collaborator

Summary

  • Replaces the v1 scaffold with a production-ready 9-section dashboard (Overview → Views → Platforms → Formats → Top Content → Members → Collabs → Cross-post → Trajectory)
  • Adds app sidebar + collapsible ToC, ⌘K command palette, timeframe dock, and compact/comfortable density toggle (localStorage-persisted)
  • Adds docs/data-requirements.md mapping each UI section to the upstream public-page signals Matt's collector will need

What changed

  • New sections: content-type (formats) performance, cross-posting matrix, collaboration hub with solo-vs-collab lift
  • Removed: Est. Brand Value KPI and per-platform Est. Value — not meaningfully sourceable from public signals
  • Removed: member-row hover preview (duplicated data already on the row)
  • New pages: /content, /members, /reports coming-soon stubs so sidebar links resolve
  • Spec + data doc: docs/data-requirements.md breaks down every chart into the SQL/collector shape needed

Test plan

  • npm run dev — all 9 sections render and scroll-link from sidebar ToC
  • ⌘K opens command palette; arrow nav + enter jumps to section / member / timeframe
  • Timeframe dock cycles 7D/30D/90D/YTD/All and numbers update across sections
  • Density toggle (comfortable ↔ compact) persists across reloads
  • Sidebar collapse state persists across reloads (⌘\)
  • npx tsc --noEmit — zero errors
  • npx eslint . — zero errors

🤖 Generated with Claude Code

jdcampos1 and others added 3 commits April 16, 2026 20:13
Initial 8-section dashboard implementation with mock data. This serves
as a working baseline and visual reference; a redesign is specified in
docs/superpowers/specs/2026-04-16-bricks-analytics-dashboard-design.md
and will replace much of this — notably cutting Audience Profile
(demographics aren't scrapable), adding navigation + timeframe
filters, renaming Campaign Value to Est. Brand Value, and introducing
cross-posting matrix + content type performance sections.

Also guards instrumentation.ts when DATABASE_URL is unset so the dev
server boots without Neon configured.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Investor-pitch framing, scraper-only data constraint, 9 sections
(Audience Profile cut; Cross-posting Matrix and Content Type
Performance added). Rationale captured for every decision so future
revisits can tell what's load-bearing vs what's a preference.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Replaces the scaffold mockup with a production-ready dashboard:

- App sidebar with collapsible ToC and persisted state
- Command palette (⌘K) for nav, timeframe, and member jumps
- 9 sections reordered for narrative flow: Overview → Views → Platforms
  → Formats → Top Content → Members → Collabs → Cross-post → Trajectory
- Timeframe dock (7D/30D/90D/YTD/All) and compact/comfortable density
  toggle, both persisted to localStorage via lazy initializers
- New sections: content-type performance, cross-posting matrix,
  collaboration hub with solo-vs-collab lift
- Removed Est. Brand Value KPI and per-platform Est. Value (not
  meaningfully sourceable from public signals)
- Removed member-row hover preview (duplicated row data)

Adds docs/data-requirements.md mapping each UI surface to the upstream
signals Matt's collector will need (public-page only, no creator access).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@mattlgroff
Copy link
Copy Markdown
Collaborator

@codex review

@chatgpt-codex-connector
Copy link
Copy Markdown

To use Codex here, create a Codex account and connect to github.

@mattlgroff mattlgroff merged commit 8c21608 into master Apr 17, 2026
4 checks passed
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR replaces the existing BRICKS analytics dashboard scaffold with a multi-section, production-style dashboard experience (sidebar + ToC, command palette, timeframe dock, density toggle), and adds supporting product/spec documentation describing the required upstream scraped data signals.

Changes:

  • Introduces a new 9-section dashboard page (/dashboard) with multiple new visualization components (views-over-time, platform cards, leaderboard, collaboration graph, cross-post matrix, trajectory, etc.).
  • Adds global UI controls and shell features: sidebar + collapsible ToC, ⌘K command palette, timeframe selector dock, and density toggle (localStorage persisted).
  • Adds documentation: a design spec and a section-by-section data requirements mapping for the future scraper/collector pipeline.

Reviewed changes

Copilot reviewed 28 out of 30 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
package-lock.json Lockfile updates from dependency install (adds peer flags to multiple entries).
lib/dashboard/data.ts Adds a comprehensive mock data model backing all dashboard sections.
instrumentation.ts Skips Drizzle migrations when DATABASE_URL is missing in Node runtime.
docs/superpowers/specs/2026-04-16-bricks-analytics-dashboard-design.md New design spec describing goals, constraints, and section behavior.
docs/data-requirements.md New data contract mapping each UI section to scraper/DB signals and cadence.
components/dashboard/views-over-time.tsx New stacked-area views chart with hover/tooltip, prior overlay, and target line.
components/dashboard/top-content.tsx New “Top Content” hero card grid with velocity and outbound links.
components/dashboard/top-bar.tsx New sticky top bar showing active section and density toggle.
components/dashboard/timeframe-dock.tsx New fixed-position timeframe selector (tablist UI).
components/dashboard/timeframe-context.tsx New React context for global timeframe state.
components/dashboard/section.tsx New reusable section wrapper with copy-link/download/expand actions.
components/dashboard/scoreboard.tsx New scoreboard grid driven by timeframe-based mock scorecards.
components/dashboard/platform-icon.tsx New inline SVG icons per platform (YT/TT/IG/FB).
components/dashboard/platform-cards.tsx New 2×2 platform breakdown cards with sparkline visualization.
components/dashboard/member-leaderboard.tsx New member leaderboard table with views/followers toggle and density support.
components/dashboard/growth-trajectory.tsx New trajectory chart with baseline/actual/projected series and baseline table.
components/dashboard/density-context.tsx New React context for comfortable/compact density (localStorage persisted).
components/dashboard/cross-posting-matrix.tsx New cross-posting matrix table with incoming/outgoing summaries.
components/dashboard/content-type-performance.tsx New content-type mix bars with avg views and engagement per format.
components/dashboard/command-palette.tsx New ⌘K command palette to jump to sections/members/timeframes.
components/dashboard/coming-soon.tsx New shared “Coming Soon” shell for stub pages (sidebar + providers).
components/dashboard/collaboration.tsx New collaboration hero stats + network graph with hover card.
components/dashboard/app-sidebar.tsx New sidebar with workspace nav, collapsible ToC, and persisted collapse state.
components/dashboard/active-section-context.tsx New scroll-based active-section tracking for sidebar/top bar highlighting.
app/reports/page.tsx Adds /reports coming-soon stub page.
app/members/page.tsx Adds /members coming-soon stub page.
app/dashboard/page.tsx Adds the new main dashboard page wiring all new sections/components together.
app/content/page.tsx Adds /content coming-soon stub page.
.gitignore Fixes tmp/ ignore entry and adds .playwright-mcp/ artifacts.
.claude/launch.json Adds a Claude launch config for running npm run dev on port 3000.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +31 to +37
<p className="font-mono text-[10px] tracking-[0.22em] text-white/45 uppercase">
Cross-post Matrix · {tfPhrase}
</p>
<p className="mt-1 text-xs text-white/50">
Green cell = member in that row cross-posted to the column
member&apos;s account at least once this week.
</p>
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

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

This section header/tooltip copy says cells indicate cross-posting "this week", but the UI also displays the selected timeframe via timeframePhrase(timeframe) (which could be 30D/90D/YTD/All-time). Either tie the matrix data + copy to the active timeframe, or make the section explicitly fixed to a weekly window and remove the timeframe-dependent label to avoid misleading users.

Copilot uses AI. Check for mistakes.
Comment on lines +51 to +53
const { timeframe } = useTimeframe();
const viewsLabel = timeframeViewsLabel(timeframe);
return (
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

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

viewsLabel is derived from the selected timeframe (timeframeViewsLabel(timeframe)), but the displayed p.views values come from the static platformCards mock data. This makes the UI misleading when switching timeframes (label changes, numbers don’t). Either make platformCards timeframe-aware or keep the label fixed to the window the mock values represent.

Copilot uses AI. Check for mistakes.
Comment on lines +30 to +38
<svg viewBox={`0 0 ${w} ${h}`} className="h-9 w-full">
<defs>
<linearGradient id={`spark-${color}`} x1="0" x2="0" y1="0" y2="1">
<stop offset="0%" stopColor={color} stopOpacity="0.35" />
<stop offset="100%" stopColor={color} stopOpacity="0" />
</linearGradient>
</defs>
<path d={area} fill={`url(#spark-${color})`} />
<polyline
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

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

Sparkline uses the raw hex color (e.g. #FF0000) in the SVG linearGradient id (spark-${color}), which makes the id contain # and produces an invalid fragment reference in url(#spark-...). This can break the gradient fill (and potentially collide across cards). Use a sanitized id (e.g. based on platform key or a hash) rather than the color string.

Copilot uses AI. Check for mistakes.
Comment on lines +156 to +157
function onKeyDown(e: React.KeyboardEvent) {
if (e.key === "Escape") {
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

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

onKeyDown is typed as React.KeyboardEvent, but this file doesn't import the React namespace (and with the modern JSX runtime it won't be in scope automatically). This will cause a TS error (Cannot find namespace 'React'). Import the appropriate type from react (e.g. type KeyboardEvent as ReactKeyboardEvent) or change the annotation to use the imported types.

Copilot uses AI. Check for mistakes.
Comment on lines +48 to +59
const maxTotal = Math.max(
...s.map((x) => x.total),
...priorTotals,
);
const rounded = Math.ceil(maxTotal / 250_000) * 250_000;
return {
stacks: s,
prior: priorTotals,
yMax: rounded,
days: current,
targetDaily: 3_333_333, // 100M/mo pace
};
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

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

yMax is derived only from the current/prior data max, but the chart always draws a targetDaily line. With the provided mock data, targetDaily (3.33M/day) exceeds yMax, so the target line/text will render outside the plot area (negative y) and may not be visible. Include targetDaily when computing maxTotal (or clamp it) so the target reference line is always in-range.

Copilot uses AI. Check for mistakes.
Comment on lines +23 to +34
return (
<svg viewBox="0 0 24 24" className={className}>
<defs>
<linearGradient id="ig-grad" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stopColor="#F58529" />
<stop offset="50%" stopColor="#DD2A7B" />
<stop offset="100%" stopColor="#8134AF" />
</linearGradient>
</defs>
<path
fill="url(#ig-grad)"
d="M12 2.2c3.2 0 3.6 0 4.8.1 1.2.1 1.8.3 2.3.4.6.2 1 .5 1.5 1 .5.5.8.9 1 1.5.1.5.3 1.1.4 2.3.1 1.2.1 1.6.1 4.8s0 3.6-.1 4.8c-.1 1.2-.3 1.8-.4 2.3-.2.6-.5 1-1 1.5-.5.5-.9.8-1.5 1-.5.1-1.1.3-2.3.4-1.2.1-1.6.1-4.8.1s-3.6 0-4.8-.1c-1.2-.1-1.8-.3-2.3-.4a4 4 0 0 1-1.5-1 4 4 0 0 1-1-1.5c-.1-.5-.3-1.1-.4-2.3C2.2 15.6 2.2 15.2 2.2 12s0-3.6.1-4.8c.1-1.2.3-1.8.4-2.3.2-.6.5-1 1-1.5.5-.5.9-.8 1.5-1 .5-.1 1.1-.3 2.3-.4C8.4 2.2 8.8 2.2 12 2.2zm0 5.3a4.5 4.5 0 1 0 0 9 4.5 4.5 0 0 0 0-9zm5.8-.3a1 1 0 1 0 0-2 1 1 0 0 0 0 2zM12 9.3a2.7 2.7 0 1 1 0 5.4 2.7 2.7 0 0 1 0-5.4z"
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

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

The Instagram SVG uses a hard-coded gradient id (id="ig-grad"). When PlatformIcon is rendered multiple times on the same page, duplicate ids can cause the gradient reference (url(#ig-grad)) to resolve unpredictably. Use a per-instance unique id (e.g. via useId() or an idSuffix prop) and reference that in the fill URL.

Copilot uses AI. Check for mistakes.
Comment on lines +74 to +90
export function MemberLeaderboard() {
const [mode, setMode] = useState<Mode>("views");
const { timeframe } = useTimeframe();
const { density } = useDensity();
const viewsLabel = timeframeViewsLabel(timeframe);
const compact = density === "compact";
const cellPad = compact ? "px-3 py-1.5" : "px-4 py-3";
const avatarSize = compact ? 24 : 32;

const ranked = useMemo(() => {
const sorted = [...members].sort((a, b) =>
mode === "views"
? b.views30d - a.views30d
: b.followers - a.followers,
);
return sorted.map((m, i) => ({ ...m, rank: i + 1 }));
}, [mode]);
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

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

The leaderboard label adapts to the selected timeframe (timeframeViewsLabel(timeframe)), but the ranking/values are always based on views30d (and growth30d). This means switching to 7D/YTD/ALL will display a mismatched label while still showing 30D numbers. Either (1) drive the metric selection from timeframe (e.g. use views7d for 7D) or (2) keep the label fixed to 30D until other timeframe rollups exist.

Copilot uses AI. Check for mistakes.
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.

3 participants