Skip to content

docs: interactive resilience demo pages#109

Merged
lesnik512 merged 18 commits into
mainfrom
resilience-demo-pages
Jul 19, 2026
Merged

docs: interactive resilience demo pages#109
lesnik512 merged 18 commits into
mainfrom
resilience-demo-pages

Conversation

@lesnik512

Copy link
Copy Markdown
Member

Adds a Demos section to the docs site: five self-contained, browser-based pages that animate httpware's resilience patterns under load, so a reader viscerally sees the failure each pattern prevents. One page per pattern (CircuitBreaker, Retry + RetryBudget, Bulkhead, AsyncTimeout) plus a composed full-stack page.

Full rationale, design, and decisions: planning/changes/2026-07-18.02-resilience-demo-pages.md.

What it is

  • One shared vanilla-JS engine (docs/demos/engine.js): virtual clock, seeded PRNG, backend/fault model, one faithful model per pattern, a flow+counters renderer, and a hand-rolled guided-tour driver (spotlight + placed coach-marks). Each page is a markdown file with a mount <div> + a small config.
  • Guided walk-through: a plain client and an httpware client run the same seeded outage side by side; the animation freezes at each beat and a callout points at the exact number to watch.

Faithfulness

The whole point is that the models match real httpware — verified against src/httpware/middleware/resilience/*.py: RetryBudget (ceil + two-sided ttl-purged window), CircuitBreaker (consecutive-failure, lazy half-open, OPEN no-op, records once per exhausted retry sequence, timed-out requests don't feed it), Bulkhead (semaphore fast-reject, slot held across retries), AsyncTimeout (outermost total deadline). Fidelity constants live in one cited block in engine.js.

Constraints honored

  • No new Python dependency; no third-party JS / no JS build step (docs-only static assets).
  • Themes off mkdocs-material (light/dark); prefers-reduced-motion; outcomes not by color alone (dots differ by shape).

Verification

  • just docs-build --strict clean; all six demo routes build.
  • No JS test runner by design; verified with headless jsdom harnesses covering (a) real built-page script order and (b) every scenario's full guided tour to completion — all pass.
  • Real-browser QA pass (font/layout/first-paint) done.

🤖 Generated with Claude Code

lesnik512 and others added 18 commits July 18, 2026 22:14
Move brk.res() from unconditional-per-landing to the terminal
(non-retried) branch only, and skip it when the landing was cut short
by AsyncTimeout. Per architecture/resilience.md the breaker sits
outside retry and counts one outcome per fully-exhausted retry
sequence, not per attempt; a deadline-cancelled operation surfaces as
the outer TimeoutError and the inner breaker never sees a counted
outcome for it. Only observable once a page combines circuitBreaker
with retry/timeout (task 5's full-stack page); verified byte-for-byte
no regression on circuit-breaker.md/retry.md/bulkhead.md/timeout.md.
Composes all four resilience patterns (timeout, circuitBreaker,
bulkhead, retry) on one page against a 16s multi-phase incident.

Deviates from the drafted scenario in one number: hard-down fault
latency 3.0s -> 0.3s. At 3.0s every hard-down attempt already exceeds
the 2.0s AsyncTimeout on attempt 0, so (per the composition fix) the
breaker never receives a real outcome and never trips -- the page's
own third and fourth coach stops become unreachable. 0.3s lets real
failures land and trip the breaker within the hard-down window; see
.superpowers/sdd/task-5-report.md for the full numeric trace.
Flesh out the demos index with links to all five pages and a
faithful-model note, cross-link resilience.md to the demos, and
correct the circuit-breaker comment describing how the async breaker
handles an outer-timeout cancellation (it does see the CancelledError
via except BaseException, it just doesn't count it as an outcome).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… spike phase

latB was a hardcoded 40ms even on timeout/full-stack pages, contradicting
elapsedB as it climbed toward the deadline. Derive it from the same
bounded-latency measure as elapsedB when a timeout is configured; leave it
untouched (40ms) elsewhere.

computeOutageWindow only flagged hard failures, so full-stack's decorative
outage bar silently dropped the 2-5s latency-spike phase. Also flag
high-latency ok samples (ms >= 1.0, matching updateUI's existing `slow`
threshold), falling back to a generic "degraded" label when a window mixes
down and slow phases.
- inherit mkdocs-material font instead of a hardcoded system stack
- fill the content column (drop 760px centering that left a stray margin)
- keep the timeline outage label on one line (nowrap + vertical center)
- plain-client p99 reflects the in-flight latency tail, so it stays high in
  a brownout instead of flickering to 40ms and contradicting the callout
- derive the flow-diagram breaker/pool/elapsed boxes from the scenario chain
  and auto-select the first scenario, so first paint shows a coherent state

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@lesnik512
lesnik512 merged commit 10b9e8c into main Jul 19, 2026
7 checks passed
@lesnik512
lesnik512 deleted the resilience-demo-pages branch July 19, 2026 09:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant