Greenside Changelog

Reverse-chronological, one entry per push. Surfaced at /changelog.

2026-05-17 — Visual bet grouping + upload→Live persistence

Two related fixes that make the "this is a parlay vs. several

single bets" distinction obvious + close the loop on the upload

flow.

and legs now live inside one thick-bordered card with the border

color picking up rollup status (green winning, amber live, red

losing). Reads as ONE bet visually, not a stack of independent

sections.

parse out with the same stake (signature of a parlay), they

render inside one outer container with a header row showing

combined odds + payout + W/L/X rollup. Single bets and

mixed-stake uploads keep individual bordered cards. Mirrors how

DK / Hard Rock present parlay slips.

on the upload results section calls useBetSlip().importLegs()

to write parsed legs into the localStorage slip store, then

redirects to /dashboard/parlay. The Live page now prefers the

user's actual slip when non-empty, falls back to the demo parlay

otherwise. "Clear slip" button on Live returns to demo. Closes

the upload → live tracker loop without requiring Supabase.

2026-05-17 — Matchup + 3-ball live tracking, upload-to-tracker, IA consolidation

Big batch driven by sportsbook ticket research and user feedback on

what's actually missing.

three players by current to-par on the leg's round. 1st = won,

T1 = push, anywhere else = lost. Live reason text reads natural:

"Leading by 2 on R4", "T1 of 3 on R4", "3rd of 3 · 4 back of

Scheffler". Dispatched before the generic matchup branch so 3-ball

market strings don't get misrouted.

when present to compare R{n} strokes only, instead of always falling

back to tournament total. Fixes round-specific h2h bets that were

being graded against 4-round totals.

/ 3-ball leg on /dashboard/parlay. Each player gets their name +

to-par + thru + hole trail. Matchups show a big "X UP" / "AS" /

"X DN" state chip. 3-balls rank 1st/2nd/3rd with green dot on the

user's pick.

Nth-ranked player and surfaces "1 stroke clear of top 10" or

"3 back of top 10" instead of just the position. Underdog-style

margin-aware feedback.

now flow through a classifier (lib/parsers/to-slip-leg.ts) that

pattern-matches the market string into typed SlipLeg shapes

(3-ball, 2-ball, top_n, round_prop, winner, make_cut). The page

fetches a live ESPN snapshot, grades each parsed bet, and shows

the LIVE / WON / LOST status pill right under the bet card before

the user clicks Save. Unclassifiable bets render as "manual review"

cards instead of dropping silently.

primary + a More dropdown. Today / Tickets / Live / Leaderboard /

Ask / DFS stay top-level; Course Lab, Schedule, Previews, Ownership,

Showdown, Backtest, Model, Newsletter, Bankroll, Admin live under

More. "Parlay" relabeled to "Live" since that's what the page

actually is.

2026-05-17 — FAQ, loading skeletons, off-week empty state

addressing the real bettor objections (sportsbook passwords,

legality, supported books, conditions engine, affiliate

kickbacks, cancellation). Uses native <details> so it works

with no JS. Links out to the full FAQ on /pricing#faq.

.gs-skeleton shimmer keyframe in globals.css). Composable

<Skeleton> rect plus pre-built <SkeletonRow> and

<SkeletonCard> shapes that mirror the production layouts so

the page doesn't jump on hydration.

rows during the first ESPN fetch instead of showing a dashed-out

table. Leaderboard's empty state also got better copy — now

differentiates "no matches, try clearing filters" from "no

leaderboard data yet".

post state, a dim banner clarifies that grading is permanent

and live tracking resumes Thursday. Stops the page from

pretending to be live between tournaments.

2026-05-17 — UX polish pass: trust pages, toasts, leaderboard motion, showdown share

Round-out of the polish bundle started this morning. No new dependencies, no API keys.

with quick-pins to dashboard, parlay, ownership, pricing, and

changelog so misdirected visitors land somewhere useful instead

of bouncing.

again" button (uses Next's reset() so it re-renders without a

full reload), branded copy, and the error digest surfaced so

bug reports are actionable. Stack traces show only in dev.

