# Attention Routing 01 - Review queue (`attention-routing-01`) - SignalOS page block

> Fully reusable list-based workflow block composed from Page Header, Filter Toolbar, Record List, and Pagination. Supports generic filtering, search, selection, and custom rendering. Domain-agnostic and configurable via props.

- **Version:** 0.5.1
- **Kind:** block (group: attention-routing) · **Category:** pages
- **Install:** `npx shadcn@4.1.2 add @signalos/attention-routing-01`
- **Registry dependencies (pulled automatically):** @signalos/tokens, @signalos/utils, @signalos/filter-toolbar, @signalos/page-header, @signalos/record-list, @signalos/pagination, @signalos/signal-feed-card, @signalos/status-badge, @signalos/metric-tile, @signalos/circular-progress, @signalos/sidebar-detail-panel, @signalos/data-primitives, @signalos/lifecycle-timeline, @signalos/badge, @signalos/skeleton
- **npm dependencies:** lucide-react@^1.7.0
- **Files installed:** `src/components/blocks/attention-routing-01/AttentionRouting01.tsx`, `src/components/blocks/attention-routing-01/AttentionRouting01.types.ts`, `src/components/blocks/attention-routing-01/AttentionRouting01.constants.ts`, `src/components/blocks/attention-routing-01/AttentionRouting01.utils.ts`, `src/components/blocks/attention-routing-01/components/PrintAllRoutingDetails.tsx`, `src/components/blocks/attention-routing-01/components/routing-detail-sections.tsx`, `src/components/blocks/attention-routing-01/components/routing-feed.mappers.tsx`, `src/components/blocks/attention-routing-01/components/RoutingDeadlineBar.tsx`, `src/components/blocks/attention-routing-01/components/RoutingDetailPanel.tsx`, `src/components/blocks/attention-routing-01/components/RoutingRelatedList.tsx`, `src/components/blocks/attention-routing-01/components/RoutingSignalsSection.tsx`, `src/components/blocks/attention-routing-01/components/RoutingSummaryTiles.tsx`, `src/components/blocks/attention-routing-01/components/RoutingTransfersList.tsx`

## Access

This is a private registry: pulling source requires a `SIGNALOS_REGISTRY_TOKEN`
(GitHub fine-grained PAT with read access to the signal-widgets repo) and an
`@signalos` entry in components.json `"registries"`. Previews and this document are public.

## Usage

```tsx
// after: npx shadcn@4.1.2 add @signalos/attention-routing-01
// mount the pulled page in a route, e.g. src/app/attention-routing/page.tsx
import { AttentionRouting01 } from "@/components/blocks/attention-routing-01/AttentionRouting01"

export default function Page() {
  return <AttentionRouting01 />
}
```

## Props

### `AttentionSegmentFilter`

| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `key` | `string` | yes | - | Stable key. |
| `value` | `string` | yes | - | Current value. |
| `onChange` | `(value: string) => void` | yes | - | Change handler. |
| `options` | `readonly AttentionFilterOption[]` | yes | - | Selectable options. |
| `tone` | `"warning" \| "primary" \| "neutral" \| undefined` | no | - | Accent tone for the active segment. |

### `AttentionSelectFilter`

| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `key` | `string` | yes | - |  |
| `value` | `string` | yes | - |  |
| `onChange` | `(value: string) => void` | yes | - |  |
| `options` | `readonly AttentionFilterOption[]` | yes | - |  |
| `placeholder` | `string \| undefined` | no | - |  |

### `AttentionFilterOption`

| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `value` | `string` | yes | - |  |
| `label` | `string` | yes | - |  |

### `AttentionRouting01Props`

| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `items` | `readonly TData[]` | yes | - | The items to render in the queue. |
| `title` | `ReactNode` | no | `"Attention Routing"` | Page heading. |
| `description` | `ReactNode` | no | - | Sub-heading under the title. |
| `loading` | `boolean \| undefined` | no | - | Loading state — renders skeletons via `renderSkeleton`. |
| `error` | `boolean \| undefined` | no | - | Error state — renders `errorMessage`. |
| `errorMessage` | `ReactNode` | no | - |  |
| `segmentFilters` | `readonly AttentionSegmentFilter[] \| undefined` | no | - | Segmented pill-group filters (severity, deadline window, …). |
| `selectFilters` | `readonly AttentionSelectFilter[] \| undefined` | no | - | Dropdown filters (owner, role, …). |
| `toolbarExtras` | `ReactNode` | no | - | Additional content rendered inside the FilterToolbar (e.g. action buttons, toggles, badges). |
| `searchValue` | `string \| undefined` | no | - | Free-text search value + handler. |
| `onSearchChange` | `((value: string) => void) \| undefined` | no | - |  |
| `searchPlaceholder` | `string \| undefined` | no | `"Search attention items..."` |  |
| `resultLabel` | `string \| undefined` | no | `"item"` | Singular noun for the result count, e.g. "item". |
| `onRefresh` | `(() => void) \| undefined` | no | - | Refresh affordance. |
| `isRefreshing` | `boolean \| undefined` | no | - |  |
| `selectedId` | `string \| null \| undefined` | no | - | Currently-selected item id (for highlight state in `renderItem`). |
| `onSelect` | `((item: TData) => void) \| undefined` | no | - | Fired when an item is activated (click / Enter / Space). |
| `renderItem` | `((item: TData, index: number) => ReactNode) \| undefined` | no | - | Renders a single item. |
| `renderSkeleton` | `((index: number) => ReactNode) \| undefined` | no | - | Renders a single loading skeleton row. |
| `getItemKey` | `((item: TData, index: number) => string) \| undefined` | no | - | Stable key for an item. |
| `emptyTitle` | `ReactNode` | no | - | Empty-state copy. |
| `emptyDescription` | `ReactNode` | no | - |  |
| `summary` | `ReactNode` | no | - | KPI/summary content rendered above the toolbar. |
| `showPagination` | `boolean \| undefined` | no | - | Pagination (hidden while searching). |
| `currentPage` | `number \| undefined` | no | - |  |
| `pageSize` | `number \| undefined` | no | - |  |
| `totalRecords` | `number \| undefined` | no | - |  |
| `onPageChange` | `((page: number) => void) \| undefined` | no | - |  |
| `onPageSizeChange` | `((pageSize: number) => void) \| undefined` | no | - |  |
| `detailPanel` | `ReactNode` | no | - | Detail drawer/panel rendered at the end of the block. |
| `className` | `string \| undefined` | no | - |  |
| `data-testid` | `string \| undefined` | no | - |  |

### `BlackboardSeverity`

```ts
export type BlackboardSeverity = "info" | "low" | "medium" | "high" | "critical"
```

### `RoutingDetailSignal`

| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `id` | `string` | yes | - |  |
| `metric_id` | `string` | yes | - |  |
| `signal_type` | `string` | yes | - |  |
| `severity` | `BlackboardSeverity` | yes | - |  |
| `deviation_std` | `number` | yes | - |  |
| `crlb_score` | `number` | yes | - |  |
| `fired_at` | `string` | yes | - |  |
| `source_connector_id` | `string \| null` | yes | - |  |
| `dimension_key` | `Record<string, unknown>` | yes | - |  |

### `HypothesisStatus`

```ts
export type HypothesisStatus =
  "FORMING" | "SUFFICIENT" | "ROUTED" | "DECIDED" | "EXPIRED"
```

### `RoutingRelatedHypothesis`

| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `id` | `string` | yes | - |  |
| `label` | `string` | yes | - |  |
| `severity` | `BlackboardSeverity` | yes | - |  |
| `status` | `HypothesisStatus` | yes | - |  |
| `created_at` | `string` | yes | - |  |

### `RoutingDetailResponse`

| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `item` | `RoutingFeedItem` | yes | - |  |
| `signals` | `RoutingDetailSignal[]` | yes | - |  |
| `transfer_history` | `RoutingTransferEntry[]` | yes | - |  |
| `related_hypotheses` | `RoutingRelatedHypothesis[]` | yes | - |  |

### `RoutingFeedItem`

| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `slot` | `RoutingFeedSlot` | yes | - |  |
| `hypothesis` | `RoutingFeedHypothesis` | yes | - |  |
| `signal_summary` | `RoutingFeedSignalSummary` | yes | - |  |
| `routed_to` | `RoutingFeedUser \| null` | yes | - |  |
| `routed_to_role` | `RoutingFeedRole \| null` | yes | - |  |
| `lifecycle` | `RoutingFeedLifecycle` | yes | - |  |

