Skip to content

Commit 2ad0d72

Browse files
refactor: clean up catalog→plots rename across frontend, backend, docs
Revert retro UI additions on plots page (man-page header, comment dividers, statusline, $-prompt search, box-drawing card headers, cat-style code reveal). Keep the catalog→plots route rename and make it fully consistent: - Backend: /seo-proxy/catalog → /seo-proxy/plots, /og/catalog.png → /og/plots.png, sitemap now includes /plots and /specs, analytics page key catalog → plots; new /seo-proxy/specs endpoint. - Frontend: rename CatalogPage.tsx → PlotsPage.tsx (and test), CATALOG_SIZES → SPECS_SIZES (now correctly reflects its only caller, SpecsListPage), catalog_rotate event → plot_rotate, CatalogSpec → SpecListItem, catalogSpecs → specList, catalogRef → plotsRef. - SpecsListPage was incorrectly self-identifying as /plots after the rename (title, h1, canonical, trackPageview all said plots). Fix to use /specs consistently. - Docs: update seo.md, api.md, plausible.md, contributing.md. Brand-voice "catalogue" in brand.md / hero copy left intentionally. Frontend tests: 350/350 pass. Backend tests: 183/183 pass. tsc clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 4be4cc0 commit 2ad0d72

24 files changed

+135
-235
lines changed

