mirror of
https://github.com/anomalyco/opencode.git
synced 2026-11-14 20:21:39 +03:00
fix(app): order the inbox Working shelf by when each run started (#60147)
Co-authored-by: Brendonovich <14191578+Brendonovich@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
Unverified
parent
bb85c5414b
commit
2999751632
@@ -0,0 +1,26 @@
|
||||
import { expect, test } from "bun:test"
|
||||
import { compareWorking, workingSince } from "./inbox-working"
|
||||
|
||||
test("uses the current run's start", () => {
|
||||
expect(workingSince({ time: { busy: 20 } })).toBe(20)
|
||||
expect(workingSince({ time: { busy: 20, idle: 10 } })).toBe(20)
|
||||
})
|
||||
|
||||
test("has no start while only an earlier run's start is known", () => {
|
||||
expect(workingSince({ time: { busy: 10, idle: 20 } })).toBeUndefined()
|
||||
expect(workingSince({ time: { idle: 20 } })).toBeUndefined()
|
||||
expect(workingSince({ time: {} })).toBeUndefined()
|
||||
})
|
||||
|
||||
test("orders by run start, then puts runs with no known start last", () => {
|
||||
const entry = (key: string, time: { busy?: number; idle?: number }) => ({ key, session: { time } })
|
||||
|
||||
const sorted = [
|
||||
entry("d", {}),
|
||||
entry("b", { busy: 30 }),
|
||||
entry("c", { busy: 5, idle: 10 }),
|
||||
entry("a", { busy: 20, idle: 10 }),
|
||||
].toSorted(compareWorking)
|
||||
|
||||
expect(sorted.map((item) => item.key)).toEqual(["a", "b", "c", "d"])
|
||||
})
|
||||
@@ -0,0 +1,22 @@
|
||||
type SessionTime = { time: { busy?: number; idle?: number } }
|
||||
|
||||
/**
|
||||
* When the session's current run started. Undefined while the client still holds an earlier run's start (it ended at
|
||||
* `time.idle`, after `time.busy`) or the server does not record `time.busy`.
|
||||
*/
|
||||
export function workingSince(session: SessionTime) {
|
||||
const { busy, idle } = session.time
|
||||
|
||||
if (busy === undefined || (idle !== undefined && idle > busy)) return undefined
|
||||
|
||||
return busy
|
||||
}
|
||||
|
||||
/** Earliest run start first, so a session that starts working joins the end. Runs with no known start go last. */
|
||||
export function compareWorking(a: { key: string; session: SessionTime }, b: { key: string; session: SessionTime }) {
|
||||
const start = (workingSince(a.session) ?? Infinity) - (workingSince(b.session) ?? Infinity)
|
||||
|
||||
if (start) return start
|
||||
|
||||
return a.key < b.key ? -1 : a.key > b.key ? 1 : 0
|
||||
}
|
||||
@@ -16,6 +16,7 @@ import { SessionTabAvatarView } from "@/shell/layout/session-tab-avatar"
|
||||
import { sessionTabTitle } from "./tab-title"
|
||||
import { sessionTitle } from "@/session/title"
|
||||
import { createSessionInbox, inboxArchived, inboxBusy, type InboxEntry, type InboxStatus } from "./inbox-model"
|
||||
import { compareWorking, workingSince } from "./inbox-working"
|
||||
|
||||
// `since` is when the inbox was first used; Recent only lists sessions that finished a run after it.
|
||||
const InboxState = Persistence.struct({
|
||||
@@ -35,6 +36,7 @@ export function SidebarInbox() {
|
||||
|
||||
const sections = createMemo(() => {
|
||||
const list = inbox.entries()
|
||||
|
||||
// The most recently finished session comes first.
|
||||
const finished = list
|
||||
.filter((entry) => {
|
||||
@@ -48,7 +50,8 @@ export function SidebarInbox() {
|
||||
needs: list.filter((entry) => entry.status === "approval" || entry.status === "input"),
|
||||
inbox: finished.filter((entry) => !inboxArchived(entry)),
|
||||
archived: finished.filter(inboxArchived),
|
||||
working: list.filter((entry) => entry.status === "working"),
|
||||
// `time.updated` moves with every message of a run, so order by when each run started to keep rows in place.
|
||||
working: list.filter((entry) => entry.status === "working").toSorted(compareWorking),
|
||||
}
|
||||
})
|
||||
|
||||
@@ -77,6 +80,7 @@ export function SidebarInbox() {
|
||||
await inbox.remove(props.entry)
|
||||
dialog.close()
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog fit>
|
||||
<DialogHeader hideClose>
|
||||
@@ -251,13 +255,22 @@ function InboxRow(props: {
|
||||
return language.plural("sidebar.inbox.elapsed.days", Math.floor(seconds / 86_400))
|
||||
}
|
||||
|
||||
const elapsed = () => {
|
||||
// `time.updated` moves when a prompt is enqueued, so it marks when the current run was requested.
|
||||
const seconds = Math.max(0, Math.floor((props.now - props.entry.session.time.updated) / 1000))
|
||||
// Without the current run's start yet, say only that the session is working.
|
||||
const working = () => {
|
||||
const since = workingSince(props.entry.session)
|
||||
|
||||
return seconds < 60 ? language.plural("sidebar.inbox.elapsed.seconds", seconds) : compact(seconds)
|
||||
if (since === undefined) return language.t("sidebar.inbox.status.working")
|
||||
|
||||
const seconds = Math.max(0, Math.floor((props.now - since) / 1000))
|
||||
const duration = seconds < 60 ? language.plural("sidebar.inbox.elapsed.seconds", seconds) : compact(seconds)
|
||||
|
||||
return language.t("sidebar.inbox.status.workingFor", { duration })
|
||||
}
|
||||
|
||||
// Ready rows show their age instead of a status.
|
||||
const statusLabel = (value: InboxStatus) =>
|
||||
value === "ready" ? undefined : language.t(`sidebar.inbox.status.${value}`)
|
||||
|
||||
const age = () => {
|
||||
const seconds = Math.max(
|
||||
0,
|
||||
@@ -304,10 +317,7 @@ function InboxRow(props: {
|
||||
"text-v2-state-fg-success": status() === "done",
|
||||
}}
|
||||
>
|
||||
<Show
|
||||
when={status() === "working"}
|
||||
fallback={language.t(`sidebar.inbox.status.${status() as Exclude<InboxStatus, "ready">}`)}
|
||||
>
|
||||
<Show when={status() === "working"} fallback={statusLabel(status())}>
|
||||
{/* Announce only the status: the ticking duration would be re-read every second. */}
|
||||
<span class="sr-only">{language.t("sidebar.inbox.status.working")}</span>
|
||||
{/* Keep the status colour under the sweep. */}
|
||||
@@ -315,7 +325,7 @@ function InboxRow(props: {
|
||||
aria-hidden="true"
|
||||
class="[&_[data-component=text-shimmer]]:[--text-shimmer-base-color:var(--v2-state-fg-info)]"
|
||||
>
|
||||
<TextShimmer text={language.t("sidebar.inbox.status.workingFor", { duration: elapsed() })} active />
|
||||
<TextShimmer text={working()} active />
|
||||
</span>
|
||||
</Show>
|
||||
</span>
|
||||
|
||||
Reference in New Issue
Block a user