U of I × MindRouter App Gallery

Seeing Statistics

Education Built with Codex CLI v0.154.0 Critic status: shipped

Seeing Statistics

Explorable explanations for intro stats

Build intuition by playing: draw samples, watch the central limit theorem happen, catch 95% confidence intervals missing the truth, and feel how p-values move as data changes.

Seeing Statistics running on a desktop screen, explorable explanations for intro stats Desktop · click to enlarge
Seeing Statistics running on a phone, explorable explanations for intro stats
Phone · click to enlarge

What it is ✎ blurb written by the AI critic

Five short, hands-on chapters for intro statistics. Sample real daily high temperatures from the University of Idaho campus weather station, sketch any population and watch sample means form a bell curve, rain 100 confidence intervals and count the misses, drag data points to move a t-test's p-value, and fit a regression line against least squares. Each chapter ends with quick questions that explain the answer.

Try this

  1. In Chapter 2 choose 'draw your own', sketch a lopsided shape, press +1,000, and compare the SD of the means with the CLT prediction
  2. In Chapter 1 press 'Take consecutive days' a few times, then 'Draw random sample' a few times, and compare how widely the two kinds of means spread on the strip
  3. In Chapter 4 set H0: mu = 10 and drag the rightmost dot far to the right: p goes up, not down, because the outlier inflates s

Highlights ✎ written by the AI critic

  • Real data: 4,018 days (2015-2025) of NOAA daily highs from the MOSCOW U OF I station, cited in the app
  • CLT lab with uniform, skewed, bimodal or hand-drawn populations and a live sampling distribution with its normal curve
  • Confidence-interval rain at 90/95/99% with a coverage counter against the nominal level
  • Drag-the-data t-test that updates t, the shaded null-distribution tails and the p-value as you move points
  • Regression lab: click to add points, drag your own line, and compare its R2, SSE and residuals with least squares

Who it's for

STAT 251 students, instructors, anyone who never quite 'got' confidence intervals

Features

  • Chapter navigation (Sampling, CLT, Confidence intervals, Hypothesis tests, Regression) with short prose and live figures
  • CLT lab: pick a population shape (uniform, skewed, bimodal, draw-your-own), animate repeated sampling, watch the sampling distribution build up
  • Confidence-interval rain: 100 intervals falling into place, the misses highlighted, coverage counter vs nominal level
  • p-value playground: drag data points and see the test statistic, null distribution and p-value update
  • Regression: click to add points, drag the line vs least-squares fit, residual plot and R²
  • Short check-your-understanding questions with instant feedback

Data & sources

  • data.rcc-acis.org — NOAA NWS Cooperative Observer Program, station MOSCOW U OF I, ID (COOP 106152 / GHCN USC00106152): daily maximum temperature 2015-01-01 to 2025-12-31 (4,018 days), via RCC-ACIS StnData
  • All other figures are simulated in the browser and labelled illustrative in the app
Full data provenance ✎ written by the AI reviewer

Raw data sources — stats-explorable ("Seeing Statistics")

Retrieval date for everything in this directory: 2026-09-13 (HTTP Date: Sun, 13 Sep 2026 17:51:40 GMT).

The app is mostly simulation-based. The one real dataset below is for the "try it on real data" step. The file is the unmodified body of the HTTP response. Nothing was filtered, reshaped or aggregated.


1. acis_moscow_maxt_2015-2025.json

FieldValue
PublisherNOAA Regional Climate Centers, Applied Climate Information System (RCC-ACIS). This response was served by the Northeast RCC host (x-rcc-host: nrcc 0).
Underlying observationsNOAA National Weather Service Cooperative Observer Program (COOP) station MOSCOW U OF I, ID: COOP id 106152, GHCN-Daily id USC00106152, NWSLI MUNI1. It sits on the University of Idaho campus in Moscow, Latah County (FIPS 16057), at lat 46.728, lon -116.9557, elevation 2634 ft.
Endpointhttps://data.rcc-acis.org/StnData
HTTP methodPOST, Content-Type: application/json
Request body (exact){"sid":"USC00106152","sdate":"2015-01-01","edate":"2025-12-31","elems":"maxt","meta":"name,state,ll"}
ResponseHTTP 200, content-type: application/json, 84,490 bytes
ContentsDaily maximum air temperature for every calendar day from 2015-01-01 to 2025-12-31 inclusive
Record count4,018 daily rows. That is every day in the range, contiguous, with no gaps and no duplicates.
Date range2015-01-01 through 2025-12-31
Missing values0 rows with "M" in this download. All 4,018 values are integer strings.
Format / how to parse

