# useThreadSeparators

Where a thread's dividers go, chosen by how long the thread runs: for each message that starts a new period, the props for its ThreadDivider. Draw `<ThreadDivider {...separators.get(i)} />` before message `i` when the map has it.

- A thread that runs under 14 days is divided by day, and each divider gets `date`, so it reads "Today", "Yesterday" or the date.
- Under 120 days, by week: "Week of 9 Nov 2026", the week starting on the locale's first day (Monday when the browser can't say).
- Under 730 days, by month: "November 2026". Longer, by year: "2025".

`granularity` fixes the period instead of choosing it. Days, weeks, months and years are counted in LocaleProvider's time zone (the browser's when it sets none, and UTC until the page hydrates, as every Candor time), and the labels are in its locale. A message whose time is missing or can't be read joins the period before it, so it never starts one.

The times are compared by what they hold, not by identity, so a `times` array made afresh on each render (a `messages.map(…)`) doesn't work the dividers out again, and the map it returns stays the same object until a time changes.

- Registry name: `use-thread-separators` (`@candor/use-thread-separators`)
- Tier: Pro. Installing it needs a Pro, Premium or Team licence key: the person runs `npx candor-ui key <key>` themselves; never ask for the key in chat or write it to a file.
- Docs: https://candor.themethodic.com/docs/use-thread-separators

## Install

```bash
npx candor-ui add use-thread-separators
```

Run `npx candor-ui@latest init` once first (https://candor.themethodic.com/get-started.md). Without the CLI: `npx shadcn@latest add @candor/use-thread-separators`.
It brings in: locale, tokens.

## Example

```tsx
"use client"

import { ThreadDivider } from "@/components/candor/thread-divider"
import { useThreadSeparators } from "@/lib/candor/use-thread-separators"

// A thread that runs for about five months, so it is divided by month.
const messages = [
  { id: "m1", at: "2026-06-03T09:12:00Z", text: "Can you watch the refund queue for duplicates?" },
  { id: "m2", at: "2026-06-03T09:13:00Z", text: "Yes. I’ll flag any order charged twice." },
  { id: "m3", at: "2026-08-19T16:40:00Z", text: "Three duplicates this week, all from the same card reader." },
  { id: "m4", at: undefined, text: "A message whose time wasn’t recorded joins the month before it." },
  { id: "m5", at: "2026-11-02T08:05:00Z", text: "The reader was replaced; no duplicates since." },
]

export default function Example() {
  const separators = useThreadSeparators(messages.map((m) => m.at))
  return (
    <div className="flex max-w-lg flex-col gap-2">
      {messages.map((m, i) => (
        <div key={m.id} className="flex flex-col gap-2">
          {separators.has(i) && <ThreadDivider {...separators.get(i)} />}
          <p className="text-sm">{m.text}</p>
        </div>
      ))}
    </div>
  )
}
```

## Functions

### useThreadSeparators

```ts
useThreadSeparators(times: (string | Date | null | undefined)[], options?: { granularity?: "auto" | "day" | "week" | "month" | "year" | undefined } | undefined)
```

## Strings (`threadSeparators`)

The fixed words, English by default. Override any of them under `threadSeparators` in `<LocaleProvider strings={{ threadSeparators: { … } }}>`; the table is exported as `threadSeparatorsStrings`.

- `week`: Week of {date}

## Rules for using Candor UI

- Components take plain typed data. Never pass model output as HTML or render it as a link; messages are typed blocks.
- A control that can't be used says why: wrap it in `<Locked reason="…">`, don't just disable it.
- Don't show a value you don't have: leave it out (it renders as —) rather than guess. An estimate is labelled as one.
- Nothing is pre-selected in a decision a person makes, and the agent is named from your app's data, never from model output.
- Your app's words go in `LocaleProvider`: its fixed strings in `strings`, its nouns in `nouns`; entity kinds and agent roles in `EntityKindsProvider` and `AgentRolesProvider`.
