Subscribe to real-time events pushed from the host via the framework, and enrich identity
claims. Each event type has a dedicated hook (`useIdentityEvent` / `useMessagingEvent` /
`useActivityEvent`) — never use `capabilities.events.*` directly. Identity enrichment
supports both a login-time hook (`useExtendIdentity`) and an imperative post-login API
(`capabilities.identity.extend`).

## Messaging Events

Subscribe to postback button clicks from the Messaging widget.
The `actionName` is the button's display text, not a programmatic identifier.

**Permission:** `events:messaging`

### Hook usage

```tsx
import { useMessagingEvent } from '@stackable-labs/sdk-extension-react'

useMessagingEvent('postback:Buy Now', (event) => {
  console.log('Postback:', event.data.actionName, event.data.conversationId)
})
```

### Full component example

```tsx
import { useMessagingEvent, Surface, ui } from '@stackable-labs/sdk-extension-react'
import type { MessagingEventHandler } from '@stackable-labs/sdk-extension-contracts'
import { useState } from 'react'

export function Content(): React.ReactElement {
  const [lastPostback, setLastPostback] = useState<string | null>(null)

  useMessagingEvent('postback', (event) => {
    setLastPostback(event.data.actionName)
  })

  return (
    <Surface id="slot.content">
      <ui.Text className="text-xs">{lastPostback ?? 'No postbacks yet'}</ui.Text>
    </Surface>
  )
}
```

## Activity Events

Subscribe to host site activity events like page views, clicks, and purchases pushed from
the host via the framework. Activity event names are domain-stripped —
use `useActivityEvent('product_view', ...)` not `'activity:product_view'`.

**Permission:** `events:activity`
**Well-known events:** `click`, `page_view`, `form_submit`, `product_view`, `add_to_cart`, `purchase`, `search`

### Hook usage

```tsx
import { useActivityEvent } from '@stackable-labs/sdk-extension-react'

useActivityEvent('product_view', (event) => {
  console.log('Activity:', event.eventName, event.data)
})
```

### Full component example

```tsx
import { useActivityEvent, Surface, ui } from '@stackable-labs/sdk-extension-react'
import type { ActivityEventHandler } from '@stackable-labs/sdk-extension-contracts'
import { useState } from 'react'

export function Content(): React.ReactElement {
  const [lastEvent, setLastEvent] = useState<string | null>(null)

  useActivityEvent('page_view', (event) => {
    setLastEvent(event.data.url as string)
  })

  return (
    <Surface id="slot.content">
      <ui.Text className="text-xs">{lastEvent ?? 'No activity yet'}</ui.Text>
    </Surface>
  )
}
```

## Identity Events

Subscribe to login, logout, refresh, and expired events. Useful for tracking
agent authentication state and reacting to identity enrichment pushed from sibling
extensions (via `capabilities.identity.extend` — see *Extend Identity* below).

**Permission:** `events:identity`
**Event types:** `login`, `logout`, `refresh`, `expired`

### Hook usage

```tsx
import { useIdentityEvent } from '@stackable-labs/sdk-extension-react'

// manifest events: ["identity:login", "identity:logout", "identity:refresh"]
useIdentityEvent('login', (event) => {
  // event.data.state.user.metadata is populated with any enrichment from sibling
  // extensions with identity:extend (declared in their manifest.identityClaims)
  console.log('User logged in:', event.data.state.user?.email, event.data.state.user?.metadata)
})
useIdentityEvent('logout', () => {
  console.log('User logged out')
})
// identity:refresh fires after any extension calls capabilities.identity.extend({...}).
// Listen here to react to post-login enrichment (verification, tier upgrades, etc.).
useIdentityEvent('refresh', (event) => {
  console.log('Identity refreshed — metadata:', event.data.state.user?.metadata)
})
```

### Full component example

