Skip to content

Commit 0f103ec

Browse files
alexnodelandclaude
andcommitted
docs: alive everywhere — autoplay, ambient micro-widgets across all pages
Every widget now autoplays when scrolled into view (pausing offscreen; reduced-motion gets a rich pre-rendered frame, never an empty axis) and heroes pre-warm so first paint already shows a running system. Adds docs/viz/inline.js: ten parameterized ambient micro-widgets (dist-strip, posterior-morph, trace-ticker, rhat-spark, shrinkage, regression-mini, mixture-resp, logistic-boundary, elbo-climb, abc-eps) with real math inside (split-R-hat and ESS ported from src/inference/diagnostics.rs, EM responsibilities, conjugate updates), embedded at the point of discussion across ~20 pages — including a deliberately unconverged rhat-spark on debugging-models and a live partial-pooling shrinkage widget on hierarchical-models. Removes all "new in 0.2.0"-style callouts (13 sites incl. the distributions widget's selector group) — version history lives in the CHANGELOG, not the docs. Browser-verified: all 25 widget-bearing pages animate on arrival, zero console errors, zero >50ms long tasks during ambient play on the busiest pages, reduced-motion renders rich static frames (fixed a resize race that blanked paused micro-widgets). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DEAKcbnLVP8iSXub2Pqor2
1 parent a88dbea commit 0f103ec

35 files changed

Lines changed: 1162 additions & 72 deletions

docs/book.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ preferred-dark-theme = "navy"
1818
git-repository-url = "https://github.com/alexnodeland/fugue"
1919
site-url = "/fugue/"
2020
additional-css = ["./mdbook-admonish.css", "fugue-viz.css"]
21-
additional-js = ["mermaid.min.js", "mermaid-init.js", "fugue-viz.js", "viz/anatomy.js", "viz/monad.js", "viz/metropolis.js", "viz/hmc.js", "viz/smc.js", "viz/distributions.js"]
21+
additional-js = ["mermaid.min.js", "mermaid-init.js", "fugue-viz.js", "viz/anatomy.js", "viz/monad.js", "viz/metropolis.js", "viz/hmc.js", "viz/smc.js", "viz/distributions.js", "viz/inline.js"]
2222

2323
[output.html.fold]
2424
enable = true

docs/fugue-viz.css

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,3 +254,61 @@ html.rust .fv-btn.fv-primary {
254254
animation: none !important;
255255
}
256256
}
257+
258+
/* ---- The micro-widget family (docs/viz/inline.js) ------------------------- */
259+
/* Ambient inline figures embeddable on any page. Tighter chrome than the
260+
full-width hero explorables; a single pause/play glyph; an optional caption. */
261+
262+
.fv-inline {
263+
position: relative; /* anchor the pause/play glyph */
264+
padding: 8px 10px;
265+
border-radius: 6px;
266+
margin: 1.1rem 0;
267+
}
268+
269+
.fv-inline .fv-canvas {
270+
border-radius: 5px;
271+
}
272+
273+
/* The single unobtrusive pause/play glyph, top-right of the panel. */
274+
.fv-glyph {
275+
position: absolute;
276+
top: 6px;
277+
right: 8px;
278+
z-index: 2;
279+
width: 20px;
280+
height: 20px;
281+
padding: 0;
282+
display: flex;
283+
align-items: center;
284+
justify-content: center;
285+
border: none;
286+
background: transparent;
287+
color: var(--fv-ink);
288+
opacity: 0.35;
289+
cursor: pointer;
290+
font-size: 0.72rem;
291+
line-height: 1;
292+
font-family: inherit;
293+
transition: opacity 0.15s ease;
294+
}
295+
296+
.fv-glyph:hover {
297+
opacity: 1;
298+
}
299+
300+
.fv-glyph:focus-visible {
301+
opacity: 1;
302+
outline: 2px solid var(--fv-prior);
303+
outline-offset: 2px;
304+
border-radius: 4px;
305+
}
306+
307+
/* Caption rendered under a micro-widget's canvas (data-caption). */
308+
.fv-caption {
309+
font-style: italic;
310+
opacity: 0.7;
311+
font-size: 0.76rem;
312+
margin-top: 6px;
313+
line-height: 1.4;
314+
}

