docs: add /progress page with live stats cards and update navigation and component imports

This commit is contained in:
Prad Nukala committed 2026-10-13 09:16:30 -04:00
1 parent cb4a03b6fa
commit 13734e2e27
11 files changed
+88 -57

No files matched your search

+8 -6
View File
@@ -127,10 +127,11 @@ export default defineConfig({
llmsTxt: true, llmsTxt: true,
mcp: { enabled: false }, mcp: { enabled: false },
}, },
// The only content source. `/changelog` and `/appendix` are custom pages // The only content source. `/changelog`, `/appendix` and `/progress` are
// (pages/*.astro): `/changelog` reads the GitHub releases itself — one // custom pages (pages/*.astro): `/changelog` reads the GitHub releases
// timeline, no per-release pages — and `/appendix` is one glossary page // itself — one timeline, no per-release pages — `/appendix` is one glossary
// built from appendix.ts, so neither enters the sidebar tree or prev/next. // page built from appendix.ts, and `/progress` is the live stats cards, so
// none enters the sidebar tree or prev/next.
content: { content: {
sources: [filesystem({ root: "content" })], sources: [filesystem({ root: "content" })],
}, },
@@ -142,10 +143,11 @@ export default defineConfig({
sidebar: { sidebar: {
display: "flat", // "flat" | "group" | "page" display: "flat", // "flat" | "group" | "page"
}, },
// The two custom pages (pages/*.astro) as header tabs. Neither is a // The custom pages (pages/*.astro) as header tabs. None is a content
// content folder, so the tabs scope nothing out of the sidebar tree. // folder, so the tabs scope nothing out of the sidebar tree.
tabs: [ tabs: [
{ label: "Changelog", path: "/changelog" }, { label: "Changelog", path: "/changelog" },
{ label: "Progress", path: "/progress" },
{ label: "Appendix", path: "/appendix" }, { label: "Appendix", path: "/appendix" },
], ],
}, },
@@ -5,12 +5,10 @@
* hover. A row links to the problem's docs page when one exists: the * hover. A row links to the problem's docs page when one exists: the
* sync-generated short links (`redirects.ts`) are exactly the pages built. * sync-generated short links (`redirects.ts`) are exactly the pages built.
*/ */
import { StatsPending } from "../components/progress/shared"; import { Card, CardContent, CardHeader, CardTitle } from "../ui/card";
import { Card, CardContent, CardHeader, CardTitle } from "../components/ui/card"; import { useStats, utcDate } from "../../lib/stats";
import { useStats, utcDate } from "../lib/stats"; import redirects from "../../redirects";
import redirects from "../redirects"; import { StatsPending } from "./shared";
export const client = "load";
const PAGES = new Map(redirects.map((r) => [r.from, r.to])); const PAGES = new Map(redirects.map((r) => [r.from, r.to]));
@@ -8,13 +8,11 @@
*/ */
import { useState } from "react"; import { useState } from "react";
import { Pie, PieChart } from "recharts"; import { Pie, PieChart } from "recharts";
import { StatsPending } from "../components/progress/shared"; import { Card, CardContent } from "../ui/card";
import { Card, CardContent } from "../components/ui/card"; import { type ChartConfig, ChartContainer } from "../ui/chart";
import { type ChartConfig, ChartContainer } from "../components/ui/chart"; import { DIFFICULTIES, type Difficulty, useStats } from "../../lib/stats";
import { DIFFICULTIES, type Difficulty, useStats } from "../lib/stats"; import { cn } from "../../lib/utils";
import { cn } from "../lib/utils"; import { StatsPending } from "./shared";
export const client = "load";
const chartConfig = { const chartConfig = {
easy: { label: "Easy", color: "var(--chart-1)" }, easy: { label: "Easy", color: "var(--chart-1)" },
@@ -9,13 +9,11 @@
*/ */
import { useState } from "react"; import { useState } from "react";
import { Bar, BarChart, CartesianGrid, XAxis, YAxis } from "recharts"; import { Bar, BarChart, CartesianGrid, XAxis, YAxis } from "recharts";
import { StatsPending } from "../components/progress/shared"; import { Card, CardAction, CardContent, CardFooter, CardHeader } from "../ui/card";
import { Card, CardAction, CardContent, CardFooter, CardHeader } from "../components/ui/card"; import { type ChartConfig, ChartContainer, ChartTooltip, ChartTooltipContent } from "../ui/chart";
import { type ChartConfig, ChartContainer, ChartTooltip, ChartTooltipContent } from "../components/ui/chart"; import { ToggleGroup, ToggleGroupItem } from "../ui/toggle-group";
import { ToggleGroup, ToggleGroupItem } from "../components/ui/toggle-group"; import { DIFFICULTIES, type Day, type Difficulty, useStats, utcDate } from "../../lib/stats";
import { DIFFICULTIES, type Day, type Difficulty, useStats, utcDate } from "../lib/stats"; import { StatsPending } from "./shared";
export const client = "load";
const chartConfig = { const chartConfig = {
easy: { label: "Easy", color: "var(--chart-1)" }, easy: { label: "Easy", color: "var(--chart-1)" },
@@ -8,11 +8,9 @@
* 4) and rows start on Monday like it, so the three heatmaps read the same. * 4) and rows start on Monday like it, so the three heatmaps read the same.
* Drawn as SVG in a viewBox, so it scales to its column instead of scrolling. * Drawn as SVG in a viewBox, so it scales to its column instead of scrolling.
*/ */
import { StatsPending, TipRow, useHoverTip } from "../components/progress/shared"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "../ui/card";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "../components/ui/card"; import { type Day, useStats, utcDate } from "../../lib/stats";
import { type Day, useStats, utcDate } from "../lib/stats"; import { StatsPending, TipRow, useHoverTip } from "./shared";
export const client = "load";
const MONTHS = 6; const MONTHS = 6;
const CELL = 10; const CELL = 10;
@@ -9,12 +9,10 @@
*/ */
import { packEnclose, packSiblings } from "d3-hierarchy"; import { packEnclose, packSiblings } from "d3-hierarchy";
import { useId } from "react"; import { useId } from "react";
import { StatsPending, TipRow, useHoverTip } from "../components/progress/shared"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "../ui/card";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "../components/ui/card"; import { type Stats, useStats } from "../../lib/stats";
import { type Stats, useStats } from "../lib/stats"; import { cn } from "../../lib/utils";
import { cn } from "../lib/utils"; import { StatsPending, TipRow, useHoverTip } from "./shared";
export const client = "load";
const PAD = 2; // gap between bubbles, in layout units const PAD = 2; // gap between bubbles, in layout units
const UNIT = 12; // radius of a one-problem bubble const UNIT = 12; // radius of a one-problem bubble
+1 -1
View File
@@ -1,5 +1,5 @@
/** /**
* What the four progress islands share besides the data: the not-ready body * What the progress cards share besides the data: the not-ready body
* (a skeleton while /api/stats loads, one muted line if it fails) and the * (a skeleton while /api/stats loads, one muted line if it fails) and the
* hover tooltip for the two charts drawn as plain SVG (the heatmap calendar * hover tooltip for the two charts drawn as plain SVG (the heatmap calendar
* and the topic bubbles — Recharts has neither layout). The tooltip wears the * and the topic bubbles — Recharts has neither layout). The tooltip wears the
-15
View File
@@ -1,15 +0,0 @@
---
title: Progress
description: Live campaign progress — solved by difficulty, recent attempts, the year by difficulty, topic coverage, and the most recently solved problems, fetched from the SRS Worker.
icon: loader
---
<div className="not-prose flex flex-col gap-6">
<SolvedRing />
<div className="grid gap-6 md:grid-cols-2">
<SubmissionHeatmap />
<SubmissionChart />
</div>
<TopicBubbles />
<RecentSolves />
</div>
+3 -1
View File
@@ -1,7 +1,9 @@
--- ---
title: System Design title: System Design
description: "Notes on System Design Interview — An Insider's Guide (Volumes 1 and 2): how to scope a problem, size it, and pick the parts that carry the load." description: "Notes on System Design Interview — An Insider's Guide (Volumes 1 and 2): how to scope a problem, size it, and pick the parts that carry the load."
icon: network icon: play
sidebar:
label: 'Getting Started'
--- ---
These notes follow Alex Xu's [System Design Interview — An Insider's Guide](https://bytebytego.com/courses/system-design-interview), [Volumes 1 and 2](https://www.goodreads.com/book/show/54109255-system-design-interview-an-insider-s-guide). These notes follow Alex Xu's [System Design Interview — An Insider's Guide](https://bytebytego.com/courses/system-design-interview), [Volumes 1 and 2](https://www.goodreads.com/book/show/54109255-system-design-interview-an-insider-s-guide).
+3 -3
View File
@@ -3,9 +3,9 @@
* browser so `blume build` never depends on the API being up. API_BASE is the * browser so `blume build` never depends on the API being up. API_BASE is the
* deployed Worker — do not inline it anywhere else. * deployed Worker — do not inline it anywhere else.
* *
* Four islands read this (SolvedRing, SubmissionHeatmap, RecentSolves, * The five cards on pages/progress.astro read this (components/progress/).
* TopicBubbles). Each hydrates on its own, but they share this module, so the * Each hydrates on its own, but they share this module, so the one in-flight
* one in-flight request is shared too: the page asks the Worker once. * request is shared too: the page asks the Worker once.
* *
* Invariants: a malformed body is an error state, never a throw; the first * Invariants: a malformed body is an error state, never a throw; the first
* frame is always the loading state, so server and client render identically. * frame is always the loading state, so server and client render identically.
+52
View File
@@ -0,0 +1,52 @@
---
/**
* `/progress`: the live campaign dashboard — five LeetCode-style React cards
* over GET /api/stats (lib/stats.ts), hydrated on load. A custom page like
* `/changelog` and `/appendix`, linked from the header's `navigation.tabs`,
* so it enters neither the sidebar tree nor prev/next. The cards live in
* components/progress/, not islands/: they are imported here, never used from
* MDX, so Blume has no reason to register them as global MDX components.
*/
import PageLayout from "blume/components/layout/PageLayout.astro";
import data from "blume:data";
import RecentSolves from "../components/progress/RecentSolves";
import SolvedRing from "../components/progress/SolvedRing";
import SubmissionChart from "../components/progress/SubmissionChart";
import SubmissionHeatmap from "../components/progress/SubmissionHeatmap";
import TopicBubbles from "../components/progress/TopicBubbles";
const { config } = data;
const description =
"Live campaign progress — solved by difficulty, recent attempts, the year by difficulty, topic coverage, and the most recently solved problems, fetched from the SRS Worker.";
---
<PageLayout
site={{ title: config.title, description: config.description }}
logo={config.logo}
favicon={config.favicon}
banner={config.banner}
analytics={config.analytics}
navigation={data.navigation}
fontCssVars={data.fontCssVars}
themeMode={config.theme.mode}
searchEnabled={config.search.enabled}
siteUrl={config.site}
ogEnabled={config.og.enabled}
structuredDataEnabled={config.structuredData}
page={{ title: `Progress - ${config.title}`, description, route: "/progress" }}
>
<section class="mx-auto w-full max-w-5xl px-6 py-16">
<h1 class="m-0 text-4xl font-semibold tracking-tight text-foreground">Progress</h1>
<p class="mt-3 max-w-3xl text-lg text-muted-foreground">{description}</p>
<div class="mt-10 flex flex-col gap-6">
<SolvedRing client:load />
<div class="grid gap-6 md:grid-cols-2">
<SubmissionHeatmap client:load />
<SubmissionChart client:load />
</div>
<TopicBubbles client:load />
<RecentSolves client:load />
</div>
</section>
</PageLayout>