Members
(static, constant) LATEST_STAND_IN_ID
sectionId of the row that stands in for the latest-media rows when the FIRST libraries load fails and the layout has no My Media row to show it (homeRowFailure.needsLatestMediaStandIn). Not "latest_"-prefixed on purpose: it is no library's row, and latestRows treats that prefix as one.
- Default Value
- latestStandIn
Methods
(static) applyPendingRowRemovals() → {void}
applyPendingRowRemovals: Drop the rows whose libraries returned nothing, queued during a run so the row list and the geometry arrays never disagree mid-load. Looked up by sectionId rather than a stored index: each removal shifts the ones after it.
- Type:
- void
(static) applySectionResult(sectionId, status, itemData) → {void}
applySectionResult: A section's load has finished. An answered one fills its row (or removes it, when the section is empty); a failed one says nothing about what the section holds, so the row stands (showRowFailed).
| Name | Type | Description |
|---|---|---|
sectionId | string | The sectionId of the target row |
status | dynamic | The task's status field ("ok" / "failed") |
itemData | dynamic | Array of content nodes from the task |
- Type:
- void
(static) buildSectionPlan() → {object}
buildSectionPlan: Reads the homeSection settings (slots 0 to homeSlots.SLOT_COUNT - 1), returns ordered array of sections to display
Array of { type: string, settingIndex: integer }
- Type:
- object
(static) checkVisibleRowsFilled() → {void}
checkVisibleRowsFilled: Mark Home painted once every row in the visible span has landed.
Waits for the final row layout (m.initialLoadComplete): before the libraries answer, the latest-media rows are not in the list yet, so the span would describe rows that are about to be pushed down. Called after every resolve and after every structural change that can shift rows into the span. The decision itself is homeScreenLoad.visibleRowsResolved.
- Type:
- void
(static) createSkeletonRow(sectionType) → {object}
createSkeletonRow: Creates a single empty HomeRow with correct title, sectionId, and cursorSize. Adds a single placeholder child node so the RowList renders the row.
| Name | Type | Description |
|---|---|---|
sectionType | string | The section type from user settings |
HomeRow node, or invalid if section type is unsupported
- Type:
- object
(static) createSkeletonRows() → {void}
createSkeletonRows: Creates empty HomeRow nodes for all planned sections except latestmedia. Each skeleton row gets a single placeholder child so the RowList renders the row label and a loading indicator at the correct slot size.
- Type:
- void
(static) discardRowBatch() → {void}
discardRowBatch: Drop pending row work without paying for it.
Teardown only. setRowItemSize() is the most expensive call in this file (~90-200 ms measured 2026-08-19, nearly all of it the RowList re-measuring every cell against the new rowItemSize) and it runs on the render thread, so flushing a batch for a node that is about to be released spends that on geometry nobody will see. Clearing the state first makes resetLatestRowsRun's applyPendingRowRemovals + flushRowSizes no-op.
WHEN this is reached, because the obvious answer is wrong: not on navigating away from Home. Home is SUSPENDED rather than destroyed by the router (see startLatestMediaLoads, which relies on that). HomeRows.onDestroy comes from Home.destroyActiveContent() (a tab switch to Favorites) and from Home.onDestroy when sgRouter permanently closes the view. So the way to force a run in flight into this path is to switch tabs during a Home load.
- Type:
- void
(static) findInsertIndexForSection(sectionId) → {integer}
findInsertIndexForSection: Determines the correct content index for a section that needs to be inserted. Uses the section plan ordering to maintain row order.
| Name | Type | Description |
|---|---|---|
sectionId | string | The sectionId to insert |
Index where the row should be inserted
- Type:
- integer
(static) findLatestMediaInsertIndex() → {integer}
findLatestMediaInsertIndex: Determines the correct content index for latestmedia rows based on the section plan ordering.
Index where latestmedia rows should be inserted
- Type:
- integer
(static) findRowBySectionId(sectionId) → {object}
findRowBySectionId: Find a row in content by its sectionId field
| Name | Type | Description |
|---|---|---|
sectionId | string | The sectionId to search for |
{ row: node, index: integer } or invalid if not found
- Type:
- object
(static) flushRowSizes() → {void}
flushRowSizes: Recompute if a structural change is still pending. No-op otherwise, so it is safe to call unconditionally at a batch boundary.
- Type:
- void
(static) getItemAtIndices(indices) → {dynamic}
Gets an item from content at specified row and item indices Performs all necessary bounds checking and validation
| Name | Type | Description |
|---|---|---|
indices | roArray | [rowIndex, itemIndex] |
ContentNode if valid, invalid otherwise
- Type:
- dynamic
(static) getRowConfigForSection(sectionId) → {object}
getRowConfigForSection: Returns title and slotSize for a sectionId. Used when inserting a row that was previously removed (no skeleton exists).
| Name | Type | Description |
|---|---|---|
sectionId | string | The sectionId |
{ title: string, slotSize: array } or invalid if unknown
- Type:
- object
(static) getSectionIdForType(sectionType) → {dynamic}
getSectionIdForType: Maps a section type string to its sectionId
| Name | Type | Description |
|---|---|---|
sectionType | string | Section type from user settings |
sectionId, or invalid if type is unsupported
- Type:
- dynamic
(static) init() → {void}
- Type:
- void
(static) insertLatestMediaSkeletons() → {void}
insertLatestMediaSkeletons: Creates skeleton rows for each non-excluded library and inserts them at the correct position in the content node.
- Type:
- void
(static) itemSelected() → {void}
- Type:
- void
(static) loadLibraries() → {void}
loadLibraries: Entry point called by Home.bs via callFunc. Builds ordered section plan, creates skeleton rows, then fires data tasks.
- Type:
- void
(static) logRowRemoved(sectionId) → {void}
logRowRemoved: Publish an immediate row removal AT THE INSTANT IT HAPPENS.
Its own line and its own moment, because sizeRemove cannot answer the question it was built for: those counters are zeroed at run start and read at run end, so a removal landing outside that window increments nothing anybody reports. And on a server with no recordings in progress the removal is unconditional, which makes sizeRemove a race outcome rather than an occurrence count. That result is in docs/dev/home-first-paint-performance.md ("row removed"); why it is unconditional is in the commit of #862 (git show 74d23223).
What a change to this function has to preserve:
- The ORDINAL on
at, and that it is per HomeRows INSTANCE.assembleSamplescloses a sample when a line it already holds repeats, so a second immediate removal in one launch splits that launch across two samples. The ordinal is what makes the split visible in the record instead of a silent short read. - BOTH halves of
cells, and that both are signed.loadsStartedis the discriminator andbindsis the postcondition on it.loadsStarted 0is what a truly early removal prints AND what a probe reading a counter-less root would print, so droppingbindsmakes those one reading. The-1guard cannot fire in HomeRows today (init createsm.top.contentand attaches the counters to that same node in the same init), but it is nearly free and the alternative to printing it is dropping the line, which reads as "no removal happened": the one wrong answer. - The argument COUNT. roku-log faults at RUNTIME (&hf1, straight into the debugger) past nine call-site arguments once the BSC plugin has spent one on the injected pkg path. Three label/value pairs with two composed values sits a pair clear of that; nine is legal and this line would fit, but a probe whose overflow costs a whole device run is not worth writing at the ceiling.
| Name | Type | Description |
|---|---|---|
sectionId | string | the sectionId of the row being removed |
- Type:
- void
(static) noteSectionResult(sectionId, status) → {void}
noteSectionResult: record a section's latest load result for the RTA suite (homeRowFailure.noteResult); nothing in the app reads it.
| Name | Type | Description |
|---|---|---|
sectionId | string | The sectionId the load was for |
status | dynamic | The load's status |
- Type:
- void
(static) onDestroy() → {void}
onDestroy: Full teardown releasing all resources before component removal Called by Home.bs onDestroy() before nulling the homeRows reference
- Type:
- void
(static) onItemFocused() → {void}
Observer for rowItemFocused field - delegates to updateBackdropForFocusedItem
- Type:
- void
(static) onKeyEvent(key, press) → {boolean}
| Name | Type | Description |
|---|---|---|
key | string | |
press | boolean |
- Type:
- boolean
(static) onLatestRowsReady(event) → {void}
onLatestRowsReady: Drains all unprocessed result children from the orchestrator. Each wake may cover several results: wake events can coalesce, the children can't.
| Name | Type | Description |
|---|---|---|
event | object |
- Type:
- void
(static) onLibrariesLoaded(event) → {void}
onLibrariesLoaded: Handler when LoadLibrariesTask returns data. Populates the library row, creates latestmedia skeleton rows, then fires latest tasks.
| Name | Type | Description |
|---|---|---|
event | object |
- Type:
- void
(static) onProgramsExpired() → {void}
Fires when JRRowList's progress tick detects at least one expired Program or Recording. Re-runs the On Now and/or Active Recordings loads to pull fresh data. Loading guards debounce repeated expiry signals while a load is already in flight, and section plan checks avoid wasted requests when the user has disabled the relevant sections.
- Type:
- void
(static) onSectionPopulated() → {void}
onSectionPopulated: Called after each section is populated with data. Signals AppLaunchComplete after 2 sections have loaded.
- Type:
- void
(static) populateLibraryRow() → {void}
populateLibraryRow: Fills the library row with filtered library items. Uses populateRowFromData for consistent in-place update behavior.
- Type:
- void
(static) populateRowFromData(sectionId, itemData) → {void}
populateRowFromData: Unified row population logic used by all update callbacks. Updates the children of the existing row node in place to avoid RowList re-layout and focus disruption. Removes the row if data is empty. If the row doesn't exist but data is available (for example, a previously empty section now has content on refresh), creates and inserts the row at the correct position.
| Name | Type | Description |
|---|---|---|
sectionId | string | The sectionId of the target row |
itemData | dynamic | Array of content nodes from the task, or invalid/empty |
- Type:
- void
(static) removeLatestMediaStandIn() → {void}
removeLatestMediaStandIn: Drop the stand-in, if there is one. Leaves the row-size recompute to the caller, which is about to insert the real latest-media rows and recompute once for both.
- Type:
- void
(static) removeRowAtIndex(index) → {void}
removeRowAtIndex: Removes a row at the given index and recalculates sizes
Recomputes IMMEDIATELY, batch or no batch. This path is only reached for a row the current run does not own (latestRows.removalIsDeferrable said no), so nothing later is going to flush on its behalf. A removal without its recompute leaves the three geometry arrays describing the old row list, which draws every row below it at its neighbor's size.
| Name | Type | Description |
|---|---|---|
index | integer | Content child index to remove |
- Type:
- void
(static) replacePlaceholder(row, placeholder) → {void}
replacePlaceholder: Swap a row's lone placeholder for another, in place. Appended before the old one is removed, as populateRowFromData does, so the row is never empty and the RowList keeps its layout and focus.
| Name | Type | Description |
|---|---|---|
row | object | |
placeholder | object |
- Type:
- void
(static) resetLatestRowsRun() → {void}
resetLatestRowsRun: Return to a clean pre-run state: the orchestrator's node released (unobserved and stopped; its delivered result children go with it) and the drain cursor rewound.
Called before starting a run and from onDestroy. Named for the run rather than the task (the old cleanupLatestMediaTasks) because there is exactly one task now; what gets reset is the run's state around it.
- Type:
- void
(static) resolveRowFill(sectionId) → {void}
resolveRowFill: A row's data has landed: close its screen-load fill, then see whether that completed the visible span.
Resolves only a fill that is still OPEN. The same handlers serve every later load, and one can deliver a second time before Home settles (a return to Home mid-load refreshes, and onProgramsExpired re-fires On Now), so an unguarded resolve would raise the ledger's "unbalanced" warning on ordinary use, the warning that is meant to signal real drift.
| Name | Type | Description |
|---|---|---|
sectionId | string | The sectionId the fill was declared under |
- Type:
- void
(static) retryFailedLatestRows() → {void}
retryFailedLatestRows: OK on a failed tile reloads every latest-media row showing one NOW, from the libraries Home already has, rather than after Home's libraries request answers (which may fail again). Each row shows the spinner from the moment its reload starts until that reload answers.
A run in flight cannot take more rows, so those rows are OWED: they show the spinner now and load when the run ends (retryOwedLatestRows). A run gone silent past its budget is reclaimed with every row, as a refresh reclaims it: a run of only the failed rows would drop the rows the reclaimed run was still loading.
- Type:
- void
(static) retryOwedLatestRows() → {void}
retryOwedLatestRows: The run the owed rows waited for has ended, so load them. If no run can start, nothing will answer them, so they go back to the failed tile rather than spin.
- Type:
- void
(static) rowStructureChanged(immediateopt) → {void}
rowStructureChanged: Announce that a row was added to or removed from m.top.content.
Outside a batch this recomputes immediately, so the single-row callers are unchanged.
Don't go looking for the deferred path: no caller reaches it. Every mid-run caller passes immediate (it has to, or the geometry arrays stop describing the row list) and the rest only run while m.rowSizesBatching is already false, so m.rowSizesDirty is false at both batch boundaries and flushRowSizes() there is a no-op. What actually coalesces a run into one recompute is m.pendingRowRemovals.
m.rowSizesDirty is a FAIL-SAFE, not scaffolding for a future feature: a new mid-run caller that forgets immediate recomputes at the batch boundary instead of never. That turns the failure from wrong geometry (rows drawn at their neighbor's size, which produces no error, no log line and no timing change) into geometry that is merely late. Deleting it makes that mistake silent, which is the one failure mode in this file nothing else catches.
| Name | Type | Attributes | Default | Description |
|---|---|---|---|---|
immediate | boolean | <optional> | false |
- Type:
- void
(static) setRowItemSize() → {void}
setRowItemSize: Loops through all home sections and sets the correct item sizes, heights, and spacings per row. rowItemSize[i] = slot size [width, posterHeight]; determines focus ring dimensions (poster only, no text). rowHeights[i] = total row height: slot + 90px text area for standard rows; slot-only for library tiles. rowSpacings[i] = gap after each row before the next row label. Must be set for ALL rows because Roku ignores itemSpacing entirely once rowSpacings is assigned (even partially). Standard rows use 60px; My Media uses 78px to partially compensate for its absent text area.
Reach for rowStructureChanged() rather than this: it is the coalescing seam, and this is expensive enough (measured 2026-08-19: ~85 ms early in a load, ~200 ms once every row is populated) that how many times it runs is the whole cost model.
- Type:
- void
(static) showLatestMediaStandIn() → {void}
showLatestMediaStandIn: Put one failed "Recently Added" row where the latest-media rows go, when the first libraries load failed and the layout has no My Media row to say so (homeRowFailure.needsLatestMediaStandIn). Without it, a layout like that shows no sign the load failed: the latest-media rows are missing. Pressing OK on it retries like any failed tile; the next answered libraries load removes it (onLibrariesLoaded).
- Type:
- void
(static) showRowFailed(sectionId) → {void}
showRowFailed: A section's load failed. Its row keeps its items; a row still showing its loading placeholder shows the failed tile instead, so the failure does not read as loading forever (homeRowFailure.afterFailure). A section with no row stays off screen.
| Name | Type | Description |
|---|---|---|
sectionId | string | The sectionId of the row |
- Type:
- void
(static) showRowRetrying(sectionId) → {void}
showRowRetrying: A section's load has started again. A row showing the failed tile goes back to the spinner, so a retry shows it is under way (homeRowFailure.afterRetry).
| Name | Type | Description |
|---|---|---|
sectionId | string | The sectionId of the row |
- Type:
- void
(static) startActiveRecordingsLoad() → {void}
- Type:
- void
(static) startLatestMediaLoads(onlyLibIdsopt) → {boolean}
startLatestMediaLoads: Hands the eligible libraries (in row order) to the orchestrator task.
Skips outright while a run is in flight, matching the isLoading* rule updateHomeRows() already applies to the other section loads: the running orchestrator is about to deliver fresh data, so restarting buys nothing and costs a cancel. Home is SUSPENDED rather than destroyed on navigation (onScreenHidden stops no tasks), so a Home revisit lands here mid-run routinely.
Every run gets a NEW node (see replaceTask in source/utils/tasks.bs): a stop-and-relaunch of the same node while its function still runs is sometimes silently ignored, and then no row would ever load.
default) loads every latest row
| Name | Type | Attributes | Default | Description |
|---|---|---|---|---|
onlyLibIds | dynamic | <optional> | invalid | library ids to load, for a retry of failed rows; invalid (the |
true when a run started
- Type:
- boolean
(static) startLibrariesLoad() → {void}
- Type:
- void
(static) startNextUpLoad() → {void}
- Type:
- void
(static) startOnNowLoad() → {void}
- Type:
- void
(static) startParallelLoads() → {void}
startParallelLoads: Fires off data tasks for sections that don't need library data. Library-dependent sections (library row, latestmedia) are handled in onLibrariesLoaded.
Launches in the user's layout order. The loop may call a helper more than once per refresh only if a type repeats; each helper's own isLoading* guard holds its section to one run, and states so at its launch: no-task-fanout follows these calls and fails the build otherwise.
- Type:
- void
(static) startResumeLoad() → {void}
- Type:
- void
(static) updateActiveRecordingsItems(event) → {void}
| Name | Type | Description |
|---|---|---|
event | object |
- Type:
- void
(static) updateBackdropForFocusedItem() → {void}
Update backdrop to match currently focused item Handles all validation and edge cases Used by: onItemFocused observer and row update functions after replaceChild
- Type:
- void
(static) updateContinueWatchingItems(event) → {void}
| Name | Type | Description |
|---|---|---|
event | object |
- Type:
- void
(static) updateHomeRows() → {void}
updateHomeRows: Refresh data for all rows without tearing down the UI. Keeps existing row nodes in place to avoid focus disruption. As fresh data arrives, populateRowFromData updates row children in place.
- Type:
- void
(static) updateNextUpItems(event) → {void}
| Name | Type | Description |
|---|---|---|
event | object |
- Type:
- void
(static) updateOnNowItems(event) → {void}
| Name | Type | Description |
|---|---|---|
event | object |
- Type:
- void
(static) updateSize() → {void}
- Type:
- void