Commit Graph

14 Commits

Author SHA1 Message Date
thejayman77 667b1a82c3 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>
2026-06-28 20:01:20 -04:00
thejayman77 2cfffdfd6a NEWS RELAUNCH CUTOVER: promote the hub to /, feed to /news, go public
The big flip. /home3 (hub) becomes /; the feed lives at /news; both indexable.
- PROMOTE: routes/+page.svelte is now the hub (was the interim NewsFeed wrapper);
  noindex removed; "Read more good news" → /news. routes/home3 + home2 deleted.
- routes/+page.js: redirects legacy root-query links (/?view=latest, /?tag, /?source,
  /?q, /?view=today→highlights) to /news before the hub renders (no flash).
- /news: noindex dropped (route meta + Caddy @newsHidden removed); now public.
- LINKS: HubBar brand/Home → /, News default → /news; HubShell/art/play back → /;
  account Following + share.py Explore/Browse/source → /news.
- FOOTER: one shared Footer.svelte (motto + Send feedback + slot) across Hub/News/
  Play/Art/HubShell/Account/Zen; global layout footer removed (FeedbackModal stays).
- SITEMAP: + /news /art /play /word /quote /onthisday; cap 5k→50k; gated on
  has-summary; paywalled excluded; HEAD now 200 (api_route GET+HEAD).
- Head-patcher: /news entry. PWA + shell description broadened to the hub.
- Caddy: @newsHidden dropped; @hidden now admin-only (word/quote/onthisday public);
  /home2,/home3 → / 301. Mirrored to deploy/caddy snapshot.

425 backend + 36 frontend tests green; build clean; Caddy valid.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 19:16:43 -04:00
thejayman77 59ff48ae90 Game share-loop: instrument funnel, deep-link shares, /play metadata
Sharpen the existing daily-game share loop into something measurable (per Codex's
"instrument what you have, then feed people into it" plan), ahead of a Show HN launch.

Analytics:
- Per-game funnel events <game>_{arrival,started,completed,shared} (article_id=0).
  arrival = landed via a shared link (utm_source=game_share); started = first move
  (guess/find/flip); completed = solved/cleared/Full Bloom; shared = on share success.
- trackVisit() moved into the global layout so direct /play landings count; the
  server-rendered /a/ share page now creates a visitor token + sends a daily visit
  beacon (first-time /a/-only visitors were previously dropped).
- Admin "Games funnel" panel: arrivals / engaged / completed / shared, per game.

Sharing:
- Memory Match gains a Share button (it was the only game without one).
- All shares deep-link to the exact game+variant with a full https:// URL +
  utm_source=game_share (gameShareUrl helper), instead of a bare /play.
- "shared" is counted only after navigator.share()/clipboard.writeText() succeeds.

/play social metadata:
- /play served homepage canonical/OG (static SPA, ssr=false). postbuild script
  patches build/play.html's head to /play canonical/title/description/OG; fails the
  build if the homepage tags drift. Caddy try_files now serves {path}.html so /play
  is served from the patched file (snapshot in deploy/caddy/).

Tests: backend 352, frontend 27.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 16:22:06 -04:00
thejayman77 337dc3f901 Article pages: structured "Why it belongs" editorial read
Per Codex — make /a/<id> feel like Upbeat Bytes has editorial judgment, not just
a summary wrapper. Trust-building, short, not an essay.

* article_summaries gains what_happened / why_matters / why_belongs (+ migration).
* summarize.explain_article: a separate, fallback-able LLM pass producing three
  short notes (parsed from a labelled WHAT/MATTERS/BELONGS format). generate_summary
  now stores them alongside the summary, and tops up older summaries on next view.
  get_explanation returns them only when all three are present.
* API: share_page + /api/summary expose the explanation.
* share.py: renders the three-part section (accent rule) when complete; otherwise
  the single "Why it's here" reason line is the calm fallback. The page polls and
  swaps in both the summary and the section as they cache.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 20:05:26 -04:00
thejayman77 8c52582ae3 In-feed Back button + clickable source on the article page
* Back button on feed views: drilling into a tag or source from a card now
  remembers where you came from (a small history stack), and a "← Back" appears
  in the view header to return there — chains of drill-ins included. Top-level
  nav (rail/bottom bar) resets the history.