The top-level JSON object has exactly two keys:

{"meta": {"state": "ID", "ll": [-116.9557, 46.728], "name": "MOSCOW U OF I"},
 "data": [["2015-01-01","19"], ["2015-01-02","23"], ...]}
  • meta.name: station name (string), here "MOSCOW U OF I".

  • meta.state: two-letter state code, here "ID".

  • meta.ll: [longitude, latitude] in decimal degrees. Longitude comes first.

  • data: array of 2-element arrays, [date, value], in ascending date order.

    • data[i][0]: date string YYYY-MM-DD (a local calendar day, with no time zone).

    • data[i][1]: daily maximum temperature, whole degrees Fahrenheit (°F), encoded as a string. Convert it with parseInt / Number. For °C use (F − 32) × 5/9.

  • Missing-value code: ACIS returns missing data as the string "M". None occur in this file, but a robust parser should still skip any non-numeric value. From the ACIS docs: "T" (trace) applies only to precipitation, snowfall and snow depth, so it cannot occur for maxt. An "A" suffix marks an accumulated value. No flags were requested (add was not used), so values carry no flag columns.

  • Units: ACIS element table, maxt = "Maximum temperature (°F)". degreeF is the temperature default (no units parameter was sent).

  • Observation-day convention: COOP observers record a once-daily max/min covering the 24 hours ending at their observation time. The value dated D is ACIS's daily value for day D. The observation time was not requested.

  • Data quality: ACIS warns that recent data are preliminary and may change after final quality control. The last months of 2025 are the most likely to be revised.

Validation performed (derived, for reference only; not part of the raw file)

Parsed with Python 3 json.load. Checked that len(data) == 4018, which equals the number of days from 2015-01-01 to 2025-12-31, that consecutive dates differ by exactly 1 day, that every row has length 2, and that every value matches ^-?\d+$. Summary of the 4,018 values:

StatisticValue
Minimum2 °F (2024-01-13)
Maximum105 °F (2018-08-09, 2021-06-29, 2021-07-31, 2024-07-21, 2024-08-02)
Arithmetic mean60.74 °F

The builder can use these numbers to check its own parsing. They are not an authoritative climate summary.

A separate StnMeta call confirmed the station identity and period of record. It was POST https://data.rcc-acis.org/StnMeta with body {"sids":"USC00106152","elems":"maxt","meta":"name,state,ll,elev,sids,valid_daterange,county"}. It returned valid_daterange for maxt of 1893-11-07 to 2026-09-07. That response was used only for this documentation and is not stored here.

