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

# `lr-sequence-strip`

- **Import** `import '@aceshooting/lyra-ui/components/lr-sequence-strip.js';` (stable tag alias; registers the tag)
- **Class** `LyraSequenceStrip`, also available unregistered from `@aceshooting/lyra-ui/components/data/sequence-strip/sequence-strip.class.js`
- **Family** `components/data/` — 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** 11 parts, 5 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-sequence-strip`

A compact, one-thin-cell-per-item (or per item range) strip visualizing a sequence of categorical
states, with an
optional secondary per-cell marker. Pure CSS/flex — no chart.js, no SVG, no canvas — sized/named
consistently with the sparkline/heatmap family, and read as a glanceable aggregate. `[part="base"]`
is a labeled `role="list"` and each cell a named `role="listitem"` (`aria-label`, `aria-posinset`,
`aria-setsize`), so the sequence is walkable item by item rather than collapsed into one summary
string. Exactly one cell is tabbable at a time (roving `tabindex`); ArrowLeft/ArrowRight and
Home/End move the stop — direction-aware, so the arrows swap under RTL — and focusing a cell shows
the same `[part="tooltip"]` detail that pointer hover does. That tooltip is positioned from the
active cell, not from the center of the whole strip. The tooltip is visual only and is not
wired through `aria-describedby`, because the cell's own `aria-label` already exposes the identical
text and describing it again would duplicate the announcement. Cells are actionable: clicking a
cell, or pressing Enter/Space on the roving cell, emits `lr-item-activate`. Selection is controlled,
so the consumer updates `selectedIndex` when it accepts that activation. Setting `showLegend`
additionally renders a static `[part="legend"]` key below the strip, so the color-to-category
mapping is readable without visiting each cell.

A standard host `aria-label` names the host itself and is not copied verbatim to the internal
list; `accessible-label` remains the list-specific override and otherwise the generated
category-count summary names it. When an
`items` refresh occurs while a cell owns focus, its `id` remains the sole roving stop; removal
clamps focus to the nearest survivor, or to the stable list base when no cells remain. Unfocused
refreshes do not move focus. A queued Arrow/Home/End focus is bound to the current item-array
identity and connection generation, so a same-turn replacement or disconnect/reconnect cannot
focus an unrelated cell that merely inherited the old numeric index.

High-cardinality strips retain at most the first 10,000 assigned items and categories as detached,
frozen canonical snapshots; reassign either collection after changing it. At most 200 cells are
rendered. **At or below that cap each cell is one item. Past it the strip becomes a span-preserving
overview rather than a window:** the retained items are distributed over exactly 200 contiguous
ranges, each cell painted by its range's dominant category (a tie goes to the category appearing
earliest in the range) and carrying `[part="marker"]` when any item inside it sets `marker`. The 200
cells tile the sequence exactly once, so the strip always covers the full span at full width and
never needs horizontal scrolling. Roving focus, `aria-posinset`/`aria-setsize`, `aria-current` and
activation all address cells rather than items: ArrowLeft/ArrowRight and Home/End step one range at
a time, and each cell exposes `data-range-start` / `data-range-end` (zero-based, inclusive item
indices) alongside its `data-index` range position. `[part="bucket-summary"]` visibly discloses the
item total and range count, and the same localized text is appended as a trailing clause to the
generated `aria-label` so assistive technology learns the cells are ranges. The optional legend
likewise mounts at most 200 categories and exposes `[part="legend-limit"]` as a rendered/total
numeric disclosure.

**Properties:**

- `items: readonly SequenceStripItem[] = []` (attribute: false) — `{ readonly id, readonly
categoryId, readonly marker?, readonly label?, readonly disabled? }`;
  `marker` renders a small bottom marker on that cell independent of the category color (e.g. a
  subagent-dispatched turn); `label` is per-item hover/focus tooltip text _and_ that cell's own
  `role="listitem"` accessible name, falling back to the matching category's own nonblank `label`,
  then localized `sequenceStripUnnamedCategory` (`"Unnamed category"` in the built-in English
  catalog) when unset — it is not read by `[part="base"]`'s auto-generated `aria-label`, which
  summarizes by category/count only. `disabled` marks the item non-actionable:
  `aria-disabled="true"` replaces the selected/active affordances of the cell that represents it,
  activating it (click or Enter/Space) emits nothing, and roving Left/Right/Home/End navigation —
  including the default resting tab stop — steps past it. Above the 200-cell cap, a range cell's
  disabled state follows its own activated item, the range's first. Omitted or `false` renders the
  item exactly as before this field existed
- `categories: readonly SequenceStripCategory[] = []` (attribute: false) — `{ readonly id,
readonly color, readonly label? }`; `color`
  is the cell background for every item whose `categoryId` matches `id`; invalid CSS colors,
  declaration-breaking input, `url()`, and unmatched categories render `transparent`. `label` is
  used in the auto-generated `aria-label` summary and as the hover-tooltip fallback text, falling
  back to localized `sequenceStripUnnamedCategory` when omitted or blank rather than exposing the
  internal `id`. Both collection properties are cloned and frozen at assignment,
  bounded to the first 10,000 source entries, and require reassignment after changes; empty/blank
  ids are omitted and duplicates use the first valid entry, so identity is deterministic
- `accessibleLabel?: string` (attribute `accessible-label`) — overrides the auto-generated
  `aria-label` (a per-category "label: count" summary, e.g. `"Text: 2, Tool: 1"`). Unset computes the
  summary from `items`/`categories`; a standard host `aria-label` remains a distinct host name
- `showLegend: boolean = false` (attribute `show-legend`, reflected) — renders a static
  `[part="legend"]` key below the strip, one swatch + label row per `categories` entry, in array
  order. The key describes the _scheme_, not the current data: a category with no matching item
  still gets a row, and an item whose `categoryId` matches no entry adds none. Deliberately
  non-interactive — it toggles nothing and emits nothing (`lr-graph-legend` is the interactive,
  filtering legend). Because it only repeats the category names `[part="base"]`'s own `aria-label`
  summary already announces, the legend is `aria-hidden` — visible on screen, announced exactly
  once — and it wraps onto further rows in a narrow allocation rather than overflowing
- `markerLabel?: string` (attribute `marker-label`) — names what an item's `marker` _means_ (e.g.
  `"Subagent"`). Setting it does two things: with `showLegend` on it adds one trailing
  `[part="legend-item"]`, whose `[part="legend-marker-swatch"]` reproduces the cell's own marker
  treatment, and it adds the marker to the auto-generated `aria-label` summary, which is otherwise
  per-category only. The marker count is reported as its own clause rather than folded into any
  category's count. Unset (the default) changes nothing: no extra legend row, no extra summary clause

- `selectedIndex: number = -1` (attribute `selected-index`) — the currently selected item, or `-1`
  for none. **Controlled:** activating a cell emits `lr-item-activate` and does *not* move the
  selection itself, so the consumer stays the single source of truth and the strip cannot drift from
  a playback index it does not own (its natural companion is `lr-sequence-playback`). Mirrors the
  shape `lr-lite-chart`'s `selectedIndices` and `lr-heatmap`'s `selectedCell` already establish. An
  out-of-range or non-integer value selects nothing. The selected cell carries `aria-current="true"`
  and `data-selected`; the selection is drawn as a ring rather than a background change, because a
  cell's background is data (its category colour) and tinting it would misreport the category. Past
  the 200-cell cap the whole range that contains `selectedIndex` reads as selected, since that range
  is the only thing the strip draws for it

The single-member `orientation: 'horizontal'` property was **removed in 9.0.0**: nothing read it and
the stylesheet never mentioned it, so the reflected attribute styled nothing. Delete the attribute;
the strip has always laid out horizontally.

**Events:** `lr-item-activate` — `detail: { index: number; id: string; item: SequenceStripItem }`,
fired when a cell is clicked or activated with Enter/Space on the roving-tabindex focus. Past the
200-cell cap a cell stands for a range, and the event reports that range's **first** item — a real
sequence index a playback consumer can scrub from, not a synthesized midpoint. The `detail` shape is
unchanged. Not
cancelable: nothing in the component branches on `defaultPrevented`. Bubbles and composed, like every
library event.

**Slots:** none.

**CSS parts:** `base` (the root strip, `role="list"`), `cell` (each item's `role="listitem"` cell,
background-colored by its category, carrying the roving `tabindex`, and activatable by click or
Enter/Space — it has a pointer cursor plus paired hover/press treatments, and `[data-selected]` when
it is `selectedIndex`), `marker` (the small bottom
marker on a cell whose item sets `marker: true`), `tooltip` (the detail tooltip showing the active
item's label, hidden until a cell is hovered or focused),
`legend` (the static category key rendered below the strip when `showLegend` is set — `aria-hidden`,
as it repeats the strip's own `aria-label`), `legend-item` (one swatch + label pair, one per
`categories` entry, plus one trailing marker row when `markerLabel` is set), `legend-swatch` (the
color chip, matching that category's cell color), `legend-marker-swatch` (the marker row's chip
instead: a neutral chip carrying the same bottom bar a `marker: true` cell paints, in the same
`--lr-sequence-strip-marker-color`), `legend-label` (the category's nonblank `label`, or localized
`sequenceStripUnnamedCategory`), `bucket-summary` (the item-total/range-count disclosure, rendered
only while the strip is past its 200-cell cap; it replaced 15.x's `window-range`, which disclosed a
projection window this component no longer has), and `legend-limit`
(bounded legend/total).

**Themeable custom properties:** `--lr-sequence-strip-height` (default `1.5rem` — the strip's
block-size), `--lr-sequence-strip-marker-color` (default `var(--lr-color-text)` — the
`[part="marker"]` fill, and of the marker legend row's bar), `--lr-sequence-strip-legend-swatch-size`
(default `0.625rem` — a legend swatch's inline- and block-size, category and marker rows alike), and
`--lr-sequence-strip-legend-marker-bg` (default `var(--lr-color-surface-raised)` — the neutral chip
background behind the marker legend row's bar; it stands in for "any cell", so it deliberately
matches no category color), and `--lr-sequence-strip-disabled-opacity` (default `var(--lr-opacity-disabled)` — opacity of
a cell whose activated item sets `disabled`); the tooltip also consumes shared tokens
`--lr-color-surface`, `--lr-color-text`, `--lr-font-size-xs`, `--lr-radius`, and `--lr-shadow`, and
the legend consumes `--lr-space-2xs`, `--lr-space-xs`, `--lr-space-s`, `--lr-font-size-xs`,
`--lr-color-text-quiet`, and `--lr-radius-xs`.

**Optional peer deps:** none.

```html
<lr-sequence-strip></lr-sequence-strip>
<script>
  const strip = document.querySelector("lr-sequence-strip");
  strip.categories = [
    { id: "text", color: "#4f46e5", label: "Text" },
    { id: "tool", color: "#16a34a", label: "Tool" },
  ];
  strip.items = [
    { id: "1", categoryId: "text", label: "Turn 1: text" },
    { id: "2", categoryId: "tool", marker: true, label: "Turn 2: tool call" },
    { id: "3", categoryId: "text", label: "Turn 3: text" },
  ];
