extrasRows

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}

Parameters:
NameTypeDescription
itemobject
Returns:
Type: 
object

(static) appendCastAndLikeThis(rows, itemType, item) → {void}

Cast, then More Like This — the tail every video-library type shares.

Parameters:
NameTypeDescription
rowsobject
itemTypestring
itemobject
Returns:
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

Parameters:
NameTypeDescription
planobject

the plan rows, in display order

slotsobject

one { status, count } per plan row ("ok" | "failed" | "unchanged")

resultsobject

one { items, shape, digest, contentKind } per plan row, invalid where the run

titlesobject

the title each plan row would be committed with

onScreenobject

kind -> { title, shape, digest, contentKind }, one entry per row on screen

Returns:

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.

Parameters:
NameTypeDescription
itemsobject
currentIdstring
Returns:
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.

Parameters:
NameTypeDescription
textstring

the source text; UTF-8 encoded before digesting

Returns:

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

Parameters:
NameTypeDescription
rowobject

the plan row the items were fetched for

itemsobject

array of item nodes or AAs, in server order

Returns:

an AA { items, shape, contentKind } — contentKind is playlistContentKind's answer for

Type: 
object

(static) firstAlbumArtistId(item) → {string}

Parameters:
NameTypeDescription
itemobject
Returns:
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.

Parameters:
NameTypeDescription
oldTitlestring

the title on screen

oldDigeststring

the digest the on-screen row was built from

newTitlestring

the delivered title

newDigeststring

the delivered row's digest

Returns:

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.

Parameters:
NameTypeDescription
slotsobject

array of { status: "pending" | "ok" | "failed" | "unchanged", count }

Returns:

true when no slot is still pending

Type: 
boolean

(static) likeThisRow(itemType, item) → {object}

Parameters:
NameTypeDescription
itemTypestring
itemobject
Returns:
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.

Parameters:
NameTypeDescription
itemTypestring

the item's type, e.g. "Movie"

itemobject

the item node or AA

Returns:

array of row AAs in display order

Type: 
object

(static) planRow(kind, shape, titleKey, itemId) → {object}

Parameters:
NameTypeDescription
kindstring
shapestring
titleKeystring
itemIdstring
Returns:
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).

Parameters:
NameTypeDescription
itemsdynamic

array of item nodes or AAs, or invalid

Returns:

"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.

Parameters:
NameTypeDescription
statusstring

"ok" | "failed"

itemCountinteger

items the row resolved with

hasExistingRowboolean

whether the list already shows this row

Returns:

"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.

Parameters:
NameTypeDescription
shapesobject

array of "PORTRAIT" | "WIDE" | "SQUARE", one per row on screen

Returns:

an AA { rowItemSize, rowHeights }

Type: 
object

(static) seasonEpisodesRow(item) → {object}

Parameters:
NameTypeDescription
itemobject
Returns:
Type: 
object