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>
Rebuilt /quote to CD's QOTD v3 — a warm letter card with a dashed deckle frame,
an overlapping wax-seal date stamp, a giant faded quote-mark watermark, the quote
in Playfair Display italic, attribution, a "What it means" note, and Copy/Share.
Wired to live /api/quote/today (date → seal, text, author/work, meaning).
Uses our HubShell (HubBar + Back + footer + account icon) per the brief. Self-
hosted Playfair Display (roman+italic, OFL) for the signature quote/watermark/
seal; Newsreader for the body serif + Hanken for sans labels (kept to our stack
to avoid font sprawl). Rose accent matches CD (#a4607a family).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Rebuilt /art to CD's "The Story" hybrid: a warm-tan editorial card with the
Daily-Art purple accent. Left = guide write-up (kicker · title · attribution ·
the museum-guide blurb · Collection/Rights · View at The Met). Right = the framed
piece (our existing frame + thickness customizer, on one line each) on a deeper-
tan ground, then a divider, the "Colors in this piece" palette, and Share /
Download (Download gated on public-domain license; image is same-origin so it
just works). Self-hosted Space Mono for the curatorial micro-labels; Newsreader
display + Hanken UI like the rest of the hub.
Mobile: the left/right wrappers collapse (display:contents) so the blocks reflow
to head → artwork → writeup → controls — the art sits high, seen before it's read.
Kept HubBar, the Back button, and the fullscreen lightbox (incl. landscape rotate).
Save-to-account + framed-composite export deferred to Phase 2.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Restore desktop/tablet two-up Small Joys (arrows rotate which two show); the
swipe rail is now phone-only (≤520). One isNarrow flag drives both the DOM
(2 vs 3 cards) and the layout, so they can't disagree.
- Contain the overflow that let the phone rail widen the whole page: min-width:0
+ max-width:100% down the chain (bento/rightcol/pair-wrap/joys-shelf/joys),
mobile bento column to minmax(0,1fr), and overflow-x:clip on .page as a seatbelt.
- Read-time badge: overflow-wrap:anywhere + line-height so a long
"1 min brief · ~N min full story" never causes width pressure.
- Self-host Work Sans (latin variable woff2, OFL) and apply it to the cards —
the bolder/darker look from CD's mockup; headings stay Newsreader.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Rebuilds the design handoff's preferred Frame A: Newsreader serif headlines + Hanken
Grotesk body (both self-hosted, OFL, no Google hotlink), warm cream canvas, per-card
accent tints (News teal, Art plum, Play amber, Moment green), bento grid.
- Uses our real /logo.svg instead of the mock's Bricolage wordmark + sunrise.
- Wired live: Good News pulls the top headline/summary/photo (respects the saved
Closer-to-Home filter); Daily Art pulls today's Met piece (title/artist/year + thumbnail).
- Hidden prototype (noindex), spacing tuned per the /home2 pass (hero pulled up, more air
before the bento). Sits beside /home2 for comparison.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Self-hosted Manrope (OFL) as the hub sans; nav lighter (weight 500, soft slate, not all
"on"). Logo up to 58px.
- News card: photo on top + headline below, and it now respects the reader's saved
Closer-to-Home filter (goodnews:home/homeScope) so the headline matches their Brief.
- Art card: rectangular cover-cropped thumbnail on the LEFT (crops ragged scan edges),
text on the right — variety against the photo-top news card.
- Play/Daily Moment: tinted backgrounds, bigger centered icon+title, blurb left-aligned.
- /fonts/* + /textures/* served immutable (Caddy live + snapshot).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Woods are now built from four real mitered rails clipped to 45° joints, each showing the
grain along its length — horizontal on top/bottom (rotated texture wood-grain-h.jpg),
vertical on the sides — so the grain TURNS at every corner like real cut moulding. Each
rail carries a directional bevel (lit top-left) for a rounded profile; per-species recolor
moved onto the rails. Rendered via a Svelte snippet in both page + full-screen frames.
- Gold/Silver gain fine brushed-metal striations over the existing sheen.
- New asset: wood-grain-h.jpg (the CC0 texture rotated 90°), credited in textures/CREDITS.txt.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Replace procedural grain with a real fine-grain wood texture (CC0, Poly Haven —
static/textures/wood-grain.jpg). The three woods share it, recolored per species via
CSS filter on ::before: Walnut (as-is dark), Oak (lightened/warmed), Mahogany (new —
deepened/reddened). Miters now apply to all three woods; mat sits between texture and
miters via z-index.
- Full-screen sizing fixed: image capped (max-height 66vh) so the bottom rail always
fits — no more cutoff. Rail/mat dialed back to ~1:1 wood:white (was mat-heavy), only
slightly larger than the page so the proportion matches the small view.
Texture credit (CC0, no attribution required) recorded in static/textures/CREDITS.txt.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The deploy pipeline runs from the working tree, so a wave of shipped features
had never been committed. This snapshots git to what's actually running.
SEO impression recovery (live + verified):
- Duplicate /a/{id} now 301-redirect to their canonical twin instead of 404
(a hard 404 silently dropped already-indexed URLs and tanked impressions).
- Dedup representative selection reworked: accepted/serveable -> established
rep (URL stability) -> quality score, so an accepted page never retires to a
rejected rep and an indexed canonical doesn't churn when a newer twin arrives.
- HEAD /a/{id} returns the same status as GET (api_route GET+HEAD) instead of
falling through to the static mount and 404ing.
- `dedup --force-recluster`: cycle-locked, model-free re-cluster to re-apply the
policy to the existing corpus (shared cycle_lock context manager).
- CLI honors GOODNEWS_DB for its default --db (was silently ignored).
Publishing Desk (admin tool to post highlights to X via Web Intents):
- publishing.py queue/rank/handle-resolution; admin UI; full searchable emoji
picker (bundled data, no CDN) for the blurb editor.
Play games + site:
- Bloom (word-wheel), Memory Match, daily ritual set, Zen Den (dev-gated).
- English-only language gate; source prospecting; paywall + dedup hardening.
Tests: full suite green (349). Ignores tightened (node_modules, data/*.db).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Per Codex + Jay: the SW was added for nice-to-have PWA/offline caching, but it
sat in the boot path and put first loads at risk (post-deploy tiny-chunk stalls
of 4-10s — fast HTML, then delayed chunks). For a young site where a handful of
visitors a day IS the audience, a broken first impression is a huge share of
traffic. The site's value doesn't need offline caching; browser HTTP cache +
the Cloudflare edge are enough.
Removed cleanly (not just deleted — that strands the old worker on existing
clients):
- Delete src/service-worker.js → SvelteKit stops auto-registering.
- static/service-worker.js is now a one-shot KILL SWITCH: takes over, wipes all
caches, unregisters itself, no fetch handler (requests go straight to network/
browser cache). Served no-cache so existing clients pick it up.
- app.html boot script unregisters any worker + clears caches on load, as a
backstop so no returning visitor stays stuck on the old boot path.
The boot seatbelt (timeout card, preloadError reload-once, telemetry) stays —
that, not the SW, was the real blank-screen protection. Build clean, 11 vitest.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A calm /play space — "after the brief, a small thing to enjoy." Framework-ready
for more games (Word Search next; zen/coloring later).
* Daily Word (5 letters / 6 guesses) + Long Word (6 / 7) — same Wordle mechanic,
Upbeat Bytes flavor (no "Wordle" in the UI). Hopeful answers; after solving, a
one-line "why this word matters."
* LLM proposes, code disposes: answers are picked deterministically by date-seed
from a hand-curated hopeful pool that's pre-validated ⊆ the guess dictionary
(always typeable), avoiding recent repeats; the LLM only adds the optional
"why" (with fallback). daily_puzzles(date, game, variant, payload) stores them
so everyone gets the same daily; the cycle pre-generates with the "why".
* Bundled guess dictionaries (words-5/6.json, ~12.6k/22.4k) for client-side guess
validation — never the LLM. Answer lightly obfuscated (base64) in the payload.
* Private, gentle stats (played/solved/streak, guess distribution); spoiler-free
emoji-grid share. No leaderboard, no timer, no streak-loss drama.
* Play in the bottom nav (replacing Browse, still on the lane rail) + the header.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Two calm returning-reader features.
Since-last-visit (Highlights companion, not a nav lane — per Codex):
* queries.feed gains a `since` filter; GET /api/since?ts= returns the count +
a few accepted/non-dup/visible articles discovered since the reader's last
visit (boundary-respecting; invalid/future ts → 0, no error).
* Home stores last_seen in localStorage (reads prev, then stamps now); on
Highlights, a gentle "Since you were last here, N new calm reads came in"
note with a "See what's new" reveal of a compact inline section. Dismissible.
No badges, no unread counts, no "missed" language.
PWA:
* Real PNG icons (192/512 + full-bleed maskable) rasterized from favicon.svg;
manifest fixed (azure theme to match the brand, PNG icons); apple-touch-icon.
* Minimal service worker: precache the app shell, always-fresh API + /a/ pages.
* Gentle, dismissible install banner (beforeinstallprompt → Install; iOS → the
Share → Add to Home Screen hint). Never nags.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Per Codex — cleanest, most brand-consistent option. Rasterized logo.svg to a
small transparent logo-email.png (360px wide, shown at 180px for retina), served
at /logo-email.png. alt="Upbeat Bytes" keeps the brand if a client blocks
images. SVG isn't email-safe; a hosted PNG is the standard approach.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Make the summary pages discoverable so traffic compounds passively:
- /today: a server-rendered, shareable + indexable digest of today's brief —
each item's title (→ /a summary), our summary, and a source link. OG/Twitter
meta + self-canonical.
- /sitemap.xml: dynamic — home, /today, and every accepted non-duplicate /a page
with lastmod. robots.txt allows all and points to it.
- Home (SPA shell) gains canonical + OG/Twitter tags for cleaner unfurls.
- Caddy routes /today + /sitemap.xml to the API. 133 tests pass.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Header logo sized up to read clearly (54px desktop / 46px mobile, bars to match).
- Self-host Inter (variable, latin) — no external font calls — and use it for
the category tags as uppercase Light (300) kickers with tight tracking, for a
clean, polished label feel.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Add the real Upbeat Bytes lockup (logo.svg) and use it in the header,
replacing the placeholder inline mark + text wordmark.
- New square favicon: the logo's rising sun (bright gold) on azure.
- Recolor the design system around the logo's #0083ad azure: rename the
--sage* accent vars to --accent*, with deep/soft azure tints; navy ink
(#16263a) echoing the logo's "Bytes"; cool slate muted text; a deep gold
for text-weight accents plus --gold-bright for decorative fills; warm
sand paper background. No urgency colors.
- Retint the hero image overlay and the no-image card gradients to match.
- theme-color → azure.
Built clean; frontend tests/build pass.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Logo mark: SVG rising-dots wave (sage dots + warm gold peak = 'upbeat bytes'),
used as favicon/PWA icon and in the header.
- Header: full-width app bar — mark + mixed-type wordmark (Upbeat serif ink /
Bytes sans sage) on the left, housed Boundaries/History utility cluster on the
right (desktop). No more floating text links.
- Mobile: fixed bottom tab bar (Today / Browse / You); utilities move into a
'You' sheet. One-handed, modern, calm.
- Browse: moods stay the primary front door; added a quiet 'Explore by topic'
section (existing topics) below the content — selecting a topic loads its feed.
- Layout trimmed (header now in-page, full width); footer keeps clearance for the
bottom bar.
Phase A of the consensus pass; Phase B (add technology + learning topics and
reclassify) is next. Live site untouched until publish.sh.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Masthead, page <title>, PWA manifest name, and footer now say 'Upbeat Bytes';
README headline updated. The internal Python package/CLI stay 'goodnews' (no
functional reason to rename, and it avoids churn).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- New frontend/ SvelteKit static SPA (Svelte 5), served by FastAPI from
frontend/build (falls back to the legacy page if unbuilt).
- Calm design system: cream/sage palette, serif headlines, generous space,
no urgency colors, gentle motion (respects prefers-reduced-motion).
- Home screen: mood-mode nav (Today/Wonder/People Helping/Solutions/Light
Only/Grounded), the daily brief as a hero + remaining four, browsable mood
lanes, an explicit calm end-state, inline Not today / Less like this / Hide
affordances, and device-local Calm Filters mirroring goodnews/filters.py.
- Backend: moods.py + GET /api/moods (single source of truth for the modes);
FilterPrefs gains max_cortisol/max_ragebait ceilings (for Light Only).
- Push categorical filters (include/mute topics+flavors, ceilings) into SQL in
queries.feed so low-ranked-but-matching items (e.g. discovery for Wonder)
are not truncated by ranking; only avoid-terms stay a Python pass.
- PWA manifest + icon (installable; offline deferred per plan).
- Multi-stage Dockerfile builds the site then serves it from the API.
- Tests: queries.feed categorical filters (63 total). README updated.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>