docs/fugue-viz.js

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
* (docs/viz/*.js) call FugueViz.register("name", fn) and consume this API; they
77
* MUST NOT duplicate what lives here.
88
*
9-
* The distribution math mirrors fugue 0.2.0's src/core/distribution.rs EXACTLY
9+
* The distribution math mirrors fugue's src/core/distribution.rs EXACTLY
1010
* (parameterizations, support, boundary limits). See the RETURN contract in the
1111
* foundation agent's report for the full per-distribution parameter list.
1212
*/
@@ -131,7 +131,7 @@
131131
}
132132

133133
// ==========================================================================
134-
// Distribution math — parameterizations match fugue 0.2.0 exactly.
134+
// Distribution math — parameterizations match fugue exactly.
135135
// logpdf/logpmf are pure log-space (no exp), finite for every finite input.
136136
// ==========================================================================
137137

@@ -990,7 +990,21 @@
990990
} catch (e) {}
991991
}
992992

993-
function loop(widgetRootEl, tickFn) {
993+
// loop(widgetRootEl, tickFn, opts) -> {play(), pause(), step(), playing, reduced}.
994+
//
995+
// Drives a rAF animation of tickFn(dt). Auto-pauses when the widget scrolls
996+
// offscreen (IntersectionObserver) or the tab is hidden, and resumes on return.
997+
//
998+
// opts.autoplay (boolean): when true, the widget begins playing the moment it
999+
// initializes — init is already lazy on scroll-into-view, so nobody lands on a
1000+
// dead canvas. Autoplay routes through play(), which is a no-op under
1001+
// prefers-reduced-motion and under the offscreen/hidden guards; so a
1002+
// reduced-motion visitor never gets autoplaying animation (the widget should
1003+
// render a fully-formed static frame instead), and an offscreen autoplay simply
1004+
// resumes once scrolled into view. The returned API is identical with or without
1005+
// opts — autoplay only changes whether play() is invoked once at the end of setup.
1006+
function loop(widgetRootEl, tickFn, opts) {
1007+
opts = opts || {};
9941008
var raf = null;
9951009
var playing = false;
9961010
var onscreen = true;
@@ -1061,6 +1075,8 @@
10611075
}
10621076
});
10631077
}
1078+
// Autoplay on init (respects reduced-motion / offscreen guards inside play()).
1079+
if (opts.autoplay) play();
10641080
return api;
10651081
}
10661082

