From 374275eeb691006fa8f422d6267aa694ab38a992 Mon Sep 17 00:00:00 2001 From: Adam <2363879+adamdotdevin@users.noreply.github.com> Date: Wed, 7 Jan 2026 08:41:16 -0600 Subject: feat(app): chunk message loading, lazy load diffs --- packages/app/src/pages/layout.tsx | 17 +++ packages/app/src/pages/session.tsx | 243 ++++++++++++++++++++++++++----------- 2 files changed, 192 insertions(+), 68 deletions(-) (limited to 'packages/app/src/pages') diff --git a/packages/app/src/pages/layout.tsx b/packages/app/src/pages/layout.tsx index a0f364d9a..50553795c 100644 --- a/packages/app/src/pages/layout.tsx +++ b/packages/app/src/pages/layout.tsx @@ -55,6 +55,7 @@ import { DialogEditProject } from "@/components/dialog-edit-project" import { DialogSelectServer } from "@/components/dialog-select-server" import { useCommand, type CommandOption } from "@/context/command" import { ConstrainDragXAxis } from "@/utils/solid-dnd" +import { navStart } from "@/utils/perf" import { DialogSelectDirectory } from "@/components/dialog-select-directory" import { useServer } from "@/context/server" @@ -309,6 +310,14 @@ export default function Layout(props: ParentProps) { if (targetIndex >= 0 && targetIndex < sessions.length) { const session = sessions[targetIndex] + if (import.meta.env.DEV) { + navStart({ + dir: base64Encode(session.directory), + from: params.id, + to: session.id, + trigger: offset > 0 ? "alt+arrowdown" : "alt+arrowup", + }) + } navigateToSession(session) queueMicrotask(() => scrollToSession(session.id)) return @@ -325,6 +334,14 @@ export default function Layout(props: ParentProps) { } const targetSession = offset > 0 ? nextProjectSessions[0] : nextProjectSessions[nextProjectSessions.length - 1] + if (import.meta.env.DEV) { + navStart({ + dir: base64Encode(targetSession.directory), + from: params.id, + to: targetSession.id, + trigger: offset > 0 ? "alt+arrowdown" : "alt+arrowup", + }) + } navigateToSession(targetSession) queueMicrotask(() => scrollToSession(targetSession.id)) } diff --git a/packages/app/src/pages/session.tsx b/packages/app/src/pages/session.tsx index d3d8ef387..4a40ae7d2 100644 --- a/packages/app/src/pages/session.tsx +++ b/packages/app/src/pages/session.tsx @@ -1,4 +1,4 @@ -import { For, onCleanup, Show, Match, Switch, createMemo, createEffect, on } from "solid-js" +import { For, onCleanup, onMount, Show, Match, Switch, createMemo, createEffect, on } from "solid-js" import { createMediaQuery } from "@solid-primitives/media" import { createResizeObserver } from "@solid-primitives/resize-observer" import { Dynamic } from "solid-js/web" @@ -8,6 +8,7 @@ import { createStore } from "solid-js/store" import { PromptInput } from "@/components/prompt-input" import { SessionContextUsage } from "@/components/session-context-usage" import { IconButton } from "@opencode-ai/ui/icon-button" +import { Button } from "@opencode-ai/ui/button" import { Icon } from "@opencode-ai/ui/icon" import { Tooltip, TooltipKeybind } from "@opencode-ai/ui/tooltip" import { DiffChanges } from "@opencode-ai/ui/diff-changes" @@ -49,6 +50,7 @@ import { NewSessionView, } from "@/components/session" import { usePlatform } from "@/context/platform" +import { navMark, navParams } from "@/utils/perf" import { same } from "@/utils/same" type DiffStyle = "unified" | "split" @@ -162,6 +164,46 @@ export default function Page() { const tabs = createMemo(() => layout.tabs(sessionKey())) const view = createMemo(() => layout.view(sessionKey())) + if (import.meta.env.DEV) { + createEffect( + on( + () => [params.dir, params.id] as const, + ([dir, id], prev) => { + if (!id) return + navParams({ dir, from: prev?.[1], to: id }) + }, + ), + ) + + createEffect(() => { + const id = params.id + if (!id) return + if (!prompt.ready()) return + navMark({ dir: params.dir, to: id, name: "storage:prompt-ready" }) + }) + + createEffect(() => { + const id = params.id + if (!id) return + if (!terminal.ready()) return + navMark({ dir: params.dir, to: id, name: "storage:terminal-ready" }) + }) + + createEffect(() => { + const id = params.id + if (!id) return + if (!file.ready()) return + navMark({ dir: params.dir, to: id, name: "storage:file-view-ready" }) + }) + + createEffect(() => { + const id = params.id + if (!id) return + if (sync.data.message[id] === undefined) return + navMark({ dir: params.dir, to: id, name: "session:data-ready" }) + }) + } + const isDesktop = createMediaQuery("(min-width: 768px)") function normalizeTab(tab: string) { @@ -216,6 +258,8 @@ export default function Page() { }) const info = createMemo(() => (params.id ? sync.session.get(params.id) : undefined)) + const reviewCount = createMemo(() => info()?.summary?.files ?? 0) + const hasReview = createMemo(() => reviewCount() > 0) const revertMessageID = createMemo(() => info()?.revert?.messageID) const messages = createMemo(() => (params.id ? (sync.data.message[params.id] ?? []) : [])) const messagesReady = createMemo(() => { @@ -223,6 +267,16 @@ export default function Page() { if (!id) return true return sync.data.message[id] !== undefined }) + const historyMore = createMemo(() => { + const id = params.id + if (!id) return false + return sync.session.history.more(id) + }) + const historyLoading = createMemo(() => { + const id = params.id + if (!id) return false + return sync.session.history.loading(id) + }) const emptyUserMessages: UserMessage[] = [] const userMessages = createMemo(() => messages().filter((m) => m.role === "user") as UserMessage[], emptyUserMessages) const visibleUserMessages = createMemo(() => { @@ -290,6 +344,12 @@ export default function Page() { } const diffs = createMemo(() => (params.id ? (sync.data.session_diff[params.id] ?? []) : [])) + const diffsReady = createMemo(() => { + const id = params.id + if (!id) return true + if (!hasReview()) return true + return sync.data.session_diff[id] !== undefined + }) const idle = { type: "idle" as const } let inputRef!: HTMLDivElement @@ -643,12 +703,10 @@ export default function Page() { .filter((tab) => tab !== "context"), ) - const reviewTab = createMemo(() => diffs().length > 0 || tabs().active() === "review") - const mobileReview = createMemo(() => !isDesktop() && diffs().length > 0 && store.mobileTab === "review") + const reviewTab = createMemo(() => hasReview() || tabs().active() === "review") + const mobileReview = createMemo(() => !isDesktop() && hasReview() && store.mobileTab === "review") - const showTabs = createMemo( - () => layout.review.opened() && (diffs().length > 0 || tabs().all().length > 0 || contextOpen()), - ) + const showTabs = createMemo(() => layout.review.opened() && (hasReview() || tabs().all().length > 0 || contextOpen())) const activeTab = createMemo(() => { const active = tabs().active() @@ -664,10 +722,22 @@ export default function Page() { createEffect(() => { if (!layout.ready()) return if (tabs().active()) return - if (diffs().length === 0 && openedTabs().length === 0 && !contextOpen()) return + if (!hasReview() && openedTabs().length === 0 && !contextOpen()) return tabs().setActive(activeTab()) }) + createEffect(() => { + const id = params.id + if (!id) return + if (!hasReview()) return + + const wants = isDesktop() ? layout.review.opened() && activeTab() === "review" : store.mobileTab === "review" + if (!wants) return + if (diffsReady()) return + + sync.session.diff(id) + }) + const isWorking = createMemo(() => status().type !== "idle") const autoScroll = createAutoScroll({ working: isWorking, @@ -779,7 +849,7 @@ export default function Page() {
{/* Mobile tab bar - only shown on mobile when there are diffs */} - 0}> + setStore("mobileTab", "review")} > - {diffs().length} Files Changed + {reviewCount()} Files Changed @@ -821,21 +891,26 @@ export default function Page() { when={!mobileReview()} fallback={
- { - const value = file.tab(path) - tabs().open(value) - file.load(path) - }} - classes={{ - root: "pb-[calc(var(--prompt-height,8rem)+32px)]", - header: "px-4", - container: "px-4", - }} - /> + Loading changes...
} + > + { + const value = file.tab(path) + tabs().open(value) + file.load(path) + }} + classes={{ + root: "pb-[calc(var(--prompt-height,8rem)+32px)]", + header: "px-4", + container: "px-4", + }} + /> +
} > @@ -868,42 +943,69 @@ export default function Page() { "mt-0": showTabs(), }} > - - {(message) => ( -
+
+ +
+ + + {(message) => { + if (import.meta.env.DEV) { + onMount(() => { + const id = params.id + if (!id) return + navMark({ dir: params.dir, to: id, name: "session:first-turn-mounted" }) + }) + } + + return ( +
-
- )} + > + + setStore("expanded", message.id, (open: boolean | undefined) => !open) + } + classes={{ + root: "min-w-0 w-full relative", + content: + "flex flex-col justify-between !overflow-visible [&_[data-slot=session-turn-message-header]]:top-[-32px]", + container: + "px-4 md:px-6 " + + (!showTabs() + ? "md:max-w-200 md:mx-auto" + : visibleUserMessages().length > 1 + ? "md:pr-6 md:pl-18" + : ""), + }} + /> +
+ ) + }}
@@ -1035,17 +1137,22 @@ export default function Page() {
- { - const value = file.tab(path) - tabs().open(value) - file.load(path) - }} - /> + Loading changes...
} + > + { + const value = file.tab(path) + tabs().open(value) + file.load(path) + }} + /> +
-- cgit v1.2.3