</script>
```

**Known gotchas:**

- the activation event is `lr-item-activate`, not `lr-cell-click`. Click and Enter/Space emit the
  activated item's `index`/`id`, but do not mutate the controlled `selectedIndex`; listen for the
  event and update that property when the application accepts the activation.
- an `items` entry whose `categoryId` has no matching `categories` entry still contributes to the
  strip (its cell's background is `transparent`) rather than being dropped, so a strip renders one
  cell per item — or, above the 200-cell cap, one cell per range — regardless of `categories`
  coverage.
- **16.0 migration:** above 200 items the strip renders item RANGES, not a 200-item window. Rename
  any `::part(window-range)` selector to `::part(bucket-summary)`. Code that read a cell's
  `data-index` as an item index must read `data-range-start` / `data-range-end` instead — above the
  cap `data-index` is the range's position in the strip, not an item's position in the sequence. Two
  localization keys are new: `sequenceStripBucketLabel` (`"{label}, items {start} to {end}"`) and
  `sequenceStripBucketSummary` (`"{items} items in {ranges} ranges"`).
- **9.0 migration:** rename category `{ key }` to `{ id }` and item `{ category }` to
  `{ categoryId }`. Reassign after changes; caller mutation no longer changes the installed
  snapshot. Category clauses use effective-locale `Intl.ListFormat` punctuation.

---