```tsx
import { Surface, ui, useContextData, useIdentityEvent } from '@stackable-labs/sdk-extension-react'
import type { IdentityEvent } from '@stackable-labs/sdk-extension-contracts'

// useIdentityEvent — for SIDE EFFECTS on identity lifecycle events.
// If you just want to RENDER based on identity state (e.g. show the current
// user's email), use useContextData() — it re-renders reactively on every
// host-pushed identity change. Use useIdentityEvent only when you need to
// RUN imperative code on a specific event: analytics beacons, cookie writes,
// downstream system notifications, cache invalidation, etc.
//
// manifest.json:
//   {
//     "permissions": ["context:read", "events:identity"],
//     "events": ["identity:login", "identity:logout"]
//   }
export function Header(): React.ReactElement {
  const ctx = useContextData()

  // Side effect: persist a "last login" hint to localStorage on every login.
  // Replace with your real-world side effect — analytics beacon, cookie write,
  // downstream system notification, etc.
  useIdentityEvent('login', (event: IdentityEvent) => {
    localStorage.setItem('last-login', JSON.stringify({
      userId: event.data.state.user?.id,
      timestamp: new Date().toISOString(),
    }))
  })

  // Clear the hint on logout / session expiry.
  useIdentityEvent('logout', () => {
    localStorage.removeItem('last-login')
  })

  // Rendering: read directly from ctx — DON'T mirror identity into local
  // useState via the event listener; useContextData is already reactive.
  const email = ctx?.identity?.user?.email ?? null

  return (
    <Surface id="slot.header">
      <ui.Text className="text-xs">{email ?? 'Not logged in'}</ui.Text>
    </Surface>
  )
}
```

## Extend Identity

Enrich identity JWT claims and `user.metadata` so the signed token AND any sibling
extension with `events:identity` can react. Two complementary paths:

- **`useExtendIdentity(handler)`** — synchronous hook, fires ONCE at initial login. Use
  for known-at-login enrichment.
- **`capabilities.identity.extend(patch)`** — imperative call, fires post-login (after
  async verification, webhook callbacks, user-triggered flows). Re-signs the JWT and
  broadcasts `identity:refresh` to all extensions with `events:identity`.

Both paths share the **`identity:extend`** permission and the **`manifest.identityClaims`**
declaration gate. Standard JWT claims (`external_id`, `email`, `name`) are exempt;
custom keys must be declared or they're dropped by the host filter with a `console.warn`.

**Permission:** `identity:extend`

### Login-time hook (useExtendIdentity)

```tsx
import { useExtendIdentity } from '@stackable-labs/sdk-extension-react'

// manifest.json:
//   {
//     "permissions": ["identity:extend"],
//     "identityClaims": ["loyalty_tier", "verified", "verified_by", "verified_at"]
//   }
// Standard JWT claims (external_id, email, name) are exempt from declaration.
// Custom claims must be in manifest.identityClaims or they're dropped with a warn.
//
// Fires ONCE at initial login — return what's known synchronously. For post-login
// async updates (e.g., after verification completes via a webhook or polling),
// use capabilities.identity.extend(patch) — see the 'identity.extend' capability.
useExtendIdentity((claims) => ({
  external_id: `custom_${claims.external_id}`,   // standard claim override (exempt)
  loyalty_tier: 'bronze',                           // custom — sync, known at login
  verified: false,                                  // custom — default; updated async post-verification
}))
```

### Imperative post-login (capabilities.identity.extend)

```tsx
import { useCapabilities, useContextData, Surface, ui } from '@stackable-labs/sdk-extension-react'
import type { ContextData } from '@stackable-labs/sdk-extension-contracts'

// manifest.json:
//   {
//     "permissions": ["identity:extend"],
//     "identityClaims": ["verified", "verified_by", "verified_at"]
//   }
//
// PUSH side — capabilities.identity.extend(patch) sends new claims to the host
// AFTER initial login. The host filters the patch against manifest.identityClaims,
// merges into user.metadata, re-signs the JWT, and broadcasts identity:refresh.
//
// CONSUMER side — useContextData() already re-renders on every host-pushed
// context update (login/logout/refresh/expired). For pure rendering, read the
// enriched value directly from ctx.identity.user.metadata — no event listener
// needed. Use useIdentityEvent only when you need a SIDE EFFECT (analytics,
// cache invalidation, etc.) on a specific event.
//
// Standard JWT claims (external_id, email, name) are exempt from declaration.
// Undeclared custom keys are dropped client-side with a console.warn.
export function Content(): React.ReactElement {
  const capabilities = useCapabilities()
  const ctx = useContextData() as ContextData & { loading: boolean }

  // Push: trigger after async verification (webhook, polling, user action)
  const runVerification = async () => {
    await new Promise(r => setTimeout(r, 1000))   // simulated async work
    await capabilities.identity.extend({
      verified: true,
      verified_by: 'xyzProvider',
      verified_at: new Date().toISOString(),
    })
  }

  // Consume: read directly from ctx — re-renders automatically on identity:refresh.
  const isVerified = Boolean(ctx.identity?.user?.metadata?.verified)

  return (
    <Surface id="slot.content">
      <ui.Stack direction="column" gap="2" className="p-3">
        <ui.Button onClick={runVerification}>Verify</ui.Button>
        <ui.Text>Status: {isVerified ? 'verified' : 'unverified'}</ui.Text>
      </ui.Stack>
    </Surface>
  )
}
```
