Skip to content

Commit 884b8e6

Browse files
lesnik512claude
andcommitted
docs: fix demo rendering and first-paint state from browser QA
- 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>
1 parent ea65915 commit 884b8e6

2 files changed

Lines changed: 28 additions & 15 deletions

File tree

docs/demos/demos.css

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,8 @@
3131
.hw-demo, .hw-demo *, .hw-demo *::before, .hw-demo *::after { box-sizing: border-box; }
3232

3333
.hw-demo .hw-wrap {
34-
max-width: 760px;
35-
margin: 0 auto;
3634
color: var(--hw-fg);
37-
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
35+
font-family: inherit;
3836
line-height: 1.5;
3937
}
4038
.hw-demo h2 { font-size: 1.25rem; margin: 0 0 4px; }
@@ -63,7 +61,7 @@
6361
position: absolute; top: 0; bottom: 0; background: rgba(229, 72, 77, .18);
6462
border-left: 2px solid var(--hw-bad); border-right: 2px solid var(--hw-bad);
6563
}
66-
.hw-demo .outage span { position: absolute; top: 6px; left: 8px; color: var(--hw-bad); }
64+
.hw-demo .outage span { position: absolute; top: 50%; transform: translateY(-50%); left: 8px; white-space: nowrap; color: var(--hw-bad); }
6765
.hw-demo .playhead { position: absolute; top: 0; bottom: 0; width: 2px; background: var(--hw-fg); left: 0; transition: left .15s linear; }
6866

