Decision layer for Home's latest-media rows — which of the orchestrator's delivered results the UI may act on, and where the drain cursor lands.
It lives in source/ rather than in HomeRows.bs because a component codebehind's functions are scoped to that component: a Rooibos suite (which runs in the source scope) cannot call them without a callFunc seam on the XML. Extracting the judgment instead keeps the seam off the component and the rules under test. Same move as source/remotecontrol/remoteCommand.bs.
- Source
Methods
(static) drainReady(task, startIndex) → {object}
Walks LoadLatestRowsTask's result children from startIndex and returns the ones the UI should populate, plus the new cursor. One wake can cover several results — wake events coalesce, appended children don't — so the caller always drains to the end.
A "failed" child is SKIPPED, not returned. A timeout, a transport failure or an HTTP error says nothing about what the library holds, and populateRowFromData removes a row when handed an empty list — so acting on a failure would delete good content on a flaky refresh. The cursor still advances past it; the row self-heals on the next Home.refresh(), which fires on every onScreenShown.
DELIBERATELY does not hand failures back for the caller to clear the row. A latest row that has not been populated yet is not empty — insertLatestMediaSkeletons gives it one type: "Loading" placeholder child so the RowList renders it at the right slot size — so a failed row keeps that skeleton, and the next successful refresh fills it via populateRowFromData's "update children in place" branch: items appended, placeholder dropped, no insert. Removing the row instead would send that refresh down the else-branch, which CreateObject+insertChild's a fresh row — the row visibly pops in and shifts every row below it. Measured on device (8 libraries, HTTP 500 forced on five): the skipped rows sat at their single placeholder child and nothing shifted. Keep it that way.
| Name | Type | Description |
|---|---|---|
task | object | the LoadLatestRowsTask node holding { libId, items, status } children |
startIndex | integer | first unprocessed child index |
- Source
an AA { ready, nextIndex } - the children to populate, and the new cursor
- Type:
- object
(static) removalIsDeferrable(sectionId, batching) → {boolean}
May a row's removal be held back until the current latest-rows run ends?
Only for the rows THAT run delivers. A run coalesces one row-size recompute per structural change into one for the whole run, which is only safe while the row list and the three geometry arrays stay in step — so the removals have to wait alongside the recompute.
Every other Home section (Continue Watching, Next Up, On Now, Active Recordings) removes and recomputes immediately, mid-run or not. Two reasons, and the second is load-bearing:
- Those sections are fired by startParallelLoads, which RACES the orchestrator. Whether one lands inside a run depends on which HTTP response wins, so batching them would make a user-visible row collapse race-dependent — same server, two loads, two behaviours.
- The queue is keyed by sectionId and holds its entry until the run ends. The orchestrator yields each library exactly once per run (see drainReady), so a queued latest_ row cannot be repopulated before the flush. A parallel section CAN be — onProgramsExpired and Home.refresh() both re-fire those tasks mid-run — and the flush would then delete a row that had just been filled.
| Name | Type | Description |
|---|---|---|
sectionId | string | the row's sectionId |
batching | boolean | whether a latest-rows run is currently holding the recompute |
- Source
true when the caller may queue the removal instead of applying it now
- Type:
- boolean
(static) runIsStalled(elapsedMs) → {boolean}
Has a run been in flight long enough that its orchestrator thread must be gone?
HomeRows skips a refresh while a run is in flight, which is only safe if the in-flight state can never get stuck: a Task thread that crashes never delivers its last child, so the completion check never fires and the guard would freeze Home's latest rows for the life of that Home instance. This is the backstop, and epic #728 is precisely a history of Task threads dying.
The threshold is derived, not tuned: apiPipeline bounds a whole run at PIPELINE_RUN_MS, and API_WAIT_MS is one request's own deadline — the largest overshoot a live run can post between its last wait returning and the loop exiting. Anything past the two combined is not a slow run, it is a dead one.
| Name | Type | Description |
|---|---|---|
elapsedMs | integer | milliseconds since the run was started |
- Source
true when the run has outlived any legitimate duration
- Type:
- boolean