/** * How to reach what the agent changed while you were reading somewhere else. * * A mark is held back until it is on screen, which answers the question of * whether it was worth showing but not the question of how to find it. These * are the other half: one chip per edge, counting only what is still unread in * that direction, and going to the nearest one when clicked — which reveals it * the ordinary way, by putting it in front of the reader. * * The list behind them shows every live change, seen or not. That is the only * place a removal can be read: the hole in the prose can say that something * was here, but the block itself is gone and cannot be asked what it was. */ import { useCallback, useEffect, useRef, useState, useSyncExternalStore } from "react"; import type { ChangeStore, Entry, Snapshot } from "./changes"; /** Past this the number stops being informative and starts being noise. */ const MANY = 9; export function useChanges(store: ChangeStore): Snapshot { return useSyncExternalStore(store.subscribe, store.snapshot, store.snapshot); } function count(value: number): string { return value > MANY ? `${MANY}+` : String(value); } function label(entry: Entry): string { switch (entry.kind) { case "added": return "Written"; case "moved": return "Moved"; case "removed": return entry.blocks.length > 1 ? `Removed ${entry.blocks.length} blocks` : "Removed"; } } function describe(entry: Entry): string { let first = entry.blocks[0]; if (!first) return ""; let text = first.preview.trim(); // A block with no words of its own — a divider, an image, a component with // nothing written in it — is named by what it is instead. return text || first.type; } function List({ entries }: { entries: Entry[] }) { if (entries.length === 0) { return
Nothing recent.
; } return (