Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 9 additions & 3 deletions app/src/main/java/io/github/rayvader/kirocrew/core/Upcoming.kt
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,9 @@ object Upcoming {
const val FIELD = "open_items"

/**
* The field Froyo's sync job keeps current every 15 min: an object whose `items` is the
* same rows as [FIELD]. Preferred when it holds a list; [FIELD] is the fallback.
* The field Froyo's sync job keeps current every 15 min: the gateway's custom fold row
* {data: {items, as_of}, at, stale_after}, whose `data.items` is the same rows as [FIELD].
* Preferred when it holds a list; [FIELD] is the fallback.
*/
const val LIVE_FIELD = "open_items_live"
private const val MAX_TITLE = 200
Expand Down Expand Up @@ -126,7 +127,12 @@ object Upcoming {
val html = JSONObject(body).optString("rendered_html")
val island = island(html) ?: return null
val fields = JSONObject(island).optJSONObject("fields") ?: return null
val live = fields.optJSONObject(LIVE_FIELD)?.optJSONArray("items")
// The gateway's custom fold row is {data: {items, as_of}, at, stale_after}: the
// sync job's record sits under `data`, which is where the page itself reads it.
// A bare {items} is still read, in case a page binds `.data` directly.
val liveObj = fields.optJSONObject(LIVE_FIELD)
val live = liveObj?.optJSONObject("data")?.optJSONArray("items")
?: liveObj?.optJSONArray("items")
(live ?: fields.optJSONArray(FIELD))?.toString()
} catch (e: JSONException) {
null
Expand Down
21 changes: 21 additions & 0 deletions app/src/test/java/io/github/rayvader/kirocrew/core/UpcomingTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,27 @@ class UpcomingTest {
assertEquals(listOf("Test dinner 18:30", "Test flight"), items.map { it.title })
}

/** The real shape: the gateway's custom fold row wraps the job's record in `data`. */
@Test
fun theLiveListIsReadFromTheFoldRowsData() {
val old = """[{"n":1,"id":"a","title":"Old item","due":"2026-11-04","kind":"booking","flag":null}]"""
val live = """{"data":{"items":[{"n":1,"id":"b","title":"Test dinner 18:30","due":"2026-11-05","kind":"booking"},""" +
"""{"n":2,"id":"c","title":"Test snorkel 14:30","due":"2026-11-06","kind":"booking"}],""" +
""""as_of":"Thu 5 Nov, 21:28 SGT · 2 open"},"at":"2026-11-05T13:28:55Z",""" +
""""writer":"4cbe3e0f","stale_after":"2026-11-05T14:28:55Z"}"""
val items = Upcoming.parseItems(Upcoming.extractItems(v4Dashboard(live, old))!!, zone)
assertEquals(listOf("Test dinner 18:30", "Test snorkel 14:30"), items.map { it.title })
}

@Test
fun aFoldRowWithNoListInItsDataFallsBackToOpenItems() {
val old = """[{"n":1,"title":"Only item","due":null,"kind":"bill","flag":"x"}]"""
val live = """{"data":{"as_of":"x"},"at":"2026-11-05T13:28:55Z"}"""
assertEquals(listOf("Only item"), Upcoming.parseItems(Upcoming.extractItems(v4Dashboard(live, old))!!, zone).map { it.title })
// An empty list under data is a real answer: nothing open.
assertEquals("[]", Upcoming.extractItems(v4Dashboard("""{"data":{"items":[]}}""", old)))
}

@Test
fun aV4PageWithoutTheLiveListFallsBackToOpenItems() {
val old = """[{"n":1,"title":"Only item","due":null,"kind":"bill","flag":"x"}]"""
Expand Down
Loading