### `RoutingTransferEntry`

| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `offer_id` | `string` | yes | - |  |
| `status` | `OpenEnum<RoutingTransferStatus>` | yes | - |  |
| `from_user` | `RoutingFeedUser \| null` | yes | - |  |
| `to_user` | `RoutingFeedUser \| null` | yes | - |  |
| `reason` | `string \| null` | yes | - |  |
| `created_at` | `string` | yes | - |  |
| `responded_at` | `string \| null` | yes | - |  |

### `RoutingFeedSlot`

| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `id` | `string` | yes | - |  |
| `status` | `SlotStatus` | yes | - |  |
| `priority_rank` | `number` | yes | - |  |
| `priority_score` | `number` | yes | - |  |
| `created_at` | `string` | yes | - |  |
| `escalated_from` | `string \| null` | yes | - |  |

### `SlotStatus`

```ts
export type SlotStatus = "ACTIVE" | "IN_PROGRESS" | "EXPIRED" | "ESCALATED"
```

### `RoutingFeedHypothesis`

| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `id` | `string` | yes | - |  |
| `label` | `string` | yes | - |  |
| `severity` | `BlackboardSeverity` | yes | - |  |
| `domain` | `string \| null` | yes | - |  |
| `cagg` | `number` | yes | - |  |
| `confidence_current` | `number` | yes | - |  |
| `created_at` | `string` | yes | - |  |
| `act_by_deadline` | `string \| null` | yes | - |  |
| `time_to_act_seconds` | `number \| null` | yes | - | Seconds until/past the act_by_deadline. - null = no deadline set - positive = time left - NEGATIVE = past-due (guide §4) |

### `RoutingFeedSignalSummary`

| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `count` | `number` | yes | - |  |
| `earliest_fired_at` | `string \| null` | yes | - |  |
| `latest_fired_at` | `string \| null` | yes | - |  |
| `metric_ids` | `string[]` | yes | - |  |
| `max_deviation_std` | `number \| null` | yes | - |  |
| `dimension_keys` | `Record<string, unknown>[]` | yes | - |  |

### `RoutingFeedUser`

| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `user_id` | `string` | yes | - |  |
| `email` | `string` | yes | - |  |
| `full_name` | `string` | yes | - |  |

### `RoutingFeedRole`

| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `role_id` | `string` | yes | - |  |
| `role_name` | `string` | yes | - |  |

### `RoutingFeedLifecycle`

| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `signal_to_hypothesis_seconds` | `number \| null` | yes | - |  |
| `hypothesis_to_routing_seconds` | `number \| null` | yes | - |  |
| `age_seconds` | `number` | yes | - |  |

### `RoutingTransferStatus`

```ts
export type RoutingTransferStatus =
  "PENDING" | "ACCEPTED" | "DECLINED" | "EXPIRED" | "CANCELED" | "WITHDRAWN"
```

### `OpenEnum`

```ts
export type OpenEnum<T extends string> = T | (string & {})
```

### `AttentionSlot`

| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `id` | `string` | yes | - |  |
| `executive_id` | `string` | yes | - |  |
| `hypothesis_id` | `string` | yes | - |  |
| `priority_rank` | `number` | yes | - |  |
| `priority_score` | `number` | yes | - |  |
| `signal_summary` | `SignalSummary` | yes | - |  |
| `escalation_deadline` | `string \| null` | yes | - |  |
| `escalated_from` | `string \| null` | yes | - |  |
| `status` | `SlotStatus` | yes | - |  |
| `closed_reason` | `string \| null` | yes | - |  |
| `created_at` | `string` | yes | - |  |
| `updated_at` | `string` | yes | - |  |
| `decayed_at` | `string \| null` | yes | - |  |
| `act_by_deadline` | `string \| null` | yes | - |  |
| `severity` | `BlackboardSeverity \| null` | yes | - |  |
| `confidence_initial` | `number \| null` | yes | - |  |
| `confidence_current` | `number \| null` | yes | - |  |
| `swap_out_slot_id` | `string \| null \| undefined` | no | - |  |

### `SignalSummary`

| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `label` | `string \| undefined` | no | - |  |
| `cagg` | `number \| undefined` | no | - |  |
| `sufficiency` | `number \| undefined` | no | - |  |
| `signal_count` | `number \| undefined` | no | - |  |
| `severity` | `BlackboardSeverity \| undefined` | no | - |  |
| `domain` | `string \| undefined` | no | - |  |
| `cluster` | `string \| undefined` | no | - |  |
| `equilibrium_state` | `string \| undefined` | no | - |  |
| `contributing_signals` | `ContributingSignal[] \| undefined` | no | - |  |

### `ContributingSignal`

| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `role` | `string` | yes | - |  |
| `cluster` | `string` | yes | - |  |
| `deviation` | `number` | yes | - |  |
| `metric_id` | `string` | yes | - |  |
| `signal_id` | `string` | yes | - |  |
| `confidence` | `number` | yes | - |  |

### `RoutingFlowResponse`

| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `hypothesis_id` | `string` | yes | - |  |
| `timeline` | `RoutingFlowEvent[]` | yes | - |  |

### `RoutingFlowEvent`

| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `at` | `string` | yes | - |  |
| `event` | `RoutingFlowEventType` | yes | - |  |
| `detail` | `Record<string, unknown>` | yes | - |  |

### `RoutingFlowEventType`

```ts
export type RoutingFlowEventType = OpenEnum<
  | "signal.fired"
  | "hypothesis.formed"
  | "hypothesis.routed"
  | "slot.transferred"
  | "decision.created"
>
```

### `RoutingDashboardSummary`

| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `in_flight_total` | `number` | yes | - |  |
| `by_status` | `RoutingSummaryStatusCounts` | yes | - |  |
| `by_severity` | `RoutingSummarySeverityCounts` | yes | - |  |
| `by_role` | `RoutingSummaryRoleCount[]` | yes | - |  |
| `deadlines` | `RoutingSummaryDeadlines` | yes | - |  |
| `throughput_last_24h` | `RoutingSummaryThroughput` | yes | - |  |

### `RoutingSummaryStatusCounts`

| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `ACTIVE` | `number \| undefined` | no | - |  |
| `IN_PROGRESS` | `number \| undefined` | no | - |  |
| `EXPIRED` | `number \| undefined` | no | - |  |
| `ESCALATED` | `number \| undefined` | no | - |  |

### `RoutingSummarySeverityCounts`

| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `critical` | `number \| undefined` | no | - |  |
| `high` | `number \| undefined` | no | - |  |
| `medium` | `number \| undefined` | no | - |  |
| `low` | `number \| undefined` | no | - |  |
| `info` | `number \| undefined` | no | - |  |

### `RoutingSummaryRoleCount`

| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `role_id` | `string` | yes | - |  |
| `role_name` | `string` | yes | - |  |
| `count` | `number` | yes | - |  |

### `RoutingSummaryDeadlines`

| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `breaching_within_1h` | `number` | yes | - |  |
| `breaching_within_4h` | `number` | yes | - |  |
| `breaching_within_24h` | `number` | yes | - |  |
| `past_deadline` | `number` | yes | - |  |

### `RoutingSummaryThroughput`

| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `signals_fired` | `number` | yes | - |  |
| `hypotheses_created` | `number` | yes | - |  |
| `slots_routed` | `number` | yes | - |  |
| `decisions_made` | `number` | yes | - |  |
| `median_signal_to_route_sec` | `number \| null` | yes | - |  |
| `median_route_to_decision_sec` | `number \| null` | yes | - |  |

## Changelog

# attention-routing-01
## 0.5.1

- **BREAKING**: Removed the `routing` prop (`AttentionRoutingDomainProps`)
  added in 0.4.0. Wiring `SignalFeedCard`, `RoutingSummaryTiles`, and
  `RoutingDetailPanel` directly into the block violated the "pure
  composition, no new visual language" rule the block has followed since
  0.3.0 — the block special-cased one domain instead of staying a generic
  orchestrator.
- The block is back to a pure orchestrator: only `items` / `renderItem` /
  `summary` / `detailPanel` / `getItemKey` / `onSelect` drive what renders.
  Consumers compose `RoutingSummaryTiles`, `RoutingDetailPanel`, and
  `SignalFeedCard` (via `toSignalFeedItem`) themselves and pass the result
  through those generic props — see the preview's `FullDemo` for the pattern.
