brand: standardize "Upbeat Bytes" → "upbeatBytes" everywhere

Per the logo + brand: the name is upbeatBytes (camelCase). Swept all user-facing
strings — titles/og:site_name/og:title, logo alt text, share pages (share.py),
emails (email_send), classifier prompt (llm), digest/unsubscribe (api), PWA
manifest, game share text, sign-in, the SPA shell + patch-static-heads (play
title) — plus README/publish.sh and the email test fixture. (SMTP From env was
already upbeatBytes.) Domains (upbeatbytes.com) unchanged. 425 BE + 36 FE green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
jay
2026-06-28 20:01:20 -04:00
parent f8628b3b14
commit 667b1a82c3
24 changed files with 58 additions and 58 deletions
+2 -2
View File
@@ -1,8 +1,8 @@
# Upbeat Bytes
# upbeatBytes
Calm, constructive news — local-first ingestion, scoring, and a daily brief.
(The Python package and CLI are named `goodnews` for historical reasons; the
product is **Upbeat Bytes**, at upbeatbytes.com.)
product is **upbeatBytes**, at upbeatbytes.com.)
The first milestone is intentionally small: collect public RSS/Atom metadata, dedupe it, store short source-provided snippets, and attach early reason-coded heuristic scores. It does not store full article bodies.
+2 -2
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env bash
# Publish Upbeat Bytes: build the frontend, sync it to the live Caddy site,
# Publish upbeatBytes: build the frontend, sync it to the live Caddy site,
# rebuild/restart the API container, and reload Caddy. One command to redeploy.
set -euo pipefail
@@ -26,4 +26,4 @@ curl -fsS -o /dev/null -w ' logo-email.png → %{http_code} %{content_type}\n'
https://upbeatbytes.com/logo-email.png \
|| echo " ⚠ logo-email.png is not being served — the digest masthead would break!"
echo "✓ Published Upbeat Bytes → https://upbeatbytes.com"
echo "✓ Published upbeatBytes → https://upbeatbytes.com"
+1 -1
View File
@@ -19,7 +19,7 @@ const PAGES = [
},
{
file: 'play.html', path: '/play',
title: 'Play · Upbeat Bytes — calm daily games',
title: 'Play · upbeatBytes — calm daily games',
desc: 'A calm set of daily games — Daily Word, Word Search, Bloom, and Memory Match. ' +
'A friendly little break from the doomscroll.',
},
+1 -1
View File
@@ -8,7 +8,7 @@
font-display: swap;
}
/* Upbeat Bytes calm design system.
/* upbeatBytes calm design system.
Sand, sea, and sun: warm paper surfaces, a vivid-azure accent, gold highlight,
a serif voice for headlines, strong readable contrast, generous space.
No urgency colors (no red). Built around the logo's #0083ad azure. */
+3 -3
View File
@@ -10,7 +10,7 @@
<meta name="description" content="A calmer, brighter corner of the internet: good news, daily art, small games, and little resets." />
<title>upbeatBytes — a calmer, brighter corner of the internet</title>
<link rel="canonical" href="https://upbeatbytes.com/" />
<meta property="og:site_name" content="Upbeat Bytes" />
<meta property="og:site_name" content="upbeatBytes" />
<meta property="og:type" content="website" />
<meta property="og:title" content="upbeatBytes — a calmer, brighter corner of the internet" />
<meta property="og:description" content="A calmer, brighter corner of the internet: good news, daily art, small games, and little resets. No ads, no paywalls, no doomscrolling." />
@@ -128,9 +128,9 @@
<div style="display: contents">%sveltekit.body%</div>
<div id="boot-fallback" role="alert" aria-live="polite">
<div class="bf">
<img src="%sveltekit.assets%/logo.svg" alt="Upbeat Bytes" />
<img src="%sveltekit.assets%/logo.svg" alt="upbeatBytes" />
<p>We had a little trouble loading. A quick refresh usually sorts it out.</p>
<button type="button" onclick="location.reload()">Refresh Upbeat Bytes</button>
<button type="button" onclick="location.reload()">Refresh upbeatBytes</button>
</div>
</div>
</body>
@@ -59,7 +59,7 @@
if (value) onaction?.(kind, value);
}
// Sharing: share the branded Upbeat Bytes card page (default), with copy-source.
// Sharing: share the branded upbeatBytes card page (default), with copy-source.
let shareOpen = $state(false);
let copied = $state('');
const canNativeShare = typeof navigator !== 'undefined' && !!navigator.share;
@@ -105,7 +105,7 @@
</a>
{:else if usePlaceholder}
<a class="media placeholder" href={summaryHref} onclick={opened} style="--c:{accentColor}" tabindex="-1" aria-hidden="true">
<span class="ph-word">{humanize(article.topic) || 'upbeat bytes'}</span>
<span class="ph-word">{humanize(article.topic) || 'upbeatBytes'}</span>
</a>
{/if}
+1 -1
View File
@@ -228,7 +228,7 @@
const breakdown = Object.keys(byLen).sort((a, b) => b - a).map((l) => `${l}×${byLen[l]}`).join(' ');
const pang = found.some(isPangram) ? ' · pangram ✓' : '';
const bloomV = mode === 'daily' ? 'daily' : (format === 'wild' ? 'free-wild' : 'free-center');
const text = `Upbeat Bytes · Bloom ${date}\n${fullBloom ? 'Full Bloom 🌸' : tier.name} · ${found.length} words${pang}\n${breakdown}\n${gameShareUrl('bloom', bloomV)}`;
const text = `upbeatBytes · Bloom ${date}\n${fullBloom ? 'Full Bloom 🌸' : tier.name} · ${found.length} words${pang}\n${breakdown}\n${gameShareUrl('bloom', bloomV)}`;
if (navigator.share) navigator.share({ text }).then(() => trackGame('bloom', 'shared')).catch(() => {});
else navigator.clipboard?.writeText(text).then(() => { trackGame('bloom', 'shared'); copied = true; setTimeout(() => (copied = false), 1500); });
}
+2 -2
View File
@@ -6,8 +6,8 @@
<header class="appbar">
<div class="container bar">
<a class="brand" href="/" aria-label="Upbeat Bytes — home">
<img class="logo" src="/logo.svg" alt="Upbeat Bytes" width="586" height="196" />
<a class="brand" href="/" aria-label="upbeatBytes — home">
<img class="logo" src="/logo.svg" alt="upbeatBytes" width="586" height="196" />
</a>
<nav class="utils" aria-label="Your controls">
+1 -1
View File
@@ -133,7 +133,7 @@
function share() {
const label = `${TIER_LABEL[tier] || tier} · ${format === 'colors' ? 'colors' : 'icons'}`;
const when = isFree ? 'Free play' : date;
const text = `Upbeat Bytes · Memory Match (${label}) ${when}\nCleared in ${moves} moves\n${gameShareUrl('match', `${mode}-${format}-${tier}`)}`;
const text = `upbeatBytes · Memory Match (${label}) ${when}\nCleared in ${moves} moves\n${gameShareUrl('match', `${mode}-${format}-${tier}`)}`;
if (navigator.share) navigator.share({ text }).then(() => trackGame('match', 'shared')).catch(() => {});
else navigator.clipboard?.writeText(text).then(() => { trackGame('match', 'shared'); copied = true; setTimeout(() => (copied = false), 1500); });
}
+4 -4
View File
@@ -181,7 +181,7 @@
);
let viewSubtitle = $derived(
selected === 'today' ? localDateLabel(brief)
: selected === 'search' ? 'Results across Upbeat Bytes'
: selected === 'search' ? 'Results across upbeatBytes'
: selected.startsWith('source:') ? 'Latest from this source'
: selected === 'latest' ? 'Freshest calm reads — newest first'
: selected === 'following' ? 'From the sources & topics you follow'
@@ -629,7 +629,7 @@
moods = m; topics = c.topics;
await loadView(selected);
} catch (e) {
if (!brief) error = 'Could not reach Upbeat Bytes.'; // keep painted content if a refresh failed
if (!brief) error = 'Could not reach upbeatBytes.'; // keep painted content if a refresh failed
} finally {
loading = false;
}
@@ -872,7 +872,7 @@
{#if families.length}
<section id="explore" class="explore">
<h2>Explore Upbeat Bytes</h2>
<h2>Explore upbeatBytes</h2>
<div class="families">
{#each families as f (f.name)}
{@const tags = f.tags.filter((t) => t.count > 0)}
@@ -895,7 +895,7 @@
{#if !pwa.isStandalone && !pwa.dismissed && (pwa.canInstall || pwa.isIOS)}
<aside class="install rise">
<div class="install-text">
<strong>Keep Upbeat Bytes a tap away.</strong>
<strong>Keep upbeatBytes a tap away.</strong>
{#if pwa.canInstall}Add it to your home screen — it opens like an app, no store needed.
{:else}On iPhone: tap the <span class="ios-share">Share</span> button, then “Add to Home Screen.”{/if}
</div>
+1 -1
View File
@@ -41,7 +41,7 @@
</p>
<button class="primary" onclick={onclose}>Done</button>
{:else}
<h2>Sign in to Upbeat Bytes</h2>
<h2>Sign in to upbeatBytes</h2>
<p class="sub">
Save articles and keep your history across devices.
</p>
+1 -1
View File
@@ -167,7 +167,7 @@
const label = variant === '6' ? 'Long Word' : 'Daily Word';
const score = status === 'won' ? guesses.length : 'X';
const grid = cols.map((cs) => cs.map((c) => EMOJI[c]).join('')).join('\n');
const text = `Upbeat Bytes · ${label} ${date}\n${score}/${maxGuesses}\n${grid}\n${gameShareUrl('word', variant)}`;
const text = `upbeatBytes · ${label} ${date}\n${score}/${maxGuesses}\n${grid}\n${gameShareUrl('word', variant)}`;
// Count a share only once it actually happens (sheet completed / clipboard wrote),
// never on a cancelled share sheet or denied clipboard.
if (navigator.share) navigator.share({ text }).then(() => trackGame('word', 'shared')).catch(() => {});
@@ -223,7 +223,7 @@
function share() {
const label = { small: 'Small', med: 'Medium', large: 'Large' }[size] || '';
const text = `Upbeat Bytes · Word Search (${label}) ${date}\n${theme} — cleared in ${fmt(resultMs)}\n${gameShareUrl('wordsearch', size)}`;
const text = `upbeatBytes · Word Search (${label}) ${date}\n${theme} — cleared in ${fmt(resultMs)}\n${gameShareUrl('wordsearch', size)}`;
if (navigator.share) navigator.share({ text }).then(() => trackGame('wordsearch', 'shared')).catch(() => {});
else navigator.clipboard?.writeText(text).then(() => { trackGame('wordsearch', 'shared'); copied = true; setTimeout(() => (copied = false), 1500); });
}
+1 -1
View File
@@ -101,7 +101,7 @@
<header class="bar">
<div class="container inner">
<a class="brand" href="/" aria-label="Upbeat Bytes — home"><img class="logo" src="/logo.svg" alt="Upbeat Bytes" /></a>
<a class="brand" href="/" aria-label="upbeatBytes — home"><img class="logo" src="/logo.svg" alt="upbeatBytes" /></a>
<div class="baractions">
<button class="fb" onclick={openFeedback} aria-label="Share feedback" title="Share feedback">
<svg viewBox="0 0 24 24" width="18" height="18" aria-hidden="true"><path d="M4 5h16v11H8l-4 3z" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linejoin="round" /></svg>
+1 -1
View File
@@ -675,7 +675,7 @@
<header class="bar">
<div class="container inner">
<a class="brand" href="/"><img class="logo" src="/logo.svg" alt="Upbeat Bytes" /></a>
<a class="brand" href="/"><img class="logo" src="/logo.svg" alt="upbeatBytes" /></a>
<a class="back" href="/account"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M19 12H5M11 6l-6 6 6 6" fill="none" stroke="currentColor" stroke-width="2.1" stroke-linecap="round" stroke-linejoin="round"/></svg>Account</a>
</div>
</header>
+1 -1
View File
@@ -35,7 +35,7 @@
{:else}
<h1>Couldn't sign you in</h1>
<p class="muted">{error}</p>
<a class="back" href="/">← Back to Upbeat Bytes</a>
<a class="back" href="/">← Back to upbeatBytes</a>
{/if}
</main>
+1 -1
View File
@@ -273,7 +273,7 @@
<!-- Canonical/OG/description for /play are baked into the static play.html at build
time (scripts/patch-static-heads.mjs) so non-JS social scrapers get them; we keep
only the browser-tab title + dev-gate noindex here to avoid duplicate tags. -->
<title>Play · Upbeat Bytes — calm daily games</title>
<title>Play · upbeatBytes — calm daily games</title>
{#if isDevGated(game)}<meta name="robots" content="noindex" />{/if}
</svelte:head>
+2 -2
View File
@@ -51,13 +51,13 @@
</script>
<svelte:head>
<title>The Zen Den · Upbeat Bytes</title>
<title>The Zen Den · upbeatBytes</title>
{#if isDevGated('zen')}<meta name="robots" content="noindex" />{/if}
</svelte:head>
<header class="bar">
<div class="container inner">
<a class="brand" href="/"><img class="logo" src="/logo.svg" alt="Upbeat Bytes" /></a>
<a class="brand" href="/"><img class="logo" src="/logo.svg" alt="upbeatBytes" /></a>
<a class="back" href="/play">
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M19 12H5M11 6l-6 6 6 6" fill="none" stroke="currentColor" stroke-width="2.1" stroke-linecap="round" stroke-linejoin="round"/></svg>Play
</a>
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "Upbeat Bytes",
"short_name": "Upbeat Bytes",
"name": "upbeatBytes",
"short_name": "upbeatBytes",
"description": "A calmer, brighter corner of the internet: good news, daily art, small games, and little resets.",
"start_url": "/",
"scope": "/",
+3 -3
View File
@@ -767,7 +767,7 @@ def create_app() -> FastAPI:
ok = _do_unsubscribe(u, t)
msg = (
"Youre unsubscribed from the daily digest. No hard feelings — "
"Upbeat Bytes is always here when you want it."
"upbeatBytes is always here when you want it."
if ok else
"That unsubscribe link looks invalid or expired. You can manage the "
"digest from your account settings."
@@ -776,9 +776,9 @@ def create_app() -> FastAPI:
'<!doctype html><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">'
'<div style="max-width:520px;margin:12vh auto;padding:0 24px;text-align:center;'
'font-family:-apple-system,Segoe UI,Roboto,Helvetica,Arial,sans-serif;color:#16263a">'
'<h1 style="font-size:22px">Upbeat Bytes</h1>'
'<h1 style="font-size:22px">upbeatBytes</h1>'
f'<p style="font-size:16px;line-height:1.5;color:#3b4754">{msg}</p>'
'<p><a href="/" style="color:#0083ad;text-decoration:none">← Back to Upbeat Bytes</a></p></div>'
'<p><a href="/" style="color:#0083ad;text-decoration:none">← Back to upbeatBytes</a></p></div>'
)
return HTMLResponse(html)
+9 -9
View File
@@ -59,7 +59,7 @@ def send_email(to: str, subject: str, text: str, html: str | None = None, reply_
def send_feedback(to: str, category: str, message: str, contact: str | None, who: str) -> None:
"""Notify the admin of new user feedback (plain text is plenty here)."""
subject = f"Upbeat Bytes feedback · {category}"
subject = f"upbeatBytes feedback · {category}"
reply = contact or "(none given)"
text = (
f"New feedback ({category})\n"
@@ -74,14 +74,14 @@ def send_feedback_reply(to: str, reply_text: str, reply_html: str | None, origin
"""Reply to a reader's feedback from the admin inbox. Sends multipart
text/plain + text/html (the HTML is the pre-sanitized Markdown render). Quotes
their original note for context; exposes no analytics/account details."""
subject = "Re: Your Upbeat Bytes feedback"
subject = "Re: Your upbeatBytes feedback"
quoted = "\n".join("> " + line for line in (original_message or "").splitlines())
text = (
f"{reply_text}\n\n"
"\n"
"In reply to your note to Upbeat Bytes:\n"
"In reply to your note to upbeatBytes:\n"
f"{quoted}\n\n"
"Thanks for reaching out.\nUpbeat Bytes\n"
"Thanks for reaching out.\nupbeatBytes\n"
)
body_html = None
if reply_html:
@@ -90,9 +90,9 @@ def send_feedback_reply(to: str, reply_text: str, reply_html: str | None, origin
'<div style="font-family:-apple-system,Segoe UI,Roboto,Helvetica,Arial,sans-serif;'
'color:#16263a;font-size:15px;line-height:1.5">'
f"{reply_html}"
'<p style="color:#5d6b78;font-size:13px;margin-top:20px">In reply to your note to Upbeat Bytes:</p>'
'<p style="color:#5d6b78;font-size:13px;margin-top:20px">In reply to your note to upbeatBytes:</p>'
f'<blockquote style="color:#5d6b78;border-left:3px solid #e8e3d8;margin:0;padding-left:12px">{oq}</blockquote>'
"<p>Thanks for reaching out.<br>— Upbeat Bytes</p></div>"
"<p>Thanks for reaching out.<br>— upbeatBytes</p></div>"
)
# Route the reader's reply to our chosen inbox (never back to the reader).
cfg = _cfg()
@@ -102,9 +102,9 @@ def send_feedback_reply(to: str, reply_text: str, reply_html: str | None, origin
def send_magic_link(to: str, link: str) -> None:
"""Send a calm, single-purpose sign-in email."""
subject = "Your Upbeat Bytes sign-in link"
subject = "Your upbeatBytes sign-in link"
text = (
"Welcome back to Upbeat Bytes.\n\n"
"Welcome back to upbeatBytes.\n\n"
f"Tap to sign in:\n{link}\n\n"
"This link works once and expires in 15 minutes.\n"
"If you didn't request it, you can safely ignore this email."
@@ -113,7 +113,7 @@ def send_magic_link(to: str, link: str) -> None:
html = (
'<div style="font-family:-apple-system,Segoe UI,Roboto,Helvetica,Arial,sans-serif;'
'color:#16263a;line-height:1.6">'
"<p>Welcome back to <strong>Upbeat Bytes</strong>.</p>"
"<p>Welcome back to <strong>upbeatBytes</strong>.</p>"
f'<p><a href="{safe}" style="display:inline-block;background:#0083ad;color:#fff;'
'text-decoration:none;padding:11px 20px;border-radius:999px;font-weight:600">'
"Sign in</a></p>"
+1 -1
View File
@@ -78,7 +78,7 @@ _RESPONSE_FORMATS = (
)
SYSTEM_PROMPT = """You classify article metadata for Upbeat Bytes, a calm news digest.
SYSTEM_PROMPT = """You classify article metadata for upbeatBytes, a calm news digest.
The bar is NOT "is this happy?" it is "will a reader finish this calm or a little better, never worse?" ACCEPT stories that are calm, neutral, insightful, or uplifting: they inform, teach, delight, or show progress or benefit. Neutral-but-absorbing is welcome a discovery, a clear explainer, a clever build or gadget, a fascinating bit of science, space, nature, design, or culture, a genuinely useful insight even when it isn't "feel-good."
+13 -13
View File
@@ -20,7 +20,7 @@ def _tag(name: str, content: str | None, attr: str = "property") -> str:
def render_share_page(article: dict, base_url: str, summary: str | None = None,
explanation: dict | None = None) -> str:
aid = article["id"]
title = (article.get("title") or "Upbeat Bytes").strip()
title = (article.get("title") or "upbeatBytes").strip()
why = (article.get("reason_text") or article.get("description")
or "A calm, constructive story worth your attention.").strip()
source = (article.get("source_name") or "the source").strip()
@@ -39,7 +39,7 @@ def render_share_page(article: dict, base_url: str, summary: str | None = None,
twitter_card = "summary_large_image" if image else "summary"
meta = "\n".join(filter(None, [
_tag("og:site_name", "Upbeat Bytes"),
_tag("og:site_name", "upbeatBytes"),
_tag("og:type", "article"),
_tag("og:title", title),
_tag("og:description", why),
@@ -138,7 +138,7 @@ def render_share_page(article: dict, base_url: str, summary: str | None = None,
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{escape(title)} · Upbeat Bytes</title>
<title>{escape(title)} · upbeatBytes</title>
<meta name="description" content="{escape(why)}">
<link rel="canonical" href="{escape(page_url)}">
<link rel="icon" href="/favicon.svg">
@@ -194,7 +194,7 @@ def render_share_page(article: dict, base_url: str, summary: str | None = None,
</style>
</head>
<body>
<div class="bar"><div class="inner"><a href="/"><img src="/logo.svg" alt="Upbeat Bytes"></a><button class="back" type="button" data-back aria-label="Go back"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M19 12H5M11 6l-6 6 6 6" fill="none" stroke="currentColor" stroke-width="2.1" stroke-linecap="round" stroke-linejoin="round"/></svg>Back</button></div></div>
<div class="bar"><div class="inner"><a href="/"><img src="/logo.svg" alt="upbeatBytes"></a><button class="back" type="button" data-back aria-label="Go back"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M19 12H5M11 6l-6 6 6 6" fill="none" stroke="currentColor" stroke-width="2.1" stroke-linecap="round" stroke-linejoin="round"/></svg>Back</button></div></div>
<main class="wrap">
<article class="card">
{media}
@@ -207,9 +207,9 @@ def render_share_page(article: dict, base_url: str, summary: str | None = None,
<div class="actions">
<a class="primary" href="{escape(src_url)}" target="_blank" rel="noopener" data-src-click>Read the full story at {escape(source)}</a>
<button class="secondary" type="button" data-share>Copy link</button>
<a class="secondary" href="/news">Explore Upbeat Bytes </a>
<a class="secondary" href="/news">Explore upbeatBytes </a>
</div>
<p class="note">Upbeat Bytes summarizes in its own words and links to the original publisher it doesn't host the article.</p>
<p class="note">upbeatBytes summarizes in its own words and links to the original publisher it doesn't host the article.</p>
</div>
</article>
</main>
@@ -275,7 +275,7 @@ def render_digest(items: list[dict], base_url: str, brief_date: str | None) -> s
)
meta = "\n".join(filter(None, [
_tag("og:site_name", "Upbeat Bytes"),
_tag("og:site_name", "upbeatBytes"),
_tag("og:type", "website"),
_tag("og:title", "Today's good news, summarized"),
_tag("og:description", intro),
@@ -292,7 +292,7 @@ def render_digest(items: list[dict], base_url: str, brief_date: str | None) -> s
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Today's good news, summarized · Upbeat Bytes</title>
<title>Today's good news, summarized · upbeatBytes</title>
<meta name="description" content="{escape(intro)}">
<link rel="canonical" href="{page_url}">
<link rel="icon" href="/favicon.svg">
@@ -324,12 +324,12 @@ def render_digest(items: list[dict], base_url: str, brief_date: str | None) -> s
</style>
</head>
<body>
<div class="bar"><div class="inner"><a href="/"><img src="/logo.svg" alt="Upbeat Bytes"></a></div></div>
<div class="bar"><div class="inner"><a href="/"><img src="/logo.svg" alt="upbeatBytes"></a></div></div>
<main class="wrap">
<h1>Today's good news</h1>
<p class="lede">{escape(intro)}{f' · {escape(brief_date)}' if brief_date else ''}</p>
{cards}
<p class="more"><a href="/news">Browse more on Upbeat Bytes </a></p>
<p class="more"><a href="/news">Browse more on upbeatBytes </a></p>
</main>
</body>
</html>"""
@@ -339,7 +339,7 @@ def render_not_found(base_url: str) -> str:
return f"""<!doctype html>
<html lang="en"><head><meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Story not found · Upbeat Bytes</title><link rel="icon" href="/favicon.svg">
<title>Story not found · upbeatBytes</title><link rel="icon" href="/favicon.svg">
<style>
body {{ margin:0; min-height:100vh; display:flex; flex-direction:column; align-items:center;
justify-content:center; gap:14px; text-align:center; padding:40px;
@@ -348,8 +348,8 @@ def render_not_found(base_url: str) -> str:
a {{ color:#006b8e; }}
</style></head>
<body>
<img src="/logo.svg" alt="Upbeat Bytes" style="height:44px">
<img src="/logo.svg" alt="upbeatBytes" style="height:44px">
<h1 style="font-family:Georgia,serif;font-weight:600">That story isn't here</h1>
<p style="color:#5d6b78">It may have moved on the good news refreshes often.</p>
<a href="/"> Back to Upbeat Bytes</a>
<a href="/"> Back to upbeatBytes</a>
</body></html>"""
+2 -2
View File
@@ -43,6 +43,6 @@ def test_reply_to_uses_env_inbox(monkeypatch):
def test_reply_to_falls_back_to_from(monkeypatch):
_arm(monkeypatch)
monkeypatch.delenv("GOODNEWS_REPLY_TO_EMAIL", raising=False)
monkeypatch.setenv("GOODNEWS_SMTP_FROM", "Upbeat Bytes <hello@upbeatbytes.com>")
monkeypatch.setenv("GOODNEWS_SMTP_FROM", "upbeatBytes <hello@upbeatbytes.com>")
es.send_feedback_reply("reader@x.com", "hi", None, "o")
assert _FakeSMTP.last["Reply-To"] == "Upbeat Bytes <hello@upbeatbytes.com>"
assert _FakeSMTP.last["Reply-To"] == "upbeatBytes <hello@upbeatbytes.com>"