# Contract: ToastService

**Location**: `ui/src/app/services/toast.service.ts`
**Provided in**: `root` (singleton)

## Interface

```typescript
interface Toast {
  message: string;
  type: 'success' | 'error';
}

class ToastService {
  // Observable — emits a Toast when one is active, null when none.
  readonly current$: Observable<Toast | null>;

  // Show a toast. Replaces any currently-visible toast.
  // duration defaults to 3000ms.
  show(message: string, type?: 'success' | 'error', duration?: number): void;
}
```

## Behaviour

- `show()` emits the toast immediately on `current$`.
- After `duration` ms, emits `null` to dismiss.
- Calling `show()` again before the timer expires resets the timer (new toast replaces old).
- `type` defaults to `'success'`.
- `duration` defaults to `3000`.

## Usage Example

```typescript
// In any component:
constructor(private toast: ToastService) {}

async copyUrl() {
  try {
    await navigator.clipboard.writeText(url);
    this.toast.show('URL copied!');
  } catch {
    this.toast.show('Failed to copy URL', 'error');
  }
}
```

## Consumer: ToastComponent

`ToastComponent` subscribes to `current$` via the `async` pipe and renders/hides based on the emitted value. It is placed once in `AppComponent` and is always present in the DOM.
