mirror of
https://gitea.toothfairyai.com/ToothFairyAI/tf_code.git
synced 2026-04-24 17:44:49 +00:00
fix(app): todos not clearing
This commit is contained in:
@@ -0,0 +1,10 @@
|
|||||||
|
export const todoState = (input: {
|
||||||
|
count: number
|
||||||
|
done: boolean
|
||||||
|
live: boolean
|
||||||
|
}): "hide" | "clear" | "open" | "close" => {
|
||||||
|
if (input.count === 0) return "hide"
|
||||||
|
if (!input.live) return "clear"
|
||||||
|
if (!input.done) return "open"
|
||||||
|
return "close"
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import { describe, expect, test } from "bun:test"
|
import { describe, expect, test } from "bun:test"
|
||||||
import type { PermissionRequest, QuestionRequest, Session } from "@opencode-ai/sdk/v2/client"
|
import type { PermissionRequest, QuestionRequest, Session } from "@opencode-ai/sdk/v2/client"
|
||||||
|
import { todoState } from "./session-composer-helpers"
|
||||||
import { sessionPermissionRequest, sessionQuestionRequest } from "./session-request-tree"
|
import { sessionPermissionRequest, sessionQuestionRequest } from "./session-request-tree"
|
||||||
|
|
||||||
const session = (input: { id: string; parentID?: string }) =>
|
const session = (input: { id: string; parentID?: string }) =>
|
||||||
@@ -103,3 +104,25 @@ describe("sessionQuestionRequest", () => {
|
|||||||
expect(sessionQuestionRequest(sessions, questions, "root")?.id).toBe("q-grand")
|
expect(sessionQuestionRequest(sessions, questions, "root")?.id).toBe("q-grand")
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe("todoState", () => {
|
||||||
|
test("hides when there are no todos", () => {
|
||||||
|
expect(todoState({ count: 0, done: false, live: true })).toBe("hide")
|
||||||
|
})
|
||||||
|
|
||||||
|
test("opens while the session is still working", () => {
|
||||||
|
expect(todoState({ count: 2, done: false, live: true })).toBe("open")
|
||||||
|
})
|
||||||
|
|
||||||
|
test("closes completed todos after a running turn", () => {
|
||||||
|
expect(todoState({ count: 2, done: true, live: true })).toBe("close")
|
||||||
|
})
|
||||||
|
|
||||||
|
test("clears stale todos when the turn ends", () => {
|
||||||
|
expect(todoState({ count: 2, done: false, live: false })).toBe("clear")
|
||||||
|
})
|
||||||
|
|
||||||
|
test("clears completed todos when the session is no longer live", () => {
|
||||||
|
expect(todoState({ count: 2, done: true, live: false })).toBe("clear")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|||||||
@@ -8,8 +8,11 @@ import { useLanguage } from "@/context/language"
|
|||||||
import { usePermission } from "@/context/permission"
|
import { usePermission } from "@/context/permission"
|
||||||
import { useSDK } from "@/context/sdk"
|
import { useSDK } from "@/context/sdk"
|
||||||
import { useSync } from "@/context/sync"
|
import { useSync } from "@/context/sync"
|
||||||
|
import { todoState } from "./session-composer-helpers"
|
||||||
import { sessionPermissionRequest, sessionQuestionRequest } from "./session-request-tree"
|
import { sessionPermissionRequest, sessionQuestionRequest } from "./session-request-tree"
|
||||||
|
|
||||||
|
const idle = { type: "idle" as const }
|
||||||
|
|
||||||
export function createSessionComposerBlocked() {
|
export function createSessionComposerBlocked() {
|
||||||
const params = useParams()
|
const params = useParams()
|
||||||
const permission = usePermission()
|
const permission = usePermission()
|
||||||
@@ -59,9 +62,22 @@ export function createSessionComposerState(options?: { closeMs?: number | (() =>
|
|||||||
return globalSync.data.session_todo[id] ?? []
|
return globalSync.data.session_todo[id] ?? []
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const done = createMemo(
|
||||||
|
() => todos().length > 0 && todos().every((todo) => todo.status === "completed" || todo.status === "cancelled"),
|
||||||
|
)
|
||||||
|
|
||||||
|
const status = createMemo(() => {
|
||||||
|
const id = params.id
|
||||||
|
if (!id) return idle
|
||||||
|
return sync.data.session_status[id] ?? idle
|
||||||
|
})
|
||||||
|
|
||||||
|
const busy = createMemo(() => status().type !== "idle")
|
||||||
|
const live = createMemo(() => busy() || blocked())
|
||||||
|
|
||||||
const [store, setStore] = createStore({
|
const [store, setStore] = createStore({
|
||||||
responding: undefined as string | undefined,
|
responding: undefined as string | undefined,
|
||||||
dock: todos().length > 0,
|
dock: todos().length > 0 && live(),
|
||||||
closing: false,
|
closing: false,
|
||||||
opening: false,
|
opening: false,
|
||||||
})
|
})
|
||||||
@@ -89,10 +105,6 @@ export function createSessionComposerState(options?: { closeMs?: number | (() =>
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const done = createMemo(
|
|
||||||
() => todos().length > 0 && todos().every((todo) => todo.status === "completed" || todo.status === "cancelled"),
|
|
||||||
)
|
|
||||||
|
|
||||||
let timer: number | undefined
|
let timer: number | undefined
|
||||||
let raf: number | undefined
|
let raf: number | undefined
|
||||||
|
|
||||||
@@ -111,21 +123,42 @@ export function createSessionComposerState(options?: { closeMs?: number | (() =>
|
|||||||
}, closeMs())
|
}, closeMs())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Keep stale turn todos from reopening if the model never clears them.
|
||||||
|
const clear = () => {
|
||||||
|
const id = params.id
|
||||||
|
if (!id) return
|
||||||
|
globalSync.todo.set(id, [])
|
||||||
|
sync.set("todo", id, [])
|
||||||
|
}
|
||||||
|
|
||||||
createEffect(
|
createEffect(
|
||||||
on(
|
on(
|
||||||
() => [todos().length, done()] as const,
|
() => [todos().length, done(), live()] as const,
|
||||||
([count, complete], prev) => {
|
([count, complete, active]) => {
|
||||||
if (raf) cancelAnimationFrame(raf)
|
if (raf) cancelAnimationFrame(raf)
|
||||||
raf = undefined
|
raf = undefined
|
||||||
|
|
||||||
if (count === 0) {
|
const next = todoState({
|
||||||
|
count,
|
||||||
|
done: complete,
|
||||||
|
live: active,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (next === "hide") {
|
||||||
if (timer) window.clearTimeout(timer)
|
if (timer) window.clearTimeout(timer)
|
||||||
timer = undefined
|
timer = undefined
|
||||||
setStore({ dock: false, closing: false, opening: false })
|
setStore({ dock: false, closing: false, opening: false })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!complete) {
|
if (next === "clear") {
|
||||||
|
if (timer) window.clearTimeout(timer)
|
||||||
|
timer = undefined
|
||||||
|
clear()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (next === "open") {
|
||||||
if (timer) window.clearTimeout(timer)
|
if (timer) window.clearTimeout(timer)
|
||||||
timer = undefined
|
timer = undefined
|
||||||
const hidden = !store.dock || store.closing
|
const hidden = !store.dock || store.closing
|
||||||
@@ -142,13 +175,8 @@ export function createSessionComposerState(options?: { closeMs?: number | (() =>
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (prev && prev[1]) {
|
|
||||||
if (store.closing && !timer) scheduleClose()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
setStore({ dock: true, opening: false, closing: true })
|
setStore({ dock: true, opening: false, closing: true })
|
||||||
scheduleClose()
|
if (!timer) scheduleClose()
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user