greensideToastIn keyframe in globals.css). Provider mounted

once at app/dashboard/layout.tsx. Import toast() anywhere —

works from event handlers and useEffect without threading

context. Four tones (success/info/warn/error), bottom-center

stack, auto-dismiss at 3.2s, click-to-dismiss. Parlay & showdown

share buttons now toast instead of doing the inline

"Link copied ✓" state-flip hack.

button). Same plumbing as parlay share: encodes the live roster

into a /share/parlay?d=... URL, opens X intent with a tight

caption. Each tracked player becomes a "leg" — to-par + position

as the line, won/live/pending mapped from cut/today status.

Aggregate status drives the headline color.

caret flashes on rows where the user has bets, scoped tightly so

the full 150-entrant table doesn't flicker. Live BetPills now

pulse via .gs-status-pulse. Bet-owned rows still get the

subtle background tint so the eye locks on them.

$12/$39, canonical page was $14.99/$49 — fixed home to match,

pointed CTAs at /login and added a "See full pricing + FAQ →"

link below the tier grid.

2026-05-17 — Monetization scaffolding

Three pieces that turn Greenside into something people can find,

share, and pay for.

($12/mo) / Sharp ($39/mo). All accounts stay on Free during beta,

Stripe checkout flips on later, beta signups grandfather forever.

Pricing copy is feature-specific, not generic ("Monte Carlo DFS

optimizer with full ownership-aware sims", not "advanced

analytics").

click the new green "Share parlay" button on /dashboard/parlay

and Greenside (a) copies a public share URL, (b) opens an X

intent pre-filled with a tight caption. The public page renders

with an og:image meta tag pointing at the edge-rendered 1200×630

PNG of the parlay — so when someone posts the link to X /

iMessage / Discord, the preview shows the actual parlay with

payout hero, leg list, and won/live counts. Edge runtime,

base64url-encoded payload so it works without DB access.

app/layout.tsx. The marketing home now renders a custom

1200×630 preview card in link unfurls instead of falling back

to a generic screenshot.

globals.css .gs-status-pulse keyframe): the standalone live

dot now breathes everywhere it appears — bets page desktop table,

bets page mobile cards, anywhere <StatusDot status="live">

renders. Same logic as the parlay/showdown chip pulses but

retuned for a standalone dot.

2026-05-17 — Showdown gets the motion pass

/dashboard/showdown, mirroring the parlay-page treatment. Same

visual language (green B for birdie, solid green E for eagle, amber +

for bogey, red + for double+). Reads the same per-hole ESPN linescore

as parlay so no extra fetch.

next to posDisplay for 3.5 seconds when ESPN reports a new

position. Green up-caret = climbed leaderboard, red down-caret =

slipped. Compares against the previous tick via a useRef keyed by

the tracked-player slug.

when a player is mid-round. Same dot-pulse keyframe as the parlay

pills.

Net effect: when you have the showdown tab open during a tournament,

the leaderboard *moves* — you see who's making birdies in real time

and who just dropped a spot, without having to compare numbers between

30-second refreshes.

2026-05-17 — Deploy plumbing for Vercel

both crons (grade + newsletter) since they iterate Supabase users

+ ESPN + push, and 15s for /api/projections/this-week so the

DataGolf fetch doesn't get clipped at the 10s default.

with the full env-var table (16 vars, marked required vs optional

and which scope each one belongs to), Supabase redirect-URL setup,

custom-domain steps, post-deploy verification URLs, and the

Hobby-vs-Pro cron tier note (Hobby caps cron at daily; the

*/15 * * * * grade schedule needs Pro).

2026-05-17 — Live motion: hole trail, pulse, position flash

strokes ESPN already exposes in the snapshot, color-codes them

against course par: green chip for birdies, amber for bogeys, red

for doubles+, dim for par, ghost for unplayed. Eagle gets a solid

green E. Replaces "5 birdies+ thru 13" string-only feedback with

the actual sequence at a glance.

dot fades + scales on a 1.6s loop so live status feels alive

without the whole chip competing for attention. Applied

everywhere StatusPill renders with status=live.

