Skip to content

Commit 13a52c3

Browse files
rebrand: update references from pyplots to anyplot
- Change package and module descriptions to reflect new branding - Update URLs to include /python/ prefix for spec and implementation pages - Modify navigation links to use centralized path utility - Adjust SEO proxy routes for new URL structure
1 parent 673ef2b commit 13a52c3

File tree

28 files changed

+218
-76
lines changed

28 files changed

+218
-76
lines changed

.serena/project.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ excluded_tools: []
8686
# (contrary to the memories, which are loaded on demand).
8787
initial_prompt: ""
8888
# the name by which the project can be referenced within Serena
89-
project_name: "pyplots"
89+
project_name: "anyplot"
9090

9191
# list of tools to include that would otherwise be disabled (particularly optional tools that are disabled by default)
9292
included_optional_tools: []

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
**[anyplot.ai](https://anyplot.ai)**
44

5-
[![Python 3.13+](https://img.shields.io/badge/python-3.13+-blue.svg)](https://www.python.org/)
5+
[![Python 3.14+](https://img.shields.io/badge/python-3.14+-blue.svg)](https://www.python.org/)
66
[![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](LICENSE)
77
[![Tests](https://github.com/MarkusNeusinger/anyplot/actions/workflows/ci-tests.yml/badge.svg?branch=main)](https://github.com/MarkusNeusinger/anyplot/actions/workflows/ci-tests.yml)
88
[![Ruff](https://github.com/MarkusNeusinger/anyplot/actions/workflows/ci-lint.yml/badge.svg?branch=main)](https://github.com/MarkusNeusinger/anyplot/actions/workflows/ci-lint.yml)
@@ -63,7 +63,7 @@ See [docs/reference/](docs/reference/) for details.
6363

6464
## Tech Stack
6565

66-
**Backend**: FastAPI • PostgreSQL • SQLAlchemy • Python 3.10+
66+
**Backend**: FastAPI • PostgreSQL • SQLAlchemy • Python 3.14+
6767

6868
**Frontend**: React 19 • Vite • TypeScript • MUI
6969

agentic/commands/audit.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,7 @@ You are the **frontend-auditor** on the audit team. Analyze the `app/src/` direc
172172
5. Use `search_for_pattern` for cross-file patterns
173173
6. Use `think_about_collected_information` after research sequences
174174
7. **Do NOT use Bash** for `find`, `ls`, `grep`, `cat` — use Serena/Glob/Grep/Read tools instead
175-
8. You MAY use Bash for: `cd /home/tirao/pyplots/app && yarn tsc --noEmit 2>&1 | tail -20`
175+
8. You MAY use Bash for: `cd /home/tirao/anyplot/app && yarn tsc --noEmit 2>&1 | tail -20`
176176

177177
**Report format:** Same as backend-auditor — send findings to `audit-lead` via `SendMessage`.
178178

alembic/env.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
INSTANCE_CONNECTION_NAME = os.getenv("INSTANCE_CONNECTION_NAME", "")
3535
DB_USER = os.getenv("DB_USER", "postgres")
3636
DB_PASS = os.getenv("DB_PASS", "")
37-
DB_NAME = os.getenv("DB_NAME", "pyplots")
37+
DB_NAME = os.getenv("DB_NAME", "anyplot")
3838

3939
# Override sqlalchemy.url with DATABASE_URL from environment (for direct connections)
4040
if DATABASE_URL:

api/analytics.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -163,9 +163,9 @@ def track_og_image(
163163
elif page == "catalog":
164164
url = "https://anyplot.ai/catalog"
165165
elif spec is not None and library:
166-
url = f"https://anyplot.ai/{spec}/{library}"
166+
url = f"https://anyplot.ai/python/{spec}/{library}"
167167
elif spec is not None:
168-
url = f"https://anyplot.ai/{spec}"
168+
url = f"https://anyplot.ai/python/{spec}"
169169
else:
170170
# Fallback: missing spec for a spec-based page
171171
url = "https://anyplot.ai/"

api/mcp/server.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ async def list_specs(limit: int = 100, offset: int = 0) -> list[dict[str, Any]]:
102102
item = SpecListItem(
103103
id=spec.id, title=spec.title, description=spec.description, tags=spec.tags, library_count=impl_count
104104
)
105-
result.append({**item.model_dump(), "website_url": f"{ANYPLOT_WEBSITE_URL}/{spec.id}"})
105+
result.append({**item.model_dump(), "website_url": f"{ANYPLOT_WEBSITE_URL}/python/{spec.id}"})
106106

107107
return result
108108
finally:
@@ -228,7 +228,7 @@ async def search_specs_by_tags(
228228
item = SpecListItem(
229229
id=spec.id, title=spec.title, description=spec.description, tags=spec.tags, library_count=impl_count
230230
)
231-
result.append({**item.model_dump(), "website_url": f"{ANYPLOT_WEBSITE_URL}/{spec.id}"})
231+
result.append({**item.model_dump(), "website_url": f"{ANYPLOT_WEBSITE_URL}/python/{spec.id}"})
232232

233233
return result
234234
finally:
@@ -289,7 +289,10 @@ async def get_spec_detail(spec_id: str) -> dict[str, Any]:
289289
impl_tags=impl.impl_tags,
290290
)
291291
implementations.append(
292-
{**impl_response.model_dump(), "website_url": f"{ANYPLOT_WEBSITE_URL}/{spec_id}/{impl.library.id}"}
292+
{
293+
**impl_response.model_dump(),
294+
"website_url": f"{ANYPLOT_WEBSITE_URL}/python/{spec_id}/{impl.library.id}",
295+
}
293296
)
294297

295298
# Build full spec response
@@ -308,7 +311,7 @@ async def get_spec_detail(spec_id: str) -> dict[str, Any]:
308311
implementations=implementations,
309312
)
310313

311-
return {**response.model_dump(), "website_url": f"{ANYPLOT_WEBSITE_URL}/{spec_id}"}
314+
return {**response.model_dump(), "website_url": f"{ANYPLOT_WEBSITE_URL}/python/{spec_id}"}
312315
finally:
313316
await session.close()
314317

@@ -380,7 +383,7 @@ async def get_implementation(spec_id: str, library: str) -> dict[str, Any]:
380383
impl_tags=impl.impl_tags,
381384
)
382385

383-
return {**response.model_dump(), "website_url": f"{ANYPLOT_WEBSITE_URL}/{spec_id}/{library}"}
386+
return {**response.model_dump(), "website_url": f"{ANYPLOT_WEBSITE_URL}/python/{spec_id}/{library}"}
384387
finally:
385388
await session.close()
386389

api/routers/seo.py

Lines changed: 36 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,11 @@ def _build_sitemap_xml(specs: list) -> str:
3737
for spec in specs:
3838
if spec.impls:
3939
spec_id = html.escape(spec.id)
40-
xml_lines.append(f" <url><loc>https://anyplot.ai/{spec_id}</loc>{_lastmod(spec.updated)}</url>")
40+
xml_lines.append(f" <url><loc>https://anyplot.ai/python/{spec_id}</loc>{_lastmod(spec.updated)}</url>")
4141
for impl in spec.impls:
4242
library_id = html.escape(impl.library_id)
4343
xml_lines.append(
44-
f" <url><loc>https://anyplot.ai/{spec_id}/{library_id}</loc>{_lastmod(impl.updated)}</url>"
44+
f" <url><loc>https://anyplot.ai/python/{spec_id}/{library_id}</loc>{_lastmod(impl.updated)}</url>"
4545
)
4646

4747
xml_lines.append("</urlset>")
@@ -182,17 +182,15 @@ async def seo_mcp():
182182
)
183183

184184

185-
@router.get("/seo-proxy/{spec_id}")
186-
async def seo_spec_overview(spec_id: str, db: AsyncSession | None = Depends(optional_db)):
187-
"""Bot-optimized spec overview page with collage og:image."""
185+
async def _seo_spec_overview_html(spec_id: str, db: AsyncSession | None) -> HTMLResponse:
186+
"""Shared logic for bot-optimized spec overview page with collage og:image."""
188187
if db is None:
189-
# Fallback when DB unavailable
190188
return HTMLResponse(
191189
BOT_HTML_TEMPLATE.format(
192190
title=f"{html.escape(spec_id)} | anyplot.ai",
193191
description=DEFAULT_DESCRIPTION,
194192
image=DEFAULT_HOME_IMAGE,
195-
url=f"https://anyplot.ai/{html.escape(spec_id)}",
193+
url=f"https://anyplot.ai/python/{html.escape(spec_id)}",
196194
)
197195
)
198196

@@ -206,31 +204,28 @@ async def seo_spec_overview(spec_id: str, db: AsyncSession | None = Depends(opti
206204
if not spec:
207205
raise HTTPException(status_code=404, detail="Spec not found")
208206

209-
# Use collage og:image if implementations exist, otherwise default
210207
has_previews = any(i.preview_url for i in spec.impls)
211208
image = f"https://api.anyplot.ai/og/{spec_id}.png" if has_previews else DEFAULT_HOME_IMAGE
212209

213210
result = BOT_HTML_TEMPLATE.format(
214211
title=f"{html.escape(spec.title)} | anyplot.ai",
215212
description=html.escape(spec.description or DEFAULT_DESCRIPTION),
216213
image=html.escape(image, quote=True),
217-
url=f"https://anyplot.ai/{html.escape(spec_id)}",
214+
url=f"https://anyplot.ai/python/{html.escape(spec_id)}",
218215
)
219216
set_cache(key, result)
220217
return HTMLResponse(result)
221218

222219

223-
@router.get("/seo-proxy/{spec_id}/{library}")
224-
async def seo_spec_implementation(spec_id: str, library: str, db: AsyncSession | None = Depends(optional_db)):
225-
"""Bot-optimized spec implementation page with branded og:image."""
220+
async def _seo_spec_impl_html(spec_id: str, library: str, db: AsyncSession | None) -> HTMLResponse:
221+
"""Shared logic for bot-optimized spec implementation page with branded og:image."""
226222
if db is None:
227-
# Fallback when DB unavailable
228223
return HTMLResponse(
229224
BOT_HTML_TEMPLATE.format(
230225
title=f"{html.escape(spec_id)} - {html.escape(library)} | anyplot.ai",
231226
description=DEFAULT_DESCRIPTION,
232227
image=DEFAULT_HOME_IMAGE,
233-
url=f"https://anyplot.ai/{html.escape(spec_id)}/{html.escape(library)}",
228+
url=f"https://anyplot.ai/python/{html.escape(spec_id)}/{html.escape(library)}",
234229
)
235230
)
236231

@@ -244,16 +239,40 @@ async def seo_spec_implementation(spec_id: str, library: str, db: AsyncSession |
244239
if not spec:
245240
raise HTTPException(status_code=404, detail="Spec not found")
246241

247-
# Find the implementation for this library
248242
impl = next((i for i in spec.impls if i.library_id == library), None)
249-
# Use branded og:image endpoint if implementation has preview
250243
image = f"https://api.anyplot.ai/og/{spec_id}/{library}.png" if impl and impl.preview_url else DEFAULT_HOME_IMAGE
251244

252245
result = BOT_HTML_TEMPLATE.format(
253246
title=f"{html.escape(spec.title)} - {html.escape(library)} | anyplot.ai",
254247
description=html.escape(spec.description or DEFAULT_DESCRIPTION),
255248
image=html.escape(image, quote=True),
256-
url=f"https://anyplot.ai/{html.escape(spec_id)}/{html.escape(library)}",
249+
url=f"https://anyplot.ai/python/{html.escape(spec_id)}/{html.escape(library)}",
257250
)
258251
set_cache(key, result)
259252
return HTMLResponse(result)
253+
254+
255+
# New /python/ prefixed routes (canonical paths)
256+
@router.get("/seo-proxy/python/{spec_id}")
257+
async def seo_python_spec_overview(spec_id: str, db: AsyncSession | None = Depends(optional_db)):
258+
"""Bot-optimized spec overview page (canonical /python/ path)."""
259+
return await _seo_spec_overview_html(spec_id, db)
260+
261+
262+
@router.get("/seo-proxy/python/{spec_id}/{library}")
263+
async def seo_python_spec_implementation(spec_id: str, library: str, db: AsyncSession | None = Depends(optional_db)):
264+
"""Bot-optimized spec implementation page (canonical /python/ path)."""
265+
return await _seo_spec_impl_html(spec_id, library, db)
266+
267+
268+
# Legacy routes (old URLs without /python/ prefix) — serve same content with /python/ canonical
269+
@router.get("/seo-proxy/{spec_id}")
270+
async def seo_spec_overview(spec_id: str, db: AsyncSession | None = Depends(optional_db)):
271+
"""Bot-optimized spec overview page (legacy path, canonical points to /python/)."""
272+
return await _seo_spec_overview_html(spec_id, db)
273+
274+
275+
@router.get("/seo-proxy/{spec_id}/{library}")
276+
async def seo_spec_implementation(spec_id: str, library: str, db: AsyncSession | None = Depends(optional_db)):
277+
"""Bot-optimized spec implementation page (legacy path, canonical points to /python/)."""
278+
return await _seo_spec_impl_html(spec_id, library, db)

app/nginx.conf

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,13 @@ map $http_user_agent $is_bot {
3030
~*showyoubot 1;
3131
}
3232

33+
# Marketing subdomain: python.anyplot.ai → 301 to /python/
34+
server {
35+
listen 8080;
36+
server_name python.anyplot.ai;
37+
return 301 https://anyplot.ai/python$request_uri;
38+
}
39+
3340
server {
3441
listen 8080;
3542
server_name _;

app/public/logo.svg

Lines changed: 1 addition & 1 deletion
Loading

app/src/components/PlotOfTheDay.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import AutoAwesomeIcon from '@mui/icons-material/AutoAwesome';
1010
import { API_URL, GITHUB_URL } from '../constants';
1111
import { colors, typography, fontSize, semanticColors } from '../theme';
1212
import { buildSrcSet, getFallbackSrc } from '../utils/responsiveImage';
13+
import { specPath } from '../utils/paths';
1314

1415
interface PlotOfTheDayData {
1516
spec_id: string;
@@ -125,7 +126,7 @@ export function PlotOfTheDay() {
125126
{/* Image */}
126127
<Link
127128
component={RouterLink}
128-
to={`/${data.spec_id}/${data.library_id}`}
129+
to={specPath(data.spec_id, data.library_id)}
129130
sx={{
130131
display: 'block',
131132
textDecoration: 'none',
@@ -181,7 +182,7 @@ export function PlotOfTheDay() {
181182
{/* Title */}
182183
<Link
183184
component={RouterLink}
184-
to={`/${data.spec_id}/${data.library_id}`}
185+
to={specPath(data.spec_id, data.library_id)}
185186
sx={{
186187
textDecoration: 'none',
187188
color: colors.gray[800],

0 commit comments

Comments
 (0)