14 Commits

Author SHA1 Message Date
thejayman77 c350a2713b git: stop tracking the runtime image cache (data/img_cache)
The cache-feature commit swept warmed WebPs into Git via 'git add -A' — 257 tracked
files (~12 MB) + churn making prod checkouts dirty. Add data/img_cache/ to .gitignore
(beside art_cache) and untrack with 'git rm -r --cached' (live files retained; no
history rewrite). [Codex audit blocker]

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 12:14:32 -04:00
thejayman77 ee43bb0df6 analytics: filter known-bot User-Agents at /api/events (honest visitor counts)
Many modern crawlers (AI scrapers, headless Chrome, link-preview fetchers) run JS and
fire the visit/summary_viewed beacon, inflating "visitors" even though there's no
human discovery channel. Apply queries.is_bot_ua() at /api/events — the same filter
the load-error beacon uses — so honest bot UAs (GPTBot, AhrefsBot, headless Chrome,
python/curl, …) are dropped before recording. Response is identical so a bot can't
detect it. Counts read lower but truer going forward (past rows unchanged). Won't catch
UA-spoofing bots; that needs a heavier heuristic. Tests: bot UAs dropped, real browser
counted; existing event tests send a real UA (default client UA contains "python").

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 11:19:51 -04:00
thejayman77 27022108b4 caddy: block vuln-scanner probe paths (no-PHP/WP stack) → 403, not the SPA shell
Path-only @junk matcher on upbeatbytes.com (*.php, /wp-*, /.env, /.git, /phpmyadmin,
/vendor, etc.) returns 403 instead of falling through try_files to a 200 SPA shell.
Never matches by User-Agent, so real users + Googlebot/Bing are untouched. Applied to
the live Caddyfile (validated + reloaded) and mirrored into the repo snapshot.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 10:11:57 -04:00
thejayman77 86d9897113 ui: reserve the scrollbar gutter so the top bar stops shifting between pages
Pages tall enough to scroll showed a ~15px scrollbar; short pages didn't — so the
centered top bar jumped left/right as you navigated. scrollbar-gutter: stable on html
(SPA app.css + the server-rendered share pages) keeps the layout width constant. No-op
on overlay-scrollbar platforms (mobile), which never shifted.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 04:52:59 -04:00
thejayman77 3740e09d02 share pages: carry the real HubBar toolbar (consistency with the SPA)
The server-rendered /a/<id> and digest pages predated "HubBar everywhere" and showed
a stripped bar (logo + a bespoke Back pill). They can't run the Svelte component, so
add a hand-kept static replica of HubBar (logo + News/Play/Art nav + account glyph +
mobile burger/drop-panel) plus HubShell's borderless ← Back. A signed-in reader's
avatar paints from the same localStorage cache HubBar uses. /a/<id> now looks like any
detail page (/art, /word). Reusable _top_bar_html/_TOP_BAR_CSS/_TOP_BAR_JS/_back_link
helpers; applied to both share pages. Kept in sync with HubBar.svelte by hand (noted).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 04:26:01 -04:00
thejayman77 c33dad9832 images: add Pillow to the web extra so the API container downscales too
The on-demand /api/img path runs in the container (only fastapi+uvicorn), so without
Pillow it fell back to caching the original full-size bytes instead of a downscaled
WebP. Add Pillow>=10 to the web extra. The host cycle already had it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 20:30:05 -04:00
thejayman77 f4a7a7bcc7 news relaunch step 1: extract the feed into NewsFeed.svelte, mount at / and /news
Pure refactor, no visible/behavioral change. The ~1065-line feed moves verbatim
from routes/+page.svelte into lib/components/NewsFeed.svelte; both routes/+page
and the new routes/news render it. Link generation is base-aware (feedBase()):
on `/` it builds `/?…` exactly as today (bug-for-bug parity); on `/news` it
builds `/news?…` so /news is self-coherent. At cutover, `/` becomes the hub and
the feed lives only at /news.

/news is kept hidden during the transition (noindex, follow) so we never publish
a duplicate indexable feed: route <meta robots> + a Caddy @newsHidden X-Robots-Tag
(follow, so link equity flows). Removed at cutover, when /news enters the sitemap.

27/27 frontend tests green; build clean; /news.html prerenders.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 14:11:06 -04:00
thejayman77 485c4a7805 WOTD: commit cached pronunciation clip for 'beauty' (matches tracked audio)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 13:23:02 -04:00
thejayman77 b174d8d2a7 home3/hub: shared HubBar with mobile hamburger + Art-card mobile crop fix
#2 Mobile top bar → hamburger: extracted the editorial bar (brand + nav + new
collapsible hamburger drop-panel) into a shared lib/components/HubBar.svelte,
used by both /home3 and HubShell (the /word /quote /onthisday detail pages), so
there's one nav to maintain/audit. Full horizontal nav ≥721px; hamburger + drop
panel ≤720px. Escape + link-click close it; panel is hidden on desktop as a
safety. Removed the duplicated bar markup/CSS from home3 + HubShell.

