fix(app): session loading loop
This commit is contained in:
@@ -45,6 +45,8 @@ export function SessionHeader() {
|
|||||||
|
|
||||||
const currentSession = createMemo(() => sync.data.session.find((s) => s.id === params.id))
|
const currentSession = createMemo(() => sync.data.session.find((s) => s.id === params.id))
|
||||||
const shareEnabled = createMemo(() => sync.data.config.share !== "disabled")
|
const shareEnabled = createMemo(() => sync.data.config.share !== "disabled")
|
||||||
|
const showReview = createMemo(() => !!currentSession()?.summary?.files)
|
||||||
|
const showShare = createMemo(() => shareEnabled() && !!currentSession())
|
||||||
const sessionKey = createMemo(() => `${params.dir}${params.id ? "/" + params.id : ""}`)
|
const sessionKey = createMemo(() => `${params.dir}${params.id ? "/" + params.id : ""}`)
|
||||||
const view = createMemo(() => layout.view(sessionKey()))
|
const view = createMemo(() => layout.view(sessionKey()))
|
||||||
|
|
||||||
@@ -172,12 +174,14 @@ export function SessionHeader() {
|
|||||||
{/* <SessionMcpIndicator /> */}
|
{/* <SessionMcpIndicator /> */}
|
||||||
{/* </div> */}
|
{/* </div> */}
|
||||||
<div class="flex items-center gap-1">
|
<div class="flex items-center gap-1">
|
||||||
<Show when={currentSession()?.summary?.files}>
|
<div
|
||||||
<TooltipKeybind
|
|
||||||
class="hidden md:block shrink-0"
|
class="hidden md:block shrink-0"
|
||||||
title="Toggle review"
|
classList={{
|
||||||
keybind={command.keybind("review.toggle")}
|
"opacity-0 pointer-events-none": !showReview(),
|
||||||
|
}}
|
||||||
|
aria-hidden={!showReview()}
|
||||||
>
|
>
|
||||||
|
<TooltipKeybind title="Toggle review" keybind={command.keybind("review.toggle")}>
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
class="group/review-toggle size-6 p-0"
|
class="group/review-toggle size-6 p-0"
|
||||||
@@ -202,7 +206,7 @@ export function SessionHeader() {
|
|||||||
</div>
|
</div>
|
||||||
</Button>
|
</Button>
|
||||||
</TooltipKeybind>
|
</TooltipKeybind>
|
||||||
</Show>
|
</div>
|
||||||
<TooltipKeybind
|
<TooltipKeybind
|
||||||
class="hidden md:block shrink-0"
|
class="hidden md:block shrink-0"
|
||||||
title="Toggle terminal"
|
title="Toggle terminal"
|
||||||
@@ -233,8 +237,13 @@ export function SessionHeader() {
|
|||||||
</Button>
|
</Button>
|
||||||
</TooltipKeybind>
|
</TooltipKeybind>
|
||||||
</div>
|
</div>
|
||||||
<Show when={shareEnabled() && currentSession()}>
|
<div
|
||||||
<div class="flex items-center">
|
class="flex items-center"
|
||||||
|
classList={{
|
||||||
|
"opacity-0 pointer-events-none": !showShare(),
|
||||||
|
}}
|
||||||
|
aria-hidden={!showShare()}
|
||||||
|
>
|
||||||
<Popover
|
<Popover
|
||||||
title="Publish on web"
|
title="Publish on web"
|
||||||
description={
|
description={
|
||||||
@@ -309,7 +318,6 @@ export function SessionHeader() {
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
</Show>
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
</Show>
|
|
||||||
</div>
|
</div>
|
||||||
</Portal>
|
</Portal>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -88,6 +88,10 @@ type VcsCache = {
|
|||||||
ready: Accessor<boolean>
|
ready: Accessor<boolean>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ChildOptions = {
|
||||||
|
bootstrap?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
function createGlobalSync() {
|
function createGlobalSync() {
|
||||||
const globalSDK = useGlobalSDK()
|
const globalSDK = useGlobalSDK()
|
||||||
const platform = usePlatform()
|
const platform = usePlatform()
|
||||||
@@ -127,8 +131,10 @@ function createGlobalSync() {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const children: Record<string, [Store<State>, SetStoreFunction<State>]> = {}
|
const children: Record<string, [Store<State>, SetStoreFunction<State>]> = {}
|
||||||
|
const booting = new Map<string, Promise<void>>()
|
||||||
|
const sessionLoads = new Map<string, Promise<void>>()
|
||||||
|
|
||||||
function child(directory: string) {
|
function ensureChild(directory: string) {
|
||||||
if (!directory) console.error("No directory provided")
|
if (!directory) console.error("No directory provided")
|
||||||
if (!children[directory]) {
|
if (!children[directory]) {
|
||||||
const cache = runWithOwner(owner, () =>
|
const cache = runWithOwner(owner, () =>
|
||||||
@@ -163,7 +169,6 @@ function createGlobalSync() {
|
|||||||
message: {},
|
message: {},
|
||||||
part: {},
|
part: {},
|
||||||
})
|
})
|
||||||
bootstrapInstance(directory)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
runWithOwner(owner, init)
|
runWithOwner(owner, init)
|
||||||
@@ -173,11 +178,23 @@ function createGlobalSync() {
|
|||||||
return childStore
|
return childStore
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function child(directory: string, options: ChildOptions = {}) {
|
||||||
|
const childStore = ensureChild(directory)
|
||||||
|
const shouldBootstrap = options.bootstrap ?? true
|
||||||
|
if (shouldBootstrap && childStore[0].status === "loading") {
|
||||||
|
void bootstrapInstance(directory)
|
||||||
|
}
|
||||||
|
return childStore
|
||||||
|
}
|
||||||
|
|
||||||
async function loadSessions(directory: string) {
|
async function loadSessions(directory: string) {
|
||||||
const [store, setStore] = child(directory)
|
const pending = sessionLoads.get(directory)
|
||||||
|
if (pending) return pending
|
||||||
|
|
||||||
|
const [store, setStore] = child(directory, { bootstrap: false })
|
||||||
const limit = store.limit
|
const limit = store.limit
|
||||||
|
|
||||||
return globalSDK.client.session
|
const promise = globalSDK.client.session
|
||||||
.list({ directory, roots: true })
|
.list({ directory, roots: true })
|
||||||
.then((x) => {
|
.then((x) => {
|
||||||
const nonArchived = (x.data ?? [])
|
const nonArchived = (x.data ?? [])
|
||||||
@@ -208,11 +225,21 @@ function createGlobalSync() {
|
|||||||
const project = getFilename(directory)
|
const project = getFilename(directory)
|
||||||
showToast({ title: `Failed to load sessions for ${project}`, description: err.message })
|
showToast({ title: `Failed to load sessions for ${project}`, description: err.message })
|
||||||
})
|
})
|
||||||
|
|
||||||
|
sessionLoads.set(directory, promise)
|
||||||
|
promise.finally(() => {
|
||||||
|
sessionLoads.delete(directory)
|
||||||
|
})
|
||||||
|
return promise
|
||||||
}
|
}
|
||||||
|
|
||||||
async function bootstrapInstance(directory: string) {
|
async function bootstrapInstance(directory: string) {
|
||||||
if (!directory) return
|
if (!directory) return
|
||||||
const [store, setStore] = child(directory)
|
const pending = booting.get(directory)
|
||||||
|
if (pending) return pending
|
||||||
|
|
||||||
|
const promise = (async () => {
|
||||||
|
const [store, setStore] = ensureChild(directory)
|
||||||
const cache = vcsCache.get(directory)
|
const cache = vcsCache.get(directory)
|
||||||
if (!cache) return
|
if (!cache) return
|
||||||
const sdk = createOpencodeClient({
|
const sdk = createOpencodeClient({
|
||||||
@@ -342,6 +369,13 @@ function createGlobalSync() {
|
|||||||
]).then(() => {
|
]).then(() => {
|
||||||
setStore("status", "complete")
|
setStore("status", "complete")
|
||||||
})
|
})
|
||||||
|
})()
|
||||||
|
|
||||||
|
booting.set(directory, promise)
|
||||||
|
promise.finally(() => {
|
||||||
|
booting.delete(directory)
|
||||||
|
})
|
||||||
|
return promise
|
||||||
}
|
}
|
||||||
|
|
||||||
const unsub = globalSDK.event.listen((e) => {
|
const unsub = globalSDK.event.listen((e) => {
|
||||||
|
|||||||
@@ -563,9 +563,13 @@ export default function Layout(props: ParentProps) {
|
|||||||
if (!project) return [] as Session[]
|
if (!project) return [] as Session[]
|
||||||
if (workspaceSetting()) {
|
if (workspaceSetting()) {
|
||||||
const dirs = workspaceIds(project)
|
const dirs = workspaceIds(project)
|
||||||
|
const activeDir = params.dir ? base64Decode(params.dir) : ""
|
||||||
const result: Session[] = []
|
const result: Session[] = []
|
||||||
for (const dir of dirs) {
|
for (const dir of dirs) {
|
||||||
const [dirStore] = globalSync.child(dir)
|
const expanded = store.workspaceExpanded[dir] ?? dir === project.worktree
|
||||||
|
const active = dir === activeDir
|
||||||
|
if (!expanded && !active) continue
|
||||||
|
const [dirStore] = globalSync.child(dir, { bootstrap: true })
|
||||||
const dirSessions = dirStore.session
|
const dirSessions = dirStore.session
|
||||||
.filter((session) => session.directory === dirStore.path.directory)
|
.filter((session) => session.directory === dirStore.path.directory)
|
||||||
.filter((session) => !session.parentID && !session.time?.archived)
|
.filter((session) => !session.parentID && !session.time?.archived)
|
||||||
@@ -1238,8 +1242,12 @@ export default function Layout(props: ParentProps) {
|
|||||||
if (!project) return
|
if (!project) return
|
||||||
|
|
||||||
if (workspaceSetting()) {
|
if (workspaceSetting()) {
|
||||||
|
const activeDir = params.dir ? base64Decode(params.dir) : ""
|
||||||
const dirs = [project.worktree, ...(project.sandboxes ?? [])]
|
const dirs = [project.worktree, ...(project.sandboxes ?? [])]
|
||||||
for (const directory of dirs) {
|
for (const directory of dirs) {
|
||||||
|
const expanded = store.workspaceExpanded[directory] ?? directory === project.worktree
|
||||||
|
const active = directory === activeDir
|
||||||
|
if (!expanded && !active) continue
|
||||||
globalSync.project.loadSessions(directory)
|
globalSync.project.loadSessions(directory)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
@@ -1558,7 +1566,7 @@ export default function Layout(props: ParentProps) {
|
|||||||
|
|
||||||
const SortableWorkspace = (props: { directory: string; project: LocalProject; mobile?: boolean }): JSX.Element => {
|
const SortableWorkspace = (props: { directory: string; project: LocalProject; mobile?: boolean }): JSX.Element => {
|
||||||
const sortable = createSortable(props.directory)
|
const sortable = createSortable(props.directory)
|
||||||
const [workspaceStore, setWorkspaceStore] = globalSync.child(props.directory)
|
const [workspaceStore, setWorkspaceStore] = globalSync.child(props.directory, { bootstrap: false })
|
||||||
const [menuOpen, setMenuOpen] = createSignal(false)
|
const [menuOpen, setMenuOpen] = createSignal(false)
|
||||||
const [pendingRename, setPendingRename] = createSignal(false)
|
const [pendingRename, setPendingRename] = createSignal(false)
|
||||||
const slug = createMemo(() => base64Encode(props.directory))
|
const slug = createMemo(() => base64Encode(props.directory))
|
||||||
@@ -1569,12 +1577,17 @@ export default function Layout(props: ParentProps) {
|
|||||||
.toSorted(sortSessions),
|
.toSorted(sortSessions),
|
||||||
)
|
)
|
||||||
const local = createMemo(() => props.directory === props.project.worktree)
|
const local = createMemo(() => props.directory === props.project.worktree)
|
||||||
|
const active = createMemo(() => {
|
||||||
|
const current = params.dir ? base64Decode(params.dir) : ""
|
||||||
|
return current === props.directory
|
||||||
|
})
|
||||||
const workspaceValue = createMemo(() => {
|
const workspaceValue = createMemo(() => {
|
||||||
const branch = workspaceStore.vcs?.branch
|
const branch = workspaceStore.vcs?.branch
|
||||||
const name = branch ?? getFilename(props.directory)
|
const name = branch ?? getFilename(props.directory)
|
||||||
return workspaceName(props.directory, props.project.id, branch) ?? name
|
return workspaceName(props.directory, props.project.id, branch) ?? name
|
||||||
})
|
})
|
||||||
const open = createMemo(() => store.workspaceExpanded[props.directory] ?? true)
|
const open = createMemo(() => store.workspaceExpanded[props.directory] ?? local())
|
||||||
|
const boot = createMemo(() => open() || active())
|
||||||
const loading = createMemo(() => open() && workspaceStore.status !== "complete" && sessions().length === 0)
|
const loading = createMemo(() => open() && workspaceStore.status !== "complete" && sessions().length === 0)
|
||||||
const hasMore = createMemo(() => local() && workspaceStore.sessionTotal > workspaceStore.session.length)
|
const hasMore = createMemo(() => local() && workspaceStore.sessionTotal > workspaceStore.session.length)
|
||||||
const loadMore = async () => {
|
const loadMore = async () => {
|
||||||
@@ -1591,6 +1604,11 @@ export default function Layout(props: ParentProps) {
|
|||||||
if (editorOpen(`workspace:${props.directory}`)) closeEditor()
|
if (editorOpen(`workspace:${props.directory}`)) closeEditor()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
createEffect(() => {
|
||||||
|
if (!boot()) return
|
||||||
|
globalSync.child(props.directory, { bootstrap: true })
|
||||||
|
})
|
||||||
|
|
||||||
const header = () => (
|
const header = () => (
|
||||||
<div class="flex items-center gap-1 min-w-0 flex-1">
|
<div class="flex items-center gap-1 min-w-0 flex-1">
|
||||||
<div class="flex items-center justify-center shrink-0 size-6">
|
<div class="flex items-center justify-center shrink-0 size-6">
|
||||||
|
|||||||
Reference in New Issue
Block a user