DFS Simulator

Changelog

Every user-visible shipment on DFS Simulator. Chronological, no filler. RSS · Roadmap.

  1. v2026.07.28Improved

    Privacy controls you can actually reach, and opt-in session replay

    Account settings now has a **Privacy & analytics** panel with a real on/off switch. The cookie banner has always said "change later in Account settings" — now that link goes somewhere. Turning analytics off stops recording immediately and deletes the identifier stored on your device.

    With your consent we've enabled session replay so we can see how the interface actually behaved when something broke, instead of guessing from a bug report. Clicks and navigation are recorded; **the contents of every form field and password are masked before the recording leaves your browser**, network request bodies are never captured, and administrative pages are excluded entirely.

    Before you consent, nothing at all is stored on your device — we measure page views with an anonymous identifier held only in memory that disappears when you close the tab. No cookies, no localStorage, no recording.

    The privacy policy has been rewritten to match: section 6 now spells out exactly what is captured, what is masked, where it goes, and how to withdraw consent or request deletion.

  2. v2026.07.27bNew

    Faster first sim + mobile slate page + saved & curated lineup templates

    Faster first sim: the sim worker's HTTP bundle now pre-warms on the Run button's hover / focus. Two parallel fetches (worker script + orchestrator chunk) populate the browser's HTTP cache while your mouse moves into position, shaving ~50-150 ms of cold-cache latency off the first sim. Idempotent — subsequent clicks are no-ops. Also scaffolded @next/bundle-analyzer (npm run analyze in apps/web) for the recurring main-bundle-bloat audits.

    Mobile slate page redesign: the player pool now renders a full-width card grid below 768 px instead of the desktop table's horizontal-scroll experience. Each card packs starter icon, name, team @ opp, positions, salary, editable projection, ceil/floor/value, and 44 × 44 px lock / block buttons — WCAG-AAA hit targets on every phone from iPhone SE up. Desktop table unchanged at md+.

    Curated stacking templates: eight per-sport shortcuts in the Rules drawer — NFL 4-2 onslaught (with optional bring-back), NFL QB + 2 pass-catchers, NBA 3-team correlation, NBA 4-player game stack, MLB 5-hitter stack (with pitcher-avoid), MLB pitcher-safety, NHL 3-line stack, soccer attack stack. Pick a template, choose the target team or game, click Apply. Each carries a 'why this stack' rationale toggle.

    Saved lineup templates: a new backend-persisted 'Saved templates' section in the Rules drawer captures your current sim config (model + strategy + iterations + num_lineups + stacking rules) under a name of your choice and reapplies it to any future slate of the same sport with one click. Up to 100 templates per user. Sign-in required — anon users see a sign-in prompt instead of a broken button.

    Under the hood: fixed a duplicate-revision Alembic bug where the news-feed migration collided with an earlier revision id, and added the new lineup_templates table on top.

  3. v2026.07.27New

    Northstar metrics dashboard + persistent slate countdown + billing E2E

    New /admin/metrics page renders the 4-tile northstar dashboard from LONG_TERM_GOALS §7 — MRR, 7-day Pending→Paid conversion, Pro M3 retention, sim p95 (24h). Every tile carries a health verdict against its target with an insufficient-sample grace period so a fresh-launch dashboard doesn't light up red before real data accumulates. /admin now redirects to this dashboard as the Monday-review starting point.

    New persistent <SlateCountdownBar /> in the site header shows the earliest upcoming slate lock across the user&apos;s selected sports. Auto-hides when no user, no selected sports, or no lock within the 24h horizon. Adaptive poll cadence: 2 minutes normally, 20 seconds when a lock is within 10 minutes. Urgency color palette matches the existing LockCountdown so header and slate-page countdowns feel like the same widget.

    Playwright billing-checkout suite covers the full Stripe-test-mode flow: live-mode checkout posts the correct body + redirects to Stripe; demo-mode short-circuits and lands on /account?billing=demo-success; the account page reflects the new tier and cadence; annual toggle carries cycle=annual through to checkout. 4 test cases × 2 browsers, laptop-runnable via route.fulfill mocking.

  4. v2026.07.26New

    Real RSS news ingestion + k6 load-test scaffold

    New RSS ingestion pipeline replaces the synthetic news generator in production. Configure per-sport RSS feeds via DFS_NEWS_RSS_FEEDS (comma-separated sport:url pairs); the news job polls each feed every 10 min, parses RSS 2.0 + Atom formats with stdlib xml.etree (no feedparser dep), fuzzy-matches items to canonical players via rapidfuzz, and upserts with dedup on (source, external_id). Severity classifier reads keyword patterns from headline + description.

    Production without any feed configured is now a hard no-op with an actionable next-step message (previously it silently no-op&apos;d with an opaque skipped flag). The dead-job detector surfaces the stale run within ~20 minutes, giving operators a clear nudge to configure feeds.

    New k6 load-test scaffold at apps/api/loadtest/ with smoke.js + simulations.js. Manual-trigger GitHub Actions workflow (.github/workflows/loadtest.yml) with profile selector (smoke / light / medium / heavy — up to 100 VUs). Not scheduled — routine CI budget stays clean; operator runs pre-major-deploy against staging. Thresholds encode LONG_TERM_GOALS §3.2 targets (p95 <300ms, error rate <1%).

    GitHub issue templates verified — existing bug_report, feature_request, incident templates + contact-links config all in place. No changes needed.

  5. v2026.07.26Improved

    Scheduler reliability upgrade: retry policy, confirmed_starters lock-anchored scheduling, DB index

    Confirmed-starters refresh no longer runs every 5 minutes against every unlocked slate. It now fires at three lock-anchored trigger windows per slate: T-3h (early inactives / line rushes), T-1h30m (official lineups drop), and T-5m (final late-swap window). Between windows the job is a no-op — cheaper and more targeted than the previous constant polling.

    New RetryPolicy declaration on scheduled jobs — ownership.recompute gets one retry (30s backoff) so late-swap live-ownership feeds stay fresh through transient blips; salary_cap_projections.refresh gets 2 retries with exponential backoff for data-provider maintenance windows; historical_game_logs.sync gets 3 retries because it runs only 4x/day. news.fetch deliberately keeps zero retries (retries can double-count sources).

    New composite index on simulation_jobs (user_id, created_at DESC) accelerates the /account history tab. Single-column indexes on user_id and created_at deliberately preserved because retention scans and data-privacy exports prefer their singletons.

    New pip-audit + npm audit CI workflow (.github/workflows/dep-audit.yml) — runs on every PR + weekly against main. CRITICAL fails the build; HIGH informational. Same threshold policy as Trivy. Complementary data source (language-native advisory DBs surface same-day CVEs faster than Trivy&apos;s DB refresh cadence).

  6. v2026.07.26Improved

    Live ownership feed, mandatory admin 2FA, Trivy CVE scanning in CI

    New /api/slates/{id}/ownership/live endpoint serves the freshest projected ownership snapshot for the late-swap flow. ETag support means polling clients transfer nothing on unchanged data; stale_minutes tells the UI honestly how old the snapshot is. The 10-minute recompute cadence caps upper-bound staleness.

    Mandatory admin 2FA enforcement ships behind DFS_REQUIRE_ADMIN_2FA=1. When enabled, admins without TOTP enrollment get a distinct 403 (Admin 2FA enrollment required) on every admin route — clear signal to the frontend to prompt enrollment rather than confuse with a generic access-denied. The 2FA setup endpoints deliberately gate on current_user, not require_admin, so un-enrolled admins can still complete enrollment on a flag-on deployment. Rollout: flag off by default so live deployments don&apos;t lock out admins mid-upgrade.

    Trivy CVE scanning shipped in CI (.github/workflows/trivy-scan.yml). Runs on every PR (filesystem + config scan modes) plus weekly against main. CRITICAL CVEs fail the build; HIGH CVEs warn without failing. Findings upload to the GitHub Security tab. .trivyignore at repo root suppresses evaluated-safe CVEs with dated rationale.

  7. v2026.07.26New

    15 more blog posts + 3 site-comparison landing pages

    Sport-specific strategy guides for MMA, NASCAR, soccer, esports. Format guides for showdown Captain strategy, tiers, and DraftKings-vs-Yahoo / FanDuel-vs-Yahoo comparisons. Tactical deep-dives on chalk vs contrarian, punt plays, DFS correlation math, projection quality evaluation, injury news timing, single- vs multi-entry, and cash-game strategy. Blog surface now at 34 posts.

    Three new site-vs-site comparison pages at /dfs/compare/[pair] — the SEO complement to the site-comparison blog posts, aimed at the head evaluation query &quot;X vs Y DFS&quot;.

    Total programmatic SEO surface now at 203 pages (92% of the 220-page LONG_TERM_GOALS §2.1 target).

  8. v2026.07.26New

    Public /status page with live health checks + incident log

    New /status page runs live health checks against the API and background scheduler on every render (server-side, revalidated every 30 seconds). Three tiles — Web app, API, Scheduler — each with an explicit Operational / Degraded / Down / Unknown state. Aggregated banner headlines the current overall state.

    Incident history lives in a hand-curated registry (lib/incidents.ts) — empty at launch and staying that way until something actually breaks. Every future incident gets a blameless entry with severity, components affected, impact, and resolution. 90-day approximate uptime derives from the log; the external Uptime Kuma monitor (when NEXT_PUBLIC_STATUS_MONITOR_URL is set) is the third-party source of truth.

    Third piece of the trust triad alongside /changelog and /roadmap — all three linked from the footer and cross-linked from each other.

  9. v2026.07.26New

    SEO expansion: site pillars + category pillars + guides hub + 9 more blog posts

    Three new site-first pillars at /dfs/site/[site] (DraftKings, FanDuel, Yahoo) — the sibling of the sport-first /dfs/[sport] pages. Three new sport-category pillars at /dfs/category/[category] (team-sports, individual-sports, esports). One new /guides hub curating strategy posts by learning path.

    Nine additional blog posts: NHL strategy, WNBA strategy, CFB strategy, cash-game strategy, contest selection, weather in DFS, exposure caps explained, DFS for beginners, and a full DFS glossary. Blog surface now stands at 20 posts.

    Total programmatic SEO pages: 185, up from 168 this morning. 84% of the 220-page LONG_TERM_GOALS §2.1 target.

  10. v2026.07.26New

    Public roadmap + weekly changelog cadence

    The site now has a public product roadmap at /roadmap — under-promise, over-deliver framing, entries move out to the changelog as they ship. Silence is a signal too; a quiet month means nothing landed that users would notice, and we'd rather say so than pad with filler.

    The changelog carries its own RSS feed at /changelog.xml (separate from the blog RSS at /rss.xml) so operators, journalists, and API clients can subscribe to shipments without also getting strategy posts.

    Both surfaces are indexable, cross-linked, and structured with schema.org so a search for '[product] roadmap' or '[product] changelog' finds them directly.

  11. v2026.07.26New

    Influencer partnership landings and admin attribution stats

    New /with/[slug] landing pages for content-creator partners — one custom page per active partner with their bio, personal pitch, and a signup CTA that attributes the referred user for lifetime revenue share. Attribution flows through a dedicated dfs_partner cookie set by both the middleware and the landing page (three-layer capture: server cookie, client cookie, query-string fallback).

    Admin dashboard picks up two new endpoints — /admin/partners/stats (aggregated per-partner conversion counts) and /admin/partners/{slug}/users (per-partner drilldown). Empty state until we onboard our first partner; the plan is 10 in year 1.

    Partner attribution coexists cleanly with the friend-referral system that shipped earlier in the day — a signup can carry both signals and both are honored.

  12. v2026.07.26New

    SEO surface: 89 new sport / site / slate-type landing pages

    The /dfs/[sport]/[site] pages we shipped this morning now have per-format sub-pages at /dfs/[sport]/[site]/[slate-type] — 89 more URLs covering classic, showdown, tiers, and single-stat variants on every valid (sport, site) pair. That takes the programmatic-page surface to 168 URLs, up from 29 at the start of the day.

    Every generated page renders unique sport × site × slate-type content — no template smell, no near-duplicates. Sitemap prioritization biases crawl budget toward the pillar pages and lets the deeper cluster earn qualified long-tail traffic.

    Four new blog posts landed alongside the routes: NBA DFS strategy, PGA DFS strategy, DraftKings vs FanDuel, and DFS late-swap strategy.

  13. v2026.07.26Improved

    Annual billing awareness across /account and /pricing

    Your subscription now shows its cadence — Monthly or Annual — as a badge next to the tier on the account page. The upgrade button inlines a monthly/annual toggle so switching cadence is one click, not a trip through /pricing.

    Under the hood we now persist the cadence on every Stripe subscription event (checkout, webhook, reconcile). Grandfathered rows without a cadence stay unlabeled until their next subscription event fires, which backfills automatically.

    The pricing page defaults the toggle to your current cadence when you're signed in, with the ?cycle= URL param still winning if present. Shared 'switch to annual' links land where they should.

  14. v2026.07.26New

    Refer-a-friend Street Team card

    Every account now has a referral code and a shareable link — /?ref=<your code>. When a friend signs up and pays for any tier, both of you get 30 extra days of subscription. Stacks every time; no cap.

    New Street Team card on your account page shows your code, the shareable URL (with copy button), the number of paid referrals you've landed, and the free days you've earned. Refresh on demand.

    Attribution rides on a cookie set by the site middleware — the friend hits your link, signs up any time in the next 30 days, and the bonus fires on their first paid activation. Bonus is applied server-side and idempotent, so a subsequent tier upgrade doesn't double-pay.

  15. v2026.07.17Improved

    Launch: account page fix, cookie consent, footer refresh

    The account page no longer crashes for first-time visitors — a subtle zustand-selector pattern was allocating fresh objects on every render, triggering React error #185 in the DFS-history panel. First-time visitors now see the panel's empty state cleanly.

    Added a cookie / analytics consent banner. Essential storage (login, theme, saved slates) works as always; optional session-replay recording is now opt-in.

    Site footer got five columns (product, community, account, legal, trust row) and an env-gated Discord / Twitter / Status / Support column. Configure the community links via ``NEXT_PUBLIC_DISCORD_INVITE_URL`` and friends.

  16. v2026.07.17New

    Landing page: hero screencast section and honest CTAs

    The landing hero now shows a 30-second product screencast (or a placeholder poster if none is uploaded yet). Drop the video files into ``public/marketing/`` and they render automatically.

    The primary CTA is now honest about pricing: ``Get started — from $19.99/mo``. The previous ``Try it out`` link led to the sport catalog and immediately hit a paywall.

    Landing page keyboard focus indicators now paint a visible ring on every form input.

  17. v2026.07.17Fixed

    Sim engine: projection overrides now reach the API-side engine

    User-typed projection edits (and CSV-uploaded overrides) now flow through the API sim path correctly. Previously the frontend baked the effective projection into the players array — which the server discards — and forwarded an empty override map, silently reverting the user's edits on the API sim run.

    The frontend now sends the raw ``projection_overrides`` map so both the WebWorker path and the API path apply overrides consistently. As a side benefit, floor and ceiling now scale proportionally to the override, matching the sim engine's intended semantics.

    New regression tests in ``apps/api/tests/test_sim_cache.py`` verify that changing an override busts the cross-user cache and that identical overrides correctly share it.

Want the strategy angle too?

The blog covers the “why” behind the features — simulation math, correlation, ownership, portfolio construction.