# Signal Feed Card (`signal-feed-card`) - SignalOS widget

> Item-driven feed row for prioritised work queues: severity and status chips, a stage progress chain, act-by countdown, assignee, and priority score. Takes one flat item and owns every presentation decision, so screens map their domain record and nothing else.

- **Version:** 0.1.0
- **Kind:** widget · **Category:** data-display
- **Install:** `npx shadcn@latest add @signalos/signal-feed-card`
- **Registry dependencies (pulled automatically):** @signalos/tokens, @signalos/utils, @signalos/ranked-list-card, @signalos/status-badge
- **npm dependencies:** lucide-react@^1.7.0
- **Files installed:** `src/components/widgets/signal-feed-card/SignalFeedCard.tsx`, `src/components/widgets/signal-feed-card/SignalFeedCard.types.ts`

## 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
import { SignalFeedCard } from "@/components/widgets/signal-feed-card/SignalFeedCard"
```

## Example

```tsx
// Example: a work-queue row mapped from an arbitrary domain record.
// The screen formats every value; the card owns the layout.
import { Route, Zap } from "lucide-react"

import { SignalFeedCard } from "@/components/widgets/signal-feed-card/SignalFeedCard"
import type { SignalFeedItem } from "@/components/widgets/signal-feed-card/SignalFeedCard.types"

interface QueueRecord {
  id: string
  headline: string
  priority: number
  confidence: number
  severity: "critical" | "high" | "medium" | "low"
  owner: string | null
  team: string
  dueInHours: number
}

function toItem(record: QueueRecord): SignalFeedItem {
  return {
    id: record.id,
    rank: record.priority,
    confidence: record.confidence,
    title: record.headline,
    severity: record.severity,
    status: "IN PROGRESS",
    assignee: { name: record.owner ?? undefined, role: record.team },
    deadline: {
      label:
        record.dueInHours < 0
          ? `Past due by ${Math.abs(record.dueInHours)}h`
          : `${record.dueInHours}h`,
      tone:
        record.dueInHours < 0
          ? "past-due"
          : record.dueInHours < 4
            ? "urgent"
            : "normal",
    },
    stages: [
      {
        id: "detected",
        label: "Detected",
        icon: <Zap className="h-2.5 w-2.5" />,
      },
      {
        id: "routed",
        label: "Routed",
        value: "+2m",
        icon: <Route className="h-2.5 w-2.5" />,
        emphasized: true,
      },
    ],
  }
}

const records: QueueRecord[] = [
  {
    id: "rec-1",
    headline: "Coverage collapse across multiple tenants",
    priority: 1,
    confidence: 62,
    severity: "high",
    owner: "Priya N.",
    team: "Integration",
    dueInHours: 3,
  },
]