License / terms
  • RCC-ACIS "Terms of Use and Disclaimer" (https://www.rcc-acis.org/disclaimer.html): the data are provided as-is. Near-real-time data are preliminary and may contain errors, and the RCCs are not responsible for misuse. The page states no explicit license and no attribution requirement.

  • The underlying observations come from the NOAA/NWS Cooperative Observer Program, a U.S. federal government program. U.S. government works are generally not subject to copyright in the United States. Still, credit NOAA/NWS COOP and RCC-ACIS as the source, and link to https://www.rcc-acis.org/.

  • API documentation: https://www.rcc-acis.org/docs_webservices.html

Suggested citation (for the app's "Data & sources" panel)
Daily maximum temperature, MOSCOW U OF I, ID (COOP 106152 / GHCN USC00106152), 2015–2025. NOAA NWS Cooperative Observer Program data via the NOAA Regional Climate Centers' Applied Climate Information System (RCC-ACIS), https://data.rcc-acis.org/ — retrieved 2026-09-13.

How it was built

Written by Codex CLI 0.154.0 (github.com) on qwen/qwen3.8-27b, served by MindRouter, the University of Idaho's on-premises AI platform.

12harness rounds
136.4minutes of harness work (2.3 h)
2,493lines of code
179KB, single-file app

Rounds by kind

build1
feedback7
continue2
critic2

Lines of code per file

body html67
app css246
app js2,106
scripts74

Critic scores ✎ scored by the independent AI critic, 1–10

visual 8 out of 10
usefulness 8 out of 10
correctness 9 out of 10
interactivity 8 out of 10

Independent audits ✎ by the AI critic

Data honesty: checked against raw data Authorship audit: every app-code commit is a harness round

Automated checks

Console no console errors No network 0 external requests Phone layout 0 px overflow Canvases no empty canvases Brand header header present No key leak no key in source

Checked by critic-verify.

Data spot checks ✎ written by the AI critic

  • Moscow card n / mean / min / max = 4018 / 60.74 F / 2 F / 105 F; raw acis_moscow_maxt_2015-2025.json gives 4018 / 60.74 / 2 / 105; embedded vals identical to raw
  • Histogram tooltips 40-45 F = 375 days, 80-85 F = 300 days, 100-105 F = 24 days; Python binning of raw data gives 375 / 300 / 24
  • Ch4 default points, mu0=12: app t = 0.74, p = 0.484; scipy ttest_1samp t = 0.739, p = 0.484. Dot dragged to 20: app p = 0.347, scipy 0.3471. mu0 = 10 drag 14.1->20: app p 0.001 -> 0.022, scipy 0.0011 -> 0.0228
  • Ch5 seeded 9 points: app LS y = 0.76 + 0.98x, R2 0.932, SSE 4.8, your line R2 0.870, SSE 9.2; numpy polyfit gives identical values; 10-point snapped fit 1.38 + 0.72x, R2 0.474, SSE 45.1 also matches
  • Ch3 CI rows: hit flags agree with lo <= 50 <= hi for all 100 rows; 95%/n=25 mean width 9.89 vs expected 9.80 (t* 2.064); 99%/n=10 mean width 24.10 vs expected 23.99 (t* 3.250)
Show all 7 spot checks
  • CLT SE theory: uniform(10,30) n=25 shows 1.15 vs exact 1.155; bimodal n=25 shows 1.90 vs exact 1.897; skewed shows 0.41 vs exact 0.44 (app uses SD of a fixed 2,000-draw population snapshot)
  • Ch1 quiz claim: consecutive 10-day August means from raw data range 74.6-94.7 F (median 87.4), 74% within 75-90 F, consistent with the keyed answer
Build notes ✎ written by the AI reviewer

The app ships: Codex CLI 0.154.0 with MindRouter default-agent wrote all application code in 10 recorded rounds (build 1, feedback 2-6, 9, 10; continue 7, 8), 7 of the 8 allowed feedback rounds, about 122 minutes of harness time. The tamper guard never fired, there were no network errors, and the driver only wrote FEEDBACK.md and files under runs/. FEEDBACK.md was deleted and committed as 17e33f0.

  • Progress by round: R1 had polished markup, but a two-argument div() bug left every figure unattached: chapters 2-4 crashed and no quizzes rendered. R2 fixed the crashes, but 20 more call sites repeated the bug, so no charts rendered.

  • R3 got charts rendering but introduced a load-time ReferenceError ('ypy'). R4 was the first working build.

  • R5 fixed the biggest bug the review found: a resize (common on phones) wiped chapter state and left an Auto timer throwing errors. It also made the sketch drive the population and added +100/+1,000.

  • R6 was spoiled: the model emitted degenerate text after reading FEEDBACK.md and changed no code. R7 re-ran it and stopped after reading source, committing nothing. R8 re-ran it again and implemented all 8 items.

  • R9 fixed a click swallowed after drags and the chart width. R10 fixed three statistical-accuracy problems in the teaching text: '1.5 SE = unusual', a contradictory CLT quiz answer, and a p-value quiz contradicted by the app's own behavior.

Harness behaviour: its sandbox cannot run headless Chrome, so it built its own Node DOM stub (tools/test-harness.js, committed in the project). That stub missed several real-browser bugs, so every fix was checked with the driver's Playwright scripts. The harness's own summaries twice claimed fixes that were not true in the browser (R2 drag fix; R4 click-to-remove). Codex warned that model metadata for default-agent was missing.

Buildlog verify_summary is null for R6 and R7 because neither changed index.html. Remaining gaps are listed in features_missing_or_broken.

Round-by-round timeline

Every harness round, in order. The prompts and FEEDBACK.md were written by the AI reviewer; the app code was written by the harness. Expand a round to see the exact prompt and the FEEDBACK.md the harness received.

    1 build 16.9 min · 6 files changed, 3057 insertions(+), 3 deletions(-) Show prompt

    Prompt given to the harness:

    Read AGENTS.md, SPEC.md and BRAND.md in this directory. Then build the complete app they describe, step by step: first write a script in scripts/ that turns the one real dataset in data/raw/ (NOAA daily maximum temperatures for the MOSCOW U OF I station — see data/raw/SOURCES.md for its format and citation) into compact JSON in data/embed/ for the app's "try it on real data" step, and run it (everything else in the app is simulation-based, generated in the browser); then write src/body.html, src/app.css and src/app.js; run python3 build.py --check and fix any error it reports. Implement every required feature in SPEC.md with a polished, modern, responsive University of Idaho design. Do not stop until python3 build.py --check succeeds and every feature is implemented.
    

    No feedback file was sent in this round.

    7 continue 1.6 min · — Show prompt

    Prompt given to the harness:

    Read FEEDBACK.md in this directory — it lists problems a reviewer found when testing index.html in a real browser. Fix every item, keeping everything that already works, then run python3 build.py --check.
    

    No feedback file was sent in this round.

    8 continue 4.1 min · 4 files changed, 262 insertions(+), 143 deletions(-) Show prompt

    Prompt given to the harness:

    Read FEEDBACK.md in this directory — it lists problems a reviewer found when testing index.html in a real browser. Fix every item, keeping everything that already works, then run python3 build.py --check.
    

    FEEDBACK.md the harness received ✎ written by the AI reviewer:

    Reviewer feedback — round 5 (tested index.html in headless Chromium at 1440x900 and 390x844)

    Excellent progress — verified in a real browser: resize keeps state in every chapter (and all clicks, drags and buttons still work after a resize); leaving Chapter 2 while Auto runs throws no errors; "+100"/"+1,000" build a clean bell curve and "SD of x̄s 1.86 vs CLT predicts 1.90" converges; the sketch now reshapes the population histogram; CI axis zoomed with a coverage bar; p-value drag, regression add/remove/drag/snap and all quizzes work; no horizontal page overflow. Keep all of that. Remaining issues, most important first:

    1. The page still auto-scrolls past the hero on first load. window.scrollY right after load is 637 px on desktop and 1,010 px on a 390 px phone, so first-time visitors land on the Chapter 1 header and never see the title, intro, "First time here?" hint or the chapter cards. Cause: the initial goChapter(0) at the bottom of app.js is not called in "silent" mode, so the stage.scrollIntoView(...) branch runs on load. Expected: scrollY stays 0 on load; scroll only when the user navigates.

    2. Moscow "sample means" strip is broken and logs console errors. Every click on "Draw random sample" or "Take consecutive days" logs Error: <circle> attribute cx: Expected length, "NaN". All strip dots pile up at the far-left edge (at the "20" tick) instead of at their °F values; the summary reads "random: 4 draws · SD —°F consecutive: —" after 2 random + 2 consecutive draws (consecutive draws are never counted and the SD never computes). Cause: moscowDraw pushes the bare number sm into drawnMeans, but renderStrip expects objects with kind and sm. Expected: gold dots for random draws and teal dots for consecutive draws at their correct positions, correct counts and SDs for each kind, no console errors.

    3. Moscow chart label collision. After a draw the gold "x̄ 67.1" label is printed on top of the "11-yr mean 60.74" label at the top of the histogram (both unreadable). Also the strip's summary line is tiny mono text drawn over the dashed mean line, and the strip leaves ~150 px of empty space below its axis. Put the two labels on separate rows (or opposite sides), move the summary into normal HTML text below the strip, and tighten the strip height.

    4. Phone: the CLT button row overflows its card. At 390 px the "+1,000" button's right edge is at 393 px while the card ends at 359 px — it sticks out of the card and is clipped at the screen edge. "Sample once" also wraps onto two lines (on desktop too). Let the row wrap cleanly (e.g. two rows of equal-width buttons on phones) and keep button labels on one line.

    5. Chapter 3: x-axis title collides with the tick labels. "value (true μ = 50)" is drawn on the same line as the tick labels and covers "50" (and "45"/"55" on phones). Put the axis title below the ticks. Also the coverage text appears twice ("coverage so far: 97.2% vs nominal 95%" in the stat line and again "coverage so far: 97.2% · tick = nominal 95%" under the bar) — show it once, e.g. a legend under the bar: "▮ nominal 95%".

    6. Chapter 4 labels still collide when x̄ is near μ₀. Example: μ₀ = 14, x̄ = 13.6 — the red "μ₀ = 14" and the brown "x̄ 13.6" labels overlap around the two dashed lines. When t is near 0, the "t = −0.37" label sits on the peak of the t curve. Place labels so they never overlap each other or the curve (e.g. μ₀ label left of its line, x̄ label right of its line on a second row; t label above the plot area).

    7. Chapter 1 first card uses only 60% of the width on desktop. "A single sample of 30 from N(0, 1)" draws its charts 760 px wide in a 1,200 px card, leaving an empty right band — give it a right column like the Moscow card (live stats + a short "What to notice" list, e.g. "x̄ lands much closer to 0 than most dots").

    8. CLT Auto default speed is still slow (8 samples after 3 s). Start the speed slider at a faster default (e.g. ~8–10 samples/s) so pressing Auto visibly builds the histogram right away; keep "+100"/"+1,000".

    After fixing, run python3 build.py --check and your DOM harness (add a check that Moscow random and consecutive draws produce finite strip positions and correct per-kind counts, and that scrollY is 0 after load).

    11 critic 13.5 min · 4 files changed, 514 insertions(+), 164 deletions(-) Show prompt

    Prompt given to the harness:

    Read FEEDBACK.md in this directory — it lists problems an independent reviewer found when testing index.html in a real browser, with exact causes in src/app.js and the numbers to check against. Fix every item in priority order (items 1-3 matter most: the mis-scaled normal curves, the sketch that ignores height, and the p-value quiz that the app contradicts), keeping everything that already works, then run python3 build.py --check and fix any error it reports. Do not stop until all 12 items are addressed and the check succeeds.
    

    FEEDBACK.md the harness received ✎ written by the AI reviewer:

    Reviewer findings (independent critic, tested in headless Chromium at 1440x900 and 390x844)

    Fix every item, in priority order, then run python3 build.py --check. All line references are to src/app.js.

    1. HIGH: the normal curves are drawn 4 to 7 times too low (Chapter 1 and the CLT lab)
    • normalCurve(fig, mu, sigma, n, color) scales the density by n * bw with bw = (hi - lo) / 80, the curve's sampling step, not the histogram's bin width. The curve therefore never lines up with the bars: Ch1 "single sample of 30": bins are 6.4/12 = 0.533 wide, so the expected peak is 30 x 0.533 x 0.399 = 6.4 values. The curve peaks at 0.96, down near the axis while the bars reach 6 to 9.

    • CLT lab "Sampling distribution of x̄": bins are 8σ/18 wide, but the curve uses 8σ/80, so it is 4.44x too low. After +1,000 the bell sits at a quarter of the bar height. That undermines the chapter's main message ("the histogram becomes this bell").

    Fix: pass the histogram's real bin width into normalCurve (e.g. normalCurve(fig, mu, sigma, n, binWidth, color)) and scale by n * binWidth. Then verify: after +1,000 with n = 25 (uniform), the curve peak should roughly match the tallest bars.

    2. HIGH: "draw your own" ignores how tall you draw

    stamp(e) uses only canvasX(e) and sets custom[i] = max(custom[i], 1 - d/30), so every stroke paints a full-height plateau whatever the pointer's y. The instruction "the taller you draw, the more common the value" is false: you cannot sketch a skewed or lopsided shape. Fix: set the sketch height from the pointer's y (h = 1 - (canvasY / CH), clamped to 0..1), blend nearby columns toward that height (not max-only), and interpolate between successive pointermove positions so fast strokes leave no gaps. Low strokes should give short columns.

    3. HIGH: the p-value quiz (Ch4 Q2) is still contradicted by what the app shows
    • The answer and explanation say that dragging one dot far right makes "p fall, hit a minimum, then climb — Try it". With the default data and H₀: μ = 12, dragging the rightmost dot (14.1) all the way to the axis end (20) makes p fall steadily from 0.484 to 0.347. It never climbs, and it would not visibly climb even with a wider axis. Independent scipy checks with the default 8 points: μ₀ = 10, rightmost dot dragged from 14.1 to 20 (away from μ₀): p RISES from about 0.001 to about 0.023, because s grows faster than x̄ moves.

    • μ₀ = 12, same drag: p falls only a little (0.484 to 0.347).

    Rewrite Q2 so that its correct answer and "Try it" instruction match that behaviour. For example: "Set H₀: μ = 10 and drag the rightmost dot further right, away from μ₀. What happens to p?" with the correct answer "p goes UP: moving the dot away raises x̄ a little but inflates s a lot, so |t| falls". Explain it with the numbers above. Keep the other options plausible but wrong.

    4. MEDIUM: confidence intervals overflow the chart at 99% / n = 10

    The x-axis is fixed at 30–70, but at 99% with n = 10 intervals reach 24–76. The lines run through the y-axis margin and past the right edge of the plot area. Also the first (top) interval row is drawn above the plot's top grid line, where it touches the "μ = 50" label. Fix: clip interval lines to the plot area with an SVG clipPath, and draw a small arrowhead or chevron where an interval is cut off. Or pick the x-domain from the widest possible interval for the current level and n. Keep every row inside the plot area, below the μ label.

    5. MEDIUM: "draw your own" population goes stale

    a) When you pick "draw your own" with nothing sketched, the canvas says "Draw your population shape here" but the population histogram shows a hidden default two-spike shape (defaultCustom()), which contradicts the blank sketch. Either paint that default shape into the canvas, or show an empty state in the population chart and disable Sample once / Auto / +100 / +1,000 until something is drawn. b) If you sketch more after collecting means, the population changes but the old means, "SE theory" and "CLT predicts" values stay. Means from two different populations get mixed. When a stroke ends (pointerup) and the sketch has changed, reset the collected means, and call updateStatus() so SE theory matches the new population.

    6. MEDIUM: after a window resize, the Moscow rug shows a different sample

    restoreOverlays() rebuilds the gold rug from makeRng(42), so after any width change the dots are a different random sample from the one whose mean is displayed (x̄ label, "sample mean", error). Store the drawn sample's values in MSTATE.last (e.g. MSTATE.last.values) and redraw exactly those values.

    7. MEDIUM (layout): Moscow card has a large blank area and no empty state

    On desktop, the left column (histogram + means strip) ends about 300 px above the right column (controls, stats, 2 notes), leaving an empty block. Before the first draw, the strip area is completely empty. Fix: show an empty state in the strip ("Press Draw random sample or Take consecutive days — each sample's mean will stack here"). Move the long explanatory fig__note ("Each random sample grabs n days…") from the right column to under the strip in the left column, so the columns balance.

    8. LOW: label collisions and x̄ glyph
    • Ch1 dot plot: when x̄ is near 0, the "x̄ −0.03" label sits on the μ = 0 dashed line and its label. Put the x̄ label on its own row below the μ label, always clear of the dashed line.

    • Ch1 "What to notice" list: the plain-text "x̄" (combining macron) renders with the bar detached ("x ̄="). Use the same <span class="xbar">x</span> markup the stat lines already use.

    9. LOW: regression residual plot and empty state
    • renderResid() does residPlot.innerHTML = '', which deletes the "Residuals — gold: your line, teal rings: least squares" subtitle created once in buildChapter5. Re-add a heading on every render, and label the y-axis "residual".

    • After "Clear points" the readout shows "SSE you = 0.0" in green, as if the fit were perfect. Show "—" for SSE when there are fewer than 2 points. Also put a centered hint inside the empty scatter plot ("Click anywhere to add points").

    10. LOW: CLT Auto at the default speed

    At the default speed (8), Auto adds about 10 samples/s and skips the "sample drops out of the population" animation (fast = spd > 7), so a first-time visitor pressing Auto neither sees the animation nor sees a bell form for several seconds. Make Auto start slowly with the drop animation visible (speed around 5), then speed up automatically (e.g. after 20 samples, several samples per tick), so a bell appears within about 5 seconds.

    11. LOW: inaccurate sentence in "Data & sources"

    "…(and the sampling/CI/p-value/regression figures in Chapter 1) are randomly generated…". Chapter 1 has no CI, p-value or regression figures. Change it to "…(and the N(0, 1) sample of 30 in Chapter 1)…".

    12. LOW: keyboard nudging in Ch4 loses focus

    Arrow-key nudging a data dot calls update(), which re-renders the SVG and destroys the focused circle, so only one key press works. After re-render, restore focus to the circle with the same index.

    Do not change anything else that already works: chapter navigation, state restore on resize and chapter return, quizzes, coverage counter, least-squares math, t-test math (these were verified against scipy and are correct).

    12 critic 0.8 min · 3 files changed, 116 insertions(+), 118 deletions(-) Show prompt

    Prompt given to the harness:

    Read FEEDBACK.md in this directory — a reviewer re-tested index.html in a real browser and lists 4 small remaining polish problems with their causes in src/app.js. Fix exactly those 4 items and nothing else (everything else has been verified correct and must keep working), then run python3 build.py --check and fix any error it reports.
    

    FEEDBACK.md the harness received ✎ written by the AI reviewer:

    Reviewer findings, pass 2 (independent critic, headless Chromium 1440x900 and 390x844)

    The previous 12 items were checked in a real browser: the normal curves now match the bars, the sketch follows pointer height, the p-value quiz matches the app, CI lines are clipped with chevrons, and the resize, sources, keyboard and regression readouts are all fixed. Good work. The remaining items are small polish. Fix only these (keep everything else exactly as it is), then run python3 build.py --check.

    1. Moscow strip: empty-state hint never shows; strip and rug missing when you return to Chapter 1
    • buildChapter1() never calls renderStrip() (or restoreOverlays()) when it builds. So: On first load, the "Press Draw random sample or Take consecutive days — each sample's mean will stack here." hint added in the last round never appears. A blank gap is left between the histogram and the note.

    • After drawing a few samples, switching to another chapter and back shows the x̄ label but no means strip and no gold rug until you draw again.

    Fix: at the end of the Moscow card setup, call restoreOverlays() (only if MSTATE.last exists) and then renderStrip(), so the empty hint (or the saved strip and rug) is visible immediately.

    2. "x̄" in quiz text renders with a detached bar

    Quiz questions, options and feedback use the Unicode combining macron "x̄". In this font the bar floats past the x and runs into the next word: Ch1 Q3 shows "x̄wanders", Ch2 Q1 shows "x̄look". It also appears in Ch3 Q3's explanation and in Ch4 Q2's option and explanation. The stat lines already use <span class="xbar">x</span>, which renders correctly. Fix: in buildQuiz, render question, option and feedback text with the .xbar span. For example, replace "x̄" with <span class="xbar">x</span> and set innerHTML (the strings are static and authored in app.js) instead of textContent for options and feedback. The SVG chart labels ("x̄ 55.8", "x̄ 13.1") look fine; leave them alone.

    3. Regression: the empty-plot hint is drawn on top of the gold line

    After "Clear points", "Click anywhere to add points" is centered in the plot and the gold line runs straight through the text. Put the hint on a white rounded rectangle (a background pill at ~90% opacity, drawn above the line) near the top of the plot area, so it stays readable whatever the line's position. Keep it non-interactive (pointer-events: none) so clicks still add points.

    4. Ch1 dot plot: x̄ label sits on the μ = 0 line when x̄ ≈ 0

    With x̄ = 0.09 or −0.03, the overlined-x glyph of the "x̄ 0.09" label is drawn directly on the dashed μ = 0 line. When |x̄| is small, offset the label horizontally so it clears the μ line by at least 6 px (to the right if x̄ ≥ 0, to the left if x̄ < 0). Draw a short tick at the true x̄ position so the location stays exact.

    Do not change anything else. The statistics, charts, interactions, state restore and quizzes are verified correct.