- The routing-domain widgets and mappers shipped in 0.4.0
  (`RoutingSummaryTiles`, `RoutingDetailPanel`, `routing-feed.mappers`,
  `routing-detail-sections`, `RoutingSignalsSection`, `RoutingRelatedList`,
  `RoutingTransfersList`, `RoutingDeadlineBar`) are unchanged and still
  shipped — only the block's own special-casing of them was removed.

## 0.4.0

- Wired the routing-domain subcomponents (`RoutingSummaryTiles`,
  `RoutingDetailPanel`, `routing-feed.mappers`, `routing-detail-sections`,
  `RoutingSignalsSection`, `RoutingRelatedList`, `RoutingTransfersList`,
  `RoutingDeadlineBar`) into the orchestrator via a new optional `routing`
  prop (`AttentionRoutingDomainProps`). When passed, the block renders the
  built-in `SignalFeedCard` feed, KPI tiles, and detail drawer directly from
  routing-typed props — no fetching or business logic added to the block.
- The existing generic `items` / `renderItem` / `summary` / `detailPanel`
  props are unchanged and still work for non-routing consumers; `routing`
  takes precedence when supplied.
- Added `@signalos/signal-feed-card`, `@signalos/status-badge`,
  `@signalos/metric-tile`, `@signalos/circular-progress`,
  `@signalos/sidebar-detail-panel`, `@signalos/data-primitives`,
  `@signalos/lifecycle-timeline`, `@signalos/badge`, and `@signalos/skeleton`
  to `registryDependencies` for the now-wired components.

## 0.3.4
- Resolved a merge conflict.

## 0.3.3

- Title styling now comes from `page-header` defaults; `className` now
  lands on the block root instead of the inner record list.
- Use semantic status tokens (text-text-error, bg-bg-warning, border-border-*, bg-status-*) instead of raw palette scales so client theme overrides restyle status colors.

## 0.3.2

- Removed unused `Button` import from preview file.

## 0.3.1

- **BREAKING**: extra props added


## 0.3.0

- **BREAKING**: Fully componentized. The block's domain-specific local
  components (`RoutingFeedCard`, `RoutingFlowTimeline`, `SignalsList`,
  `RelatedHypothesisRow`, `SignalSummaryFallback`, and the `MetaBox` /
  `SectionLabel` / `EmptyState` atoms) were extracted into reusable,
  domain-agnostic widgets: `ranked-list-card`, `lifecycle-timeline`,
  `metric-bar-list`, and `data-primitives`.
- The block is now a pure orchestrator with a generic, strongly-typed API:
  `segmentFilters` / `selectFilters` arrays replace the hardcoded
  severity/breaching/owner filter props; `title` / `description` /
  `searchPlaceholder` / `resultLabel` are configurable; `summary` replaces
  `summaryData`.
- Removed the hardcoded filter-option constants and the item `cva` variants
  (styling now lives in `ranked-list-card`).
- Removed `class-variance-authority`, `@signalos/badge`, `@signalos/button`,
  `@signalos/dropdown-menu`, and `@signalos/status-badge` from the block's
  direct dependencies (now transitive through the extracted widgets).
- Removed leftover `console.log` calls and dead/commented code.

## 0.2.1

- **BREAKING**: Refactored to follow a fully reusable, component-driven architecture
- Removed domain-specific filter props (`severityFilter`, `breachingFilter`, `ownerFilter`, etc.)
- Added generic `segmentFilters` and `selectFilters` arrays for flexible filtering
- Added configurable `pageTitle` and `pageDescription` props
- Removed hardcoded filter options and labels from constants
- Component now acts as a pure orchestrator with no business logic
- All data, state, and behavior injected via props for maximum reusability
- Updated TypeScript interfaces to use generic, domain-agnostic types
- Improved documentation with JSDoc comments

## 0.1.4

- Add @signalos/pagination to registryDependencies.

## 0.1.3

- Remove unused pagination props from component signature.

## 0.1.2

- Remove commented unused PaginationBar import.

## 0.1.1

- Content sync fix to bring source in line with published registry payload.

## 0.1.0

- Initial release: attention-routing review queue composed from the shared
  `page-header`, `filter-toolbar`, and `list-view` widgets. Renders items
  with a severity badge, owner, wait time, and an overflow actions menu.
