# Building the timeline, end to end

A timeline is assembled when you open it, not stored. Connect reads the canonical records that own each event — conversations, calls, follow-ups, deals, cases — narrows them to one person through their identities, and orders them by when each thing happened rather than when Connect learned of it. What is missing is usually excluded on purpose or attached to a second record.

- **Status:** Available
- **Audience:** both
- **In the app:** #/timeline, #/relationships
- **Last verified:** 2026-09-10
- **Canonical:** https://connectbyjbrh.com/docs/workflows/timeline-assembly/

## The chain, stage by stage

1. Trigger — you open a person on Relationships, open the Timeline screen, or the Assistant calls `customer_360` on your behalf.
2. User or external event — none. Every event on a timeline is already a record; nothing is fetched from a provider while you read.
3. Authentication and workspace resolution — the session resolves to one workspace, and every read below is narrowed to it before a single record is loaded.
4. Request — `relationship_console` answers one relationship for either audience, so the Owner and a customer are served by one body of code behind two thin routers.
5. Canonical record — the sources are `threads` and `messages`, `calls`, follow-ups, opportunities, demos, cases and onboarding actions. The provider tables are never read here.
6. Reasoning — each event is attached to the person through an identity: one address on one channel, which resolves to one human. A person may hold many.
7. Knowledge, memory and rules — memory is shown beside the timeline rather than inside it, because what Connect knows about someone is not something that happened to them.
8. Autonomy and approval — nothing to decide. Assembly is a read; no draft is written and no approval is created by opening a timeline.
9. Action — no provider is called. This is the stage that does nothing, which is why the screen is safe to open on any record at any time.
10. Result — one list in event order, with the channel of each entry visible, and the counts that head the record derived from the same query rather than from a cached number.
11. Relationship, timeline and memory — a correction made elsewhere is visible here immediately: merging two people, attaching an identity, or restoring a deleted conversation all change what the next assembly returns.
12. Audit, usage and Needs You — reading is bounded by the same isolation as every other read, and it costs no allowance. Nothing enters Needs You because a timeline was opened.

| Stage | What you see | What changes | What can fail |
|---|---|---|---|
| Workspace resolution | The record opens | Nothing | The wrong workspace is active — the record is genuinely not there, rather than empty |
| Identity attachment | Entries from several channels under one name | Nothing | An address was never attached, so its history sits under a second person record |
| Ordering | Events in the order they happened | Nothing | A backlog fetched today reads as today if the event's own time is ignored |
| Assembly | One list, with counts above it | Nothing | A count derived from a slice rather than from the database disagrees with the list below it |

## Why it is ordered by the event, not by the fetch

The two times are different and the difference is visible on the day you connect a mailbox. A month of history arrives in one sync; if the list were ordered by when Connect stored each message, that whole month would pile onto the day you connected and the record would read as though a silent customer had suddenly written forty times.

Ordering by the event's own time keeps a returning customer legible: the gap between an enquiry two years ago and a call last week is information, and it is the first thing anybody uses a timeline for. It also means a late-arriving message lands in its correct place in the past rather than at the top, which is occasionally surprising and always right.

## What is left out on purpose

A timeline is a record of what reached, or was aimed at, this person in this workspace. Several things deliberately fail that test, and none of them is a fault to report.

**A reply that was written and never sent** — A held draft has not been handed to any provider, so nothing happened to the customer. It waits in Needs You, where a decision belongs — see [held drafts](/docs/email/held-drafts/).
**An enquiry sent to JBRH's own website** — Those live in a control-plane table outside every workspace by design. A customer not seeing them is isolation working, not a gap.
**A conversation the person deleted** — `threads.deleted_at` is the workspace's own triage, and honouring it is the point of having it.
**Anything belonging to another workspace** — Even when the same human writes to two workspaces, the two histories are separate records and stay separate.
**What Connect knows about someone** — Memory has four tiers and its own viewer. A durable fact about a person is not an event with a time, and mixing the two makes both harder to read.
**Provider-side bookkeeping** — Marking a message read, applying a label, a sync cursor moving. None of it is something that happened between two people.

## Telling a deliberate exclusion from a real gap

The two look identical at first glance — something you remember is not on the screen — and they are fixed in completely different places. Three questions separate them, in this order.

1. Ask whether anything actually left or arrived. A draft nobody approved, a call that was never placed, a follow-up still due.
   - Result: If nothing was delivered or attempted, the absence is correct. Look in Needs You or the follow-up queue, where the pending thing lives.
2. Find the event on its own channel screen — Conversations, Phone, WhatsApp — and see whether it exists at all.
   - Result: If it is there but not on the timeline, this is an attachment problem: the address it arrived on is not yet an identity of this person, or the history is split across two records that have not been merged.
3. Check whether the person is the one you think. Search the address, not the name.
   - Result: A second record with the same human behind it explains most "half the history is missing" reports. Duplicates are proposed automatically; the merge itself stays a human decision, and it preserves identities, stages, follow-ups, deals, demos, cases and onboarding from both sides.

> **Careful** A record written with an empty workspace stamp is invisible rather than missing: it matches no scope and no row-level security policy, so it is absent from every list while anything holding its id still works. That is the one absence that is a genuine defect, and it is repaired by migration rather than from a screen.

## Questions

### Is the timeline a separate copy of my history?

No. It is assembled on each read from the records that own each event, which is why a merge, an added identity or a restored conversation shows up immediately without anything being rebuilt. There is no second store to fall out of step, and nothing to reindex after a correction.

### Why does a message appear on Conversations but not on the timeline?

Because the timeline is per person and the conversation is per thread. If the address that message arrived on has not been attached as an identity of that person, the event has nowhere to hang. Attaching the identity — or merging the duplicate record it created — puts the whole history back under one name.

### Does opening a timeline cost anything against my plan?

No. Allowances meter work Connect does on your behalf, such as processing mail or placing a call. Reading a record you already own is not metered, and the assembly makes no provider call at all.

### Can I see the timeline for someone who has only ever phoned?

Yes. A person is the canonical human and a phone number is one identity among several, so a caller with no email address has a perfectly ordinary record — calls, their transcripts and summaries, follow-ups and any deal or case attached to them.

## Related

- [The customer timeline](https://connectbyjbrh.com/docs/relationships/timeline/)
- [Customer 360](https://connectbyjbrh.com/docs/relationships/customer-360/)
- [Assembling a Customer 360, end to end](https://connectbyjbrh.com/docs/workflows/customer-360-assembly/)
- [Identities](https://connectbyjbrh.com/docs/relationships/identities/)
- [Merging two records](https://connectbyjbrh.com/docs/relationships/merging/)
- [Something is missing from the timeline](https://connectbyjbrh.com/docs/troubleshooting/missing-timeline-entry/)
- [Memory in Connect](https://connectbyjbrh.com/docs/memory/)
- [Held drafts](https://connectbyjbrh.com/docs/email/held-drafts/)

## What this page is based on

- `docs-source/sources/CHANNELS.md` §5 — relationships, identity, the relationship console and merge behaviour
- `docs-source/sources/GENERAL.md` §3 and §6 — canonical records, the person's own triage columns, and the four memory tiers
- `docs-source/sources/CHANNELS.md` §7 — why website enquiries sit outside every workspace
- Connect capability registry (`docs-source/facts.py`) — `timeline`, `customer_360`, `cross_channel_identity`, `duplicate_merge`
