---
id: attributes-lifecycle
class: b2
sourced: 2026-07-12
source: "b2b-ux-patterns ch.2"
license: open
---
# Pass: attributes, lifecycle & history

**Inputs:** per field — `historySignal` (none | trajectory | compliance | derived-high-frequency), `fieldOrigin` (manual | computed | synced | imported | ai-generated), `disputeLikely`; per object — `objectHasLifecycle`, `transitionRequirements`, `timeProperty` (sla | due-escalation | recurrence | snooze | none), `endOfLife` (reference-after-completion | user-data-deletion | compliance-erasure), `multipleEntryPoints`.
**Owns:** `requiredMechanics`, `requiredStates`, `clarifications`.
**Output artifact:** per-field declarations (type, history class, origin) and per-object declarations (state machine, time properties, end-of-life tier) that every later pass derives from mechanically — chips, timelines, trends, boards, gated buttons, countdowns, trash, and merge are renders of these declarations, never inventions.

## The rule

**Every field declares one of three history classes.** *Overwrite* (the default — update in place, no history; paying history cost on every descriptive field is how products drown their own timelines). *Logged* (every change attributed: old value, new value, actor, timestamp) for fields where the trajectory is the meaning — stage, status, owner, amount — and anything with compliance weight or dispute risk; Logged fields are what make a detail page's history tab worth opening. *Sampled* (sparse snapshots or rollups) for high-frequency derived values — scores, metrics, queue depths; a Sampled field renders as a number with a trace (sparkline, as-of view), never as a change log. An empty timeline is a history-class declaration failure, not a UI failure.

**Origin decides editability and provenance.** Computed fields are never directly editable. Synced fields render their source and last-sync ("Enriched by Clearbit, 3 days ago") and need a conflict resolution when the sync wants to overwrite a human edit. Field types drive four renders at once — cell, input, filter, sort — declared once, derived everywhere; enums declare semantic color and icon here, never color alone.

**The state machine is data:** a status enum, legal transitions, terminal states. The UI derives gated verbs (Refund exists only on succeeded payments), status chips rendered identically everywhere, spatial board projection (a drag is a transition), and per-transition requirements (required fields, approvals, permissions) declared on the transition, not in form code. The status field itself is almost always Logged.

**Time properties mutate records with no user action** — SLA countdowns that re-sort queues, escalation on due-date breach, recurrence materializing instances, snooze as a time-scoped visibility state. Lists holding such objects are not stable; queues sort on the clock.

**Every object declares how it dies**, in order of preference: archive (restorable, filter-exposed) for completed referenceable work; soft-delete with a trash retention window — the data property that permits undo-toast instead of a confirm dialog; hard delete reserved for compliance erasure. Any object with multiple entry points will accumulate duplicates and needs a merge flow whose survivor keeps the Logged history — a merge that discards a Logged trail breaks the audit chain.

```json decision-table
{"pass": "attributes-lifecycle", "rows": [
  {"when": {"historySignal": "none"}, "then": {"requiredMechanics": {"mustInclude": ["overwrite-in-place"], "mustNotInclude": ["activity-timeline-entries"]}}, "reason": "descriptive-fields-overwrite"},
  {"when": {"historySignal": ["trajectory", "compliance"]}, "then": {"requiredMechanics": {"mustInclude": ["logged-field-history", "activity-timeline-entries", "changed-by-attribution"]}}, "reason": "trajectory-is-the-meaning"},
  {"when": {"historySignal": "derived-high-frequency"}, "then": {"requiredMechanics": {"mustInclude": ["sampled-snapshots", "sparkline-trend"], "mustNotInclude": ["activity-timeline-entries"]}}, "reason": "high-frequency-sampled-not-logged"},
  {"when": {"disputeLikely": true, "historySignal": "__absent__"}, "then": {"clarifications": ["history-class-unspecified"]}, "reason": "disputes-need-declared-history"},
  {"when": {"fieldOrigin": "computed"}, "then": {"requiredMechanics": {"mustInclude": ["read-only-derived-render"], "mustNotInclude": ["editable-input"]}}, "reason": "computed-never-editable"},
  {"when": {"fieldOrigin": "synced"}, "then": {"requiredMechanics": {"mustInclude": ["provenance-render", "sync-conflict-resolution"]}}, "reason": "synced-origin-carries-provenance"},
  {"when": {"objectHasLifecycle": true}, "then": {"requiredMechanics": {"mustInclude": ["status-enum-semantic-colors", "transition-graph-legal-only", "state-gated-verbs"]}}, "reason": "state-machine-is-data"},
  {"when": {"transitionRequirements": true}, "then": {"requiredMechanics": {"mustInclude": ["transition-required-fields"]}}, "reason": "requirements-on-transition-not-form"},
  {"when": {"timeProperty": ["sla", "due-escalation"]}, "then": {"requiredMechanics": {"mustInclude": ["sla-countdown-render", "breach-state-resort"]}, "requiredStates": {"mustInclude": ["stale"]}}, "reason": "clock-mutates-the-record"},
  {"when": {"timeProperty": "snooze"}, "then": {"requiredMechanics": {"mustInclude": ["snooze-until-resurface"]}}, "reason": "snooze-is-visibility-not-deletion"},
  {"when": {"endOfLife": "reference-after-completion"}, "then": {"requiredMechanics": {"mustInclude": ["archive-restorable"], "mustNotInclude": ["hard-delete"]}}, "reason": "archive-completed-work"},
  {"when": {"endOfLife": "user-data-deletion"}, "then": {"requiredMechanics": {"mustInclude": ["soft-delete-trash-retention"], "mustNotInclude": ["hard-delete"]}}, "reason": "soft-delete-enables-undo"},
  {"when": {"endOfLife": "compliance-erasure"}, "then": {"requiredMechanics": {"mustInclude": ["hard-delete-typed-confirmation"]}}, "reason": "hard-delete-compliance-only"},
  {"when": {"multipleEntryPoints": true}, "then": {"requiredMechanics": {"mustInclude": ["merge-duplicates-flow", "survivor-history-preserved"]}}, "reason": "multiple-entry-points-guarantee-duplicates"}
]}
```
