Let the reader put the session list in its own order

Nothing sorts the sessions tab any more. The order is the server's
`sessions` list, which is the reader's arrangement: holding a row puts the
screen in selection mode -- the same gesture and the same bottom bar as the
import tab -- and each card grows a burger handle at its right edge that
drags the row to a new place, with a tick of haptic feedback for each one it
passes.

The two attempts this replaces, sorting by activity and then by when each
agent was turned on, were both looking for an order a session could not move
itself out of; no rule computed from what a session is doing can be one.
`POST /sessions/order` rewrites the config's order, so it is the same on
every device and survives a backend restart, and `SessionConfig::started`
goes with the sort that needed it.

Rearranging is independent of the selection: the handle moves the row it is
on, picked out or not. The click moved off the card and onto its contents so
that a press landing on the handle cannot also select the row it is about to
move. Selection's one action is Delete, which now takes the whole set.

Two traps in `Reorder.kt`, both measured on the emulator and written down in
`this-machine-android`: a crossing is decided from how far the finger has
travelled, because a lazy list animates an item into its new place and its
`offset` reports the old one for several frames; and the viewport is pinned
with `requestScrollToItem` around each move, because a lazy list keeps its
place by the key of the top item and would otherwise follow the row being
dragged.

Verified on the emulator against the sandbox: the order survives an app
restart and a backend read-back, a two-row drag moves exactly two rows, a
drag to the bottom edge scrolls the list and lands the row last, pressing the
handle without moving changes nothing, and deleting two selected sessions
leaves the rest in place.
This commit is contained in:
iris-ai committed 2026-09-20 01:06:15 -04:00
1 parent 942edd6b31
commit b7fd18b195
12 files changed
+801 -339

No files matched your search

@@ -232,15 +232,6 @@ data class SessionSummary(
val usageProvider: String?,
val status: String,
val lastActivity: Double,
/**
* Epoch seconds a process was last started for this session -- when this agent was last turned
* on. What the list orders the running sessions by; see [sessionsInListOrder].
*
* The server's own, so the order is the same on every device and across a backend restart. An
* older server does not send it, and those sessions fall back to when the session was last
* active, which is the best this app can do without inventing a time.
*/
val started: Double,
/** Latest measured number of live background tasks; zero also covers older servers. */
val backgroundTasks: Int,
)
@@ -275,7 +266,6 @@ private fun parseSession(session: JSONObject) =
usageProvider = session.optString("usageProvider").ifEmpty { null },
status = session.getString("status"),
lastActivity = session.getDouble("lastActivity"),
started = session.optDouble("started", session.getDouble("lastActivity")),
backgroundTasks = session.optInt("backgroundTasks", 0),
)
@@ -1279,6 +1269,23 @@ fun renameSession(settings: ServerSettings, sessionId: String, title: String) {
) {}
}
/**
* Puts the session list in the order given, ids from top to bottom.
*
* Held by the server rather than by this phone because the arrangement is a fact about the
* sessions, not about the device that dragged them: a second phone opening the tab shows the same
* list. See the server's `reorder_sessions` for what it does with a list that changed underneath --
* nothing here has to be checked first.
*/
fun reorderSessions(settings: ServerSettings, sessionIds: List<String>) {
requestFromServer(
settings,
"/sessions/order",
method = "POST",
jsonBody = JSONObject().put("sessions", JSONArray(sessionIds)).toString(),
) {}
}
/** Switches a running session's model; the CLI changes it in place. */
fun setSessionModel(settings: ServerSettings, sessionId: String, model: String) {
requestFromServer(