summaryrefslogtreecommitdiffhomepage
path: root/packages/app/src/pages/session
diff options
context:
space:
mode:
authorAdam <[email protected]>2026-03-12 08:52:51 -0500
committerGitHub <[email protected]>2026-03-12 08:52:51 -0500
commit12efbbfa4c49631f8a0201459a0956f78461b355 (patch)
tree2e6b3e7403ea3564eb8b551ae91035e2d8f7b391 /packages/app/src/pages/session
parent13402529ce1ffb8aabcd4843d76dae41ba8855d4 (diff)
downloadopencode-12efbbfa4c49631f8a0201459a0956f78461b355.tar.gz
opencode-12efbbfa4c49631f8a0201459a0956f78461b355.zip
chore: cleanup (#17184)
Diffstat (limited to 'packages/app/src/pages/session')
-rw-r--r--packages/app/src/pages/session/composer/session-composer-region.tsx65
-rw-r--r--packages/app/src/pages/session/composer/session-todo-dock.tsx62
-rw-r--r--packages/app/src/pages/session/file-tabs.tsx10
-rw-r--r--packages/app/src/pages/session/message-timeline.tsx6
-rw-r--r--packages/app/src/pages/session/session-layout.ts20
-rw-r--r--packages/app/src/pages/session/session-side-panel.tsx7
-rw-r--r--packages/app/src/pages/session/terminal-panel.tsx7
-rw-r--r--packages/app/src/pages/session/use-session-commands.tsx15
8 files changed, 61 insertions, 131 deletions
diff --git a/packages/app/src/pages/session/composer/session-composer-region.tsx b/packages/app/src/pages/session/composer/session-composer-region.tsx
index 08746b51a..964bf18dd 100644
--- a/packages/app/src/pages/session/composer/session-composer-region.tsx
+++ b/packages/app/src/pages/session/composer/session-composer-region.tsx
@@ -1,11 +1,10 @@
import { Show, createEffect, createMemo, createSignal, onCleanup } from "solid-js"
-import { createStore } from "solid-js/store"
-import { useParams } from "@solidjs/router"
import { useSpring } from "@opencode-ai/ui/motion-spring"
import { PromptInput } from "@/components/prompt-input"
import { useLanguage } from "@/context/language"
import { usePrompt } from "@/context/prompt"
import { getSessionHandoff, setSessionHandoff } from "@/pages/session/handoff"
+import { useSessionKey } from "@/pages/session/session-layout"
import { SessionPermissionDock } from "@/pages/session/composer/session-permission-dock"
import { SessionQuestionDock } from "@/pages/session/composer/session-question-dock"
import { SessionRevertDock } from "@/pages/session/composer/session-revert-dock"
@@ -27,29 +26,11 @@ export function SessionComposerRegion(props: {
onRestore: (id: string) => void
}
setPromptDockRef: (el: HTMLDivElement) => void
- visualDuration?: number
- bounce?: number
- dockOpenVisualDuration?: number
- dockOpenBounce?: number
- dockCloseVisualDuration?: number
- dockCloseBounce?: number
- drawerExpandVisualDuration?: number
- drawerExpandBounce?: number
- drawerCollapseVisualDuration?: number
- drawerCollapseBounce?: number
- subtitleDuration?: number
- subtitleTravel?: number
- subtitleEdge?: number
- countDuration?: number
- countMask?: number
- countMaskHeight?: number
- countWidthDuration?: number
}) {
- const params = useParams()
const prompt = usePrompt()
const language = useLanguage()
+ const { sessionKey } = useSessionKey()
- const sessionKey = createMemo(() => `${params.dir}${params.id ? "/" + params.id : ""}`)
const handoffPrompt = createMemo(() => getSessionHandoff(sessionKey())?.prompt)
const previewPrompt = () =>
@@ -69,9 +50,7 @@ export function SessionComposerRegion(props: {
setSessionHandoff(sessionKey(), { prompt: previewPrompt() })
})
- const [gate, setGate] = createStore({
- ready: false,
- })
+ const [ready, setReady] = createSignal(false)
let timer: number | undefined
let frame: number | undefined
@@ -88,17 +67,17 @@ export function SessionComposerRegion(props: {
createEffect(() => {
sessionKey()
- const ready = props.ready
+ const active = props.ready
const delay = 140
clear()
- setGate("ready", false)
- if (!ready) return
+ setReady(false)
+ if (!active) return
frame = requestAnimationFrame(() => {
frame = undefined
timer = window.setTimeout(() => {
- setGate("ready", true)
+ setReady(true)
timer = undefined
}, delay)
})
@@ -106,22 +85,11 @@ export function SessionComposerRegion(props: {
onCleanup(clear)
- const open = createMemo(() => gate.ready && props.state.dock() && !props.state.closing())
- const config = createMemo(() =>
- open()
- ? {
- visualDuration: props.dockOpenVisualDuration ?? props.visualDuration ?? 0.3,
- bounce: props.dockOpenBounce ?? props.bounce ?? 0,
- }
- : {
- visualDuration: props.dockCloseVisualDuration ?? props.visualDuration ?? 0.3,
- bounce: props.dockCloseBounce ?? props.bounce ?? 0,
- },
- )
- const progress = useSpring(() => (open() ? 1 : 0), config)
+ const open = createMemo(() => ready() && props.state.dock() && !props.state.closing())
+ const progress = useSpring(() => (open() ? 1 : 0), { visualDuration: 0.3, bounce: 0 })
const value = createMemo(() => Math.max(0, Math.min(1, progress())))
const [height, setHeight] = createSignal(320)
- const dock = createMemo(() => (gate.ready && props.state.dock()) || value() > 0.001)
+ const dock = createMemo(() => (ready() && props.state.dock()) || value() > 0.001)
const rolled = createMemo(() => (props.revert?.items.length ? props.revert : undefined))
const lift = createMemo(() => (rolled() ? 18 : 36 * value()))
const full = createMemo(() => Math.max(78, height()))
@@ -213,19 +181,6 @@ export function SessionComposerRegion(props: {
collapseLabel={language.t("session.todo.collapse")}
expandLabel={language.t("session.todo.expand")}
dockProgress={value()}
- visualDuration={props.visualDuration}
- bounce={props.bounce}
- expandVisualDuration={props.drawerExpandVisualDuration}
- expandBounce={props.drawerExpandBounce}
- collapseVisualDuration={props.drawerCollapseVisualDuration}
- collapseBounce={props.drawerCollapseBounce}
- subtitleDuration={props.subtitleDuration}
- subtitleTravel={props.subtitleTravel}
- subtitleEdge={props.subtitleEdge}
- countDuration={props.countDuration}
- countMask={props.countMask}
- countMaskHeight={props.countMaskHeight}
- countWidthDuration={props.countWidthDuration}
/>
</div>
</div>
diff --git a/packages/app/src/pages/session/composer/session-todo-dock.tsx b/packages/app/src/pages/session/composer/session-todo-dock.tsx
index 04aeb6317..baea51593 100644
--- a/packages/app/src/pages/session/composer/session-todo-dock.tsx
+++ b/packages/app/src/pages/session/composer/session-todo-dock.tsx
@@ -7,7 +7,6 @@ import { useSpring } from "@opencode-ai/ui/motion-spring"
import { TextReveal } from "@opencode-ai/ui/text-reveal"
import { TextStrikethrough } from "@opencode-ai/ui/text-strikethrough"
import { Index, createEffect, createMemo, createSignal, on, onCleanup } from "solid-js"
-import { createStore } from "solid-js/store"
function dot(status: Todo["status"]) {
if (status !== "in_progress") return undefined
@@ -39,26 +38,10 @@ export function SessionTodoDock(props: {
title: string
collapseLabel: string
expandLabel: string
- dockProgress?: number
- visualDuration?: number
- bounce?: number
- expandVisualDuration?: number
- expandBounce?: number
- collapseVisualDuration?: number
- collapseBounce?: number
- subtitleDuration?: number
- subtitleTravel?: number
- subtitleEdge?: number
- countDuration?: number
- countMask?: number
- countMaskHeight?: number
- countWidthDuration?: number
+ dockProgress: number
}) {
- const [store, setStore] = createStore({
- collapsed: false,
- })
-
- const toggle = () => setStore("collapsed", (value) => !value)
+ const [collapsed, setCollapsed] = createSignal(false)
+ const toggle = () => setCollapsed((value) => !value)
const total = createMemo(() => props.todos.length)
const done = createMemo(() => props.todos.filter((todo) => todo.status === "completed").length)
@@ -73,19 +56,8 @@ export function SessionTodoDock(props: {
)
const preview = createMemo(() => active()?.content ?? "")
- const config = createMemo(() =>
- store.collapsed
- ? {
- visualDuration: props.collapseVisualDuration ?? props.visualDuration ?? 0.3,
- bounce: props.collapseBounce ?? props.bounce ?? 0,
- }
- : {
- visualDuration: props.expandVisualDuration ?? props.visualDuration ?? 0.3,
- bounce: props.expandBounce ?? props.bounce ?? 0,
- },
- )
- const collapse = useSpring(() => (store.collapsed ? 1 : 0), config)
- const dock = createMemo(() => Math.max(0, Math.min(1, props.dockProgress ?? 1)))
+ const collapse = useSpring(() => (collapsed() ? 1 : 0), { visualDuration: 0.3, bounce: 0 })
+ const dock = createMemo(() => Math.max(0, Math.min(1, props.dockProgress)))
const shut = createMemo(() => 1 - dock())
const value = createMemo(() => Math.max(0, Math.min(1, collapse())))
const hide = createMemo(() => Math.max(value(), shut()))
@@ -133,10 +105,10 @@ export function SessionTodoDock(props: {
class="text-14-regular text-text-strong cursor-default inline-flex items-baseline shrink-0 whitespace-nowrap overflow-visible"
aria-label={label()}
style={{
- "--tool-motion-odometer-ms": `${props.countDuration ?? 600}ms`,
- "--tool-motion-mask": `${props.countMask ?? 18}%`,
- "--tool-motion-mask-height": `${props.countMaskHeight ?? 0}px`,
- "--tool-motion-spring-ms": `${props.countWidthDuration ?? 560}ms`,
+ "--tool-motion-odometer-ms": "600ms",
+ "--tool-motion-mask": "18%",
+ "--tool-motion-mask-height": "0px",
+ "--tool-motion-spring-ms": "560ms",
opacity: `${Math.max(0, Math.min(1, 1 - shut()))}`,
}}
>
@@ -155,10 +127,10 @@ export function SessionTodoDock(props: {
>
<TextReveal
class="text-14-regular text-text-base cursor-default"
- text={store.collapsed ? preview() : undefined}
- duration={props.subtitleDuration ?? 600}
- travel={props.subtitleTravel ?? 25}
- edge={props.subtitleEdge ?? 17}
+ text={collapsed() ? preview() : undefined}
+ duration={600}
+ travel={25}
+ edge={17}
spring="cubic-bezier(0.34, 1, 0.64, 1)"
springSoft="cubic-bezier(0.34, 1, 0.64, 1)"
growOnly
@@ -168,7 +140,7 @@ export function SessionTodoDock(props: {
<div class="ml-auto">
<IconButton
data-action="session-todo-toggle-button"
- data-collapsed={store.collapsed ? "true" : "false"}
+ data-collapsed={collapsed() ? "true" : "false"}
icon="chevron-down"
size="normal"
variant="ghost"
@@ -181,14 +153,14 @@ export function SessionTodoDock(props: {
event.stopPropagation()
toggle()
}}
- aria-label={store.collapsed ? props.expandLabel : props.collapseLabel}
+ aria-label={collapsed() ? props.expandLabel : props.collapseLabel}
/>
</div>
</div>
<div
data-slot="session-todo-list"
- aria-hidden={store.collapsed || off()}
+ aria-hidden={collapsed() || off()}
classList={{
"pointer-events-none": hide() > 0.1,
}}
@@ -197,7 +169,7 @@ export function SessionTodoDock(props: {
opacity: `${Math.max(0, Math.min(1, 1 - hide()))}`,
}}
>
- <TodoList todos={props.todos} open={!store.collapsed} />
+ <TodoList todos={props.todos} open={!collapsed()} />
</div>
</div>
</DockTray>
diff --git a/packages/app/src/pages/session/file-tabs.tsx b/packages/app/src/pages/session/file-tabs.tsx
index 77643789d..4b322368f 100644
--- a/packages/app/src/pages/session/file-tabs.tsx
+++ b/packages/app/src/pages/session/file-tabs.tsx
@@ -1,7 +1,6 @@
import { createEffect, createMemo, Match, on, onCleanup, Switch } from "solid-js"
import { createStore } from "solid-js/store"
import { Dynamic } from "solid-js/web"
-import { useParams } from "@solidjs/router"
import type { FileSearchHandle } from "@opencode-ai/ui/file"
import { useFileComponent } from "@opencode-ai/ui/context/file"
import { cloneSelectedLineRange, previewSelectedLines } from "@opencode-ai/ui/pierre/selection-bridge"
@@ -12,12 +11,12 @@ import { IconButton } from "@opencode-ai/ui/icon-button"
import { Tabs } from "@opencode-ai/ui/tabs"
import { ScrollView } from "@opencode-ai/ui/scroll-view"
import { showToast } from "@opencode-ai/ui/toast"
-import { useLayout } from "@/context/layout"
import { selectionFromLines, useFile, type FileSelection, type SelectedLineRange } from "@/context/file"
import { useComments } from "@/context/comments"
import { useLanguage } from "@/context/language"
import { usePrompt } from "@/context/prompt"
import { getSessionHandoff } from "@/pages/session/handoff"
+import { useSessionLayout } from "@/pages/session/session-layout"
function FileCommentMenu(props: {
moreLabel: string
@@ -53,17 +52,12 @@ function FileCommentMenu(props: {
}
export function FileTabContent(props: { tab: string }) {
- const params = useParams()
- const layout = useLayout()
const file = useFile()
const comments = useComments()
const language = useLanguage()
const prompt = usePrompt()
const fileComponent = useFileComponent()
-
- const sessionKey = createMemo(() => `${params.dir}${params.id ? "/" + params.id : ""}`)
- const tabs = createMemo(() => layout.tabs(sessionKey))
- const view = createMemo(() => layout.view(sessionKey))
+ const { sessionKey, tabs, view } = useSessionLayout()
let scroll: HTMLDivElement | undefined
let scrollFrame: number | undefined
diff --git a/packages/app/src/pages/session/message-timeline.tsx b/packages/app/src/pages/session/message-timeline.tsx
index e64f5a7fd..50f9b452a 100644
--- a/packages/app/src/pages/session/message-timeline.tsx
+++ b/packages/app/src/pages/session/message-timeline.tsx
@@ -1,6 +1,6 @@
import { For, createEffect, createMemo, on, onCleanup, Show, Index, type JSX } from "solid-js"
import { createStore, produce } from "solid-js/store"
-import { useNavigate, useParams } from "@solidjs/router"
+import { useNavigate } from "@solidjs/router"
import { Button } from "@opencode-ai/ui/button"
import { FileIcon } from "@opencode-ai/ui/file-icon"
import { Icon } from "@opencode-ai/ui/icon"
@@ -19,6 +19,7 @@ import { shouldMarkBoundaryGesture, normalizeWheelDelta } from "@/pages/session/
import { SessionContextUsage } from "@/components/session-context-usage"
import { useDialog } from "@opencode-ai/ui/context/dialog"
import { useLanguage } from "@/context/language"
+import { useSessionKey } from "@/pages/session/session-layout"
import { useSettings } from "@/context/settings"
import { useSDK } from "@/context/sdk"
import { useSync } from "@/context/sync"
@@ -213,16 +214,15 @@ export function MessageTimeline(props: {
}) {
let touchGesture: number | undefined
- const params = useParams()
const navigate = useNavigate()
const sdk = useSDK()
const sync = useSync()
const settings = useSettings()
const dialog = useDialog()
const language = useLanguage()
+ const { params, sessionKey } = useSessionKey()
const rendered = createMemo(() => props.renderedUserMessages.map((message) => message.id))
- const sessionKey = createMemo(() => `${params.dir}${params.id ? "/" + params.id : ""}`)
const sessionID = createMemo(() => params.id)
const sessionMessages = createMemo(() => {
const id = sessionID()
diff --git a/packages/app/src/pages/session/session-layout.ts b/packages/app/src/pages/session/session-layout.ts
new file mode 100644
index 000000000..113411150
--- /dev/null
+++ b/packages/app/src/pages/session/session-layout.ts
@@ -0,0 +1,20 @@
+import { useParams } from "@solidjs/router"
+import { createMemo } from "solid-js"
+import { useLayout } from "@/context/layout"
+
+export const useSessionKey = () => {
+ const params = useParams()
+ const sessionKey = createMemo(() => `${params.dir}${params.id ? "/" + params.id : ""}`)
+ return { params, sessionKey }
+}
+
+export const useSessionLayout = () => {
+ const layout = useLayout()
+ const { params, sessionKey } = useSessionKey()
+ return {
+ params,
+ sessionKey,
+ tabs: createMemo(() => layout.tabs(sessionKey)),
+ view: createMemo(() => layout.view(sessionKey)),
+ }
+}
diff --git a/packages/app/src/pages/session/session-side-panel.tsx b/packages/app/src/pages/session/session-side-panel.tsx
index 590f5b6d9..2c499d9f4 100644
--- a/packages/app/src/pages/session/session-side-panel.tsx
+++ b/packages/app/src/pages/session/session-side-panel.tsx
@@ -1,7 +1,6 @@
import { For, Match, Show, Switch, createEffect, createMemo, onCleanup, type JSX } from "solid-js"
import { createStore } from "solid-js/store"
import { createMediaQuery } from "@solid-primitives/media"
-import { useParams } from "@solidjs/router"
import { Tabs } from "@opencode-ai/ui/tabs"
import { IconButton } from "@opencode-ai/ui/icon-button"
import { TooltipKeybind } from "@opencode-ai/ui/tooltip"
@@ -26,6 +25,7 @@ import { FileTabContent } from "@/pages/session/file-tabs"
import { createOpenSessionFileTab, getTabReorderIndex, type Sizing } from "@/pages/session/helpers"
import { StickyAddButton } from "@/pages/session/review-tab"
import { setSessionHandoff } from "@/pages/session/handoff"
+import { useSessionLayout } from "@/pages/session/session-layout"
export function SessionSidePanel(props: {
reviewPanel: () => JSX.Element
@@ -34,18 +34,15 @@ export function SessionSidePanel(props: {
reviewSnap: boolean
size: Sizing
}) {
- const params = useParams()
const layout = useLayout()
const sync = useSync()
const file = useFile()
const language = useLanguage()
const command = useCommand()
const dialog = useDialog()
+ const { params, sessionKey, tabs, view } = useSessionLayout()
const isDesktop = createMediaQuery("(min-width: 768px)")
- const sessionKey = createMemo(() => `${params.dir}${params.id ? "/" + params.id : ""}`)
- const tabs = createMemo(() => layout.tabs(sessionKey))
- const view = createMemo(() => layout.view(sessionKey))
const reviewOpen = createMemo(() => isDesktop() && view().reviewPanel.opened())
const fileOpen = createMemo(() => isDesktop() && layout.fileTree.opened())
diff --git a/packages/app/src/pages/session/terminal-panel.tsx b/packages/app/src/pages/session/terminal-panel.tsx
index 0c6db0b24..c49518656 100644
--- a/packages/app/src/pages/session/terminal-panel.tsx
+++ b/packages/app/src/pages/session/terminal-panel.tsx
@@ -1,6 +1,5 @@
import { For, Show, createEffect, createMemo, on, onCleanup } from "solid-js"
import { createStore } from "solid-js/store"
-import { useParams } from "@solidjs/router"
import { Tabs } from "@opencode-ai/ui/tabs"
import { ResizeHandle } from "@opencode-ai/ui/resize-handle"
import { IconButton } from "@opencode-ai/ui/icon-button"
@@ -18,16 +17,14 @@ import { useTerminal, type LocalPTY } from "@/context/terminal"
import { terminalTabLabel } from "@/pages/session/terminal-label"
import { createSizing, focusTerminalById } from "@/pages/session/helpers"
import { getTerminalHandoff, setTerminalHandoff } from "@/pages/session/handoff"
+import { useSessionLayout } from "@/pages/session/session-layout"
export function TerminalPanel() {
- const params = useParams()
const layout = useLayout()
const terminal = useTerminal()
const language = useLanguage()
const command = useCommand()
-
- const sessionKey = createMemo(() => `${params.dir}${params.id ? "/" + params.id : ""}`)
- const view = createMemo(() => layout.view(sessionKey))
+ const { params, view } = useSessionLayout()
const opened = createMemo(() => view().terminal.opened())
const size = createSizing()
diff --git a/packages/app/src/pages/session/use-session-commands.tsx b/packages/app/src/pages/session/use-session-commands.tsx
index ea3b5ec57..6799504ca 100644
--- a/packages/app/src/pages/session/use-session-commands.tsx
+++ b/packages/app/src/pages/session/use-session-commands.tsx
@@ -1,7 +1,8 @@
import { createMemo } from "solid-js"
-import { useNavigate, useParams } from "@solidjs/router"
+import { useNavigate } from "@solidjs/router"
import { useCommand, type CommandOption } from "@/context/command"
import { useDialog } from "@opencode-ai/ui/context/dialog"
+import { previewSelectedLines } from "@opencode-ai/ui/pierre/selection-bridge"
import { useFile, selectionFromLines, type FileSelection, type SelectedLineRange } from "@/context/file"
import { useLanguage } from "@/context/language"
import { useLayout } from "@/context/layout"
@@ -19,6 +20,7 @@ import { showToast } from "@opencode-ai/ui/toast"
import { findLast } from "@opencode-ai/util/array"
import { extractPromptFromParts } from "@/utils/prompt"
import { UserMessage } from "@opencode-ai/sdk/v2"
+import { useSessionLayout } from "@/pages/session/session-layout"
export type SessionCommandContext = {
navigateMessageByOffset: (offset: number) => void
@@ -45,12 +47,9 @@ export const useSessionCommands = (actions: SessionCommandContext) => {
const sync = useSync()
const terminal = useTerminal()
const layout = useLayout()
- const params = useParams()
const navigate = useNavigate()
+ const { params, tabs, view } = useSessionLayout()
- const sessionKey = createMemo(() => `${params.dir}${params.id ? "/" + params.id : ""}`)
- const tabs = createMemo(() => layout.tabs(sessionKey))
- const view = createMemo(() => layout.view(sessionKey))
const info = createMemo(() => (params.id ? sync.session.get(params.id) : undefined))
const idle = { type: "idle" as const }
@@ -71,11 +70,7 @@ export const useSessionCommands = (actions: SessionCommandContext) => {
const selectionPreview = (path: string, selection: FileSelection) => {
const content = file.get(path)?.content?.content
if (!content) return undefined
- const start = Math.max(1, Math.min(selection.startLine, selection.endLine))
- const end = Math.max(selection.startLine, selection.endLine)
- const lines = content.split("\n").slice(start - 1, end)
- if (lines.length === 0) return undefined
- return lines.slice(0, 2).join("\n")
+ return previewSelectedLines(content, { start: selection.startLine, end: selection.endLine })
}
const addSelectionToContext = (path: string, selection: FileSelection) => {