# Item History

## Overview

Every meaningful change to an item is captured as an append-only audit log so that the item detail timeline can reconstruct what happened, when, and by whom — analogous to GitHub Issue's timeline events. History is split across two tables:

| Table            | Written by                                                                          | Captures                                                                                  |
| ---------------- | ----------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- |
| PipelineStageTransition | MovePipelineItem                                                                            | PipelineStage changes (`fromStageId → toStageId`)                                              |
| PipelineItemChange       | UpdatePipelineItem, OpenPipelineItem, ClosePipelineItem, ReopenPipelineItem, AttachLabelToPipelineItem, DetachLabelFromPipelineItem | All other changes: content edits, field flips, lifecycle transitions, label attach/detach |

PipelineItemChange uses `changeType` (`CONTENT`, `FIELD`, `LIFECYCLE`, `LABEL`) and `fieldName` (`TITLE`, `DESCRIPTION`, `ASSIGNEE`, `PRIORITY`, `DUE_DATE`, `LIFECYCLE`, `LABEL`) to distinguish event categories, with `prevValue`/`newValue` as opaque strings.

"Append-only" means no command edits or removes individual history rows. The one exception is [DeletePipelineItem](../command/DeletePipelineItem.md): it hard-deletes the item together with all of its PipelineItemChange and PipelineStageTransition rows (the history has no reason to outlive its subject, and keeping it would leave dangling `itemId` references). Consumers that need a finished item's history to survive should close the item rather than delete it.

## Business Purpose

- Provide an audit trail for compliance and traceability
- Power the item detail "timeline" UI with chronologically merged events
- Enable analytics such as cycle time (via PipelineStageTransition) and rename frequency (via PipelineItemChange)
- Avoid concurrent-edit silent overwrites: when implemented, the timeline doubles as evidence of prior state

## Process Flow

```mermaid
flowchart TD
    A[Operator triggers a command on an item] --> B{Which command?}
    B -->|MovePipelineItem| C[Insert PipelineStageTransition]
    B -->|UpdatePipelineItem changes title/description| D[Insert PipelineItemChange changeType=CONTENT per changed field]
    B -->|UpdatePipelineItem changes assignee/priority/dueDate| E[Insert PipelineItemChange changeType=FIELD per changed field]
    B -->|OpenPipelineItem / ClosePipelineItem / ReopenPipelineItem| F[Insert PipelineItemChange changeType=LIFECYCLE]
    B -->|AttachLabelToPipelineItem / DetachLabelFromPipelineItem| G[Insert PipelineItemChange changeType=LABEL]
    C --> Z[Frontend timeline reads via PipelineItem.changes and PipelineStageTransition relations and merges chronologically]
    D --> Z
    E --> Z
    F --> Z
    G --> Z
```

## Scenario Patterns

- Operator edits an item's title and description in the same Save action → two PipelineItemChange rows (one for TITLE, one for DESCRIPTION) with prev/new values
- Operator reassigns an item and bumps its priority via the side panel → two PipelineItemChange rows (one for ASSIGNEE, one for PRIORITY), each with prev/new values
- Operator clicks "Mark complete" which closes the item → one PipelineItemChange (changeType=LIFECYCLE, OPEN → CLOSED). The stage itself didn't move, so no PipelineStageTransition
- Operator attaches the `regression` label to an item → one PipelineItemChange (changeType=LABEL, newValue=labelId)
- Reviewer scrolls through an item's timeline and sees comments, stage moves, label changes, and field changes interleaved by timestamp
- An external-facing portal built by a consuming app surfaces both event types (PipelineStageTransition and PipelineItemChange) — filtering is at the query layer, not via a database flag

## Test Cases

- UpdatePipelineItem with no actual field changes writes nothing to the audit tables
- UpdatePipelineItem changing only `position` writes nothing to the audit tables
- UpdatePipelineItem changing `title` writes exactly one PipelineItemChange with changeType=CONTENT, fieldName=TITLE
- UpdatePipelineItem changing `assigneeId` writes exactly one PipelineItemChange with changeType=FIELD, fieldName=ASSIGNEE
- UpdatePipelineItem changing `title` and `assigneeId` writes two PipelineItemChange rows (one CONTENT, one FIELD)
- OpenPipelineItem on a DRAFT item writes exactly one PipelineItemChange with changeType=LIFECYCLE, prevValue=DRAFT, newValue=OPEN
- ClosePipelineItem on an OPEN item writes exactly one PipelineItemChange with changeType=LIFECYCLE, prevValue=OPEN, newValue=CLOSED
- ReopenPipelineItem on a CLOSED item writes exactly one PipelineItemChange with changeType=LIFECYCLE, prevValue=CLOSED, newValue=OPEN
- AttachLabelToPipelineItem writes exactly one PipelineItemChange with changeType=LABEL, newValue=labelId
- DetachLabelFromPipelineItem writes exactly one PipelineItemChange with changeType=LABEL, prevValue=labelId
- Failed commands (validation errors) write nothing to any audit table
- DeletePipelineItem removes the item's PipelineItemChange and PipelineStageTransition rows together with the item

## Reference Links

- [GitHub Issues — Timeline events](https://docs.github.com/en/graphql/reference/objects#issuetimelineitems)
