> ## 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

> Real-time state synchronization across multiple clients

# 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

```typescript theme={null}
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

```tsx theme={null}
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

```bash theme={null}
npx idyllic dev
```

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.