docs/src/explorables/anatomy.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,3 +122,7 @@ explorable.
122122
- **Next explorable:** [The Model Is a Score](./monad.md) — step through the
123123
interpreter that actually runs this program.
124124
- **API:** [`Beta`](https://docs.rs/fugue-ppl/latest/fugue/), [`adaptive_mcmc_chain`](https://docs.rs/fugue-ppl/latest/fugue/inference/mh/fn.adaptive_mcmc_chain.html).
125+
126+
---
127+
128+
Next: [The Model Is a Score](./monad.md)

docs/src/explorables/distributions.md

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -108,8 +108,7 @@ let model = prob!(
108108

109109
## The full field guide
110110

111-
Every distribution in fugue 0.2.0. The `sample →` column is the natural return
112-
type; the seven marked **new in 0.2.0** arrived with this release.
111+
Every distribution in fugue. The `sample →` column is the natural return type.
113112

114113
### Continuous
115114

@@ -121,6 +120,12 @@ type; the seven marked **new in 0.2.0** arrived with this release.
121120
| `Exponential` | [0, ∞) | `rate` > 0 | `f64` | the wait until the next memoryless event; mean = 1/rate. |
122121
| `Beta` | [0, 1] | `alpha`, `beta` > 0 | `f64` | a probability about a probability — the coin-bias prior. |
123122
| `Gamma` | (0, ∞) | `shape`, `rate` > 0 | `f64` | positive quantities; **rate**-parameterized, mean = shape/rate. |
123+
| `StudentT` | (−∞, ∞) | `df`, `loc`, `scale` > 0 | `f64` | a heavier-tailed Normal that tolerates outliers. |
124+
| `Cauchy` | (−∞, ∞) | `loc`, `scale` > 0 | `f64` | pathological tails — no mean, no variance. `StudentT(df=1)`. |
125+
| `Laplace` | (−∞, ∞) | `loc`, `scale` > 0 | `f64` | a sharp peak with exponential tails; the L1 / lasso prior. |
126+
| `Weibull` | [0, ∞) | `shape`, `scale` > 0 | `f64` | time-to-failure and survival modeling. |
127+
| `ChiSquared` | (0, ∞) | `k` > 0 | `f64` | sums of squared Normals; = `Gamma(k/2, ½)`. |
128+
| `InverseGamma` | (0, ∞) | `shape`, `rate` > 0 | `f64` | the conjugate prior for a Normal's variance. |
124129

125130
### Discrete
126131

@@ -130,17 +135,6 @@ type; the seven marked **new in 0.2.0** arrived with this release.
130135
| `Categorical` | {0 … K−1} | `probs` sum to 1 | `usize` | picking one of K labels; index arrays safely. |
131136
| `Binomial` | {0 … n} | `n`, `p`[0, 1] | `u64` | successes in n independent trials. |
132137
| `Poisson` | {0, 1, 2, …} | `lambda` > 0 | `u64` | rare-event counts; mean = variance = λ. |
133-
134-
### New in 0.2.0
135-
136-
| Distribution | Support | Parameters | `sample →` | Reach for it when |
137-
|---|---|---|---|---|
138-
| `StudentT` | (−∞, ∞) | `df`, `loc`, `scale` > 0 | `f64` | a heavier-tailed Normal that tolerates outliers. |
139-
| `Cauchy` | (−∞, ∞) | `loc`, `scale` > 0 | `f64` | pathological tails — no mean, no variance. `StudentT(df=1)`. |
140-
| `Laplace` | (−∞, ∞) | `loc`, `scale` > 0 | `f64` | a sharp peak with exponential tails; the L1 / lasso prior. |
141-
| `Weibull` | [0, ∞) | `shape`, `scale` > 0 | `f64` | time-to-failure and survival modeling. |
142-
| `ChiSquared` | (0, ∞) | `k` > 0 | `f64` | sums of squared Normals; = `Gamma(k/2, ½)`. |
143-
| `InverseGamma` | (0, ∞) | `shape`, `rate` > 0 | `f64` | the conjugate prior for a Normal's variance. |
144138
| `DiscreteUniform` | {low … high} | `low``high` | `i64` | a fair die over an integer range. |
145139

146140
```admonish note title="Gamma is rate-parameterized"
@@ -157,3 +151,7 @@ rate convention. If a value looks inverted, check whether you meant scale = 1/ra
157151
every constructor, its constraints, and its `log_prob`.
158152
- Next explorable: [Anatomy of a Probabilistic Program](anatomy.md) — put a
159153
`Beta` prior and `Bernoulli` data together and watch Bayes multiply them.
154+
155+
---
156+
157+
Next: [Explorables](./README.md)

docs/src/explorables/hmc.md

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,10 @@ seed and you get the exact same momenta and trajectories every time.
2020

2121
## Things to try
2222

23-
1. Press **Play**. Watch one violet **leapfrog trajectory** roll across parameter
24-
space — a single proposal travelling much farther than a random-walk hop — while
25-
its coral **fit line** swings across the data on the left.
23+
1. Watch the violet **leapfrog trajectory** roll across parameter space — a single
24+
proposal travelling much farther than a random-walk hop — while its coral **fit
25+
line** swings across the data on the left. (It is already rolling; the controls
26+
let you pause, step, and steer.)
2627
2. **Drag the rightmost yellow point far up.** The posterior heatmap tilts toward
2728
steeper slopes and the coral ball rolls after it within a few transitions. This
2829
linked deformation is the whole point of the page.
@@ -140,7 +141,7 @@ fn main() {
140141
```
141142

142143
The same **ESS** you watch converge in the widget is a real diagnostic. Run a few
143-
chains and combine them with the multi-chain estimators (new in 0.2.0):
144+
chains and combine them with the multi-chain estimators:
144145

145146
```rust,ignore
146147
use fugue::*;
@@ -169,3 +170,7 @@ discrete latents.
169170
and [`HMCConfig`](https://docs.rs/fugue-ppl/latest/fugue/inference/hmc/struct.HMCConfig.html).
170171
- **Next:** [Particles That Tell Stories](./smc.md) — inference that moves through
171172
time instead of space.
173+
174+
---
175+
176+
Next: [Particles That Tell Stories](./smc.md)

docs/src/explorables/metropolis.md

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,14 @@ A point in the right panel *is* a line in the left panel — watch them move tog
2424
The model is honest Bayesian linear regression:
2525
$y \sim \mathcal{N}(a\,x + b,\ \sigma_{\text{obs}})$ with $\sigma_{\text{obs}}$ fixed at
2626
$0.8$, and priors $a, b \sim \mathcal{N}(0, 2.5)$. The
27-
<span class="fv-c-post">split-R̂</span> and ESS readouts are fugue 0.2.0's real
27+
<span class="fv-c-post">split-R̂</span> and ESS readouts are fugue's real
2828
convergence diagnostics, computed live on the samples accruing on screen.
2929

3030
## Things to try
3131

32-
1. **Press Play, then drag a point far off the line.** The right-hand heatmap morphs
33-
and the whole chain migrates to the new best fit — same frame. This is the moment:
34-
the data *is* the posterior, and you are reshaping it with your cursor.
32+
1. **The chains are already walking — drag a point far off the line.** The right-hand
33+
heatmap morphs and the whole chain migrates to the new best fit — same frame. This
34+
is the moment: the data *is* the posterior, and you are reshaping it with your cursor.
3535
2. **Drag `PROPOSAL σ` down to `0.02`.** Acceptance climbs toward 100% — every tiny
3636
step is safe — yet the coral dot barely crawls and R̂ stays stubbornly above 1. High
3737
acceptance is not the goal.
@@ -149,8 +149,8 @@ fn main() {
149149
chains.push(draws.into_iter().map(|(_, trace)| trace).collect());
150150
}
151151
152-
// Split-R-hat (Vehtari et al. 2021) and ESS — 0.2.0's diagnostics, the same
153-
// numbers the widget shows.
152+
// Split-R-hat (Vehtari et al. 2021) and ESS — the same convergence
153+
// diagnostics the widget shows.
154154
let rhat = r_hat_f64(&chains, &addr!("slope"));
155155
let slope_chain0: Vec<f64> = chains[0]
156156
.iter()
@@ -179,3 +179,7 @@ full recording behind it. `r_hat_f64` is the coral/green R̂ readout;
179179
a chain misbehaves.
180180
- **API:** [`adaptive_mcmc_chain`](https://docs.rs/fugue-ppl/latest/fugue/fn.adaptive_mcmc_chain.html)
181181
· [`r_hat_f64`](https://docs.rs/fugue-ppl/latest/fugue/fn.r_hat_f64.html)
182+
183+
---
184+
185+
Next: [Rolling, Not Guessing: Hamiltonian Monte Carlo](./hmc.md)

docs/src/explorables/monad.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,3 +201,7 @@ with 100 000 sample sites is interpreted without overflowing the stack.
201201
- Next explorable: [Random Walks in Posterior Space](./metropolis.md) — now that
202202
the score has a value and a weight, how do we *listen* our way back to the
203203
posterior you just watched form?
204+
205+
---
206+
207+
Next: [Random Walks in Posterior Space](./metropolis.md)

docs/src/explorables/smc.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,3 +198,7 @@ dragging sliders is exactly what the library does under the hood.
198198
and [`effective_sample_size`](https://docs.rs/fugue-ppl/latest/fugue/inference/smc/fn.effective_sample_size.html).
199199
- Next explorable: [A Field Guide to Distributions](./distributions.md) — the
200200
building blocks every model above is made of.
201+
202+
---
203+
204+
Next: [A Field Guide to Distributions](./distributions.md)

docs/src/getting-started/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,8 @@ graph TB
127127

128128
**Fugue** makes this safe, fast, and composable in Rust.
129129

130+
<div class="fugue-explorable fv-inline" data-viz="posterior-morph" data-kind="beta" data-caption="Step 3, Infer, in miniature: each yellow observation sharpens the green posterior around the blue prior."></div>
131+
130132
## Next Steps
131133

132134
Ready to dive in?

0 commit comments

Comments
 (0)