fix(desktop): "load more" button behavior in desktop sidebar (#8430)
This commit is contained in:
@@ -38,6 +38,7 @@ type State = {
|
|||||||
config: Config
|
config: Config
|
||||||
path: Path
|
path: Path
|
||||||
session: Session[]
|
session: Session[]
|
||||||
|
sessionTotal: number
|
||||||
session_status: {
|
session_status: {
|
||||||
[sessionID: string]: SessionStatus
|
[sessionID: string]: SessionStatus
|
||||||
}
|
}
|
||||||
@@ -98,6 +99,7 @@ function createGlobalSync() {
|
|||||||
agent: [],
|
agent: [],
|
||||||
command: [],
|
command: [],
|
||||||
session: [],
|
session: [],
|
||||||
|
sessionTotal: 0,
|
||||||
session_status: {},
|
session_status: {},
|
||||||
session_diff: {},
|
session_diff: {},
|
||||||
todo: {},
|
todo: {},
|
||||||
@@ -117,8 +119,10 @@ function createGlobalSync() {
|
|||||||
|
|
||||||
async function loadSessions(directory: string) {
|
async function loadSessions(directory: string) {
|
||||||
const [store, setStore] = child(directory)
|
const [store, setStore] = child(directory)
|
||||||
globalSDK.client.session
|
const limit = store.limit
|
||||||
.list({ directory })
|
|
||||||
|
return globalSDK.client.session
|
||||||
|
.list({ directory, roots: true })
|
||||||
.then((x) => {
|
.then((x) => {
|
||||||
const fourHoursAgo = Date.now() - 4 * 60 * 60 * 1000
|
const fourHoursAgo = Date.now() - 4 * 60 * 60 * 1000
|
||||||
const nonArchived = (x.data ?? [])
|
const nonArchived = (x.data ?? [])
|
||||||
@@ -128,10 +132,12 @@ function createGlobalSync() {
|
|||||||
.sort((a, b) => a.id.localeCompare(b.id))
|
.sort((a, b) => a.id.localeCompare(b.id))
|
||||||
// Include up to the limit, plus any updated in the last 4 hours
|
// Include up to the limit, plus any updated in the last 4 hours
|
||||||
const sessions = nonArchived.filter((s, i) => {
|
const sessions = nonArchived.filter((s, i) => {
|
||||||
if (i < store.limit) return true
|
if (i < limit) return true
|
||||||
const updated = new Date(s.time?.updated ?? s.time?.created).getTime()
|
const updated = new Date(s.time?.updated ?? s.time?.created).getTime()
|
||||||
return updated > fourHoursAgo
|
return updated > fourHoursAgo
|
||||||
})
|
})
|
||||||
|
// Store total session count (used for "load more" pagination)
|
||||||
|
setStore("sessionTotal", nonArchived.length)
|
||||||
setStore("session", reconcile(sessions, { key: "id" }))
|
setStore("session", reconcile(sessions, { key: "id" }))
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
|
|||||||
@@ -944,7 +944,7 @@ export default function Layout(props: ParentProps) {
|
|||||||
.toSorted(sortSessions),
|
.toSorted(sortSessions),
|
||||||
)
|
)
|
||||||
const rootSessions = createMemo(() => sessions().filter((s) => !s.parentID))
|
const rootSessions = createMemo(() => sessions().filter((s) => !s.parentID))
|
||||||
const hasMoreSessions = createMemo(() => store.session.length >= store.limit)
|
const hasMoreSessions = createMemo(() => store.sessionTotal > store.session.length)
|
||||||
const loadMoreSessions = async () => {
|
const loadMoreSessions = async () => {
|
||||||
setProjectStore("limit", (limit) => limit + 5)
|
setProjectStore("limit", (limit) => limit + 5)
|
||||||
await globalSync.project.loadSessions(props.project.worktree)
|
await globalSync.project.loadSessions(props.project.worktree)
|
||||||
|
|||||||
@@ -724,6 +724,8 @@ export namespace Server {
|
|||||||
validator(
|
validator(
|
||||||
"query",
|
"query",
|
||||||
z.object({
|
z.object({
|
||||||
|
directory: z.string().optional().meta({ description: "Filter sessions by project directory" }),
|
||||||
|
roots: z.coerce.boolean().optional().meta({ description: "Only return root sessions (no parentID)" }),
|
||||||
start: z.coerce
|
start: z.coerce
|
||||||
.number()
|
.number()
|
||||||
.optional()
|
.optional()
|
||||||
@@ -737,6 +739,8 @@ export namespace Server {
|
|||||||
const term = query.search?.toLowerCase()
|
const term = query.search?.toLowerCase()
|
||||||
const sessions: Session.Info[] = []
|
const sessions: Session.Info[] = []
|
||||||
for await (const session of Session.list()) {
|
for await (const session of Session.list()) {
|
||||||
|
if (query.directory !== undefined && session.directory !== query.directory) continue
|
||||||
|
if (query.roots && session.parentID) continue
|
||||||
if (query.start !== undefined && session.time.updated < query.start) continue
|
if (query.start !== undefined && session.time.updated < query.start) continue
|
||||||
if (term !== undefined && !session.title.toLowerCase().includes(term)) continue
|
if (term !== undefined && !session.title.toLowerCase().includes(term)) continue
|
||||||
sessions.push(session)
|
sessions.push(session)
|
||||||
|
|||||||
39
packages/opencode/test/server/session-list.test.ts
Normal file
39
packages/opencode/test/server/session-list.test.ts
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
import { describe, expect, test } from "bun:test"
|
||||||
|
import path from "path"
|
||||||
|
import { Instance } from "../../src/project/instance"
|
||||||
|
import { Server } from "../../src/server/server"
|
||||||
|
import { Session } from "../../src/session"
|
||||||
|
import { Log } from "../../src/util/log"
|
||||||
|
|
||||||
|
const projectRoot = path.join(__dirname, "../..")
|
||||||
|
Log.init({ print: false })
|
||||||
|
|
||||||
|
describe("session.list", () => {
|
||||||
|
test("filters by directory", async () => {
|
||||||
|
await Instance.provide({
|
||||||
|
directory: projectRoot,
|
||||||
|
fn: async () => {
|
||||||
|
const app = Server.App()
|
||||||
|
|
||||||
|
const first = await Session.create({})
|
||||||
|
|
||||||
|
const otherDir = path.join(projectRoot, "..", "__session_list_other")
|
||||||
|
const second = await Instance.provide({
|
||||||
|
directory: otherDir,
|
||||||
|
fn: async () => Session.create({}),
|
||||||
|
})
|
||||||
|
|
||||||
|
const response = await app.request(`/session?directory=${encodeURIComponent(projectRoot)}`)
|
||||||
|
expect(response.status).toBe(200)
|
||||||
|
|
||||||
|
const body = (await response.json()) as unknown[]
|
||||||
|
const ids = body
|
||||||
|
.map((s) => (typeof s === "object" && s && "id" in s ? (s as { id: string }).id : undefined))
|
||||||
|
.filter((x): x is string => typeof x === "string")
|
||||||
|
|
||||||
|
expect(ids).toContain(first.id)
|
||||||
|
expect(ids).not.toContain(second.id)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -781,6 +781,7 @@ export class Session extends HeyApiClient {
|
|||||||
public list<ThrowOnError extends boolean = false>(
|
public list<ThrowOnError extends boolean = false>(
|
||||||
parameters?: {
|
parameters?: {
|
||||||
directory?: string
|
directory?: string
|
||||||
|
roots?: boolean
|
||||||
start?: number
|
start?: number
|
||||||
search?: string
|
search?: string
|
||||||
limit?: number
|
limit?: number
|
||||||
@@ -793,6 +794,7 @@ export class Session extends HeyApiClient {
|
|||||||
{
|
{
|
||||||
args: [
|
args: [
|
||||||
{ in: "query", key: "directory" },
|
{ in: "query", key: "directory" },
|
||||||
|
{ in: "query", key: "roots" },
|
||||||
{ in: "query", key: "start" },
|
{ in: "query", key: "start" },
|
||||||
{ in: "query", key: "search" },
|
{ in: "query", key: "search" },
|
||||||
{ in: "query", key: "limit" },
|
{ in: "query", key: "limit" },
|
||||||
|
|||||||
@@ -2589,7 +2589,14 @@ export type SessionListData = {
|
|||||||
body?: never
|
body?: never
|
||||||
path?: never
|
path?: never
|
||||||
query?: {
|
query?: {
|
||||||
|
/**
|
||||||
|
* Filter sessions by project directory
|
||||||
|
*/
|
||||||
directory?: string
|
directory?: string
|
||||||
|
/**
|
||||||
|
* Only return root sessions (no parentID)
|
||||||
|
*/
|
||||||
|
roots?: boolean
|
||||||
/**
|
/**
|
||||||
* Filter sessions updated on or after this timestamp (milliseconds since epoch)
|
* Filter sessions updated on or after this timestamp (milliseconds since epoch)
|
||||||
*/
|
*/
|
||||||
|
|||||||
Reference in New Issue
Block a user