Agent Cursors

Pro

Agents working in a document the way teammates do: a named pointer on the exact letter, a selection in their colour, suggestions typed as you watch, notes in the margin, and a face to press when you want to follow one.

Night trains are back
0 here

Night trains are back

Europe now runs more than forty overnight routes, up from twenty-five in 2020. Most leave after dinner and arrive before the first meeting.

Why now

Operators have bought new sleeper cars, and short flights cost a lot more than they used to. A berth from Vienna to Paris costs about the same as a hotel night plus a flight.

What to book

  • A couchette if you can sleep anywhere
  • A private sleeper if you want a door that locks
  • A seat only for trips under six hours

Installation#

# One-time setup (tokens + lib helpers)npx koalaui-cli@latest init# Activate your Pro license (once per machine)npx koalaui-cli@latest login <key># Add this component (its dependencies come along)npx koalaui-cli@latest add agent-cursors

Usage#

This example is part of Koala UI Pro

The preview is yours to explore. Its code comes with a Pro license, paid once and used in every project.

Have a license? Unlock it here

Anatomy#

The document is yours: plain blocks with plain text. Agent Cursors only adds a layer on top of it, so nothing you render has to know about pointers. Cursors are data, not elements: pass where each agent is and the page measures the spot on the real text.

import {  AgentCursors,  AgentCursorsHeader,  AgentCursorsTitle,  AgentCursorsPresence,  AgentCursorsActions,  AgentCursorsPage,  AgentCursorsBlock,  AgentCursorsSuggestion,  AgentCursorsComments,  AgentCursorsComment,} from "@/components/ui/agent-cursors"<AgentCursors agents={agents} cursors={cursors}>  <AgentCursorsHeader>    <AgentCursorsTitle />    <AgentCursorsPresence />    <AgentCursorsActions />  </AgentCursorsHeader>  <AgentCursorsPage>    <AgentCursorsBlock block="p1">      <AgentCursorsSuggestion />    </AgentCursorsBlock>    <AgentCursorsComments>      <AgentCursorsComment />    </AgentCursorsComments>  </AgentCursorsPage></AgentCursors>

Pointers and selections#

Each cursor is a block and a character offset. Add to and the passage between the two lights up in the agent's colour, with the pointer at its end. doing slides out beside the name on the flag and rolls when it changes. Pointers glide between any two places, so you can send positions as your agents stream them without every step in between.

Night trains are back
3 here

Night trains are back

Europe now runs more than forty overnight routes, up from twenty-five in 2020. Most leave after dinner and arrive before the first meeting.

Why now

Operators have bought new sleeper cars, and short flights cost a lot more than they used to. A berth from Vienna to Paris costs about the same as a hotel night plus a flight.

What to book

  • A couchette if you can sleep anywhere
  • A private sleeper if you want a door that locks
  • A seat only for trips under six hours

Suggestions#

An agent never writes over your text. Its change waits inline as a suggestion: what goes is struck through, what comes is underlined. Stream insert and point the cursor in the suggestion to watch it type. Click or press Enter on one to see the agent's note and accept or reject it. Try both below.

Night trains are back
1 here

Night trains are back

Europe now runs overnight routes, up from twenty-five in 2020. Most leave after dinner and arrive before the first meeting.

Why now

Operators have bought new sleeper cars, and . A berth from Vienna to Paris costs about the same as a hotel night plus a flight.

What to book

  • A couchette if you can sleep anywhere
  • A private sleeper if you want a door that locks
  • A seat only for trips under six hours

Comments#

A question is not a change, so it goes in the margin. The pin sits level with its block, in the agent's colour and with its face, and opens on hover into the full comment.

Night trains are back
1 here

Night trains are back

Europe now runs more than forty overnight routes, up from twenty-five in 2020. Most leave after dinner and arrive before the first meeting.

Why now

Operators have bought new sleeper cars, and short flights cost a lot more than they used to. A berth from Vienna to Paris costs about the same as a hotel night plus a flight.

What to book

  • A couchette if you can sleep anywhere
  • A private sleeper if you want a door that locks
  • A seat only for trips under six hours

Following an agent#

Press a face in the header to follow that agent. The others step back to a quarter of their strength, the face fills with the agent's colour and the page scrolls to keep its pointer in view. Press it again to let go.

Night trains are back
3 here

Night trains are back

Europe now runs more than forty overnight routes, up from twenty-five in 2020. Most leave after dinner and arrive before the first meeting.

Why now

Operators have bought new sleeper cars, and short flights cost a lot more than they used to. A berth from Vienna to Paris costs about the same as a hotel night plus a flight.

