Commit graph

3 commits

Author SHA1 Message Date
Emi Griffith
21f7ef4d19 Group regression tests by domain (#215)
Move the flat backend/tests/*.py into domain subfolders so the suite
mirrors the code's concerns:
  data/          climate, grading, scoring, grid, places, store
  web/           api, content, homepage, views
  notifications/ notify, digest, discord (+ dm/interactions/link)
  accounts/      api_accounts
  core/          metrics, singleton, dashboard

conftest.py stays at the tests/ root, so its sys.path setup and shared
fixtures still apply to every subfolder. The four tests that derive repo
paths from __file__ get their depth bumped one level to match their new
location. Pytest discovers the subfolders recursively; the CI command
(python -m pytest backend/tests) is unchanged.

Claude-Session: https://claude.ai/code/session_01XXxmNFy9cZ6Gh8Y9thZn62
2026-07-20 04:50:01 +00:00
Emi Griffith
c1d9fd4021 Hide footer email digest signup for now (#194)
* Hide footer email digest signup for now

Comment out the compact digest signup form in the site footer; restore
by removing the surrounding Jinja comment wrapper.

Claude-Session: https://claude.ai/code/session_01XXxmNFy9cZ6Gh8Y9thZn62

* Update digest test for hidden footer form

The footer signup form is now hidden; assert its absence site-wide.
The /digest endpoint stays live and its tests are unchanged.

Claude-Session: https://claude.ai/code/session_01XXxmNFy9cZ6Gh8Y9thZn62
2026-07-19 22:26:47 +00:00
Emi Griffith
3a66682322 Rebuild the homepage as a distribution landing; add an SMTP seam (#178)
The homepage was a bare tool: a find-bar and an empty panel reading "Find a
location to begin." A visitor arriving from a search result or a shared link
learned nothing about what the product does before deciding to leave.

Rebuild it around the Weekly view, which is untouched:

- Hero with the question as the h1, and a grade card showing a real graded
  example — the most unusual city we're currently tracking, either tail. The
  card's frame and text slots are server-rendered with reserved heights, so
  app.js re-pointing it at the visitor's own place shifts nothing.
- "Unusual right now" strip, CSS scroll-snap, no JS carousel. A cold-tail city
  is force-included whenever one qualifies.
- Stance line, how-it-works, explore cards, and 12 city chips linking into the
  ~1000-page /climate surface.
- Monthly digest form, in the footer of every page.

Serve / from Jinja instead of a static file with placeholder substitution, so
crawlers and no-JS readers get the whole page as real HTML. home.html.j2
extends base.html.j2 and carries app.js's DOM contract over verbatim; the brand
degrades to a <p> so the hero owns the sole h1. frontend/index.html is deleted
rather than left behind the static mount, where it would keep serving indexable
duplicate content.

"Where is it most unusual right now" has no cheap answer at request time —
percentiles live inside zlib-compressed payload blobs with no column to sort on.
So homepage.py sweeps the warm cache and writes data/homepage.json, read by the
template. The sweep is strictly cache-only (climate.load_cached_recent_forecast
is new, the sibling of load_cached_history), so grading ~1000 cities costs zero
upstream requests. It rides the notifier's timer behind an hourly guard rather
than starting a second daemon, and also runs at the tail of warm_cities so a
fresh deploy has a populated feed.

Instrumentation: metrics gains a product-event counter keyed by (event,
referrer domain, UTC day), behind an allowlist and a per-IP rate limit, fed by
POST /api/v2/event. The referrer is taken from the request's own header, never
from the client. The beacon gets its own inbound category that record_inbound
ignores, so reporting an interaction doesn't also count as traffic. The
dashboard grows an events block with per-referrer attribution.

Email is scaffolded but sends nothing yet. mailer.py talks stdlib smtplib to a
local Postfix null client on 127.0.0.1:25 (deploy/provision-mail.sh), so the
choice between direct-to-MX and relaying through a provider stays a Postfix
config change with no code change. The backend defaults to "console", which
logs and sends nothing, so dev and tests exercise the whole signup path safely.
Signups land in pending_digest unconfirmed; collecting the list shouldn't wait
on delivery.

Also adds /privacy, linked from the footer and kept out of the sitemap.

The strip's classes are named unusual-* rather than record-*: .record-card is
already the SEO records page's, and reusing it leaked layout rules onto
/climate/<slug>/records.
2026-07-18 07:39:47 +00:00