From 03c7f58262c9a753c53b7608bb399a62f372f6fd Mon Sep 17 00:00:00 2001 From: Pratyush Sharma <56130065+pratyush618@users.noreply.github.com> Date: Sun, 21 Jun 2026 15:23:22 +0530 Subject: [PATCH 1/5] fix(docs): size demo modal to content, scroll tall demos --- docs/app/styles/demos.css | 6 ++---- docs/app/styles/landing.css | 8 ++++++-- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/docs/app/styles/demos.css b/docs/app/styles/demos.css index 2baac6a4..96c2b7ae 100644 --- a/docs/app/styles/demos.css +++ b/docs/app/styles/demos.css @@ -3,11 +3,9 @@ scoped under .dm-stage so the generic class names never collide with the docs site. Tokens (--cyan/--grn/--panel2/--mono/…) come from tokens.css. */ -/* React demo fills the modal stage (the iframe slot) and scrolls if taller. */ +/* React demo flows in the modal stage; the stage (.dm-stage) is the scroll + container and the dialog sizes to this content up to its max-height. */ .dm-stage .dm-frame-react { - position: absolute; - inset: 0; - overflow: auto; background: var(--bg); } diff --git a/docs/app/styles/landing.css b/docs/app/styles/landing.css index c1b462d8..e7592380 100644 --- a/docs/app/styles/landing.css +++ b/docs/app/styles/landing.css @@ -1780,7 +1780,10 @@ main { .dm-dialog { position: relative; width: min(1060px, 95vw); - height: min(86vh, 880px); + /* Fit the demo's natural height, capped so tall demos scroll instead of + overflowing the viewport. Avoids the dead space a fixed height left under + short demos. */ + max-height: min(86vh, 880px); display: flex; flex-direction: column; background: var(--bg); @@ -1878,7 +1881,8 @@ main { flex: 1; position: relative; background: var(--bg); - min-height: 0; + min-height: 120px; + overflow: auto; } .dm-loading { position: absolute; From 05b9fc011a0bf8aa10ba63cc08712e8833d58397 Mon Sep 17 00:00:00 2001 From: Pratyush Sharma <56130065+pratyush618@users.noreply.github.com> Date: Sun, 21 Jun 2026 15:38:31 +0530 Subject: [PATCH 2/5] fix(docs): pin demo bar+readout when viz scrolls --- docs/app/styles/demos.css | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/app/styles/demos.css b/docs/app/styles/demos.css index 96c2b7ae..b35385d9 100644 --- a/docs/app/styles/demos.css +++ b/docs/app/styles/demos.css @@ -17,6 +17,10 @@ border-bottom: 1px solid var(--line); background: color-mix(in oklch, var(--panel) 60%, #000); flex-wrap: wrap; + /* Pin controls while a tall demo's viz scrolls beneath them. */ + position: sticky; + top: 0; + z-index: 3; } [data-theme="light"] .dm-stage .demo-bar { background: var(--panel2); @@ -166,6 +170,10 @@ background: var(--line); border-top: 1px solid var(--line); grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); + /* Pin stats while a tall demo's viz scrolls above them. */ + position: sticky; + bottom: 0; + z-index: 3; } .dm-stage .stat { background: var(--panel); From 39c7c5934e8dbcf13ba690623f80d600ec790d53 Mon Sep 17 00:00:00 2001 From: Pratyush Sharma <56130065+pratyush618@users.noreply.github.com> Date: Sun, 21 Jun 2026 15:38:32 +0530 Subject: [PATCH 3/5] fix(docs): add standard mask alongside -webkit-mask --- docs/app/styles/landing.css | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/app/styles/landing.css b/docs/app/styles/landing.css index e7592380..719fb3a9 100644 --- a/docs/app/styles/landing.css +++ b/docs/app/styles/landing.css @@ -199,6 +199,9 @@ main { -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0); + mask: + linear-gradient(#000 0 0) content-box, + linear-gradient(#000 0 0); -webkit-mask-composite: xor; mask-composite: exclude; pointer-events: none; From b075d5741806034d6a5df1d07838504fb1af939f Mon Sep 17 00:00:00 2001 From: Pratyush Sharma <56130065+pratyush618@users.noreply.github.com> Date: Sun, 21 Jun 2026 15:46:52 +0530 Subject: [PATCH 4/5] feat(docs): dedicated progress demo for heavy scenario --- docs/app/components/demos/progress-demo.tsx | 446 ++++++++++++++++++ docs/app/components/demos/registry.ts | 1 + docs/app/components/demos/types.ts | 1 + .../components/landing/scenario-finder.tsx | 6 +- 4 files changed, 451 insertions(+), 3 deletions(-) create mode 100644 docs/app/components/demos/progress-demo.tsx diff --git a/docs/app/components/demos/progress-demo.tsx b/docs/app/components/demos/progress-demo.tsx new file mode 100644 index 00000000..0da3be22 --- /dev/null +++ b/docs/app/components/demos/progress-demo.tsx @@ -0,0 +1,446 @@ +import { useCallback, useEffect, useReducer, useRef, useState } from "react"; +import { useRafLoop, useReducedMotion } from "./lib"; +import type { DemoProps } from "./types"; + +/* + * Progress-streaming demo — the heavy-processing scenario. A big upload is handed + * off with .delay(); the endpoint returns 202 in milliseconds while a worker + * crunches the file chunk by chunk, calling progress.update() per chunk so the + * caller's UI shows a live % bar. SVG scene + RAF simulation. + */ + +const W = 940; +const H = 270; +const RESPONSE_MS = 3; // the instant hand-off — the whole point of .delay() +const CHUNK_MS = 180; // wall time the worker spends per chunk + +interface SizeDef { + n: number; + label: string; +} +const SIZES: Record<"small" | "large", SizeDef> = { + small: { n: 16, label: "12 MB" }, + large: { n: 40, label: "60 MB" }, +}; + +interface FlowNode { + x: number; + w: number; + title: string; + sub: string; +} +const NODE_Y = 24; +const NODE_H = 54; +const NODE_W = 190; +const NODES: FlowNode[] = [ + { x: 24, w: NODE_W, title: "POST /process", sub: "your endpoint" }, + { x: 375, w: NODE_W, title: "taskito queue", sub: "buffers the job" }, + { x: 726, w: NODE_W, title: "worker", sub: "" }, +]; + +// Chunk grid geometry — laid out left-to-right, wrapping at COLS_MAX per row. +const GRID_X = 24; +const GRID_Y = 202; +const GRID_W = W - 48; +const COLS_MAX = 20; +const CELL_H = 16; +const CELL_GAP = 6; +function gridCell(i: number, n: number) { + const cols = Math.min(n, COLS_MAX); + const cellW = (GRID_W - (cols - 1) * CELL_GAP) / cols; + const col = i % cols; + const row = Math.floor(i / cols); + return { + x: GRID_X + col * (cellW + CELL_GAP), + y: GRID_Y + row * (CELL_H + CELL_GAP), + w: cellW, + }; +} + +export default function ProgressDemo(_props: DemoProps) { + const reduced = useReducedMotion(); + const [size, setSize] = useState<"small" | "large">("large"); + const [paused, setPaused] = useState(false); + const tRef = useRef(0); // virtual sim time (ms), advances only while running + const lastRef = useRef(0); // last wall-clock RAF stamp, for real dt + const [, repaint] = useReducer((n: number) => n + 1, 0); + + const n = SIZES[size].n; + const total = n * CHUNK_MS; + + const tick = useCallback( + (wall: number) => { + if (!lastRef.current) lastRef.current = wall; + const dt = Math.min(wall - lastRef.current, 100); + lastRef.current = wall; + tRef.current = Math.min(total, tRef.current + dt); + repaint(); + }, + [total], + ); + + // Reset to the start on size change (total changes with size); reduced motion + // shows the finished frame. + useEffect(() => { + tRef.current = reduced ? total : 0; + lastRef.current = 0; + repaint(); + }, [reduced, total]); + + const finished = tRef.current >= total; + useRafLoop(tick, !paused && !reduced && !finished); + + const restart = () => { + tRef.current = 0; + lastRef.current = 0; + setPaused(false); + repaint(); + }; + const togglePause = () => { + // Drop the stale wall stamp so resume doesn't jump by the paused gap. + lastRef.current = 0; + setPaused((p) => !p); + }; + + const t = tRef.current; + const done = Math.min(n, Math.floor(t / CHUNK_MS)); + const frac = finished ? 1 : (done + (t % CHUNK_MS) / CHUNK_MS) / n; + const pct = Math.round(frac * 100); + const elapsed = (t / 1000).toFixed(1); + const workerSub = finished + ? "result ready" + : t === 0 + ? "idle" + : `chunk ${done + 1} / ${n}`; + + return ( +