What to book

  • A couchette if you can sleep anywhere
  • A private sleeper if you want a door that locks
  • A seat only for trips under six hours

Plain and compact#

variant="plain" drops the tinted frame for a pane that already has one, and density="compact" tightens the page margins and the gap between blocks.

Night trains are back
3 here

Night trains are back

Europe now runs more than forty overnight routes, up from twenty-five in 2020. Most leave after dinner and arrive before the first meeting.

Why now

Operators have bought new sleeper cars, and short flights cost a lot more than they used to. A berth from Vienna to Paris costs about the same as a hotel night plus a flight.

What to book

  • A couchette if you can sleep anywhere
  • A private sleeper if you want a door that locks
  • A seat only for trips under six hours

API reference#

AgentCursors

The frame and the context every part reads: who the agents are, where each one is, and who you follow. Forwards native <div> props. Every part accepts className, merged last.

PropTypeDefaultDescription
agentsAgentCursorsAgent[]-{ id, name, color?, avatar? } for everyone who can be in the document. color is brand, purple, teal, pink or orange and paints the agent's pointer, selection, suggestions and face.
cursorsRecord<string, AgentCursor | null>{}Where each agent is: { block, at?, to?, in?, doing?, typing? }. at is a character offset in the block's rendered text, to turns it into a selection, in counts the offset inside a suggestion's inserted text, doing slides out on the flag, typing makes the pointer follow every letter. null or missing means the agent left.
followingstring | null-The agent you follow, controlled. The others fade back and the page scrolls to keep the followed pointer in view.
defaultFollowingstring | nullnullThe agent followed at first, uncontrolled.
onFollowingChange(id: string | null) => void-Called when a face is toggled.
variant"card" | "plain""card"card sets the page inside a tinted frame, like Artifact; plain drops the frame for a pane that already has one.
density"comfortable" | "compact"-The page margins and the gap between blocks. Inherits the nearest DensityProvider.
Data attributeDescription
data-followingThe id of the followed agent, when there is one.

AgentCursorsHeader

The row above the page: title, presence, actions. Forwards native <div> props.

AgentCursorsTitle

The document's name, truncated to one line.

AgentCursorsPresence

One face per agent with a ring in its colour while it is in the document, faded when it is away. Each face is a toggle button: press it to follow that agent. A tooltip says what the agent is doing.

PropTypeDefaultDescription
hereLabel(count: number) => ReactNode(n) => ${n} hereThe count beside the faces. Hidden below 30rem.
awayLabelstring"Away"What the tooltip says for an agent with no cursor.
followLabel(name: string) => string(name) => Follow ${name}The accessible name of each face.

AgentCursorsActions

A slot at the end of the header for Accept all, Share or a menu.

AgentCursorsPage

The scrolling page. Measures every cursor against the real text, so pointers stay on their letter at any width, font size or zoom, and re-measures when the text changes or the page resizes. Give it a height with className to make it scroll.

AgentCursorsBlock

A piece of the document that cursors can point at. Renders <h2>, <h3>, <li> or <p> by kind.

PropTypeDefaultDescription
blockstring-The id cursors and comments use to find it.
kind"title" | "heading" | "paragraph" | "item""paragraph"Type style and element.
asChildbooleanfalseRenders your own element as the block via Radix Slot.

AgentCursorsSuggestion

A change waiting for you, inline in the text: removed words struck through and new words underlined, both in the agent's colour. It is a button; it opens a card with the agent, its note, Reject and Accept. New suggestions are announced politely.

PropTypeDefaultDescription
idstring-Its id, for cursor.in.
agentstring-The agent that made it.
removestring-The text it takes out.
insertstring-The text it puts in. Stream it as it arrives and the agent types it letter by letter.
noteReactNode-Why, in the agent's words.
timeReactNode-When, beside the agent's name.
onAccept / onReject() => void-From the card's buttons. Write the change into your text, or drop it.
acceptLabel / rejectLabelstring"Accept" / "Reject"The button labels.

AgentCursorsComments

The margin layer for comment pins. Place it inside the page, after the blocks.

AgentCursorsComment

A pin in the right margin, level with its block: the agent's face in a speech-bubble shape. It opens on hover or click into a card with the comment.

PropTypeDefaultDescription
agentstring-Who wrote it.
blockstring-The block it sits beside.
timeReactNode-When, beside the agent's name.
footerReactNode-Actions under the comment, such as Resolve.
open / defaultOpen / onOpenChangeboolean / boolean / (open) => void-Keep the card open while the agent writes, or control it.

FAQ#

In the block's rendered text, characters in order, struck-through text included. With in set to a suggestion's id, the offset counts inside that suggestion's inserted text instead, which is what you want while an agent is typing it.