export default function Example() {
  return (
    <div className="flex flex-col gap-3">
      {records.map((record) => (
        <SignalFeedCard
          key={record.id}
          item={toItem(record)}
          onOpen={(item) => console.info("open", item.id)}
        />
      ))}
    </div>
  )
}
```

## Props

### `SignalFeedDeadlineTone`

```ts
export type SignalFeedDeadlineTone = "normal" | "urgent" | "past-due"
```

### `SignalFeedHighlightTone`

```ts
export type SignalFeedHighlightTone = "default" | "warning" | "error"
```

### `SignalFeedStage`

| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `id` | `string` | yes | - | Stable key for the stage. |
| `label` | `ReactNode` | yes | - | Stage name, e.g. "Detected" or "Assigned". |
| `value` | `ReactNode` | no | - | Pre-formatted elapsed/relative value shown next to the label, e.g. "+37s". |
| `icon` | `ReactNode` | no | - | Optional leading icon (a ~10px glyph reads best). |
| `emphasized` | `boolean \| undefined` | no | - | Renders the stage with the primary accent — use for the current stage. |

### `SignalFeedDeadline`

| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `label` | `ReactNode` | yes | - | Pre-formatted countdown, e.g. "1d 21h 55m", "Past due by 2h", "No deadline". |
| `tone` | `SignalFeedDeadlineTone \| undefined` | no | `"normal"` | Urgency tone. |

### `SignalFeedAssignee`

| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `name` | `ReactNode` | no | - | Display name of the person. |
| `role` | `ReactNode` | no | - | Secondary line — the role, team, or queue the item was routed to. |

### `SignalFeedHighlight`

| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `label` | `ReactNode` | yes | - | Pre-formatted value, e.g. "max 12.4σ" or "peak 3.1x". |
| `tone` | `SignalFeedHighlightTone \| undefined` | no | `"default"` | Emphasis tone. |

### `SignalFeedItem`

| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `id` | `string` | yes | - | Stable identifier; also used as the React key by list callers. |
| `title` | `ReactNode` | yes | - | Primary headline (clamped to two lines). |
| `rank` | `ReactNode` | no | - | Left-column rank. A `number` renders as `#2`; any other node renders verbatim. Omit to hide the entire left column. |
| `confidence` | `number \| undefined` | no | - | Confidence/progress arc under the rank, 0–100. |
| `confidenceColorClass` | `string \| undefined` | no | `"text-primary"` | Tailwind `text-*` class driving the arc color. |
| `severity` | `Severity \| null \| undefined` | no | - | Severity chip and accent border. `null` renders a neutral "UNSET" chip. |
| `status` | `ReactNode` | no | - | Workflow status chip, e.g. "IN PROGRESS". |
| `reference` | `ReactNode` | no | - | Short mono identifier shown beside the chips, e.g. "A1B2C3D4". |
| `category` | `ReactNode` | no | - | Grouping label in the meta row, e.g. a domain or product area. |
| `metric` | `ReactNode` | no | - | Primary measurement name in the meta row, rendered as `<code>`. |
| `signalCount` | `number \| undefined` | no | - | Number of underlying signals; pluralised with `labels.signalNoun`. |
| `highlight` | `SignalFeedHighlight \| undefined` | no | - | Emphasised measurement in the meta row. |
| `escalated` | `boolean \| undefined` | no | `false` | Shows the corner ribbon (e.g. an escalation flag). |
| `deadline` | `SignalFeedDeadline \| undefined` | no | - | Act-by countdown; its tone also drives the card's urgency border. |
| `assignee` | `SignalFeedAssignee \| undefined` | no | - | Current owner shown in the trailing column and mobile footer. |
| `score` | `ReactNode` | no | - | Pre-formatted priority score shown in the trailing footer, e.g. "0.746". |
| `stages` | `readonly SignalFeedStage[] \| undefined` | no | - | Horizontal progress chain under the title. |

### `SignalFeedCardLabels`

| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `rank` | `ReactNode` | no | `"Rank"` | Caption above the rank number. |
| `deadline` | `ReactNode` | no | `"Act by"` | Caption above the countdown. |
| `assignee` | `ReactNode` | no | `"Routed to"` | Caption above the assignee. |
| `escalated` | `ReactNode` | no | `"Escalated"` | Corner ribbon text when `item.escalated` is set. |
| `signalNoun` | `string \| undefined` | no | `"signal"` | Singular noun for `item.signalCount`; an "s" is appended when != 1. |
| `scorePrefix` | `ReactNode` | no | `"score"` | Prefix before `item.score` in the trailing footer. |

### `SignalFeedCardProps`

| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `item` | `SignalFeedItem` | yes | - | The record to render. |
| `selected` | `boolean \| undefined` | no | `false` | Selected state (primary ring + border). |
| `onOpen` | `((item: SignalFeedItem) => void) \| undefined` | no | - | Invoked on click / Enter / Space. Omit to render a non-interactive card. |
| `labels` | `SignalFeedCardLabels \| undefined` | no | - | Caption overrides. |
| `className` | `string \| undefined` | no | - |  |
| `data-testid` | `string \| undefined` | no | `"signal-feed-card"` | Root test id; inner elements derive from it (`-deadline`, `-deadline-mobile`, `-assignee`, `-score`, `-escalated`, `-stage-<id>`). |

### `SignalFeedCardSkeletonProps`

| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `className` | `string \| undefined` | no | - |  |
| `data-testid` | `string \| undefined` | no | `"signal-feed-card-skeleton"` |  |

## Changelog

# signal-feed-card

## 0.1.0

- Initial release: item-driven feed card for prioritised queues, built on
  `ranked-list-card`. Renders severity/status chips, a reference, a meta row
  (category, metric, signal count, highlight), a stage progress chain, an
  act-by countdown with urgency tone, assignee, and a priority score, plus a
  mobile footer and a loading skeleton. Every value arrives pre-formatted so
  the widget never guesses a locale or unit.