6967
.hw-demo .lane {

docs/demos/engine.js

Lines changed: 26 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -402,11 +402,15 @@ window.HttpwareDemo = (function () {
402402
els.ifB.textContent = '0'; els.okB.textContent = '0'; els.badB.textContent = '0'; els.rejB.textContent = '0';
403403
els.latA.textContent = '40ms'; els.latB.textContent = '40ms';
404404
[els.latA, els.latB, els.ifA, els.ifB].forEach((n) => { n.style.color = ''; });
405-
els.brkB.className = 'box'; els.brkB.textContent = brk ? 'breaker CLOSED' : 'no breaker';
406-
els.poolWrap.style.display = bulk ? '' : 'none';
407-
els.poolB.textContent = bulk ? (bulk.inUse + '/' + bulk.max) : '—';
408-
els.elapsedWrap.style.display = tmoCfg ? '' : 'none';
409-
els.elapsedB.textContent = tmoCfg ? ('0.0s / ' + tmoCfg.timeout.toFixed(1) + 's') : '—';
405+
// Flow-diagram boxes reflect the selected scenario's CHAIN config, not the runtime
406+
// middleware objects (which aren't constructed until run()). Otherwise the breaker
407+
// box reads "no breaker" on a circuit-breaker page until the first Play.
408+
const cfgB = selectedScenario ? selectedScenario.chainB : {};
409+
els.brkB.className = 'box'; els.brkB.textContent = cfgB.circuitBreaker ? 'breaker CLOSED' : 'no breaker';
410+
els.poolWrap.style.display = cfgB.bulkhead ? '' : 'none';
411+
els.poolB.textContent = cfgB.bulkhead ? ('0/' + cfgB.bulkhead.maxConcurrent) : '—';
412+
els.elapsedWrap.style.display = cfgB.timeout ? '' : 'none';
413+
els.elapsedB.textContent = cfgB.timeout ? ('0.0s / ' + cfgB.timeout.timeout.toFixed(1) + 's') : '—';
410414
els.srvA.className = 'box'; els.srvA.textContent = 'server ✓';
411415
els.srvB.className = 'box'; els.srvB.textContent = 'server ✓';
412416
els.laneA.className = 'lane'; els.laneB.className = 'lane';
@@ -433,8 +437,15 @@ window.HttpwareDemo = (function () {
433437
const down = !!(lastFault && !lastFault.ok);
434438
const slow = !!(lastFault && lastFault.ok && lastFault.ms >= 1.0);
435439
const stressed = down || slow;
436-
els.latA.textContent = stressed ? (A.if > 4 ? '12s+' : formatMs(lastFault.ms)) : '40ms';
437-
els.latA.style.color = stressed ? 'var(--hw-bad)' : '';
440+
// latA is the plain client's p99: reflect the ACTUAL in-flight latency tail, not
441+
// the last request. In a brownout (mixed fast successes + slow failures) the last
442+
// sample flickers, so a last-request p99 drops to 40ms whenever the last request
443+
// was a fast success — contradicting the "p99 blown" narration while slow requests
444+
// are still piled up in flight. The in-flight max is stable and honest.
445+
const aTailMs = A.pend.length ? A.pend.reduce((m, p) => Math.max(m, p.ms), 0) : 0.04;
446+
const plainStress = aTailMs >= 1.0 || A.if > 4;
447+
els.latA.textContent = plainStress ? (A.if > 4 ? '12s+' : formatMs(aTailMs)) : '40ms';
448+
els.latA.style.color = plainStress ? 'var(--hw-bad)' : '';
438449
// On timeout pages, AsyncTimeout bounds every lane-B attempt at the deadline —
439450
// p99 must reflect that ACTUAL bounded latency, not the constant 40ms, or it
440451
// visually contradicts elapsedB as it climbs toward the same deadline. Pages
@@ -449,10 +460,10 @@ window.HttpwareDemo = (function () {
449460
: slow ? ('server ' + (lastFault.label || 'slow')) : 'server ✓';
450461
els.srvB.className = 'box' + (stressed ? ' down' : '');
451462
els.srvB.textContent = down ? 'server ✗' : slow ? 'server slow' : 'server ✓';
452-
els.laneA.classList.toggle('hot', stressed && A.if > 8);
463+
els.laneA.classList.toggle('hot', plainStress && A.if > 8);
453464
els.laneB.classList.toggle('safe', st === 'CLOSED' && !stressed);
454-
els.badgeA.className = 'badge' + (stressed ? ' bad' : '');
455-
els.badgeA.textContent = stressed ? 'drowning' : 'no protection';
465+
els.badgeA.className = 'badge' + (plainStress ? ' bad' : '');
466+
els.badgeA.textContent = plainStress ? 'drowning' : 'no protection';
456467
const okBadge = st === 'CLOSED' && brk && !stressed;
457468
els.badgeB.className = 'badge ' + (st === 'OPEN' ? 'warn' : okBadge ? 'ok' : '');
458469
els.badgeB.textContent = st === 'OPEN' ? 'fast-failing' : st === 'HALF_OPEN' ? 'probing' : chainLabel(selectedScenario.chainB);
@@ -554,7 +565,7 @@ window.HttpwareDemo = (function () {
554565
const f = scenario.fault(now, rnd);
555566
lastFault = f;
556567
const landTicks = Math.max(1, Math.round(f.ms * 1000 / TICK));
557-
A.if++; A.pend.push({ land: tick + landTicks, ok: f.ok, attempt: 0 });
568+
A.if++; A.pend.push({ land: tick + landTicks, ok: f.ok, attempt: 0, ms: f.ms });
558569
spawnDot(dotsA, els.trackA, f.ok ? 'ok' : 'bad');
559570
if (brk && !brk.allow(now)) {
560571
B.rej++; spawnDot(dotsB, els.trackB, 'rej');
@@ -595,6 +606,10 @@ window.HttpwareDemo = (function () {
595606
});
596607
els.play.addEventListener('click', run);
597608
els.replay.addEventListener('click', run);
609+
// Auto-select the first scenario so first paint shows a coherent ready-to-play
610+
// state (correct flow-diagram boxes, Play enabled) instead of bare "—" placeholders.
611+
const firstScenarioBtn = els.scenarios.querySelector('.scenario-btn');
612+
if (firstScenarioBtn) firstScenarioBtn.click();
598613
}
599614

600615
return { mount, _models: { makeCircuitBreaker, makeRetryBudget, makeBulkhead }, _util: { mulberry }, REAL, TICK, ADV };

0 commit comments

Comments
 (0)