* Article page: the source name is now a link into that source's in-app feed
  (/?source=<id>); the SPA reads the param on load and opens the source view
  (label falls back to the loaded feed's source name). Completes the
  "cards-only v1" — source is clickable on /a/ too now.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 08:38:44 -04:00
thejayman77 86a6bd3b45 Card + article-page polish
* Placeholder: bold, slightly larger initial letter on the topic word; make
  health (teal) and environment (leaf green) clearly distinct and show more hue
  in the deepened word so they're easy to tell apart.
* Article page: the source name was chopped to its first word ("Read the full
  story at The") — use the full publisher name; open the source link in a new
  tab so upbeatbytes.com stays put.
* Use the new SVG back arrow on the account and admin top bars (matching the
  article page) instead of the old "←" glyph.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 15:05:32 -04:00
thejayman77 c64d0fda09 Article Back button: bolder SVG arrow, vertically centered
The bare "←" glyph rendered tiny and sat low (baseline-aligned). Swap it for a
crisp SVG arrow and lay the button out as a centered inline-flex (arrow + label),
so it reads juicier and sits properly centered in the top bar.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 21:56:40 -04:00
thejayman77 67d6b82ed3 Article page: add a Back button in the top bar
The server-rendered /a/ summary page had no in-page way back — the only option
was the browser's back button, which feels unfinished. Add a "← Back" control on
the right of the top bar (desktop + mobile). It uses history.back() when the
reader came from within the site, and falls back to the home page for visitors
who arrived via a shared link (no useful history).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 21:52:43 -04:00
thejayman77 ea58039fb9 SEO flywheel: /today digest, sitemap, robots, home OG tags
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>
2026-06-05 19:37:05 +00:00
thejayman77 427210ac3e User feedback + expanded privacy-respecting admin stats
Feedback:
- feedback table; POST /api/feedback (anonymous-ok, optional category/email,
  honeypot + per-day flood cap) stores + emails the admin; GET /api/admin/feedback.
- Shared feedback store + FeedbackModal; a speech-bubble opens it from the desktop
  header, the mobile top bar (logo moves left), the footer, and /account. Feedback
  section in /admin.

Stats (additive, same privacy model — no IP/UA/referrer/raw terms):
- Event vocab: summary_viewed (fired on /a load), full_story (card → source),
  not_today/less_like_this/hide_topic, replace_used/replace_none, paywall_replace,
  paywalled_source_open. Card title/image opens /a (no double-count); history
  records via keepalive so it survives the nav.
- Dashboard: Accounts card (counts only), reading funnel (summary→source rate),
  emotional-mix & friction, paywall, returning-visitor buckets. (Health metrics
  deferred to a future monitoring dashboard.) 131 tests pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 12:58:49 +00:00
thejayman77 cfde4e22db Summary briefing layer: Today pre-summarized, /a is the canonical read
Make summaries the core reading experience (summary-first, source-forward):
- Cycle pre-warms summaries for Today's 7 (idempotent → only new ones hit the LLM).
- /api/brief items carry their cached summary; Today cards (hero + tiles) show it
  inline, so Today reads as a calm briefing.
- Card title/image now open the /a summary page (the canonical artifact), with a
  visible "Full story" link straight to the source on every card (the escape hatch).
- /a gains related-grouping chips + a Copy-link/share control.
- Tighten the summary prompt: original, factual, no quotations / no close paraphrase.
Long tail stays lazy+cached. No article bodies stored. 129 tests pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 19:48:32 +00:00
thejayman77 1a778e1334 Admin step A: privacy-respecting first-party event logging
- events table (kind, article_id, visitor_hash, day) with a UNIQUE key that dedups
  to one row per visitor-day — caps volume and makes counts mean distinct
  visitor-days. NO ip/ua/referrer/url. Groupings derived from article_id at query
  time, never stored.
- POST /api/events (public): whitelisted kinds (visit/open/share_ub/copy_source/
  native_share/source_click); visitor token hashed server-side (never raw).
- Frontend analytics.js: random localStorage visitor token; track() via sendBeacon;
  visit once/day; open on article click; share_ub/copy_source/native_share from the
  share menu; /a landing pages fire source_click. 127 tests pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 18:21:49 +00:00
thejayman77 1d71575982 Share pages: lazy, cached, our-own-words article summaries
The /a/<id> page now carries an original short summary so it stands on its own,
without republishing the publisher's article:
- summarize.py: transient SSRF-guarded fetch of the article text → local LLM
  writes a 2-4 sentence ORIGINAL summary (our words). Cached in article_summaries
  forever; we store only our summary, never the body. Generated lazily (only for
  shared/viewed articles), de-duped so concurrent hits don't double-generate.
- /a serves cached-or-pending; when pending it shows a calm "summary on its way,
  read at {source}" note and self-polls /api/summary/<id>, swapping the summary
  in the moment it's ready (never blocks the page on the batch-tier LLM).
- Share menu warms generation on open so recipients usually get the rich version.
- Container reaches the arbiter at arbiter:8080 over caddy_web (LLM env added to
  the API container). 124 tests pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 18:08:40 +00:00
thejayman77 3d9900cdfc Article sharing: branded /a/<id> page + share menu
- Server-rendered /a/<id> "pointer" page (FastAPI): OG/Twitter meta from the
  article's title/why/image, self-canonical (UB pages are the canonical for
  themselves), a prominent "Read the full story at {source}" button and a quiet
  "Explore more on Upbeat Bytes". No article body. Unknown/rejected/duplicate/
  malformed ids → a calm 404 (no stack traces). Text-card preview when no image.
- Caddy routes /a/* to the API.
- Card Share control → menu: native Share… (where available), Copy link (the UB
  card page), Copy source link. Boundary actions now hide-on-hover via a .mute
  class so Save/Replace/Share stay visible. 122 tests pass.

(Event tracking for shares/opens lands with the analytics step next.)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 15:27:30 +00:00