From 824d849c6353b77ccaf2d6a464f260d4a3c32b0b Mon Sep 17 00:00:00 2001 From: Giorgio Gilestro Date: Fri, 22 May 2026 19:39:38 +0100 Subject: [PATCH] brand: rename product to "Read the Markets" (read.markets) The product is now "Read the Markets" served at https://read.markets, with the app at https://app.read.markets. "Cassandra" survives only as the in-product AI persona (system prompt + "Ask Cassandra" chat label). Centralised the brand in app/branding.py: BRAND_NAME, BRAND_SHORT, DOMAIN, SITE_URL, APP_URL, EMAIL_FROM_DEFAULT. Jinja templates pull {{ BRAND_NAME }} via globals registered in templates_env.py; Python code reads branding.BRAND_NAME directly. The future-rename surface is now a one-liner. Updated: FastAPI app title, every page title (dashboard, news, log, settings, upload, login, verify), header brand div, auth-card brands, OTP email subject + HTML + plain-text bodies (incl. uppercase header tag), OpenRouter X-Title + HTTP-Referer attribution headers, README. Email tests now assert against branding.BRAND_NAME rather than the literal string. Internal identifiers deliberately kept on the legacy "cassandra" name to avoid invalidating live sessions / advisory locks / configs: cookies (cassandra_session, cassandra_pending) + itsdangerous salts, MariaDB GET_LOCK keys, CASSANDRA_TOKEN env var, cassandra.css filename, pyproject package name, localStorage prefs, outbound User-Agent strings. Co-Authored-By: Claude Opus 4.7 (1M context) --- README.md | 10 ++++++++-- app/branding.py | 31 ++++++++++++++++++++++++------- app/main.py | 3 ++- app/services/email_service.py | 25 ++++++++++++++++--------- app/services/openrouter.py | 8 +++++--- app/templates/base.html | 4 ++-- app/templates/dashboard.html | 2 +- app/templates/log.html | 2 +- app/templates/login.html | 4 ++-- app/templates/news.html | 2 +- app/templates/settings.html | 2 +- app/templates/upload.html | 10 +++++----- app/templates/verify.html | 4 ++-- app/templates_env.py | 8 ++++++++ tests/test_email_service.py | 6 ++++-- 15 files changed, 82 insertions(+), 39 deletions(-) diff --git a/README.md b/README.md index 043161c..b80289e 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,12 @@ -# Cassandra +# Read the Markets -Containerised macro-strategy dashboard — hourly market data, RSS news, Trading 212 portfolio, and an AI-generated strategic log. Read-only by design. +Containerised macro-strategy dashboard — hourly market data, RSS news, Trading 212 portfolio, and an AI-generated strategic log written by **Cassandra**, the in-product seer. Read-only by design. + +Production: +- Landing: +- App: + +The Python package is still named `cassandra` and several internal identifiers (cookie names, advisory-lock keys, `CASSANDRA_TOKEN` env var, CSS filename) keep the legacy name on purpose — renaming them would invalidate live sessions / locks / configs for no user benefit. See `app/branding.py` for the brand single-source-of-truth. ## Quick start diff --git a/app/branding.py b/app/branding.py index 82eb67f..9769d2f 100644 --- a/app/branding.py +++ b/app/branding.py @@ -1,11 +1,20 @@ -"""Cassandra brand palette — single source of truth. +"""Brand single source of truth — name, domain, palette, fonts. -Both the website's CSS (`app/static/css/cassandra.css`) and the email -templates (`app/services/email_service.py`) draw from these dicts. CSS -hand-authors the values in its `:root` / `[data-theme="light"]` blocks; -a drift-detection test (`tests/test_branding_consistency.py`) asserts -that what's in this module matches what's in the CSS, so updating the -brand in one place without the other fails CI. +The product is **Read the Markets** (read.markets). "Cassandra" remains +the in-product *AI persona* (system prompt + chat label) — distinct from +the brand, the way Slackbot is distinct from Slack. Anything that crosses +into user-visible chrome (page titles, email headers, OpenRouter referer) +must read `BRAND_NAME` from here; do not hard-code the string. + +Internal identifiers (`cassandra_session` cookie, pyproject package name, +SQLAlchemy GET_LOCK keys, file `cassandra.css`, env var `CASSANDRA_TOKEN`) +keep the legacy name on purpose — renaming them would invalidate live +sessions / advisory locks / configs for zero brand benefit. + +The colour palette below is hand-authored in CSS as well; a drift- +detection test (`tests/test_branding_consistency.py`) parses +`cassandra.css` and asserts every variable matches. Update both or +neither. The light theme is the *default* in emails — mail clients can't read `localStorage`, so we can't replicate the dashboard's user-toggled @@ -15,6 +24,14 @@ via media query. from __future__ import annotations +BRAND_NAME = "Read the Markets" +BRAND_SHORT = "Read" +DOMAIN = "read.markets" +SITE_URL = "https://read.markets" +APP_URL = "https://app.read.markets" +EMAIL_FROM_DEFAULT = f"noreply@{DOMAIN}" + + DARK: dict[str, str] = { "bg": "#0a0e14", "surface": "#11151c", diff --git a/app/main.py b/app/main.py index 5f3c74b..241aa7e 100644 --- a/app/main.py +++ b/app/main.py @@ -13,6 +13,7 @@ from fastapi import FastAPI from fastapi.middleware.gzip import GZipMiddleware from fastapi.staticfiles import StaticFiles +from app import branding from app.config import get_settings from app.db import get_session_factory from app.logging import configure_logging, get_logger @@ -56,7 +57,7 @@ async def lifespan(app: FastAPI): app = FastAPI( - title="Cassandra", + title=branding.BRAND_NAME, description="Macro-strategy dashboard", version="0.1.0", lifespan=lifespan, diff --git a/app/services/email_service.py b/app/services/email_service.py index 70ec7c9..274e526 100644 --- a/app/services/email_service.py +++ b/app/services/email_service.py @@ -44,7 +44,7 @@ async def send_email( """Send a (potentially multipart) email. `text_body` is required — it's the fallback for clients that can't or won't render HTML.""" s = get_settings() - sender = s.SMTP_FROM or s.SMTP_USER or "noreply@cassandra.local" + sender = s.SMTP_FROM or s.SMTP_USER or branding.EMAIL_FROM_DEFAULT msg = EmailMessage() msg["From"] = sender @@ -91,7 +91,7 @@ _OTP_HTML_TEMPLATE = """\ - Your Cassandra sign-in code + Your {brand} sign-in code