githubnext/chopinPublic

Let's compose rich plans together

Give the agent a cursor while it is working

The marks say what changed and wait to be read; nothing said where the
agent was working right now. A cursor does, in the visual language the
room already has for a peer — Lexical draws it, `labels.ts` flashes its
name and fades it, and the wire already permitted a server-sent
`plan:awareness`, so none of that is new.

It rides in the presence mirror's own local slot. Everything else there
is a reflection of some socket, which is what lets a disconnect clear it;
the agent has no socket, so it goes where nothing can attribute it to
one, and it is taken down deliberately when the turn ends. That slot is
also in the join snapshot, so somebody arriving mid-turn sees it.

Its position comes from the same anchoring pass as the marks. Two passes
could disagree, and then the cursor would point at one block while the
marks described another.

Peers drop a state they have not heard about for thirty seconds, and an
update whose clock has not advanced is ignored without even refreshing
the timer — so the renewal sets the state again rather than re-encoding
it. `presence.test.ts` pins that, because the failure is a cursor that
disappears mid-turn with nothing anywhere to say why.

The left margin rule goes with this: it was answering "where", badly, and
the cursor answers it properly. What is left is a wash for prose that was
written, the structural colour for what moved, and a hole for what went.
A move needs the wash now that it has no rule of its own, or it would be
visible only where it left and read as a deletion.
KCKrzysztof Cieslak authored
3eb7c91Patch
Parentf419b4b
7 files changed+448-58
StackedSplit
agents.md+30 −8
apps/server/src/chat/service.ts+25 −1
apps/server/src/plan/presence.test.ts+167 −0
apps/server/src/plan/presence.ts+72 −5
apps/server/src/plan/service.ts+109 −1
packages/editor/src/presence.tsx+8 −1
packages/editor/src/styles.css+37 −42