value across snapshot refreshes. When it shifts in the bettor's

favor (e.g. Reed T7 → T5), the row tints green for 3.5s with a ▲

caret next to the player name; against the bettor, red with ▼.

Direction logic handles top-N (lower is better), over/under

round-props, and outrights.

"2 spots clear" / "Just hold — 5 holes left" — parsed from the

grader's reason for round-props and computed from n minus

position for top-N. Amber when chasing, green when clinched.

2026-05-17 — Parlay payout hero + Scandinavian name match

hero card — payout scales typographically with magnitude (5-figure

payouts render at 132px), glows yellow while live, green when won,

muted red when dead. Stake/odds/legs demoted to a smaller secondary

row. Pulls the "to win" number out of the stat-tile grid so the

upside actually feels like upside.

norm() now folds ø → o, å → a, æ → ae, ß → ss before the

ASCII strip. Fixes "Rasmus Hojgaard" failing to match ESPN's

"Højgaard" and showing "Player not in field". Same fix unblocks

any future Aberg / Hovland / Højgaard props.

the live ticket bumped from 2 → 4 so Sunday-final birdie props grade

against today's play instead of Friday's settled R2 line. Schema

fix tracked as a follow-up — eventually roundIndex: "current"

should resolve dynamically from the snapshot.

2026-05-16 — Course-fit weighting, PWA install, This-week's-edge

Course fit dropdown (Parkland · long / short, Coastal · wind,

Links, Desert). When selected, each player's projection is

scaled by their fit multiplier — sgPerRound for that archetype

divided by their weighted-mean across all archetypes, clamped

±20%. Cascades into both sampling weights and simulation means.

beforeinstallprompt, surfaces a bottom-right card with Install

/ Not now actions. Dismissal persists for 14 days; skipped

entirely when already running standalone or recently dismissed.

(both mobile + desktop) shows top players by win% from DataGolf's

pre-tournament model. Source badge flags DataGolf vs Demo;

graceful demo fallback when DATAGOLF_API_KEY isn't set.

hits /preds/pre-tournament (baseline-history-fit preferred).

2026-05-16 — Auto-grade birdies / bogeys / eagles from hole-by-hole

scoreboard feed (was being discarded). Each entry: hole number,

strokes, par when available.

25 most common 2026 PGA courses (Quail Hollow, Augusta, Pebble,

Sawgrass, Riviera, etc.). Fallback is par-4 to avoid false

birdie counts on par-3s.

birdies / eagles / pars / bogeys / doublesOrWorse computed from

hole strokes vs hole par.

eagles using roundStats. Live status shows current count

thru holes played, plus "need N more in X remaining" math.

Fairways and greens still flagged manual (need DataGolf).

coverage; MANUAL pill now only shows on FIR / GIR legs.

2026-05-16 — Live parlay tracker + showdown roster refresh

ticket: Reed to win, Rasmus / Lowry / Stevens O3.5 birdies, Spieth

O7.5 FIR, Novak / Fowler Top 20, Rose Top 10, Scheffler U5.5 birdies.

Refreshes every 30s

against ESPN. Parlay-level status (LOST > UNKNOWN > LIVE > WON),

computed parlay odds, and per-leg observed values.

McIlroy, C. Young, Parry, Rasmus Højgaard, Novak, Stevens. Rasmus

gets accent variants + Nicolai exclude to keep the brothers

disambiguated.

grader returns "manual settle" for any per-round stat the free

ESPN feed doesn't expose (birdies / bogeys / eagles / fairways /

greens) — the parlay UI flags these with a MANUAL pill.

bet-slip so the parlay summary can roll legs into combined odds.

2026-05-16 — Live event strap + full 2026 PGA schedule

(47 events, all tours): start/end dates, course, city, type

(major/signature/sig_cut/full_cut/limited/alternate). Helpers:

statusOf, getActiveEvent, findEventByName.

strap. Priority: ESPN snapshot → static schedule → off-season

fallback. Refreshes every 60s. Auto-updates as weeks roll.

filters, Majors/Signature shortcuts. Rows that already have