What the agent was asked

The full specification the harness received, rendered from the original Markdown.

Read the specification

Seeing Statistics — Explorable explanations for intro stats

Category: Education Who it is for: STAT 251 students, instructors, anyone who never quite 'got' confidence intervals

What it should do

Build intuition by playing: draw samples, watch the central limit theorem happen, catch 95% confidence intervals missing the truth, and feel how p-values move as data changes.

Data

Simulation-based; no external data. One real, cited small dataset is welcome for a 'try it on real data' step (e.g. a public NOAA daily-high temperature series for Moscow, ID, or a classic public-domain dataset), embedded with its source.

Raw downloads (if any) are in data/raw/ with data/raw/SOURCES.md describing where each file came from. Transform what the app needs into compact JSON in data/embed/ (write a small script in scripts/ to do it, so the transformation is reproducible). Every dataset the app shows must be credited in a visible "Data & sources" section of the app, with links. If a dataset is illustrative/synthetic, label it clearly as such in the UI — never present made-up numbers as real University of Idaho figures.

Required features

  • Chapter navigation (Sampling, CLT, Confidence intervals, Hypothesis tests, Regression) with short prose and live figures

  • CLT lab: pick a population shape (uniform, skewed, bimodal, draw-your-own), animate repeated sampling, watch the sampling distribution build up

  • Confidence-interval rain: 100 intervals falling into place, the misses highlighted, coverage counter vs nominal level

  • p-value playground: drag data points and see the test statistic, null distribution and p-value update

  • Regression: click to add points, drag the line vs least-squares fit, residual plot and R²

  • Short check-your-understanding questions with instant feedback

Quality bar

This app is going into a University of Idaho gallery that shows faculty, staff and students what they can build with AI coding agents. It must look polished and modern (think a well-designed data product, not a homework demo), feel alive (smooth transitions, responsive hover/selection states, animated where it helps understanding), and do real, useful work — every control should change something meaningful. It must work well on a laptop and on a phone. Include a short "How to use" hint for first-time visitors and a helpful empty state wherever the user must supply data.

Known limitations

Reported by the independent critic. ✎ written by the AI critic

  • CLT 'SE theory' for the skewed population shows 0.41 while the exact sigma/sqrt(n) is 0.44, because it uses the SD of a fixed 2,000-draw population snapshot (the observed SD of the means, 0.43-0.44, is right)
  • CLT Auto at the default speed still builds slowly at first (about 19 samples in 4 s); +100 and +1,000 give an instant bell
  • Moscow card: the note under the means strip still says 'The strip below...' and the desktop left column keeps some blank space
  • Each visit to Chapter 4 adds window-level pointer listeners that are never removed (harmless leak)
  • Not tested on real Safari/Firefox/Edge or physical touch devices; checks ran in headless Chromium with touch emulated at 390 px