[Traits](https://oods-foundry.com/traits) / Statusable

# Statusable

Semantic status presentation layer that maps business statuses to visual tones, design tokens, and badge/banner rendering. Statusable owns the status registry pattern: a domain-scoped lookup table that translates raw status strings into consistent visual output (tone, label, icon, token sets). DISTINCT from Stateful — Stateful governs state machine transitions and guards; Statusable governs how status LOOKS. Statusable is the bridge between data-layer state and the component layer (Badge, Banner, StatusBadge). Core concepts: - Status Registry: Map\<domain, Map\<status, StatusPresentation>> - Tone System: Six semantic tones (neutral, info, accent, success, warning, critical) - Token Resolution: --cmp-status-{tone}-{surface|border|text} CSS variables - Domain Scoping: Each domain (subscription, invoice, ticket, etc.) has its own status map - Emphasis Variants: badge.subtle / badge.solid, banner.subtle / banner.solid

Generated from @oods/foundry 0.10.1

- Group

  visual

- Maturity

  experimental

- Contexts

  detail, form, list, timeline

## Fields it adds

| Field | Type | Required | Description |
| - | - | - | - |
| `status` | `string` | yes | Raw business status string (e.g., "active", "past_due", "unpaid"). Resolved against the domain's status map to derive presentation data. |
| `domain` | `StatusDomain` | yes | Domain scope for registry lookup. Determines which status map is consulted (e.g., "subscription" statuses differ from "invoice" statuses). |
| `tone` | `StatusTone` (neutral, info, accent, success, warning, critical) | no | Semantic tone derived from the registry or inferred from token paths. One of: neutral, info, accent, success, warning, critical. Falls back to the defaultTone parameter when unresolvable. |
| `badge_tokens` | `StatusTokenVariants` | no | Resolved badge token sets with subtle and solid emphasis variants. Each variant contains { background, border, foreground } CSS variable references. Structure: { subtle: StatusTokenSet, solid: StatusTokenSet } |
| `banner_tokens` | `StatusTokenVariants` | no | Resolved banner token sets with subtle and solid emphasis variants. Subtle banners use domain-specific tokens when available; solid banners use the tone-based component token set. Structure: { subtle: StatusTokenSet, solid: StatusTokenSet } |

## What it shows in each context

- [list](https://oods-foundry.com/contexts/list)

  `StatusBadge`

- [detail](https://oods-foundry.com/contexts/detail)

  `StatusBadge`, `Banner`

- [form](https://oods-foundry.com/contexts/form)

  `StatusBadge`

- [timeline](https://oods-foundry.com/contexts/timeline)

  `StatusBadge`

## Parameters

- `domains` `string[]`, required

  Domain identifiers whose statuses this object participates in. Each domain resolves to a scoped status map in the registry (e.g., "subscription", "invoice", "ticket", "user").

- `toneAliases` `Record<string, StatusTone>`

  Custom alias map for inferring tones from token path segments. Merged with built-in aliases (info, positive→success, danger→critical, etc.). Keys are token path segments; values are canonical StatusTone values.

- `defaultTone` `StatusTone`

  Fallback tone applied when a status is not found in the registry or when tone inference from token paths yields no match.

## Objects that use it

No shipped object uses this trait.

## The trait file

`traits/visual/Statusable.trait.yaml`

```
trait:
  name: Statusable
  version: 1.0.0
  description: |
    Semantic status presentation layer that maps business statuses to visual tones,
    design tokens, and badge/banner rendering. Statusable owns the status registry
    pattern: a domain-scoped lookup table that translates raw status strings into
    consistent visual output (tone, label, icon, token sets).

    DISTINCT from Stateful — Stateful governs state machine transitions and guards;
    Statusable governs how status LOOKS. Statusable is the bridge between data-layer
    state and the component layer (Badge, Banner, StatusBadge).

    Core concepts:
    - Status Registry: Map<domain, Map<status, StatusPresentation>>
    - Tone System: Six semantic tones (neutral, info, accent, success, warning, critical)
    - Token Resolution: --cmp-status-{tone}-{surface|border|text} CSS variables
    - Domain Scoping: Each domain (subscription, invoice, ticket, etc.) has its own status map
    - Emphasis Variants: badge.subtle / badge.solid, banner.subtle / banner.solid
  category: visual
  tags:
    - status
    - presentation
    - registry
    - badge
    - banner
    - tone
    - semantic
    - saas

parameters:
  - name: domains
    type: string[]
    required: true
    description: |
      Domain identifiers whose statuses this object participates in.
      Each domain resolves to a scoped status map in the registry
      (e.g., "subscription", "invoice", "ticket", "user").
    default:
      - subscription
  - name: toneAliases
    type: Record<string, StatusTone>
    required: false
    description: |
      Custom alias map for inferring tones from token path segments.
      Merged with built-in aliases (info, positive→success, danger→critical, etc.).
      Keys are token path segments; values are canonical StatusTone values.
    default: {}
  - name: defaultTone
    type: StatusTone
    required: false
    description: |
      Fallback tone applied when a status is not found in the registry
      or when tone inference from token paths yields no match.
    default: neutral
    validation:
      enum:
        - neutral
        - info
        - accent
        - success
        - warning
        - critical

schema:
  status:
    type: string
    required: true
    description: |
      Raw business status string (e.g., "active", "past_due", "unpaid").
      Resolved against the domain's status map to derive presentation data.
  domain:
    type: StatusDomain
    required: true
    description: |
      Domain scope for registry lookup. Determines which status map is consulted
      (e.g., "subscription" statuses differ from "invoice" statuses).
    validation:
      enumFromParameter: domains
  tone:
    type: StatusTone
    required: false
    description: |
      Semantic tone derived from the registry or inferred from token paths.
      One of: neutral, info, accent, success, warning, critical.
      Falls back to the defaultTone parameter when unresolvable.
    validation:
      enum:
        - neutral
        - info
        - accent
        - success
        - warning
        - critical
  badge_tokens:
    type: StatusTokenVariants
    required: false
    description: |
      Resolved badge token sets with subtle and solid emphasis variants.
      Each variant contains { background, border, foreground } CSS variable references.
      Structure: { subtle: StatusTokenSet, solid: StatusTokenSet }
  banner_tokens:
    type: StatusTokenVariants
    required: false
    description: |
      Resolved banner token sets with subtle and solid emphasis variants.
      Subtle banners use domain-specific tokens when available; solid banners
      use the tone-based component token set.
      Structure: { subtle: StatusTokenSet, solid: StatusTokenSet }

semantics:
  status:
    semantic_type: status.presentation.status
    token_mapping: tokenMap(status.registry.*)
    ui_hints:
      component: StatusBadge
      registryLookup: getStatusPresentation(domain, status)
  domain:
    semantic_type: status.presentation.domain
    token_mapping: tokenMap(status.domains.*)
    ui_hints:
      component: DomainSelector
      scopedTo: domains
  tone:
    semantic_type: status.presentation.tone
    token_mapping: tokenMap(status.tone.*)
    ui_hints:
      component: ToneBadge
      toneValues:
        - neutral
        - info
        - accent
        - success
        - warning
        - critical
  badge_tokens:
    semantic_type: status.presentation.badge
    token_mapping: tokenMap(cmp.status.{tone}.*)
    ui_hints:
      component: Badge
      emphasisVariants:
        - subtle
        - solid
  banner_tokens:
    semantic_type: status.presentation.banner
    token_mapping: tokenMap(cmp.status.{tone}.*)
    ui_hints:
      component: Banner
      emphasisVariants:
        - subtle
        - solid

view_extensions:
  # StatusBadge rather than Badge, for the reason lifecycle/Supersedable already records: the
  # canonical Badge contract carries no field directive, so a Badge authored with these props is
  # refused by the target contracts. Measured in s204-m02 against code.generate rather than assumed —
  # Badge reds on THREE of the four props below (statusField, domainField and showIcon) in both React
  # and Vue, while StatusBadge takes all four with zero errors. No object composes Statusable's list
  # extension today, so this sat unexercised as a latent refusal rather than a visible break; every
  # other context in this trait already authors StatusBadge.
  list:
    - component: StatusBadge
      position: after
      props:
        statusField: status
        domainField: domain
        emphasis: subtle
        showIcon: true
  detail:
    - component: StatusBadge
      position: main
      priority: 80
      props:
        statusField: status
        domainField: domain
        emphasis: subtle
        showIcon: true
    - component: Banner
      position: top
      priority: 90
      props:
        statusField: status
        domainField: domain
        emphasis: subtle
        showIcon: true
      conditions:
        tones:
          - warning
          - critical
  form:
    - component: StatusBadge
      position: top
      props:
        statusField: status
        domainField: domain
        emphasis: subtle
        readOnly: true
  timeline:
    - component: StatusBadge
      props:
        statusField: status
        domainField: domain
        emphasis: subtle
        showIcon: true
        compact: true

tokens:
  # Badge tone tokens — pattern: --cmp-status-{tone}-{surface|border|text}
  cmp.status.neutral.surface: "var(--sys-surface-neutral)"
  cmp.status.neutral.border: "var(--sys-border-neutral)"
  cmp.status.neutral.text: "var(--sys-text-neutral)"
  cmp.status.info.surface: "var(--sys-surface-info)"
  cmp.status.info.border: "var(--sys-border-info)"
  cmp.status.info.text: "var(--sys-text-info)"
  cmp.status.accent.surface: "var(--sys-surface-accent)"
  cmp.status.accent.border: "var(--sys-border-accent)"
  cmp.status.accent.text: "var(--sys-text-accent)"
  cmp.status.success.surface: "var(--sys-surface-success)"
  cmp.status.success.border: "var(--sys-border-success)"
  cmp.status.success.text: "var(--sys-text-success)"
  cmp.status.warning.surface: "var(--sys-surface-warning)"
  cmp.status.warning.border: "var(--sys-border-warning)"
  cmp.status.warning.text: "var(--sys-text-warning)"
  cmp.status.critical.surface: "var(--sys-surface-critical)"
  cmp.status.critical.border: "var(--sys-border-critical)"
  cmp.status.critical.text: "var(--sys-text-critical)"
  # Banner fallback tokens
  cmp.banner.background: "var(--sys-surface-raised)"
  cmp.banner.border: "var(--sys-border-default)"
  cmp.banner.text: "var(--sys-text-default)"

# Statusable can work standalone with static status strings.
# When composed with Stateful, status values are driven by the state machine.
# Stateful is optional — not listed as a dep to avoid required-dep enforcement.
dependencies: []

metadata:
  created: "2026-02-28"
  owners:
    - design@oods.systems
    - engineering@oods.systems
  maturity: experimental
  accessibility:
    keyboard: Badge and Banner elements are not interactive; no keyboard handling required.
    screenreader: |
      StatusBadge announces status label and tone as a live region.
      Banner uses role="status" with aria-live="polite" for critical/warning tones.
  regionsUsed:
    - list
    - detail
    - form
    - timeline
  examples:
    - Subscription
    - Invoice
    - Ticket
    - User
  references:
    - "objects/provenance.v1.json#/references/ref-ca663a7c5731 — StatusPresentation, StatusTone, tone inference, domain maps"
    - "objects/provenance.v1.json#/references/ref-24923e661958 — 7 sub-stories across 5 domains"
    - "objects/provenance.v1.json#/references/ref-be33a98177f7 — domain status token map consumed by registry"
    - "objects/provenance.v1.json#/references/ref-c373015a9045 — StatusableViewData, StatusTransitionEvent interfaces"
    - "objects/provenance.v1.json#/references/ref-cc4cadcf82d5 — formatStatus, resolveDisplayName utilities"
```