ownership data attached get an "Own" chip linking back.

"list the remaining signature events").

2026-05-16 — Command palette (Cmd+K / Ctrl+K)

every tournament. Arrow/Enter/Esc nav; mounted dashboard-wide.

2026-05-16 — Monte Carlo DFS lineup optimizer

per lineup, AM/PM wave-correlated wind factors, z-scored

composite ranking on expected/ceiling/leverage weights.

button, top-20 expandable table.

2026-05-16 — Marketing home: less AI-flavored

body. Replaced 3-card symmetry with asymmetric capability slabs.

Added "A Sunday at Greenside" narrative with specific times and

scenarios. Removed "trusted by" / "AI-powered" phrasing.

2026-05-16 — Live ESPN leaderboard on dashboard home

ESPN top-N every 60s, falls back to demo data on cold paint

and network errors.

2026-05-16 — Real PnL on Tickets page

/ net units / ROI), by-book and by-market breakdowns now compute

from the signed-in user's actual graded bets when ≥1 exists.

Demo data still renders for first-time visitors and signed-out.

obvious which view you're looking at.

from stake + status when the column is null).

2026-05-16 — Onboarding actually onboards

alert types to profiles.alert_prefs, (2) fetches and displays

the real bets+<token>@<domain> forwarding address with copy,

and (3) replaces the static recap with three next-step cards

(build slip / watch leaderboard / find leverage).

now auto-create the row and route to /onboarding instead of

/dashboard. Returning users still go straight in.

2026-05-16 — Course-level ownership view

rolls up all its 2026 events into one view with stats: events

on file, unique players, repeat field, most-chalked player.

ownership, plus a separate one-off list. Range column shows

min–max ownership across appearances so you can spot the

inconsistency.

helpers (groups tournaments that share a venue).

"who's chalk at Quail Hollow", "leverage plays at Riviera".

2026-05-16 — DFS leverage cards + ownership upload helper

Chalk warning cards: for each pool player with ≥2 historical

appearances, computes projected − historical_avg ownership and

surfaces the five biggest gaps in each direction.

paste a JSON blob in the same shape as the standalone HTML viewer;

the validator checks structure and returns a ready-to-paste

TypeScript snippet for OWNERSHIP_DATA. No server-side mutation —

the dataset stays in the repo where every change is reviewable.

2026-05-16 — DK ownership database (696 records, 14 events)

far in 2026: course, type, par, yards plus per-player salary +

finishing ownership %. Helpers: getPlayerHistory, getTournament,

listPlayers, leverage (salary-band median delta).

HTML viewer: tournaments grid → tournament detail with chalk recap

and full board; players list (sortable by avg / peak / apps) →

player detail with sparkline-style history.

card on the right rail with a horizontal-bar mini-chart of the

last six events.

get_tournament_ownership tools.

2026-05-16 — Supabase realtime on Tickets

realtime channel filtered by user_id, so email imports, OCR

saves, and cron-driven status transitions flow in without a

refresh. Pulsing green dot in the header signals the live link.

publication (idempotent — safe to re-run).

2026-05-16 — Upload page wired to real ingestion

bets+<token>@<domain> forwarding address (signed-in) or a

placeholder with "Sign in to claim" copy.

screenshot OCR path into Supabase as pending bets.

pushes the OCR result into the same pending-confirmation queue

the email pipeline uses.

2026-05-16 — Web Push end-to-end

package; pushes to one user or broadcasts to all, auto-prunes

410/404 dead subscriptions.

device dedup by endpoint), DELETE removes it. Persists to

profiles.push_subscription (jsonb array).

service worker, subscribes, and stores the subscription. Adds a

Send-test button and inline status banner.

bets from bets table, grades per user, and pushes when legs

transition. Demo webhook fan-out still runs alongside.

2026-05-16 — Tickets page reads real bets, email-confirm flow

flips user_confirmed (pending → live) or deletes on dismiss.

RLS-scoped to own rows.

surfaces bets imported from Postmark inbound with Confirm / Dismiss

controls, plus a "Your bets" section showing all real bets — the

demo data renders below for first-time visitors.