#1 Mobile layout / Art card: on phones the Art card now stacks image-first with
the painting in a proper 3:2 frame (aspect-ratio) instead of a stubby fixed
130px band that cropped the work to a sliver. Also drop the News gist's bottom
fade once the bento is single-column (natural height = no truncation, so the
fade was just dimming the final line), and let the joys header wrap on phones.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 21:55:36 -04:00
thejayman77 2e9d81bd86 WOTD: commit cached pronunciation clip for 'repose' (matches tracked audio)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 21:00:01 -04:00
thejayman77 84b1fb514f Small joys: Codex audit #2 fixes (route resolution, noindex, sense/tone, exclude-current re-pick)
- Admin joy item route moved to /api/admin/joys/{kind}/items/{item_id} so the
  /add and /repick verbs resolve to their own routes instead of 422-ing as a
  non-int item id (the launch blocker). Frontend mutate URL updated to match.
- Re-pick now excludes the currently-shown item: the endpoint reads today's
  daily pool_id and passes it as `avoid`, so "Re-pick today" yields a different
  item. Added `avoid` to pick_daily/_candidates across wotd/quote/onthisday.
- WOTD sense selection: the LLM now proposes word + intended part of speech, and
  _lookup prefers that sense (fixes "serene" returning the archaic noun).
- On This Day tone prompt tightened to favor genuinely uplifting events and
  exclude merely procedural/political-administrative ones.
- Caddy @hidden now also noindexes /word /quote /onthisday /admin (+ .html).
- Regression tests: add/repick resolve (401 not 422), add/feature/block/delete,
  re-pick excludes current; WOTD pos-preference + proposal parsing units.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 20:19:02 -04:00
thejayman77 3bde6534e9 Small joys: wire homepage rail to live data + rich pages (/word /quote /onthisday) + admin
- /home3: small-joys rail now reads live /api/word|quote|onthisday/today (placeholders only
  as fallback); each cell links to its detail page.
- HubShell component (shared bar/footer/fonts/tokens) for the hub + detail pages.
- /word: big word, IPA, Listen (cached clip + browser-TTS fallback), definition, sentences.
- /quote: the quote, attribution, and the AI "what it means".
- /onthisday: the date, year + fact, image, summary, source.
- Admin "Small Joys" tab: per-pool list with feature/block/delete/add + re-pick, for all
  three kinds. New admin API: GET/POST /api/admin/joys/{kind}[/{id}|/add|/repick].

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 18:52:38 -04:00
thejayman77 d2a6293a13 Local-first Brief: the landing leads with good news from your home
Per the owner's call (overrides the earlier "Brief sacred" stance): when a home is
set, the homepage opens with local good news first, not global. This is the hook —
you land and see awesome stories from YOUR corner first.

- queries.home_brief: local-first highlights (high/medium-confidence near, blended
  out to country then world so it's always a full, strong set), preferring already-
  summarized stories so the calm read stays rich. Recent window, ranked within tier.
- /api/brief gains a `home` param: private/no-store when set; over-fetches + caps so
  dismissal/boundary filtering never thins it; falls back to global top-up if needed.
- Landing UI: a Local <-> Global toggle ("📍 Near you / 🌍 Everywhere") when a home
  is set, the calm picker invite when not (dismissible), and Change. Default leads
  local; one tap back to the global brief. No home set => exactly today's behavior.

Backend + frontend tests green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 21:36:18 -04:00
thejayman77 9cdcda5e02 Durability pass: tests, clearer diversity/classify behavior, Calm Filters foundation
- Add pytest suite (34 tests) covering scoring thresholds, dedup clustering +
  representative selection + time window, brief source/category diversity,
  avoid-term phrase matching, and text canonicalization/truncation.
- Rewrite _select_diverse with an explicit, tested contract (best-first, one
  per source, backfill, then inject a second category by evicting the
  lowest-ranked pick).
- classify_articles now returns attempted/succeeded/skipped (ClassifyReport) so
  silent model failures are visible in both the cycle and classify output.
- Fix clean_text truncation to stay within max_len (ellipsis no longer
  overshoots).
- New filters.py: canonical FilterPrefs shape (include/mute topics+flavors,
  avoid_terms, pauses) and pure word/phrase-boundary matching engine seeding
  Calm Filters. Not yet wired into the API.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-30 19:07:31 +00:00