# The RecordTimeline kit — what happened to a record, on its own timeline

> `Osysharp.RecordTimeline` puts what happened to a record on its timeline: every event the app and its kits raise about it, one line each, said in the reader's words when it is read — and, on a record others roll up to, what happened to those too. A line is shown only to somebody who may read the record, and kept for a declared time. The app writes one policy and places one component.

<!-- id: ui-activity-kit · area: ui · stability: preview · html: https://osysharp.com/reference/ui/activity-kit/ -->

## Summary        {#summary}
`use Osysharp.RecordTimeline@0;` gives every record a timeline, and the app names no event and no record to get it. What
happens is already said by the app's [events](https://osysharp.com/reference/function/event/) — and every kit's — so the kit hears them all and keeps
one line for each on the record it is about:

```osy syntax
policy ReadsActivity => IsRep;                  // who reads a record's activity
ActivityTimeline(quote.Id);                     // on the record's page
```

## Signature      {#signature}
```osy syntax
use Osysharp.RecordTimeline@0;                        // with Osysharp.Identity@0, Osysharp.Workflow and Osysharp.Ui

policy ReadsActivity => <who reads a record's activity>;   // REQUIRED

[RollsUpTo] Opportunity Opportunity;            // on a reference: this record's lines are on that one's timeline too

app.Activity = new ActivitySetup { Keep = TimeSpan.FromDays(365) };   // optional — how long a line is kept

ActivityTimeline(Guid record, int take = 50)    // the timeline, newest first, on whatever page shows the record
List<ActivityLine> ActivityOf(Guid record, int take = 50)   // its lines — Id, At, Said, From, FromLink
```

## Description    {#description}

### One line for every event about the record   {#lines}
The kit hears every event ([[function-on#every]]) and writes a line on the record the event is about — its first
record-typed member — after the commit that raised it. An event about no record is not on any timeline.

### Rolled up   {#roll-up}
A reference marked `[RollsUpTo]` ([[stdlib-records#rolls-up-to]]) says that what happens to this record happened to
that one too: a quote's lines are also on its opportunity's timeline, each naming the quote it came from.

### Said when read   {#said}
A line keeps the EVENT, and says it with the event's own `Sentence()` when the timeline is read — in the reader's
language, with the event's records read as the reader may read them. A renamed record reads with its new name, and a
record the reader may not read is never named: the event says its label instead.

### Only for somebody who may read the record   {#security}
`ActivityOf` reads the record as its reader, through the line: a record they may not read has an empty timeline,
whatever `ReadsActivity` says. SECURITY-INVARIANT `activity-is-shown-only-on-a-record-its-reader-may-read`.

### Kept for a declared time   {#retention}
A line is kept a year by default — a record's history is read while it is worked and when it is looked back on — and an
hourly sweep removes older ones; set `Keep` to shorten it. A line goes with its record. What an event carried can be
personal, so a line keeps it classified as content. The platform's own copy of a delivered event is gone after a week
([[function-raise#retention]]): the timeline is where a record's history lives.

## Examples       {#examples}

### A quote's story, on its opportunity   {#example-pipeline}
```osy title="two events about a quote, rolled up to its opportunity, and the timeline on its page" test app=ui-activity-kit
app Pipeline {
  use Osysharp.Identity@0;
  use Osysharp.RecordTimeline@0;
  use Osysharp.Workflow;
  use Osysharp.Ui;
  model "model/**/*.osy";
}

using Osysharp.RecordTimeline;

[Role] enum PipelineRole { Authenticator, Rep }

// The example runs signed out, so anybody reads.
policy ReadsActivity => IsAnonymous || IsAuthenticated;

[Title(Name)]
entity Opportunity {
  [Required, MaxLength(120)] string Name;
  security { allow read, create, update when IsAnonymous || IsAuthenticated; }
}

[Title(Title)]
entity Quote {
  [Required, MaxLength(120)] string Title;
  [RollsUpTo] Opportunity Opportunity;
  security { allow read, create, update when IsAnonymous || IsAuthenticated; }
}

[Event("Quote sent")]
public sealed class QuoteSent { public Quote Quote; }

[Event("Quote accepted")]
public sealed class QuoteAccepted {
  public Quote Quote;
  public override string Sentence() { return $"{Quote.Title} was accepted."; }
}

/// An opportunity's page, with everything that happened to it and its quotes.
[Route("/opportunities/{id}")] [Authorize] [Render(CSR)]
component OpportunityPage(Guid id) {
  render { ActivityTimeline(id); }
}
```

```osy title="both lines are on the opportunity, each said its own way" run app=ui-activity-kit
[Test]
void A_quotes_events_are_on_its_opportunity_too() {
  var o = new Opportunity { Name = "Fleet renewal" };
  var q = new Quote { Title = "Twelve vans", Opportunity = o };
  Raise(new QuoteSent { Quote = q });
  Raise(new QuoteAccepted { Quote = q });
  Background.Settle();
  Assert.Equal(["Quote sent", "Twelve vans was accepted."], ActivityOf(o.Id).Select(l => l.Said).OrderBy(s => s).ToList());
  Assert.Equal(2, ActivityOf(q.Id).Count);
}
```

## See also       {#see-also}
- [[Event] — what happens to a record, declared once](https://osysharp.com/reference/function/event/) — what a line is: an event, and the sentence it says
- [[On] — hear an event, after the commit that raised it](https://osysharp.com/reference/function/on/) — how the kit hears every event
- [Records — what any record is called, where it opens, who may read it](https://osysharp.com/reference/stdlib/records/) — `[RollsUpTo]`, and what any record is called
- [The Automations kit — rules that do an app's routine work when something happens](https://osysharp.com/reference/ui/automations-kit/) — every rule's run is an event, so it is on the timeline too
- [The Notifications kit — telling a person that something needs them](https://osysharp.com/reference/ui/notifications-kit/) — telling a person, rather than keeping the record's story
- [[OnDelete]](https://osysharp.com/reference/entity/on-delete/) — why a line goes with its record
- **`kits/record-timeline/tests/`** — every behaviour above as a test, the timeline under `--pixels`
