Documentation Index
Fetch the complete documentation index at: https://docs.idyllic.so/llms.txt
Use this file to discover all available pages before exploring further.
Collaborative Counter
A shared counter that synchronizes across browser tabs. Open in two windows, click increment in one, watch both update. No AI—just the synchronization mechanism that powers all Idyllic applications.
Server Code
import { AgenticSystem, field, action } from 'idyllic';
export default class Counter extends AgenticSystem {
@field count = 0;
@field lastUpdatedBy: string | null = null;
@action()
async increment(userId?: string) {
this.count++;
this.lastUpdatedBy = userId ?? null;
}
@action()
async decrement(userId?: string) {
this.count--;
this.lastUpdatedBy = userId ?? null;
}
@action()
async reset() {
this.count = 0;
this.lastUpdatedBy = null;
}
}
When any action modifies state, Idyllic broadcasts to every connected client. You don’t write event-emission code. The framework observes property assignments and handles synchronization.
Client Code
import { useSystem } from '@idyllic/react';
import type { Counter } from '../systems/Counter';
import { useMemo } from 'react';
export default function CounterUI() {
const { count, lastUpdatedBy, increment, decrement, reset } = useSystem<Counter>();
const userId = useMemo(() => Math.random().toString(36).slice(2, 8), []);
return (
<div className="flex flex-col items-center justify-center min-h-screen">
<div className="text-6xl font-bold mb-8">{count}</div>
<div className="flex gap-4">
<button
onClick={() => decrement(userId)}
className="w-16 h-16 text-2xl bg-gray-200 rounded-full"
>
-
</button>
<button
onClick={() => increment(userId)}
className="w-16 h-16 text-2xl bg-blue-600 text-white rounded-full"
>
+
</button>
</div>
<button onClick={reset} className="mt-8 text-gray-500 underline">
Reset
</button>
{lastUpdatedBy && (
<p className="mt-4 text-sm text-gray-400">
Last updated by: {lastUpdatedBy}
{lastUpdatedBy === userId && ' (you)'}
</p>
)}
</div>
);
}
Each client generates a random identifier on mount. The UI shows who made each change.
Running the Example
Open http://localhost:3000 in two browser tabs. Click increment in one—both update. The counter value and “last updated by” identifier match in each tab.
Why This Matters
The same primitives that sync this counter also sync streaming LLM output, conversation history, and multi-agent state. The mechanism transfers directly:
- Push-based updates: Clients don’t poll. Changes push immediately via WebSocket.
- Automatic broadcast: No event emission code. The framework intercepts property assignments.
- Sequential execution: Durable Objects process actions one at a time. No race conditions, no locks needed.