From 0e21e035f5e222d0b6a47583527f531706372a6f Mon Sep 17 00:00:00 2001 From: Santhosh Janardhanan Date: Fri, 13 Feb 2026 03:12:42 -0500 Subject: [PATCH] usability enhancements --- .env.example | 2 + backend/config.py | 2 + backend/main.py | 77 +++++- backend/static/images/favicon-ai.svg | 13 + data/clawfort.db | Bin 200704 -> 200704 bytes frontend/attribution.html | 1 + frontend/index.html | 261 +++++++++++++++++- frontend/terms.html | 1 + .../p12-umami-events-and-more/design.md | 57 ++++ .../specs/hero-display/spec.md | 21 ++ .../specs/summary-analytics-tagging/spec.md | 24 ++ .../p12-umami-events-and-more/tasks.md | 15 + .../p13-usability-enhancements/design.md | 84 ++++++ .../p13-usability-enhancements/proposal.md | 36 +++ .../spec.md | 25 ++ .../spec.md | 20 ++ .../specs/footer-policy-links/spec.md | 24 ++ .../language-aware-content-delivery/spec.md | 32 +++ .../responsive-device-agnostic-layout/spec.md | 19 ++ .../specs/seo-meta-and-social-tags/spec.md | 19 ++ .../spec.md | 28 ++ .../specs/site-branding-favicon/spec.md | 13 + .../specs/summary-modal-experience/spec.md | 30 ++ .../p13-usability-enhancements/tasks.md | 41 +++ openspec/changes/p14-bugs/.openspec.yaml | 2 + openspec/changes/p14-bugs/proposal.md | 34 +++ .../p15-complete-test-suite/.openspec.yaml | 2 + .../p15-complete-test-suite/proposal.md | 36 +++ 28 files changed, 904 insertions(+), 15 deletions(-) create mode 100644 backend/static/images/favicon-ai.svg create mode 100644 openspec/changes/p12-umami-events-and-more/design.md create mode 100644 openspec/changes/p12-umami-events-and-more/specs/hero-display/spec.md create mode 100644 openspec/changes/p12-umami-events-and-more/specs/summary-analytics-tagging/spec.md create mode 100644 openspec/changes/p12-umami-events-and-more/tasks.md create mode 100644 openspec/changes/p13-usability-enhancements/design.md create mode 100644 openspec/changes/p13-usability-enhancements/proposal.md create mode 100644 openspec/changes/p13-usability-enhancements/specs/article-permalinks-and-deep-link-modal/spec.md create mode 100644 openspec/changes/p13-usability-enhancements/specs/error-pages-with-playful-ai-messaging/spec.md create mode 100644 openspec/changes/p13-usability-enhancements/specs/footer-policy-links/spec.md create mode 100644 openspec/changes/p13-usability-enhancements/specs/language-aware-content-delivery/spec.md create mode 100644 openspec/changes/p13-usability-enhancements/specs/responsive-device-agnostic-layout/spec.md create mode 100644 openspec/changes/p13-usability-enhancements/specs/seo-meta-and-social-tags/spec.md create mode 100644 openspec/changes/p13-usability-enhancements/specs/share-and-contact-microinteractions/spec.md create mode 100644 openspec/changes/p13-usability-enhancements/specs/site-branding-favicon/spec.md create mode 100644 openspec/changes/p13-usability-enhancements/specs/summary-modal-experience/spec.md create mode 100644 openspec/changes/p13-usability-enhancements/tasks.md create mode 100644 openspec/changes/p14-bugs/.openspec.yaml create mode 100644 openspec/changes/p14-bugs/proposal.md create mode 100644 openspec/changes/p15-complete-test-suite/.openspec.yaml create mode 100644 openspec/changes/p15-complete-test-suite/proposal.md diff --git a/.env.example b/.env.example index 892824c..2b9fc11 100644 --- a/.env.example +++ b/.env.example @@ -3,6 +3,8 @@ IMAGE_QUALITY=85 OPENROUTER_API_KEY= UMAMI_SCRIPT_URL= UMAMI_WEBSITE_ID= +GITHUB_REPO_URL= +CONTACT_EMAIL= RETENTION_DAYS=30 ROYALTY_IMAGE_PROVIDER=picsum ROYALTY_IMAGE_MCP_ENDPOINT= diff --git a/backend/config.py b/backend/config.py index 7e63434..3b71461 100644 --- a/backend/config.py +++ b/backend/config.py @@ -10,6 +10,8 @@ IMAGE_QUALITY = int(os.getenv("IMAGE_QUALITY", "85")) RETENTION_DAYS = int(os.getenv("RETENTION_DAYS", "30")) UMAMI_SCRIPT_URL = os.getenv("UMAMI_SCRIPT_URL", "") UMAMI_WEBSITE_ID = os.getenv("UMAMI_WEBSITE_ID", "") +GITHUB_REPO_URL = os.getenv("GITHUB_REPO_URL", "") +CONTACT_EMAIL = os.getenv("CONTACT_EMAIL", "") ROYALTY_IMAGE_MCP_ENDPOINT = os.getenv("ROYALTY_IMAGE_MCP_ENDPOINT", "") ROYALTY_IMAGE_API_KEY = os.getenv("ROYALTY_IMAGE_API_KEY", "") ROYALTY_IMAGE_PROVIDER = os.getenv("ROYALTY_IMAGE_PROVIDER", "picsum") diff --git a/backend/main.py b/backend/main.py index aee4b1c..06e3cbd 100644 --- a/backend/main.py +++ b/backend/main.py @@ -1,13 +1,15 @@ import logging import os +import random from apscheduler.schedulers.background import BackgroundScheduler from fastapi import Depends, FastAPI, Query, Request from fastapi.middleware.cors import CORSMiddleware from fastapi.middleware.gzip import GZipMiddleware from fastapi.staticfiles import StaticFiles -from fastapi.responses import FileResponse +from fastapi.responses import FileResponse, HTMLResponse, JSONResponse from sqlalchemy.orm import Session +from starlette.exceptions import HTTPException as StarletteHTTPException from backend import config from backend.database import get_db, init_db @@ -33,6 +35,77 @@ logger = logging.getLogger(__name__) app = FastAPI(title="ClawFort News API", version="0.1.0") +_ERROR_MESSAGES = { + 404: [ + "Oh no! This page wandered off to train a tiny model.", + "Oh no! We looked everywhere, even in the latent space.", + "Oh no! The link took a creative detour.", + "Oh no! This route is currently off doing research.", + "Oh no! The page you asked for is not in this timeline.", + ], + 500: [ + "Oh no! The server hit a logic knot and needs a quick reset.", + "Oh no! Our robots dropped a semicolon somewhere important.", + "Oh no! A background process got stage fright.", + "Oh no! The AI took an unexpected coffee break.", + "Oh no! Something internal blinked at the wrong moment.", + ], +} + + +def _render_error_page(status_code: int) -> str: + message = random.choice(_ERROR_MESSAGES.get(status_code, _ERROR_MESSAGES[500])) + return f""" + + + + + {status_code} - ClawFort + + + + +
+
+

{status_code}

+

Oh no!

+

{message}

+ Back to ClawFort +
+
+ +""" + + +@app.exception_handler(StarletteHTTPException) +async def http_exception_handler(request: Request, exc: StarletteHTTPException): + if request.url.path.startswith("/api/"): + return JSONResponse(status_code=exc.status_code, content={"detail": exc.detail}) + + if exc.status_code == 404: + return HTMLResponse(_render_error_page(404), status_code=404) + + return JSONResponse(status_code=exc.status_code, content={"detail": exc.detail}) + + +@app.exception_handler(Exception) +async def unhandled_exception_handler(request: Request, exc: Exception): + logger.exception("Unhandled server error: %s", exc) + if request.url.path.startswith("/api/"): + return JSONResponse(status_code=500, content={"detail": "Internal Server Error"}) + return HTMLResponse(_render_error_page(500), status_code=500) + + app.add_middleware( CORSMiddleware, allow_origins=["*"], @@ -216,6 +289,8 @@ async def serve_config() -> dict: return { "umami_script_url": config.UMAMI_SCRIPT_URL, "umami_website_id": config.UMAMI_WEBSITE_ID, + "github_repo_url": config.GITHUB_REPO_URL, + "contact_email": config.CONTACT_EMAIL, "supported_languages": config.SUPPORTED_LANGUAGES, "default_language": "en", } diff --git a/backend/static/images/favicon-ai.svg b/backend/static/images/favicon-ai.svg new file mode 100644 index 0000000..1a9d2e3 --- /dev/null +++ b/backend/static/images/favicon-ai.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/data/clawfort.db b/data/clawfort.db index 576e68842ca050cd94429fbbeae7c8bef0e4abf9..99ba3a5ced916b11deaf97a350f8bf94ca309448 100644 GIT binary patch delta 2090 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 + Attribution and Ownership Disclaimer - ClawFort diff --git a/frontend/index.html b/frontend/index.html index ff3c0ae..a1b6400 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -20,10 +20,11 @@ - ClawFort — AI News + ClawFort AI News + - +