Decision layer for ItemDetails' extras rows — which rows an item type shows, in what order and slot shape, how a delivered row is ordered and filtered, when a run may be committed to the RowList and which rows it may skip, and the layout arrays for the rows on screen.
It lives in source/ rather than in ExtrasRowList.bs because a component codebehind's functions are scoped to that component: a Rooibos suite cannot call them without a callFunc seam on the XML. Same move as source/home/latestRows.bs.
Everything here is pure: no nodes are created, no fields written, no translation looked up. A plan row names its title by translation key so the consumer resolves it on the render thread, and the item argument may be a node or an AA.
Methods
(static) albumTracksRow(item) → {object}
| Name | Type | Description |
|---|---|---|
item | object |
- Type:
- object
(static) appendCastAndLikeThis(rows, itemType, item) → {void}
Cast, then More Like This — the tail every video-library type shares.
| Name | Type | Description |
|---|---|---|
rows | object | |
itemType | string | |
item | object |
- Type:
- void
(static) commitActions(plan, slots, results, titles, onScreen) → {object}
What committing a resolved run does to each plan row. The caller applies it to the RowList; nothing here touches a node.
did not load that row actions - one per plan row: "write" build the row from its result, or rewrite the one on screen "leave" the row on screen stays exactly as it is "remove" the row on screen goes "none" there is no row, before or after isStructureChanged - true when a row appears, disappears or changes slot shape, which is the only time the RowList layout needs writing playlistContentKind - the kind of the playlist row as committed ("unknown" when it is not on screen), or invalid when the plan has no playlist row
| Name | Type | Description |
|---|---|---|
plan | object | the plan rows, in display order |
slots | object | one { status, count } per plan row ("ok" | "failed" | "unchanged") |
results | object | one { items, shape, digest, contentKind } per plan row, invalid where the run |
titles | object | the title each plan row would be committed with |
onScreen | object | kind -> { title, shape, digest, contentKind }, one entry per row on screen |
an AA:
- Type:
- object
(static) currentFirst(items, currentId) → {object}
The current item, then the items after it, then the items before it — e.g. viewing episode 3 of 5 gives [3, 4, 5, 1, 2]. Server order is kept when the item is absent.
| Name | Type | Description |
|---|---|---|
items | object | |
currentId | string |
- Type:
- object
(static) digestOf(text) → {string}
A digest identifying a row's source content.
Taken over the exact text the row is built from (the HTTP response body, on the Task thread that already holds it) rather than over the item nodes: comparing every field of every node on the render thread measured 252-306 ms per Refresh on a Stick 4K for a three-row movie.
| Name | Type | Description |
|---|---|---|
text | string | the source text; UTF-8 encoded before digesting |
a hex MD5 digest, or "" for empty text
- Type:
- string
(static) finishRow(row, items) → {object}
Orders, filters and (for playlists) re-shapes and classifies one delivered row.
a playlist row, and "" for every other row
| Name | Type | Description |
|---|---|---|
row | object | the plan row the items were fetched for |
items | object | array of item nodes or AAs, in server order |
an AA { items, shape, contentKind } — contentKind is playlistContentKind's answer for
- Type:
- object
(static) firstAlbumArtistId(item) → {string}
| Name | Type | Description |
|---|---|---|
item | object |
- Type:
- string
(static) isRowUnchanged(oldTitle, oldDigest, newTitle, newDigest) → {boolean}
Is a delivered row identical to the one on screen? Such a row is not written at all: RowList redraws its row counter on any write to an on-screen row, even one that changes nothing.
Identity is the title plus a digest of the row's SOURCE content — the server's response body, or the item data a local row is built from — taken where that content is at hand (see digestOf). Anything the server changes about the row, including a field on an item whose id did not change (its played state, an image tag), changes the digest; so an unchanged digest means the rebuilt row would be identical, and a tile can never be left showing stale data.
An empty digest means the content could not be identified, and is never treated as unchanged: the row is rewritten, which is correct if it costs a counter redraw.
| Name | Type | Description |
|---|---|---|
oldTitle | string | the title on screen |
oldDigest | string | the digest the on-screen row was built from |
newTitle | string | the delivered title |
newDigest | string | the delivered row's digest |
true when writing the delivered row would change nothing
- Type:
- boolean
(static) isRunResolved(slots) → {boolean}
Has every slot of a run resolved? A run is committed to the RowList only then, in one pass.
| Name | Type | Description |
|---|---|---|
slots | object | array of { status: "pending" | "ok" | "failed" | "unchanged", count } |
true when no slot is still pending
- Type:
- boolean
(static) likeThisRow(itemType, item) → {object}
| Name | Type | Description |
|---|---|---|
itemType | string | |
item | object |
- Type:
- object
(static) plan(itemType, item) → {object}
The ordered rows for an item type.
Each row is an AA: kind - what to fetch; the orchestrator maps it to a request ("chapters" and "cast" are built from the item itself, with no request) shape - "PORTRAIT" | "WIDE" | "SQUARE"; finishRow may revise it (playlists) titleKey - translation key; titleArgs holds its placeholders when it has any itemId - the id the request is keyed on seasonId - season rows only currentId - rows that put the viewed item first excludeId - rows that drop the viewed item
A row whose request cannot be formed for this item (an episode with no season, a track with no album) is left out of the plan rather than planned and resolved empty.
| Name | Type | Description |
|---|---|---|
itemType | string | the item's type, e.g. "Movie" |
item | object | the item node or AA |
array of row AAs in display order
- Type:
- object
(static) planRow(kind, shape, titleKey, itemId) → {object}
| Name | Type | Description |
|---|---|---|
kind | string | |
shape | string | |
titleKey | string | |
itemId | string |
- Type:
- object
(static) playlistContentKind(items) → {string}
What a playlist holds, for ItemDetails' Watched button and "Tracks"/"Items" label.
"unknown" covers both an empty playlist and one that failed to load — ItemDetails treats it as the safe default (no Watched button, "Items" label).
| Name | Type | Description |
|---|---|---|
items | dynamic | array of item nodes or AAs, or invalid |
"video" | "audio" | "mixed" | "unknown"
- Type:
- string
(static) rowDisposition(status, itemCount, hasExistingRow) → {string}
What a resolved row does to the list.
A failed request says nothing about what the item HAS, so it never removes a row — the same rule latestRows.drainReady applies to Home. Only an answered, empty row removes one.
| Name | Type | Description |
|---|---|---|
status | string | "ok" | "failed" |
itemCount | integer | items the row resolved with |
hasExistingRow | boolean | whether the list already shows this row |
"fill" | "remove" | "keep" | "skip"
- Type:
- string
(static) rowSizes(shapes) → {object}
RowList layout for the rows on screen: one slot and one row height per row, in order.
| Name | Type | Description |
|---|---|---|
shapes | object | array of "PORTRAIT" | "WIDE" | "SQUARE", one per row on screen |
an AA { rowItemSize, rowHeights }
- Type:
- object
(static) seasonEpisodesRow(item) → {object}
| Name | Type | Description |
|---|---|---|
item | object |
- Type:
- object