api/analytics.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ def track_og_image(
148148
149149
Args:
150150
request: FastAPI request for headers
151-
page: Page type ('home', 'catalog', 'spec_overview', 'spec_detail')
151+
page: Page type ('home', 'plots', 'spec_overview', 'spec_detail')
152152
spec: Spec ID (optional)
153153
library: Library ID (optional)
154154
filters: Query params for filtered home page (e.g., {'lib': 'plotly', 'dom': 'statistics'})
@@ -160,8 +160,8 @@ def track_og_image(
160160
# Build URL based on page type
161161
if page == "home":
162162
url = "https://anyplot.ai/"
163-
elif page == "catalog":
164-
url = "https://anyplot.ai/catalog"
163+
elif page == "plots":
164+
url = "https://anyplot.ai/plots"
165165
elif spec is not None and library:
166166
url = f"https://anyplot.ai/python/{spec}/{library}"
167167
elif spec is not None:

api/routers/og_images.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,10 +50,10 @@ async def get_home_og_image(request: Request) -> Response:
5050
)
5151

5252

53-
@router.get("/catalog.png")
54-
async def get_catalog_og_image(request: Request) -> Response:
55-
"""OG image for catalog page with tracking."""
56-
track_og_image(request, page="catalog")
53+
@router.get("/plots.png")
54+
async def get_plots_og_image(request: Request) -> Response:
55+
"""OG image for plots page with tracking."""
56+
track_og_image(request, page="plots")
5757

5858
return Response(
5959
content=_get_static_og_image(), media_type="image/png", headers={"Cache-Control": "public, max-age=86400"}

api/routers/seo.py

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@ def _build_sitemap_xml(specs: list) -> str:
2828
'<?xml version="1.0" encoding="UTF-8"?>',
2929
'<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">',
3030
" <url><loc>https://anyplot.ai/</loc></url>",
31-
" <url><loc>https://anyplot.ai/catalog</loc></url>",
31+
" <url><loc>https://anyplot.ai/plots</loc></url>",
32+
" <url><loc>https://anyplot.ai/specs</loc></url>",
3233
" <url><loc>https://anyplot.ai/mcp</loc></url>",
3334
" <url><loc>https://anyplot.ai/legal</loc></url>",
3435
" <url><loc>https://anyplot.ai/stats</loc></url>",
@@ -83,7 +84,7 @@ async def _refresh_sitemap() -> str:
8384

8485
# Route through API for tracking (was: anyplot.ai/og-image.png)
8586
DEFAULT_HOME_IMAGE = "https://api.anyplot.ai/og/home.png"
86-
DEFAULT_CATALOG_IMAGE = "https://api.anyplot.ai/og/catalog.png"
87+
DEFAULT_PLOTS_IMAGE = "https://api.anyplot.ai/og/plots.png"
8788
DEFAULT_DESCRIPTION = "library-agnostic, ai-powered plotting."
8889

8990

@@ -103,7 +104,7 @@ async def get_sitemap(db: AsyncSession | None = Depends(optional_db)):
103104
"""
104105
Generate dynamic XML sitemap for SEO.
105106
106-
Includes root, catalog page, and all specs with implementations.
107+
Includes root, plots/specs pages, and all specs with implementations.
107108
"""
108109
if db is None:
109110
return Response(content=_STATIC_SITEMAP, media_type="application/xml")
@@ -143,15 +144,28 @@ async def seo_home(request: Request):
143144
)
144145

145146

146-
@router.get("/seo-proxy/catalog")
147-
async def seo_catalog():
148-
"""Bot-optimized catalog page with correct og:tags."""
147+
@router.get("/seo-proxy/plots")
148+
async def seo_plots():
149+
"""Bot-optimized plots page with correct og:tags."""
149150
return HTMLResponse(
150151
BOT_HTML_TEMPLATE.format(
151-
title="Catalog | anyplot.ai",
152-
description="Browse all Python plotting specifications alphabetically. Find matplotlib, seaborn, plotly, bokeh, altair examples.",
153-
image=DEFAULT_CATALOG_IMAGE,
154-
url="https://anyplot.ai/catalog",
152+
title="plots | anyplot.ai",
153+
description="Browse and filter Python visualization examples across 9 libraries: matplotlib, seaborn, plotly, bokeh, altair, plotnine, pygal, highcharts, lets-plot.",
154+
image=DEFAULT_PLOTS_IMAGE,
155+
url="https://anyplot.ai/plots",
156+
)
157+
)
158+
159+
160+
@router.get("/seo-proxy/specs")
161+
async def seo_specs():
162+
"""Bot-optimized specs page with correct og:tags."""
163+
return HTMLResponse(
164+
BOT_HTML_TEMPLATE.format(
165+
title="specs | anyplot.ai",
166+
description="Browse all Python plotting specifications alphabetically.",
167+
image=DEFAULT_PLOTS_IMAGE,
168+
url="https://anyplot.ai/specs",
155169
)
156170
)
157171

app/src/components/CodeHighlighter.tsx

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -50,23 +50,13 @@ export default function CodeHighlighter({ code }: CodeHighlighterProps) {
5050
<SyntaxHighlighter
5151
language="python"
5252
style={okabeItoDark}
53-
showLineNumbers
54-
lineNumberStyle={{
55-
color: '#4A4A44',
56-
fontSize: '0.75rem',
57-
minWidth: '2.5em',
58-
paddingRight: '1em',
59-
borderRight: '1px solid rgba(255,255,255,0.06)',
60-
marginRight: '1em',
61-
userSelect: 'none',
62-
}}
6353
customStyle={{
6454
margin: 0,
6555
padding: '28px 32px',
6656
fontSize: '0.85rem',
6757
fontFamily: typography.fontFamily,
6858
background: '#0E0E0C',
69-
borderRadius: '0 0 12px 12px',
59+
borderRadius: '12px',
7060
lineHeight: 1.7,
7161
}}
7262
>

app/src/components/FilterBar.tsx

Lines changed: 14 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -339,20 +339,19 @@ export function FilterBar({
339339
position: { xs: 'static', md: 'relative' },
340340
}}
341341
>
342-
{/* Editor statusline - absolute left (desktop only) */}
342+
{/* Progress counter - absolute left (desktop only) */}
343343
{!isMobile && currentTotal > 0 && (
344344
<Typography
345345
sx={{
346346
position: 'absolute',
347347
left: 0,
348348
fontFamily: typography.fontFamily,
349-
fontSize: '10px',
349+
fontSize: fontSize.sm,
350350
color: semanticColors.mutedText,
351351
whiteSpace: 'nowrap',
352-
letterSpacing: '0.04em',
353352
}}
354353
>
355-
plots:{currentTotal} showing:{displayedCount} · {scrollPercent}% · python
354+
{scrollPercent}% · {currentTotal}
356355
</Typography>
357356
)}
358357
{/* Toolbar actions - absolute right (desktop only) */}
@@ -451,33 +450,17 @@ export function FilterBar({
451450
'&:focus': isSearchExpanded ? {} : { outline: `2px solid ${colors.primary}`, outlineOffset: 2 },
452451
}}
453452
>
454-
{isSearchExpanded ? (
455-
<Box
456-
component="span"
453+
<Tooltip title={isSearchExpanded ? '' : 'search'}>
454+
<SearchIcon
455+
className="search-icon"
457456
sx={{
458-
fontFamily: typography.fontFamily,
459-
fontSize: fontSize.base,
460-
color: colors.primary,
461-
fontWeight: 700,
457+
color: colors.gray[500],
458+
fontSize: isSearchExpanded ? '1rem' : '1.25rem',
459+
transition: 'all 0.2s ease',
462460
flexShrink: 0,
463-
lineHeight: 1,
464461
}}
465-
>
466-
$
467-
</Box>
468-
) : (
469-
<Tooltip title="search">
470-
<SearchIcon
471-
className="search-icon"
472-
sx={{
473-
color: colors.gray[500],
474-
fontSize: '1.25rem',
475-
transition: 'all 0.2s ease',
476-
flexShrink: 0,
477-
}}
478-
/>
479-
</Tooltip>
480-
)}
462+
/>
463+
</Tooltip>
481464
<label htmlFor="filter-search" style={{ position: 'absolute', width: 1, height: 1, overflow: 'hidden', clip: 'rect(0,0,0,0)' }}>
482465
{selectedCategory ? `Search ${FILTER_LABELS[selectedCategory]}` : 'Search filters'}
483466
</label>
@@ -486,7 +469,7 @@ export function FilterBar({
486469
id="filter-search"
487470
name="filter-search"
488471
inputProps={{ 'aria-label': selectedCategory ? `Search ${FILTER_LABELS[selectedCategory]}` : 'Search filters' }}
489-
placeholder={selectedCategory ? FILTER_LABELS[selectedCategory] : 'grep plots/'}
472+
placeholder={selectedCategory ? FILTER_LABELS[selectedCategory] : ''}
490473
value={searchQuery}
491474
onChange={(e) => {
492475
setSearchQuery(e.target.value);
@@ -553,13 +536,12 @@ export function FilterBar({
553536
<Typography
554537
sx={{
555538
fontFamily: typography.fontFamily,
556-
fontSize: '10px',
539+
fontSize: fontSize.sm,
557540
color: semanticColors.mutedText,
558541
whiteSpace: 'nowrap',
559-
letterSpacing: '0.04em',
560542
}}
561543
>
562-
{currentTotal} plots · {scrollPercent}%
544+
{scrollPercent}% · {currentTotal}
563545
</Typography>
564546
) : (
565547
<Box />

app/src/components/ImageCard.tsx

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -152,24 +152,6 @@ export const ImageCard = memo(function ImageCard({
152152
},
153153
}}
154154
>
155-
{/* Box-drawing style header */}
156-
<Box sx={{
157-
fontFamily: typography.fontFamily,
158-
fontSize: '9px',
159-
color: 'var(--ink-muted)',
160-
opacity: 0.4,
161-
px: 1.5,
162-
py: 0.5,
163-
display: 'flex',
164-
justifyContent: 'space-between',
165-
letterSpacing: '0.02em',
166-
borderBottom: '1px solid var(--rule)',
167-
transition: 'opacity 0.2s',
168-
'.MuiCard-root:hover &': { opacity: 0.7 },
169-
}}>
170-
<span>{image.spec_id}</span>
171-
<span>{image.library}</span>
172-
</Box>
173155
<Box component="picture" sx={{ display: 'contents' }}>
174156
<source
175157
type="image/webp"

app/src/components/SpecTabs.tsx

Lines changed: 1 addition & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -346,30 +346,11 @@ export function SpecTabs({
346346
{copied ? <CheckIcon color="success" /> : <ContentCopyIcon fontSize="small" />}
347347
</IconButton>
348348
</Tooltip>
349-
{/* cat-style terminal header */}
350-
<Box sx={{
351-
fontFamily: typography.fontFamily,
352-
fontSize: '11px',
353-
color: '#6E6D66',
354-
bgcolor: '#0E0E0C',
355-
px: '32px',
356-
pt: 2,
357-
pb: 0,
358-
borderRadius: '12px 12px 0 0',
359-
letterSpacing: '0.04em',
360-
}}>
361-
<Box component="span" sx={{ color: '#009E73' }}>$</Box> cat {specId}/{libraryId}.py
362-
<Box sx={{
363-
mt: 1,
364-
borderBottom: '1px solid rgba(255,255,255,0.06)',
365-
}} />
366-
</Box>
367349
<Box
368350
sx={{
369351
bgcolor: colors.background,
370352
p: 3,
371-
pt: 0,
372-
borderRadius: '0 0 12px 12px',
353+
borderRadius: 1,
373354
}}
374355
>
375356
{highlightedCode}

app/src/pages/HomePage.test.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ describe('HomePage', () => {
9999
expect(screen.getByTestId('science')).toBeInTheDocument();
100100
});
101101

102-
it('renders catalog components (filterbar, grid, footer)', () => {
102+
it('renders plots components (filterbar, grid, footer)', () => {
103103
render(<HomePage />);
104104
expect(screen.getByTestId('filterbar')).toBeInTheDocument();
105105
expect(screen.getByTestId('images-grid')).toBeInTheDocument();

app/src/pages/HomePage.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ export function HomePage() {
9494

9595
// Refs
9696
const searchInputRef = useRef<HTMLInputElement>(null);
97-
const catalogRef = useRef<HTMLDivElement>(null);
97+
const plotsRef = useRef<HTMLDivElement>(null);
9898

9999
const noFilters = isFiltersEmpty(activeFilters);
100100

@@ -148,13 +148,13 @@ export function HomePage() {
148148
(library: string) => {
149149
handleAddFilter('lib', library);
150150
setTimeout(() => {
151-
catalogRef.current?.scrollIntoView({ behavior: 'smooth' });
151+
plotsRef.current?.scrollIntoView({ behavior: 'smooth' });
152152
}, 100);
153153
},
154154
[handleAddFilter]
155155
);
156156

157-
// catalogRef used for library card clicks to scroll to grid
157+
// plotsRef used for library card clicks to scroll to grid
158158

159159
// Global keyboard shortcuts
160160
useEffect(() => {
@@ -215,8 +215,8 @@ export function HomePage() {
215215
</Alert>
216216
)}
217217

218-
{/* Catalog section — always visible */}
219-
<Box ref={catalogRef}>
218+
{/* Plots section — always visible */}
219+
<Box ref={plotsRef}>
220220
<FilterBar
221221
activeFilters={activeFilters}
222222
filterCounts={filterCounts}
Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -51,26 +51,26 @@ vi.mock('../components/Footer', () => ({
5151
Footer: () => <div data-testid="footer">Footer</div>,
5252
}));
5353

54-
import { CatalogPage } from './CatalogPage';
54+
import { PlotsPage } from './PlotsPage';
5555

56-
describe('CatalogPage', () => {
56+
describe('PlotsPage', () => {
5757
beforeEach(() => {
5858
vi.clearAllMocks();
5959
});
6060

6161
it('renders FilterBar and ImagesGrid', () => {
62-
render(<CatalogPage />);
62+
render(<PlotsPage />);
6363
expect(screen.getByTestId('filterbar')).toBeInTheDocument();
6464
expect(screen.getByTestId('images-grid')).toBeInTheDocument();
6565
});
6666

6767
it('renders footer', () => {
68-
render(<CatalogPage />);
68+
render(<PlotsPage />);
6969
expect(screen.getByTestId('footer')).toBeInTheDocument();
7070
});
7171

7272
it('renders Helmet for SEO', () => {
73-
render(<CatalogPage />);
73+
render(<PlotsPage />);
7474
expect(screen.getByTestId('helmet')).toBeInTheDocument();
7575
});
7676
});

0 commit comments

Comments
 (0)