<!-- GENERATED by scripts/build-llms.mjs from llms/conversation.md — do not edit this file. -->

# `lr-transcript-feed`

- **Import** `import '@aceshooting/lyra-ui/components/lr-transcript-feed.js';` (stable tag alias; registers the tag)
- **Class** `LyraTranscriptFeed`, also available unregistered from `@aceshooting/lyra-ui/components/conversation/transcript-feed/transcript-feed.class.js`
- **Family** `components/conversation/` — see `llms/index.md` for its siblings
- **Status** `stable` since `4.0.0` — see the maturity and deprecation policy in `llms/shared.md`
- **Release history** [CHANGELOG.md](../../CHANGELOG.md); family-wide breaking-change summaries: [llms-full.txt](../../llms-full.txt)
- **Deprecations** none
- **Optional peers** none
- **Themeable via** 10 parts, 0 custom properties — see this component's own `@csspart`/`@cssprop` list below
- **Library-wide behavior** (events, form association, `locale`/`strings`, tokens, TS types): `llms/shared.md`

---

## `lr-transcript-feed`

Live captions for an in-progress voice session: speaker-grouped entries, interim-vs-final styling
with in-place upgrades keyed by `id`, and a stick-to-bottom auto-scroll with release, the same
`follow`/`lr-follow-change` contract `<lr-terminal>` uses. Live captions only — recorded-media
transcript sync is a separate concern.

Activating the focused jump action resumes follow and transfers focus to the scroll base when the
action disappears, unless a newer outside focus move takes precedence.

**Properties:** `entries: LyraTranscriptEntry[] = []` (attribute: false) — `LyraTranscriptEntry { id:
string; speaker?: string; text: string; interim?: boolean; timestamp?: LyraTimestamp }` (exported by
this module; `LyraTimestamp = Date | string | number`, normalized through Date/TimeClip). Reconciled
keyed by nonempty, nonblank, first-wins `id` via Lit's `repeat()`; rows whose required `text` is not
a string are omitted before rendering or announcement. A
same-`id` entry with new `text` replaces in place, and a same-`id` entry whose `interim` flips from
`true` to unset/`false` moves from the interim area into the `role="log"` region and announces
exactly once. A collection with no valid entry renders the empty state. Interim entries render
_after_ the log container — visible, but structurally outside
it — so per-token mutations are never spoken by assistive tech. That announcement does **not** come
from the shadow `role="log"` region, which is explicitly `aria-live="off"`: a live region inside a
component's own shadow root is not reliably announced (JAWS with Firefox ignores one outright).
Each newly final entry's `text` is announced once through the shared light-DOM polite live region
instead, the same route `<lr-chat-viewport>` and `<lr-terminal>` take. The entries a feed is
_mounted_ with are treated as existing transcript rather than newly spoken captions, so the first
render only records them. `follow: boolean = true`
(reflected), `showTimestamps: boolean = false` (attribute `show-timestamps`), `formatTimestamp?:
(date: Date) => string` (attribute: false), `maxRenderedEntries: number = 500` (attribute
`max-rendered-entries`) — `0` explicitly renders every entry; a positive value keeps only the newest N,
`sessionId: string = ''` (attribute `session-id`) — changing session identity clears finalized-ID
announcement history and treats the new session's current entries as a silent baseline,
`label?: string` — accessible name for the `role="log"` region. Omitting it uses the localized
`transcriptFeedLabel`; an explicit empty string intentionally leaves the role unnamed. `accessibleLabel: string | null = null`
(attribute `aria-label`) — overrides the log's computed accessible name, winning over `label` and
the localized default; attribute-reflects from a host-level `aria-label`.

**Methods:** `scrollToBottom()` re-engages `follow` and instantly scrolls to the current latest
entry. The built-in jump action delegates to this method.

**Slots:** `empty` — custom empty state (default: the localized "No transcript yet").

**Events:** `lr-follow-change` — `detail: { following }`, fired when a user scroll changes follow
state or the built-in jump action re-engages it. Direct `follow` assignments and
`scrollToBottom()` calls are controlled input and do not echo an event.

**CSS parts:** `base` (the scroll container), `log` (the `role="log"` region wrapping final entries
only), `entry`, `speaker` (omitted for a row repeating the previous row's speaker), `text`
(`dir="auto"`), `timestamp` (only while `show-timestamps`), `interim` (present alongside `entry` on
an interim row), `interim-area` (the wrapper holding interim rows, rendered only while at least one
interim entry exists), `jump-button` (shown only while `follow` is `false`), and `empty`.
