summaryrefslogtreecommitdiffhomepage
path: root/packages/app/src/pages/session
diff options
context:
space:
mode:
authorAdam <[email protected]>2026-02-12 11:26:19 -0600
committerGitHub <[email protected]>2026-02-12 11:26:19 -0600
commitda952135cabba2926698298797cd301e7adaf48c (patch)
tree78635fe4f7d656266ad3cc1c353b04b56969515c /packages/app/src/pages/session
parent789705ea96ae28af7e30801fd6039ce89b6ac48e (diff)
downloadopencode-da952135cabba2926698298797cd301e7adaf48c.tar.gz
opencode-da952135cabba2926698298797cd301e7adaf48c.zip
chore(app): refactor for better solidjs hygiene (#13344)
Diffstat (limited to 'packages/app/src/pages/session')
-rw-r--r--packages/app/src/pages/session/file-tabs.tsx27
-rw-r--r--packages/app/src/pages/session/helpers.ts9
-rw-r--r--packages/app/src/pages/session/session-side-panel.tsx6
-rw-r--r--packages/app/src/pages/session/terminal-panel.tsx16
-rw-r--r--packages/app/src/pages/session/use-session-hash-scroll.ts17
5 files changed, 54 insertions, 21 deletions
diff --git a/packages/app/src/pages/session/file-tabs.tsx b/packages/app/src/pages/session/file-tabs.tsx
index c94c0ff35..5b3f57dbe 100644
--- a/packages/app/src/pages/session/file-tabs.tsx
+++ b/packages/app/src/pages/session/file-tabs.tsx
@@ -1,5 +1,5 @@
import { type ValidComponent, createEffect, createMemo, For, Match, on, onCleanup, Show, Switch } from "solid-js"
-import { createStore } from "solid-js/store"
+import { createStore, produce } from "solid-js/store"
import { Dynamic } from "solid-js/web"
import { checksum } from "@opencode-ai/util/encode"
import { decode64 } from "@/utils/base64"
@@ -112,6 +112,12 @@ export function FileTabContent(props: {
return props.comments.list(p)
})
+ const commentLayout = createMemo(() => {
+ return fileComments()
+ .map((comment) => `${comment.id}:${comment.selection.start}:${comment.selection.end}`)
+ .join("|")
+ })
+
const commentedLines = createMemo(() => fileComments().map((comment) => comment.selection))
const [note, setNote] = createStore({
@@ -164,7 +170,22 @@ export function FileTabContent(props: {
next[comment.id] = markerTop(el, marker)
}
- setNote("positions", next)
+ const removed = Object.keys(note.positions).filter((id) => next[id] === undefined)
+ const changed = Object.entries(next).filter(([id, top]) => note.positions[id] !== top)
+ if (removed.length > 0 || changed.length > 0) {
+ setNote(
+ "positions",
+ produce((draft) => {
+ for (const id of removed) {
+ delete draft[id]
+ }
+
+ for (const [id, top] of changed) {
+ draft[id] = top
+ }
+ }),
+ )
+ }
const range = note.commenting
if (!range) {
@@ -186,7 +207,7 @@ export function FileTabContent(props: {
}
createEffect(() => {
- fileComments()
+ commentLayout()
scheduleComments()
})
diff --git a/packages/app/src/pages/session/helpers.ts b/packages/app/src/pages/session/helpers.ts
index dcf2c8784..6ead7a7f2 100644
--- a/packages/app/src/pages/session/helpers.ts
+++ b/packages/app/src/pages/session/helpers.ts
@@ -1,4 +1,5 @@
import type { CommandOption } from "@/context/command"
+import { batch } from "solid-js"
export const focusTerminalById = (id: string) => {
const wrapper = document.getElementById(`terminal-wrapper-${id}`)
@@ -27,9 +28,11 @@ export const createOpenReviewFile = (input: {
loadFile: (path: string) => void
}) => {
return (path: string) => {
- input.showAllFiles()
- input.openTab(input.tabForPath(path))
- input.loadFile(path)
+ batch(() => {
+ input.showAllFiles()
+ input.openTab(input.tabForPath(path))
+ input.loadFile(path)
+ })
}
}
diff --git a/packages/app/src/pages/session/session-side-panel.tsx b/packages/app/src/pages/session/session-side-panel.tsx
index 15ad90ffe..33954f64a 100644
--- a/packages/app/src/pages/session/session-side-panel.tsx
+++ b/packages/app/src/pages/session/session-side-panel.tsx
@@ -72,6 +72,8 @@ export function SessionSidePanel(props: {
activeDiff?: string
focusReviewDiff: (path: string) => void
}) {
+ const openedTabs = createMemo(() => props.openedTabs())
+
return (
<Show when={props.open}>
<aside
@@ -140,8 +142,8 @@ export function SessionSidePanel(props: {
</div>
</Tabs.Trigger>
</Show>
- <SortableProvider ids={props.openedTabs()}>
- <For each={props.openedTabs()}>
+ <SortableProvider ids={openedTabs()}>
+ <For each={openedTabs()}>
{(tab) => <SortableTab tab={tab} onTabClose={props.tabs().close} />}
</For>
</SortableProvider>
diff --git a/packages/app/src/pages/session/terminal-panel.tsx b/packages/app/src/pages/session/terminal-panel.tsx
index d3475c714..7ec4356b1 100644
--- a/packages/app/src/pages/session/terminal-panel.tsx
+++ b/packages/app/src/pages/session/terminal-panel.tsx
@@ -1,4 +1,4 @@
-import { For, Show } from "solid-js"
+import { For, Show, createMemo } from "solid-js"
import { Tabs } from "@opencode-ai/ui/tabs"
import { ResizeHandle } from "@opencode-ai/ui/resize-handle"
import { IconButton } from "@opencode-ai/ui/icon-button"
@@ -8,7 +8,7 @@ import type { DragEvent } from "@thisbeyond/solid-dnd"
import { ConstrainDragYAxis } from "@/utils/solid-dnd"
import { SortableTerminalTab } from "@/components/session"
import { Terminal } from "@/components/terminal"
-import { useTerminal, type LocalPTY } from "@/context/terminal"
+import { useTerminal } from "@/context/terminal"
import { useLanguage } from "@/context/language"
import { useCommand } from "@/context/command"
import { terminalTabLabel } from "@/pages/session/terminal-label"
@@ -28,6 +28,10 @@ export function TerminalPanel(props: {
handleTerminalDragEnd: () => void
onCloseTab: () => void
}) {
+ const all = createMemo(() => props.terminal.all())
+ const ids = createMemo(() => all().map((pty) => pty.id))
+ const byId = createMemo(() => new Map(all().map((pty) => [pty.id, pty])))
+
return (
<Show when={props.open}>
<div
@@ -86,8 +90,8 @@ export function TerminalPanel(props: {
class="!h-auto !flex-none"
>
<Tabs.List class="h-10">
- <SortableProvider ids={props.terminal.all().map((t: LocalPTY) => t.id)}>
- <For each={props.terminal.all()}>
+ <SortableProvider ids={ids()}>
+ <For each={all()}>
{(pty) => (
<SortableTerminalTab
terminal={pty}
@@ -117,7 +121,7 @@ export function TerminalPanel(props: {
</Tabs.List>
</Tabs>
<div class="flex-1 min-h-0 relative">
- <For each={props.terminal.all()}>
+ <For each={all()}>
{(pty) => (
<div
id={`terminal-wrapper-${pty.id}`}
@@ -142,7 +146,7 @@ export function TerminalPanel(props: {
<Show when={props.activeTerminalDraggable()}>
{(draggedId) => {
return (
- <Show when={props.terminal.all().find((t: LocalPTY) => t.id === draggedId())}>
+ <Show when={byId().get(draggedId())}>
{(t) => (
<div class="relative p-1 h-10 flex items-center bg-background-stronger text-14-regular">
{terminalTabLabel({
diff --git a/packages/app/src/pages/session/use-session-hash-scroll.ts b/packages/app/src/pages/session/use-session-hash-scroll.ts
index 8952bbd98..555761ad1 100644
--- a/packages/app/src/pages/session/use-session-hash-scroll.ts
+++ b/packages/app/src/pages/session/use-session-hash-scroll.ts
@@ -1,4 +1,4 @@
-import { createEffect, on, onCleanup } from "solid-js"
+import { createEffect, createMemo, on, onCleanup } from "solid-js"
import { UserMessage } from "@opencode-ai/sdk/v2"
export const messageIdFromHash = (hash: string) => {
@@ -26,6 +26,10 @@ export const useSessionHashScroll = (input: {
scheduleScrollState: (el: HTMLDivElement) => void
consumePendingMessage: (key: string) => string | undefined
}) => {
+ const visibleUserMessages = createMemo(() => input.visibleUserMessages())
+ const messageById = createMemo(() => new Map(visibleUserMessages().map((m) => [m.id, m])))
+ const messageIndex = createMemo(() => new Map(visibleUserMessages().map((m, i) => [m.id, i])))
+
const clearMessageHash = () => {
if (!window.location.hash) return
window.history.replaceState(null, "", window.location.href.replace(/#.*$/, ""))
@@ -47,10 +51,9 @@ export const useSessionHashScroll = (input: {
}
const scrollToMessage = (message: UserMessage, behavior: ScrollBehavior = "smooth") => {
- input.setActiveMessage(message)
+ if (input.currentMessageId() !== message.id) input.setActiveMessage(message)
- const msgs = input.visibleUserMessages()
- const index = msgs.findIndex((m) => m.id === message.id)
+ const index = messageIndex().get(message.id) ?? -1
if (index !== -1 && index < input.turnStart()) {
input.setTurnStart(index)
input.scheduleTurnBackfill()
@@ -107,7 +110,7 @@ export const useSessionHashScroll = (input: {
const messageId = messageIdFromHash(hash)
if (messageId) {
input.autoScroll.pause()
- const msg = input.visibleUserMessages().find((m) => m.id === messageId)
+ const msg = messageById().get(messageId)
if (msg) {
scrollToMessage(msg, behavior)
return
@@ -144,14 +147,14 @@ export const useSessionHashScroll = (input: {
createEffect(() => {
if (!input.sessionID() || !input.messagesReady()) return
- input.visibleUserMessages().length
+ visibleUserMessages()
input.turnStart()
const targetId = input.pendingMessage() ?? messageIdFromHash(window.location.hash)
if (!targetId) return
if (input.currentMessageId() === targetId) return
- const msg = input.visibleUserMessages().find((m) => m.id === targetId)
+ const msg = messageById().get(targetId)
if (!msg) return
if (input.pendingMessage() === targetId) input.setPendingMessage(undefined)