From 298b5856890897547b1a925a11645859bd348560 Mon Sep 17 00:00:00 2001 From: "Claude (Opus 4.6)" Date: Tue, 24 Mar 2026 02:33:16 +0000 Subject: [PATCH] =?UTF-8?q?[claude]=20SEO=20foundation=20=E2=80=94=20meta?= =?UTF-8?q?=20tags,=20sitemap,=20robots.txt,=20JSON-LD=20(#813)=20(#1335)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/config.py | 5 ++ src/dashboard/app.py | 2 + src/dashboard/routes/seo.py | 73 +++++++++++++++++++++++ src/dashboard/templates/base.html | 98 ++++++++++++++++++++++++++++++- src/dashboard/templating.py | 5 ++ 5 files changed, 182 insertions(+), 1 deletion(-) create mode 100644 src/dashboard/routes/seo.py diff --git a/src/config.py b/src/config.py index bb72f70f..c257bb8d 100644 --- a/src/config.py +++ b/src/config.py @@ -571,6 +571,11 @@ class Settings(BaseSettings): content_meilisearch_url: str = "http://localhost:7700" content_meilisearch_api_key: str = "" + # ── SEO / Public Site ────────────────────────────────────────────────── + # Canonical base URL used in sitemap.xml, canonical link tags, and OG tags. + # Override with SITE_URL env var, e.g. "https://alexanderwhitestone.com". + site_url: str = "https://alexanderwhitestone.com" + # ── Scripture / Biblical Integration ────────────────────────────── # Enable the biblical text module. scripture_enabled: bool = True diff --git a/src/dashboard/app.py b/src/dashboard/app.py index 68503cdf..b4f29315 100644 --- a/src/dashboard/app.py +++ b/src/dashboard/app.py @@ -63,6 +63,7 @@ from dashboard.routes.tools import router as tools_router from dashboard.routes.tower import router as tower_router from dashboard.routes.voice import router as voice_router from dashboard.routes.work_orders import router as work_orders_router +from dashboard.routes.seo import router as seo_router from dashboard.routes.world import matrix_router from dashboard.routes.world import router as world_router from timmy.workshop_state import PRESENCE_FILE @@ -664,6 +665,7 @@ if static_dir.exists(): from dashboard.templating import templates # noqa: E402 # Include routers +app.include_router(seo_router) app.include_router(health_router) app.include_router(agents_router) app.include_router(voice_router) diff --git a/src/dashboard/routes/seo.py b/src/dashboard/routes/seo.py new file mode 100644 index 00000000..b943870f --- /dev/null +++ b/src/dashboard/routes/seo.py @@ -0,0 +1,73 @@ +"""SEO endpoints: robots.txt, sitemap.xml, and structured-data helpers. + +These endpoints make alexanderwhitestone.com crawlable by search engines. +All pages listed in the sitemap are server-rendered HTML (not SPA-only). +""" + +from __future__ import annotations + +from datetime import date + +from fastapi import APIRouter +from fastapi.responses import PlainTextResponse, Response + +from config import settings + +router = APIRouter(tags=["seo"]) + +# Public-facing pages included in the sitemap. +# Format: (path, change_freq, priority) +_SITEMAP_PAGES: list[tuple[str, str, str]] = [ + ("/", "daily", "1.0"), + ("/briefing", "daily", "0.9"), + ("/tasks", "daily", "0.8"), + ("/calm", "weekly", "0.7"), + ("/thinking", "weekly", "0.7"), + ("/swarm/mission-control", "weekly", "0.7"), + ("/monitoring", "weekly", "0.6"), + ("/nexus", "weekly", "0.6"), + ("/spark/ui", "weekly", "0.6"), + ("/memory", "weekly", "0.6"), + ("/marketplace/ui", "weekly", "0.8"), + ("/models", "weekly", "0.5"), + ("/tools", "weekly", "0.5"), + ("/scorecards", "weekly", "0.6"), +] + + +@router.get("/robots.txt", response_class=PlainTextResponse) +async def robots_txt() -> str: + """Allow all search engines; point to sitemap.""" + base = settings.site_url.rstrip("/") + return ( + "User-agent: *\n" + "Allow: /\n" + "\n" + f"Sitemap: {base}/sitemap.xml\n" + ) + + +@router.get("/sitemap.xml") +async def sitemap_xml() -> Response: + """Generate XML sitemap for all crawlable pages.""" + base = settings.site_url.rstrip("/") + today = date.today().isoformat() + + url_entries: list[str] = [] + for path, changefreq, priority in _SITEMAP_PAGES: + url_entries.append( + f" \n" + f" {base}{path}\n" + f" {today}\n" + f" {changefreq}\n" + f" {priority}\n" + f" " + ) + + xml = ( + '\n' + '\n' + + "\n".join(url_entries) + + "\n\n" + ) + return Response(content=xml, media_type="application/xml") diff --git a/src/dashboard/templates/base.html b/src/dashboard/templates/base.html index 9db388b8..19cc2e0a 100644 --- a/src/dashboard/templates/base.html +++ b/src/dashboard/templates/base.html @@ -6,7 +6,103 @@ - {% block title %}Timmy Time — Mission Control{% endblock %} + {% block title %}Timmy AI Workshop | Lightning-Powered AI Jobs — Pay Per Task with Bitcoin{% endblock %} + + {# SEO: description #} + + + + {# Canonical URL — override per-page via {% block canonical_url %} #} + {% block canonical_url %} + + {% endblock %} + + {# Open Graph #} + + + + + + + + + {# Twitter / X Card #} + + + + + + {# JSON-LD Structured Data #} + + diff --git a/src/dashboard/templating.py b/src/dashboard/templating.py index 46d60527..aea02947 100644 --- a/src/dashboard/templating.py +++ b/src/dashboard/templating.py @@ -4,4 +4,9 @@ from pathlib import Path from fastapi.templating import Jinja2Templates +from config import settings + templates = Jinja2Templates(directory=str(Path(__file__).parent / "templates")) + +# Inject site_url into every template so SEO tags and canonical URLs work. +templates.env.globals["site_url"] = settings.site_url