2026-05-16 — Persisted alert preferences

profiles.alert_prefs (jsonb) + wind_cutoff_mph / ev_cutoff_pct

columns. RLS lets users only read/write their own row.

on change; "✓ Saved" / "Sign in to sync" indicator at the bottom.

Preferences hydrate on page load.

2026-05-16 — Postmark inbound persistence, per-user forwarding address

via a service-role admin client. The bets+<token>@… address routes

to the right user by looking up profiles.bets_token.

null when not configured so callers can no-op in local / demo envs.

random value, so every signup auto-gets an inbound address.

forwarding address (creating the profile row on first hit), POST

rotates the token. The /dashboard/account page surfaces the

address with copy + rotate controls.

2026-05-16 — Shareable slips, admin integrations panel

the lineup live-graded against the leaderboard. The token is the slip

itself, base64url-encoded; no DB row, no expiry. The slip editor

gained a Generate share link button that mints the URL and offers

copy / open-preview / regenerate.

paints a green/red dot for every external service (Supabase,

Anthropic, DataGolf, weather, odds, Postmark, push, webhooks, cron).

Re-check button re-probes on demand; never returns the secrets

themselves.

prefix so future schema bumps can coexist with older links.

2026-05-16 — Slip paste, alert dispatch, bookmarklet

(lib/slip-parser.ts) recognizes top-N, outright, matchup, 3-ball,

round prop over/unders, and make-cut from free-form text dumps of

DK / FD / PrizePicks / Underdog slips. Parsed legs slot straight

into the slip; rejected lines are reported.

decision against the prior run and only fans out webhooks when a

bet actually transitions (live → won, etc.). No more cron spam.

bookmarks bar; on any sportsbook tab, highlight the bet text and

click the bookmark to hand off to /dashboard/slip#import=… with

the selection pre-filled.

2026-05-16 — Full leaderboard, unified slip editor, health

search / filter (All, Mine, Made cut, Top 30) / sort. Rows the user

has a bet on glow with a colored pill per leg showing live grading.

3-ball, round prop O/U (strokes/birdies/bogeys/eagles), make-cut.

Player inputs autocomplete from the live field. Persists to

localStorage instantly; syncs to Supabase bet_slips when signed in.

with legToOpenBet adapter feeding the existing grader.

2026-05-16 — Settlement, syndication, exports

any open bet + a live ESPN snapshot and decides won / lost / live /

push. Handles top-N, to-win, matchup, and round-prop markets. Live

smoke test at /api/bets/grade.

13:00 UTC (/api/cron/newsletter) and a 15-minute settlement check

(/api/cron/grade). Both gated by CRON_SECRET when set.

generic JSON sinks via DISCORD_WEBHOOK_URL, SLACK_WEBHOOK_URL,

NEWSLETTER_WEBHOOK_URL. Multi-target fan-out with per-target

status reporting.

and Export JSON buttons live on the Tickets page.

2026-05-16 — Live odds, newsletter, iOS share, auth chip

admin preview at /dashboard/newsletter, public API at

/api/newsletter/daily.

paired with the existing Web Share Target landing page.

sign-out — with graceful "Sign in" fallback when env vars are absent.

2026-05-16 — Wind-model calibration, preview JSON, DataGolf profiles

against actual recent rounds — bias, MAE, R², per-bucket calibration,

per-player drift with suggested coefficients, predicted-vs-actual

scatter.

schema_version and Share / Open JSON / Download JSON buttons on the

preview detail page.

returning live data when DATAGOLF_API_KEY is set, demo fixtures

otherwise. Exposed at /api/players/[slug].

get_live_odds tools.

2026-05-16 — Weather API, AI backtest tools, multi-tournament matrix

default, Tomorrow.io behind TOMORROW_IO_API_KEY, 5-minute cache,

graceful demo fallback.

/dashboard/backtest.

get_forecast.

2026-05-16 — Showdown leaderboard

Nicolai / Rasmus disambiguation, strict round-period matching, and

auto-detection of the active round.

2026-05-16 — Backtest, previews, mobile shell

player pool into a structured tournament read.