33import { useEffect , useRef , useState } from 'react'
44
55/**
6- * A small, self-contained illustration for the post: two peers editing the same document at once.
7- * A teammate ("Zoe") extends the intro line while the agent ("Sim") writes a short formatted block
8- * below — a bold lead, an inline code chip, and a bullet list, arriving already rendered. Neither one
9- * overwrites the other. It is a scripted animation, not a live CRDT, but it is faithful to the idea:
10- * both carets advance independently and both edits land.
6+ * A small illustration for the post: two peers editing one document at once. A teammate ("Zoe")
7+ * extends the intro line while the agent ("Sim") writes a short formatted block below (bold lead,
8+ * code chip, bullet list). Neither overwrites the other. A scripted animation, but faithful to the
9+ * idea: both carets advance independently and both edits land.
1110 *
12- * Everything is styled from the SAME design tokens the real markdown editor uses (`--font-inter`,
13- * `--font-martian-mono`, `--text-primary`, `--surface-5`, `--border`, `--surface-1`, `--bg`), the
14- * carets replicate `CollaborationCaret`'s bar + notched name label (rich-markdown-editor.css), and
15- * the identity colors are the real `USER_COLORS` (lib/workspaces/colors.ts). No shadows — the app
16- * frames surfaces with a 1px border. The finished document is always laid out; unrevealed text is
17- * kept `visibility: hidden` so the card never changes size as content streams in. Degrades to the
18- * finished document when JavaScript is off or reduced motion is requested.
11+ * Styled from the same design tokens as the real markdown editor, with carets replicating
12+ * `CollaborationCaret` and identity colors from `USER_COLORS`. The finished document is always laid
13+ * out; unrevealed text is kept `visibility: hidden` so the card never resizes as content streams in.
14+ * Degrades to the finished document with JavaScript off or reduced motion.
1915 */
2016
2117type Seg = { t : string } | { code : string } | { b : string }
@@ -27,16 +23,15 @@ interface Block {
2723const FONT_SANS = 'var(--font-inter, ui-sans-serif, system-ui, -apple-system, sans-serif)'
2824const FONT_MONO = 'var(--font-martian-mono, ui-monospace, SFMono-Regular, Menlo, monospace)'
2925
30- // Two collaborators, colored from the real identity palette (USER_COLORS), as getUserColor assigns .
31- const AGENT = { name : 'Sim' , color : '#60C5FF' } // Blue
32- const HUMAN = { name : 'Zoe' , color : '#F472B6' } // Pink
26+ // Colors from the real identity palette (USER_COLORS / getUserColor) .
27+ const AGENT = { name : 'Sim' , color : '#60C5FF' }
28+ const HUMAN = { name : 'Zoe' , color : '#F472B6' }
3329
3430// The intro line already exists; the teammate is still appending to it.
3531const HUMAN_BASE = 'Rollout is set for Friday.'
3632const HUMAN_ADD = ' Ops signed off this morning.'
3733
38- // The block the agent writes from empty, streamed one character at a time. It arrives already
39- // formatted: the bold lead, the code chip, and the bullets all render as they land.
34+ // The agent's block, streamed one char at a time, arriving already formatted.
4035const AGENT_BLOCKS : Block [ ] = [
4136 {
4237 kind : 'p' ,
@@ -52,11 +47,9 @@ const HUMAN_TOTAL = HUMAN_ADD.length
5247const AGENT_TOTAL = AGENT_FLAT . length
5348
5449/**
55- * Builds a per-character reveal timeline with an organic, non-uniform cadence: a smooth
56- * multi-frequency wobble in typing speed, plus natural pauses at spaces and punctuation. Fully
57- * deterministic (no RNG) so the server and client agree and it never jitters between renders — the
58- * variation comes from the character index and content, not randomness. `times[i]` is the ms at
59- * which character `i` should appear.
50+ * Per-character reveal timeline with a deterministic (no-RNG) speed wobble plus pauses at spaces and
51+ * punctuation, so the server and client agree and it never jitters between renders. `times[i]` is the
52+ * ms at which character `i` appears.
6053 */
6154function buildSchedule (
6255 text : string ,
@@ -75,8 +68,7 @@ function buildSchedule(
7568 return times
7669}
7770
78- // The agent streams a touch faster and steadier (it is a machine); the teammate types slower with
79- // bigger word/sentence pauses, and starts a beat later so the two are never metronomically locked.
71+ // Agent streams faster and steadier; the human is slower and starts a beat later, so the two never lock in sync.
8072const AGENT_TIMES = buildSchedule ( AGENT_FLAT , { base : 34 , wobble : 0.5 , space : 45 , punct : 150 } )
8173const HUMAN_TIMES = buildSchedule ( HUMAN_ADD , {
8274 base : 58 ,
@@ -93,9 +85,7 @@ const countReached = (times: number[], t: number): number => {
9385 return n
9486}
9587
96- // The demo streams through ONCE when it scrolls into view, then rests on the finished document —
97- // no loop, no fade. `RUN_MS` is when the last character has landed; at that point the reveal is
98- // clamped to the finished state and the animation stops (no further frames are scheduled).
88+ // RUN_MS: when the last character lands. The stream plays once, then clamps to the finished doc and stops.
9989const RUN_MS = Math . max ( lastTime ( AGENT_TIMES ) , lastTime ( HUMAN_TIMES ) )
10090
10191const proseStyle : React . CSSProperties = {
@@ -116,9 +106,8 @@ const codeStyle: React.CSSProperties = {
116106 padding : '0.125rem 0.375rem' ,
117107}
118108
119- /** Replicates CollaborationCaret in its active state: a 2px identity-colored bar with the notched
120- * name label above it (rich-markdown-editor.css `.collaboration-carets__bar` / `__label`). Zero
121- * inline width, so inserting it at the write head never shifts the surrounding text. */
109+ /** Replicates CollaborationCaret (rich-markdown-editor.css): a 2px identity-colored bar with the
110+ * notched name label above it. Zero inline width, so placing it at the write head never shifts text. */
122111function Caret ( { who } : { who : typeof HUMAN } ) {
123112 return (
124113 < span
@@ -165,10 +154,9 @@ function Caret({ who }: { who: typeof HUMAN }) {
165154 )
166155}
167156
168- /** Render one block's inline segments, revealing up to `revealed` characters (global index) and
169- * reserving the rest with `visibility: hidden` so layout is stable. Drops the caret exactly at the
170- * write head when the boundary falls inside this block. Returns the nodes and whether it placed the
171- * caret, so the caller can append a trailing caret when the whole document is complete. */
157+ /** Renders a block's segments, revealing up to `revealed` chars and reserving the rest with
158+ * `visibility: hidden` for stable layout. Places the caret at the write head if it falls in this
159+ * block; returns the nodes and whether the caret was placed. */
172160function renderBlockNodes (
173161 block : Block ,
174162 blockStart : number ,
@@ -187,10 +175,8 @@ function renderBlockNodes(
187175 const key = `${ blockStart } -${ si } `
188176
189177 if ( 'code' in seg ) {
190- // Type the chip in character by character, at the same cadence as the surrounding text, with
191- // the caret moving through it. The box reserves its full width up front (the unrevealed tail is
192- // visibility:hidden inside it) so the card never reflows. Revealing it as one chunk instead made
193- // it either freeze-and-jump (caret parked before an empty gap) or fly past as a skipped unit.
178+ // Type the chip char by char with the caret moving through it; the box reserves full width up
179+ // front (hidden tail) so the card never reflows. Revealing it as one chunk froze/jumped the caret.
194180 const reached = revealed >= start
195181 const shown = reached ? Math . min ( raw . length , revealed - start ) : 0
196182 const atBoundary = ! didPlace && reached && shown < raw . length
@@ -230,7 +216,7 @@ function renderBlockNodes(
230216 }
231217 offset = end
232218 }
233- // Once the whole block is written, the caret rests at the end of the last block — the peer stays present.
219+ // Caret rests at the end of the last block; the peer stays present.
234220 if ( isLast && ! didPlace ) {
235221 nodes . push ( < Caret key = 'end-caret' who = { AGENT } /> )
236222 didPlace = true
@@ -271,10 +257,8 @@ function AgentDoc({ revealed }: { revealed: number }) {
271257 offset = blockStart + block . segs . reduce ( ( m , s ) => m + segText ( s ) . length , 0 )
272258
273259 if ( block . kind === 'li' ) {
274- // The bullet is the <li>'s own list marker, so hiding the item's text with visibility:hidden
275- // would still leave an empty bullet showing. Hide the whole item until the caret reaches it, so
276- // each bullet appears with its line rather than all of them up front. The hidden item still
277- // reserves its height, so the card never changes size.
260+ // visibility:hidden on the text still leaves the <li> marker showing, so hide the whole item
261+ // until the caret reaches it. Hidden items still reserve height, so the card never resizes.
278262 list . push (
279263 < li
280264 key = { `li-${ bi } ` }
@@ -323,9 +307,7 @@ function Avatar({ who, overlap }: { who: typeof HUMAN; overlap: boolean }) {
323307}
324308
325309export function AgentPeerDemo ( ) {
326- // Initial (SSR / no-JS / reduced-motion) state is the finished document, so the content is always
327- // present without JavaScript. When the demo scrolls into view it resets to empty, streams through
328- // once, and rests on the finished document.
310+ // Initial state (SSR / no-JS / reduced-motion) is the finished document; streaming resets it on scroll-in.
329311 const [ humanN , setHumanN ] = useState ( HUMAN_TOTAL )
330312 const [ agentN , setAgentN ] = useState ( AGENT_TOTAL )
331313 const cardRef = useRef < HTMLDivElement > ( null )
@@ -335,30 +317,27 @@ export function AgentPeerDemo() {
335317 typeof window !== 'undefined' &&
336318 window . matchMedia ?.( '(prefers-reduced-motion: reduce)' ) . matches
337319 const el = cardRef . current
338- // Reduced motion, no element, or an environment without IntersectionObserver (older browsers,
339- // some test runners): leave the finished document in place as the fallback and never clear it.
320+ // No IntersectionObserver / no element / reduced motion: keep the finished document, never clear it.
340321 if ( reduce || ! el || typeof IntersectionObserver === 'undefined' ) return
341322
342323 let raf = 0
343324 let played = false
344325
345326 const play = ( ) => {
346- // Clear to empty only now that the demo is actually on screen, so it's never left blank if the
347- // observer never fires (the element never reaches the visibility threshold).
327+ // Clear to empty only once on-screen, so the doc is never left blank if the observer never fires.
348328 setHumanN ( 0 )
349329 setAgentN ( 0 )
350330 const start = performance . now ( )
351- // A character lands every ~30–60ms but frames fire every ~16ms, so most frames reveal nothing
352- // new. Track the last emitted counts and only setState on an actual change, so React renders
353- // roughly once per character (~90 times total) rather than every frame.
331+ // Frames fire ~16ms but chars land every ~30-60ms; only setState when a count actually changes,
332+ // so React re-renders ~once per character instead of every frame.
354333 let lastH = 0
355334 let lastA = 0
356335 const tick = ( now : number ) => {
357336 const t = now - start
358337 if ( t >= RUN_MS ) {
359338 if ( lastH !== HUMAN_TOTAL ) setHumanN ( HUMAN_TOTAL )
360339 if ( lastA !== AGENT_TOTAL ) setAgentN ( AGENT_TOTAL )
361- return // rest on the finished document — no loop, no further frames
340+ return // rest on the finished document; no loop
362341 }
363342 const h = countReached ( HUMAN_TIMES , t )
364343 const a = countReached ( AGENT_TIMES , t )
@@ -406,7 +385,7 @@ export function AgentPeerDemo() {
406385 overflow : 'hidden' ,
407386 } }
408387 >
409- { /* header: filename + presence — mirrors Resource.Header (min-height 48px, border-b, px-4) . */ }
388+ { /* header: filename + presence, mirroring Resource.Header. */ }
410389 < div
411390 style = { {
412391 display : 'flex' ,
@@ -451,7 +430,7 @@ export function AgentPeerDemo() {
451430 </ span >
452431 </ div >
453432
454- { /* document body — the real prose scale (15px / 430 / 25px, --font-inter, --text-primary ). */ }
433+ { /* document body: the real prose scale (see proseStyle ). */ }
455434 < div style = { { ...proseStyle , padding : '18px 22px 22px' } } >
456435 < div
457436 style = { {
0 commit comments