summaryrefslogtreecommitdiffhomepage
path: root/packages/app/src/pages
diff options
context:
space:
mode:
authorAdam <[email protected]>2026-02-17 07:16:23 -0600
committerGitHub <[email protected]>2026-02-17 07:16:23 -0600
commit10985671ad9553e7ac594ede30981166f69ba3c5 (patch)
tree0f501ccba5dcf90e86beb18a0732826f7a3b1d0a /packages/app/src/pages
parent3dfbb7059345350fdcb3f45fe9a44697c08a040a (diff)
downloadopencode-10985671ad9553e7ac594ede30981166f69ba3c5.tar.gz
opencode-10985671ad9553e7ac594ede30981166f69ba3c5.zip
feat(app): session timeline/turn rework (#13196)
Co-authored-by: David Hill <[email protected]>
Diffstat (limited to 'packages/app/src/pages')
-rw-r--r--packages/app/src/pages/directory-layout.tsx1
-rw-r--r--packages/app/src/pages/layout.tsx8
-rw-r--r--packages/app/src/pages/layout/sidebar-shell.tsx4
-rw-r--r--packages/app/src/pages/session.tsx81
-rw-r--r--packages/app/src/pages/session/message-timeline.tsx54
-rw-r--r--packages/app/src/pages/session/session-prompt-dock.tsx138
-rw-r--r--packages/app/src/pages/session/use-session-commands.tsx368
7 files changed, 367 insertions, 287 deletions
diff --git a/packages/app/src/pages/directory-layout.tsx b/packages/app/src/pages/directory-layout.tsx
index 2dee09dfb..4f1d93ab2 100644
--- a/packages/app/src/pages/directory-layout.tsx
+++ b/packages/app/src/pages/directory-layout.tsx
@@ -30,7 +30,6 @@ function DirectoryDataProvider(props: ParentProps<{ directory: string }>) {
onQuestionReject={(input: { requestID: string }) => sdk.client.question.reject(input)}
onNavigateToSession={(sessionID: string) => navigate(`/${params.dir}/session/${sessionID}`)}
onSessionHref={(sessionID: string) => `/${params.dir}/session/${sessionID}`}
- onSyncSession={(sessionID: string) => sync.session.sync(sessionID)}
>
<LocalProvider>{props.children}</LocalProvider>
</DataProvider>
diff --git a/packages/app/src/pages/layout.tsx b/packages/app/src/pages/layout.tsx
index 7d4a5c0cb..cecb52617 100644
--- a/packages/app/src/pages/layout.tsx
+++ b/packages/app/src/pages/layout.tsx
@@ -1710,7 +1710,7 @@ export default function Layout(props: ParentProps) {
return (
<div
classList={{
- "flex flex-col min-h-0 bg-background-stronger border border-b-0 border-border-weak-base rounded-tl-sm": true,
+ "flex flex-col min-h-0 bg-background-stronger border border-b-0 border-border-weak-base rounded-tl-[12px]": true,
"flex-1 min-w-0": panelProps.mobile,
}}
style={{ width: panelProps.mobile ? undefined : `${Math.max(layout.sidebar.width() - 64, 0)}px` }}
@@ -1725,8 +1725,8 @@ export default function Layout(props: ParentProps) {
id={`project:${projectId()}`}
value={projectName}
onSave={(next) => renameProject(p(), next)}
- class="text-16-medium text-text-strong truncate"
- displayClass="text-16-medium text-text-strong truncate"
+ class="text-14-medium text-text-strong truncate"
+ displayClass="text-14-medium text-text-strong truncate"
stopPropagation
/>
@@ -2042,7 +2042,7 @@ export default function Layout(props: ParentProps) {
<main
classList={{
"size-full overflow-x-hidden flex flex-col items-start contain-strict border-t border-border-weak-base": true,
- "xl:border-l xl:rounded-tl-sm": !layout.sidebar.opened(),
+ "xl:border-l xl:rounded-tl-[12px]": !layout.sidebar.opened(),
}}
>
<Show when={!autoselecting()} fallback={<div class="size-full" />}>
diff --git a/packages/app/src/pages/layout/sidebar-shell.tsx b/packages/app/src/pages/layout/sidebar-shell.tsx
index 23abdf157..d813ef3e1 100644
--- a/packages/app/src/pages/layout/sidebar-shell.tsx
+++ b/packages/app/src/pages/layout/sidebar-shell.tsx
@@ -51,7 +51,7 @@ export const SidebarContent = (props: {
>
<DragDropSensors />
<ConstrainDragXAxis />
- <div class="h-full w-full flex flex-col items-center gap-3 px-3 py-2 overflow-y-auto no-scrollbar">
+ <div class="h-full w-full flex flex-col items-center gap-3 px-3 py-3 overflow-y-auto no-scrollbar">
<SortableProvider ids={props.projects().map((p) => p.worktree)}>
<For each={props.projects()}>{(project) => props.renderProject(project)}</For>
</SortableProvider>
@@ -78,7 +78,7 @@ export const SidebarContent = (props: {
<DragOverlay>{props.renderProjectOverlay()}</DragOverlay>
</DragDropProvider>
</div>
- <div class="shrink-0 w-full pt-3 pb-3 flex flex-col items-center gap-2">
+ <div class="shrink-0 w-full pt-3 pb-6 flex flex-col items-center gap-2">
<TooltipKeybind placement={placement()} title={props.settingsLabel()} keybind={props.settingsKeybind() ?? ""}>
<IconButton
icon="settings-gear"
diff --git a/packages/app/src/pages/session.tsx b/packages/app/src/pages/session.tsx
index d958990c2..23dc0304e 100644
--- a/packages/app/src/pages/session.tsx
+++ b/packages/app/src/pages/session.tsx
@@ -5,7 +5,6 @@ import { Dynamic } from "solid-js/web"
import { useLocal } from "@/context/local"
import { selectionFromLines, useFile, type FileSelection, type SelectedLineRange } from "@/context/file"
import { createStore, produce } from "solid-js/store"
-import { SessionContextUsage } from "@/components/session-context-usage"
import { IconButton } from "@opencode-ai/ui/icon-button"
import { Button } from "@opencode-ai/ui/button"
import { Tooltip, TooltipKeybind } from "@opencode-ai/ui/tooltip"
@@ -20,9 +19,11 @@ import { Mark } from "@opencode-ai/ui/logo"
import { DragDropProvider, DragDropSensors, DragOverlay, SortableProvider, closestCenter } from "@thisbeyond/solid-dnd"
import type { DragEvent } from "@thisbeyond/solid-dnd"
import { useSync } from "@/context/sync"
+import { useGlobalSync } from "@/context/global-sync"
import { useTerminal, type LocalPTY } from "@/context/terminal"
import { useLayout } from "@/context/layout"
import { checksum, base64Encode } from "@opencode-ai/util/encode"
+import { findLast } from "@opencode-ai/util/array"
import { useDialog } from "@opencode-ai/ui/context/dialog"
import { DialogSelectFile } from "@/components/dialog-select-file"
import FileTree from "@/components/file-tree"
@@ -34,6 +35,7 @@ import { useSDK } from "@/context/sdk"
import { usePrompt } from "@/context/prompt"
import { useComments } from "@/context/comments"
import { ConstrainDragYAxis, getDraggableId } from "@/utils/solid-dnd"
+import { usePermission } from "@/context/permission"
import { showToast } from "@opencode-ai/ui/toast"
import { SessionHeader, SessionContextTab, SortableTab, FileVisual, NewSessionView } from "@/components/session"
import { navMark, navParams } from "@/utils/perf"
@@ -89,6 +91,7 @@ export default function Page() {
const local = useLocal()
const file = useFile()
const sync = useSync()
+ const globalSync = useGlobalSync()
const terminal = useTerminal()
const dialog = useDialog()
const codeComponent = useCodeComponent()
@@ -99,6 +102,7 @@ export default function Page() {
const sdk = useSDK()
const prompt = usePrompt()
const comments = useComments()
+ const permission = usePermission()
const permRequest = createMemo(() => {
const sessionID = params.id
@@ -229,7 +233,7 @@ export default function Page() {
})
}
- const isDesktop = createMediaQuery("(min-width: 1024px)")
+ const isDesktop = createMediaQuery("(min-width: 768px)")
const desktopReviewOpen = createMemo(() => isDesktop() && view().reviewPanel.opened())
const desktopFileTreeOpen = createMemo(() => isDesktop() && layout.fileTree.opened())
const desktopSidePanelOpen = createMemo(() => desktopReviewOpen() || desktopFileTreeOpen())
@@ -269,7 +273,6 @@ export default function Page() {
if (!path) return
file.load(path)
openReviewPanel()
- tabs().setActive(next)
}
createEffect(() => {
@@ -554,13 +557,11 @@ export default function Page() {
const [store, setStore] = createStore({
activeDraggable: undefined as string | undefined,
activeTerminalDraggable: undefined as string | undefined,
- expanded: {} as Record<string, boolean>,
messageId: undefined as string | undefined,
turnStart: 0,
mobileTab: "session" as "session" | "changes",
changes: "session" as "session" | "turn",
newSessionWorktree: "main",
- promptHeight: 0,
})
const turnDiffs = createMemo(() => lastUserMessage()?.summary?.diffs ?? [])
@@ -651,6 +652,7 @@ export default function Page() {
const idle = { type: "idle" as const }
let inputRef!: HTMLDivElement
let promptDock: HTMLDivElement | undefined
+ let dockHeight = 0
let scroller: HTMLDivElement | undefined
let content: HTMLDivElement | undefined
@@ -673,7 +675,8 @@ export default function Page() {
sdk.directory
const id = params.id
if (!id) return
- sync.session.sync(id)
+ void sync.session.sync(id)
+ void sync.session.todo(id)
})
createEffect(() => {
@@ -726,13 +729,17 @@ export default function Page() {
)
const status = createMemo(() => sync.data.session_status[params.id ?? ""] ?? idle)
+ const todos = createMemo(() => {
+ const id = params.id
+ if (!id) return []
+ return globalSync.data.session_todo[id] ?? []
+ })
createEffect(
on(
sessionKey,
() => {
setStore("messageId", undefined)
- setStore("expanded", {})
setStore("changes", "session")
setUi("autoCreated", false)
},
@@ -751,12 +758,6 @@ export default function Page() {
),
)
- createEffect(() => {
- const id = lastUserMessage()?.id
- if (!id) return
- setStore("expanded", id, status().type !== "idle")
- })
-
const selectionPreview = (path: string, selection: FileSelection) => {
const content = file.get(path)?.content?.content
if (!content) return undefined
@@ -767,6 +768,11 @@ export default function Page() {
return lines.slice(0, 2).join("\n")
}
+ const addSelectionToContext = (path: string, selection: FileSelection) => {
+ const preview = selectionPreview(path, selection)
+ prompt.context.add({ type: "file", path, selection, preview })
+ }
+
const addCommentToContext = (input: {
file: string
selection: SelectedLineRange
@@ -806,8 +812,8 @@ export default function Page() {
return
}
- // Don't autofocus chat if terminal panel is open
- if (view().terminal.opened()) return
+ // Don't autofocus chat if desktop terminal panel is open
+ if (isDesktop() && view().terminal.opened()) return
// Only treat explicit scroll keys as potential "user scroll" gestures.
if (event.key === "PageUp" || event.key === "PageDown" || event.key === "Home" || event.key === "End") {
@@ -905,11 +911,29 @@ export default function Page() {
const focusInput = () => inputRef?.focus()
useSessionCommands({
- activeMessage,
+ command,
+ dialog,
+ file,
+ language,
+ local,
+ permission,
+ prompt,
+ sdk,
+ sync,
+ terminal,
+ layout,
+ params,
+ navigate,
+ tabs,
+ view,
+ info,
+ status,
+ userMessages,
+ visibleUserMessages,
showAllFiles,
navigateMessageByOffset,
- setExpanded: (id, fn) => setStore("expanded", id, fn),
setActiveMessage,
+ addSelectionToContext,
focusInput,
})
@@ -933,7 +957,6 @@ export default function Page() {
onSelect={(option) => option && setStore("changes", option)}
variant="ghost"
size="large"
- triggerStyle={{ "font-size": "var(--font-size-large)" }}
/>
)
@@ -1421,12 +1444,12 @@ export default function Page() {
({ height }) => {
const next = Math.ceil(height)
- if (next === store.promptHeight) return
+ if (next === dockHeight) return
const el = scroller
const stick = el ? el.scrollHeight - el.clientHeight - el.scrollTop < 10 : false
- setStore("promptHeight", next)
+ dockHeight = next
if (stick && el) {
requestAnimationFrame(() => {
@@ -1524,13 +1547,7 @@ export default function Page() {
return (
<div class="relative bg-background-base size-full overflow-hidden flex flex-col">
<SessionHeader />
- <div
- class="flex-1 min-h-0 flex"
- classList={{
- "flex-col": !isDesktop(),
- "flex-row": isDesktop(),
- }}
- >
+ <div class="flex-1 min-h-0 flex flex-col md:flex-row">
<SessionMobileTabs
open={!isDesktop() && !!params.id}
mobileTab={store.mobileTab}
@@ -1545,12 +1562,11 @@ export default function Page() {
<div
classList={{
"@container relative shrink-0 flex flex-col min-h-0 h-full bg-background-stronger": true,
- "flex-1 pt-2 md:pt-3": true,
+ "flex-1": true,
"md:flex-none": desktopSidePanelOpen(),
}}
style={{
width: sessionPanelWidth(),
- "--prompt-height": store.promptHeight ? `${store.promptHeight}px` : undefined,
}}
>
<div class="flex-1 min-h-0 overflow-hidden">
@@ -1562,7 +1578,7 @@ export default function Page() {
mobileFallback={reviewContent({
diffStyle: "unified",
classes: {
- root: "pb-[calc(var(--prompt-height,8rem)+32px)]",
+ root: "pb-8",
header: "px-4",
container: "px-4",
},
@@ -1627,8 +1643,6 @@ export default function Page() {
navMark({ dir: params.dir, to: id, name: "session:first-turn-mounted" })
}}
lastUserMessageID={lastUserMessage()?.id}
- expanded={store.expanded}
- onToggleExpanded={(id) => setStore("expanded", id, (open: boolean | undefined) => !open)}
/>
</Show>
</Match>
@@ -1659,6 +1673,7 @@ export default function Page() {
questionRequest={questionRequest}
permissionRequest={permRequest}
blocked={blocked()}
+ todos={todos()}
promptReady={prompt.ready()}
handoffPrompt={handoff.session.get(sessionKey())?.prompt}
t={language.t as (key: string, vars?: Record<string, string | number | boolean>) => string}
@@ -1731,7 +1746,7 @@ export default function Page() {
</div>
<TerminalPanel
- open={view().terminal.opened()}
+ open={isDesktop() && view().terminal.opened()}
height={layout.terminal.height()}
resize={layout.terminal.resize}
close={view().terminal.close}
diff --git a/packages/app/src/pages/session/message-timeline.tsx b/packages/app/src/pages/session/message-timeline.tsx
index d5f04ccf9..53cf441d6 100644
--- a/packages/app/src/pages/session/message-timeline.tsx
+++ b/packages/app/src/pages/session/message-timeline.tsx
@@ -4,10 +4,10 @@ import { Icon } from "@opencode-ai/ui/icon"
import { IconButton } from "@opencode-ai/ui/icon-button"
import { DropdownMenu } from "@opencode-ai/ui/dropdown-menu"
import { InlineInput } from "@opencode-ai/ui/inline-input"
-import { Tooltip } from "@opencode-ai/ui/tooltip"
import { SessionTurn } from "@opencode-ai/ui/session-turn"
import type { UserMessage } from "@opencode-ai/sdk/v2"
import { shouldMarkBoundaryGesture, normalizeWheelDelta } from "@/pages/session/message-gesture"
+import { SessionContextUsage } from "@/components/session-context-usage"
const boundaryTarget = (root: HTMLElement, target: EventTarget | null) => {
const current = target instanceof Element ? target : undefined
@@ -88,8 +88,6 @@ export function MessageTimeline(props: {
onUnregisterMessage: (id: string) => void
onFirstTurnMount?: () => void
lastUserMessageID?: string
- expanded: Record<string, boolean>
- onToggleExpanded: (id: string) => void
}) {
let touchGesture: number | undefined
@@ -100,7 +98,7 @@ export function MessageTimeline(props: {
>
<div class="relative w-full h-full min-w-0">
<div
- class="absolute left-1/2 -translate-x-1/2 bottom-[calc(var(--prompt-height,8rem)+32px)] z-[60] pointer-events-none transition-all duration-200 ease-out"
+ class="absolute left-1/2 -translate-x-1/2 bottom-6 z-[60] pointer-events-none transition-all duration-200 ease-out"
classList={{
"opacity-100 translate-y-0 scale-100": props.scroll.overflow && !props.scroll.bottom,
"opacity-0 translate-y-2 scale-95 pointer-events-none": !props.scroll.overflow || props.scroll.bottom,
@@ -164,14 +162,15 @@ export function MessageTimeline(props: {
<Show when={props.showHeader}>
<div
classList={{
- "sticky top-0 z-30 bg-background-stronger": true,
+ "sticky top-0 z-30 bg-[linear-gradient(to_bottom,var(--background-stronger)_48px,transparent)]": true,
"w-full": true,
- "px-4 md:px-6": true,
+ "pb-4": true,
+ "pl-2 pr-4 md:pl-4 md:pr-6": true,
"md:max-w-200 md:mx-auto 2xl:max-w-[1000px]": props.centered,
}}
>
- <div class="h-10 w-full flex items-center justify-between gap-2">
- <div class="flex items-center gap-1 min-w-0 flex-1">
+ <div class="h-12 w-full flex items-center justify-between gap-2">
+ <div class="flex items-center gap-1 min-w-0 flex-1 pr-3">
<Show when={props.parentID}>
<IconButton
tabIndex={-1}
@@ -185,7 +184,10 @@ export function MessageTimeline(props: {
<Show
when={props.titleState.editing}
fallback={
- <h1 class="text-16-medium text-text-strong truncate min-w-0" onDblClick={props.openTitleEditor}>
+ <h1
+ class="text-14-medium text-text-strong truncate grow-1 min-w-0 pl-2"
+ onDblClick={props.openTitleEditor}
+ >
{props.title}
</h1>
}
@@ -194,7 +196,8 @@ export function MessageTimeline(props: {
ref={props.titleRef}
value={props.titleState.draft}
disabled={props.titleState.saving}
- class="text-16-medium text-text-strong grow-1 min-w-0"
+ class="text-14-medium text-text-strong grow-1 min-w-0 pl-2 rounded-[6px]"
+ style={{ "--inline-input-shadow": "var(--shadow-xs-border-select)" }}
onInput={(event) => props.onTitleDraft(event.currentTarget.value)}
onKeyDown={(event) => {
event.stopPropagation()
@@ -215,19 +218,24 @@ export function MessageTimeline(props: {
</div>
<Show when={props.sessionID}>
{(id) => (
- <div class="shrink-0 flex items-center">
- <DropdownMenu open={props.titleState.menuOpen} onOpenChange={props.onTitleMenuOpen}>
- <Tooltip value={props.t("common.moreOptions")} placement="top">
- <DropdownMenu.Trigger
- as={IconButton}
- icon="dot-grid"
- variant="ghost"
- class="size-6 rounded-md data-[expanded]:bg-surface-base-active"
- aria-label={props.t("common.moreOptions")}
- />
- </Tooltip>
+ <div class="shrink-0 flex items-center gap-3">
+ <SessionContextUsage placement="bottom" />
+ <DropdownMenu
+ gutter={4}
+ placement="bottom-end"
+ open={props.titleState.menuOpen}
+ onOpenChange={props.onTitleMenuOpen}
+ >
+ <DropdownMenu.Trigger
+ as={IconButton}
+ icon="dot-grid"
+ variant="ghost"
+ class="size-6 rounded-md data-[expanded]:bg-surface-base-active"
+ aria-label={props.t("common.moreOptions")}
+ />
<DropdownMenu.Portal>
<DropdownMenu.Content
+ style={{ "min-width": "104px" }}
onCloseAutoFocus={(event) => {
if (!props.titleState.pendingRename) return
event.preventDefault()
@@ -263,7 +271,7 @@ export function MessageTimeline(props: {
<div
ref={props.setContentRef}
role="log"
- class="flex flex-col gap-12 items-start justify-start pb-[calc(var(--prompt-height,8rem)+64px)] md:pb-[calc(var(--prompt-height,10rem)+64px)] transition-[margin]"
+ class="flex flex-col gap-12 items-start justify-start pb-16 transition-[margin]"
classList={{
"w-full": true,
"md:max-w-200 md:mx-auto 2xl:max-w-[1000px]": props.centered,
@@ -316,8 +324,6 @@ export function MessageTimeline(props: {
sessionID={props.sessionID}
messageID={message.id}
lastUserMessageID={props.lastUserMessageID}
- stepsExpanded={props.expanded[message.id] ?? false}
- onStepsExpandedToggle={() => props.onToggleExpanded(message.id)}
classes={{
root: "min-w-0 w-full relative",
content: "flex flex-col justify-between !overflow-visible",
diff --git a/packages/app/src/pages/session/session-prompt-dock.tsx b/packages/app/src/pages/session/session-prompt-dock.tsx
index 8ec4f3b9f..2b9571982 100644
--- a/packages/app/src/pages/session/session-prompt-dock.tsx
+++ b/packages/app/src/pages/session/session-prompt-dock.tsx
@@ -1,16 +1,17 @@
-import { For, Show } from "solid-js"
-import type { QuestionRequest } from "@opencode-ai/sdk/v2"
+import { For, Show, createEffect, createMemo, createSignal, on, onCleanup } from "solid-js"
+import type { QuestionRequest, Todo } from "@opencode-ai/sdk/v2"
import { Button } from "@opencode-ai/ui/button"
import { BasicTool } from "@opencode-ai/ui/basic-tool"
import { PromptInput } from "@/components/prompt-input"
import { QuestionDock } from "@/components/question-dock"
-import { questionSubtitle } from "@/pages/session/session-prompt-helpers"
+import { SessionTodoDock } from "@/components/session-todo-dock"
export function SessionPromptDock(props: {
centered: boolean
questionRequest: () => QuestionRequest | undefined
permissionRequest: () => { patterns: string[]; permission: string } | undefined
blocked: boolean
+ todos: Todo[]
promptReady: boolean
handoffPrompt?: string
t: (key: string, vars?: Record<string, string | number | boolean>) => string
@@ -22,10 +23,88 @@ export function SessionPromptDock(props: {
onSubmit: () => void
setPromptDockRef: (el: HTMLDivElement) => void
}) {
+ const done = createMemo(
+ () =>
+ props.todos.length > 0 && props.todos.every((todo) => todo.status === "completed" || todo.status === "cancelled"),
+ )
+
+ const [dock, setDock] = createSignal(props.todos.length > 0)
+ const [closing, setClosing] = createSignal(false)
+ const [opening, setOpening] = createSignal(false)
+ let timer: number | undefined
+ let raf: number | undefined
+
+ const scheduleClose = () => {
+ if (timer) window.clearTimeout(timer)
+ timer = window.setTimeout(() => {
+ setDock(false)
+ setClosing(false)
+ timer = undefined
+ }, 400)
+ }
+
+ createEffect(
+ on(
+ () => [props.todos.length, done()] as const,
+ ([count, complete], prev) => {
+ if (raf) cancelAnimationFrame(raf)
+ raf = undefined
+
+ if (count === 0) {
+ if (timer) window.clearTimeout(timer)
+ timer = undefined
+ setDock(false)
+ setClosing(false)
+ setOpening(false)
+ return
+ }
+
+ if (!complete) {
+ if (timer) window.clearTimeout(timer)
+ timer = undefined
+ const wasHidden = !dock() || closing()
+ setDock(true)
+ setClosing(false)
+ if (wasHidden) {
+ setOpening(true)
+ raf = requestAnimationFrame(() => {
+ setOpening(false)
+ raf = undefined
+ })
+ return
+ }
+ setOpening(false)
+ return
+ }
+
+ if (prev && prev[1]) {
+ if (closing() && !timer) scheduleClose()
+ return
+ }
+
+ setDock(true)
+ setOpening(false)
+ setClosing(true)
+ scheduleClose()
+ },
+ ),
+ )
+
+ onCleanup(() => {
+ if (!timer) return
+ window.clearTimeout(timer)
+ })
+
+ onCleanup(() => {
+ if (!raf) return
+ cancelAnimationFrame(raf)
+ })
+
return (
<div
ref={props.setPromptDockRef}
- class="absolute inset-x-0 bottom-0 pt-12 pb-4 flex flex-col justify-center items-center z-50 bg-gradient-to-t from-background-stronger via-background-stronger to-transparent pointer-events-none"
+ data-component="session-prompt-dock"
+ class="shrink-0 w-full pb-4 flex flex-col justify-center items-center bg-background-stronger pointer-events-none"
>
<div
classList={{
@@ -35,18 +114,8 @@ export function SessionPromptDock(props: {
>
<Show when={props.questionRequest()} keyed>
{(req) => {
- const subtitle = questionSubtitle(req.questions.length, (key) => props.t(key))
return (
- <div data-component="tool-part-wrapper" data-question="true" class="mb-3">
- <BasicTool
- icon="bubble-5"
- locked
- defaultOpen
- trigger={{
- title: props.t("ui.tool.questions"),
- subtitle,
- }}
- />
+ <div>
<QuestionDock request={req} />
</div>
)
@@ -122,12 +191,39 @@ export function SessionPromptDock(props: {
</div>
}
>
- <PromptInput
- ref={props.inputRef}
- newSessionWorktree={props.newSessionWorktree}
- onNewSessionWorktreeReset={props.onNewSessionWorktreeReset}
- onSubmit={props.onSubmit}
- />
+ <Show when={dock()}>
+ <div
+ classList={{
+ "transition-[max-height,opacity,transform] duration-[400ms] ease-out overflow-hidden": true,
+ "max-h-[320px]": !closing(),
+ "max-h-0 pointer-events-none": closing(),
+ "opacity-0 translate-y-9": closing() || opening(),
+ "opacity-100 translate-y-0": !closing() && !opening(),
+ }}
+ >
+ <SessionTodoDock
+ todos={props.todos}
+ title={props.t("session.todo.title")}
+ collapseLabel={props.t("session.todo.collapse")}
+ expandLabel={props.t("session.todo.expand")}
+ />
+ </div>
+ </Show>
+ <div
+ classList={{
+ "relative z-10": true,
+ "transition-[margin] duration-[400ms] ease-out": true,
+ "-mt-9": dock() && !closing(),
+ "mt-0": !dock() || closing(),
+ }}
+ >
+ <PromptInput
+ ref={props.inputRef}
+ newSessionWorktree={props.newSessionWorktree}
+ onNewSessionWorktreeReset={props.onNewSessionWorktreeReset}
+ onSubmit={props.onSubmit}
+ />
+ </div>
</Show>
</Show>
</div>
diff --git a/packages/app/src/pages/session/use-session-commands.tsx b/packages/app/src/pages/session/use-session-commands.tsx
index d2f74288f..4d68afd66 100644
--- a/packages/app/src/pages/session/use-session-commands.tsx
+++ b/packages/app/src/pages/session/use-session-commands.tsx
@@ -22,11 +22,29 @@ import { UserMessage } from "@opencode-ai/sdk/v2"
import { canAddSelectionContext } from "@/pages/session/session-command-helpers"
export type SessionCommandContext = {
- activeMessage: () => UserMessage | undefined
+ command: ReturnType<typeof useCommand>
+ dialog: ReturnType<typeof useDialog>
+ file: ReturnType<typeof useFile>
+ language: ReturnType<typeof useLanguage>
+ local: ReturnType<typeof useLocal>
+ permission: ReturnType<typeof usePermission>
+ prompt: ReturnType<typeof usePrompt>
+ sdk: ReturnType<typeof useSDK>
+ sync: ReturnType<typeof useSync>
+ terminal: ReturnType<typeof useTerminal>
+ layout: ReturnType<typeof useLayout>
+ params: ReturnType<typeof useParams>
+ navigate: ReturnType<typeof useNavigate>
+ tabs: () => ReturnType<ReturnType<typeof useLayout>["tabs"]>
+ view: () => ReturnType<ReturnType<typeof useLayout>["view"]>
+ info: () => { revert?: { messageID?: string }; share?: { url?: string } } | undefined
+ status: () => { type: string }
+ userMessages: () => UserMessage[]
+ visibleUserMessages: () => UserMessage[]
showAllFiles: () => void
navigateMessageByOffset: (offset: number) => void
- setExpanded: (id: string, fn: (open: boolean | undefined) => boolean) => void
setActiveMessage: (message: UserMessage | undefined) => void
+ addSelectionToContext: (path: string, selection: FileSelection) => void
focusInput: () => void
}
@@ -37,88 +55,45 @@ const withCategory = (category: string) => {
})
}
-export const useSessionCommands = (args: SessionCommandContext) => {
- const command = useCommand()
- const dialog = useDialog()
- const file = useFile()
- const language = useLanguage()
- const local = useLocal()
- const permission = usePermission()
- const prompt = usePrompt()
- const sdk = useSDK()
- const sync = useSync()
- const terminal = useTerminal()
- const layout = useLayout()
- const params = useParams()
- const navigate = useNavigate()
-
- 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 }
- const status = createMemo(() => sync.data.session_status[params.id ?? ""] ?? idle)
- const messages = createMemo(() => (params.id ? (sync.data.message[params.id] ?? []) : []))
- const userMessages = createMemo(() => messages().filter((m) => m.role === "user") as UserMessage[])
- const visibleUserMessages = createMemo(() => {
- const revert = info()?.revert?.messageID
- if (!revert) return userMessages()
- return userMessages().filter((m) => m.id < revert)
- })
-
- 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")
- }
-
- const addSelectionToContext = (path: string, selection: FileSelection) => {
- const preview = selectionPreview(path, selection)
- prompt.context.add({ type: "file", path, selection, preview })
- }
-
- const sessionCommand = withCategory(language.t("command.category.session"))
- const fileCommand = withCategory(language.t("command.category.file"))
- const contextCommand = withCategory(language.t("command.category.context"))
- const viewCommand = withCategory(language.t("command.category.view"))
- const terminalCommand = withCategory(language.t("command.category.terminal"))
- const modelCommand = withCategory(language.t("command.category.model"))
- const mcpCommand = withCategory(language.t("command.category.mcp"))
- const agentCommand = withCategory(language.t("command.category.agent"))
- const permissionsCommand = withCategory(language.t("command.category.permissions"))
+export const useSessionCommands = (input: SessionCommandContext) => {
+ const sessionCommand = withCategory(input.language.t("command.category.session"))
+ const fileCommand = withCategory(input.language.t("command.category.file"))
+ const contextCommand = withCategory(input.language.t("command.category.context"))
+ const viewCommand = withCategory(input.language.t("command.category.view"))
+ const terminalCommand = withCategory(input.language.t("command.category.terminal"))
+ const modelCommand = withCategory(input.language.t("command.category.model"))
+ const mcpCommand = withCategory(input.language.t("command.category.mcp"))
+ const agentCommand = withCategory(input.language.t("command.category.agent"))
+ const permissionsCommand = withCategory(input.language.t("command.category.permissions"))
const sessionCommands = createMemo(() => [
sessionCommand({
id: "session.new",
- title: language.t("command.session.new"),
+ title: input.language.t("command.session.new"),
keybind: "mod+shift+s",
slash: "new",
- onSelect: () => navigate(`/${params.dir}/session`),
+ onSelect: () => input.navigate(`/${input.params.dir}/session`),
}),
])
const fileCommands = createMemo(() => [
fileCommand({
id: "file.open",
- title: language.t("command.file.open"),
- description: language.t("palette.search.placeholder"),
+ title: input.language.t("command.file.open"),
+ description: input.language.t("palette.search.placeholder"),
keybind: "mod+p",
slash: "open",
- onSelect: () => dialog.show(() => <DialogSelectFile onOpenFile={args.showAllFiles} />),
+ onSelect: () => input.dialog.show(() => <DialogSelectFile onOpenFile={input.showAllFiles} />),
}),
fileCommand({
id: "tab.close",
- title: language.t("command.tab.close"),
+ title: input.language.t("command.tab.close"),
keybind: "mod+w",
- disabled: !tabs().active(),
+ disabled: !input.tabs().active(),
onSelect: () => {
- const active = tabs().active()
+ const active = input.tabs().active()
if (!active) return
- tabs().close(active)
+ input.tabs().close(active)
},
}),
])
@@ -126,30 +101,30 @@ export const useSessionCommands = (args: SessionCommandContext) => {
const contextCommands = createMemo(() => [
contextCommand({
id: "context.addSelection",
- title: language.t("command.context.addSelection"),
- description: language.t("command.context.addSelection.description"),
+ title: input.language.t("command.context.addSelection"),
+ description: input.language.t("command.context.addSelection.description"),
keybind: "mod+shift+l",
disabled: !canAddSelectionContext({
- active: tabs().active(),
- pathFromTab: file.pathFromTab,
- selectedLines: file.selectedLines,
+ active: input.tabs().active(),
+ pathFromTab: input.file.pathFromTab,
+ selectedLines: input.file.selectedLines,
}),
onSelect: () => {
- const active = tabs().active()
+ const active = input.tabs().active()
if (!active) return
- const path = file.pathFromTab(active)
+ const path = input.file.pathFromTab(active)
if (!path) return
- const range = file.selectedLines(path) as SelectedLineRange | null | undefined
+ const range = input.file.selectedLines(path) as SelectedLineRange | null | undefined
if (!range) {
showToast({
- title: language.t("toast.context.noLineSelection.title"),
- description: language.t("toast.context.noLineSelection.description"),
+ title: input.language.t("toast.context.noLineSelection.title"),
+ description: input.language.t("toast.context.noLineSelection.description"),
})
return
}
- addSelectionToContext(path, selectionFromLines(range))
+ input.addSelectionToContext(path, selectionFromLines(range))
},
}),
])
@@ -157,50 +132,37 @@ export const useSessionCommands = (args: SessionCommandContext) => {
const viewCommands = createMemo(() => [
viewCommand({
id: "terminal.toggle",
- title: language.t("command.terminal.toggle"),
+ title: input.language.t("command.terminal.toggle"),
keybind: "ctrl+`",
slash: "terminal",
- onSelect: () => view().terminal.toggle(),
+ onSelect: () => input.view().terminal.toggle(),
}),
viewCommand({
id: "review.toggle",
- title: language.t("command.review.toggle"),
+ title: input.language.t("command.review.toggle"),
keybind: "mod+shift+r",
- onSelect: () => view().reviewPanel.toggle(),
+ onSelect: () => input.view().reviewPanel.toggle(),
}),
viewCommand({
id: "fileTree.toggle",
- title: language.t("command.fileTree.toggle"),
+ title: input.language.t("command.fileTree.toggle"),
keybind: "mod+\\",
- onSelect: () => layout.fileTree.toggle(),
+ onSelect: () => input.layout.fileTree.toggle(),
}),
viewCommand({
id: "input.focus",
- title: language.t("command.input.focus"),
+ title: input.language.t("command.input.focus"),
keybind: "ctrl+l",
- onSelect: () => args.focusInput(),
+ onSelect: () => input.focusInput(),
}),
terminalCommand({
id: "terminal.new",
- title: language.t("command.terminal.new"),
- description: language.t("command.terminal.new.description"),
+ title: input.language.t("command.terminal.new"),
+ description: input.language.t("command.terminal.new.description"),
keybind: "ctrl+alt+t",
onSelect: () => {
- if (terminal.all().length > 0) terminal.new()
- view().terminal.open()
- },
- }),
- viewCommand({
- id: "steps.toggle",
- title: language.t("command.steps.toggle"),
- description: language.t("command.steps.toggle.description"),
- keybind: "mod+e",
- slash: "steps",
- disabled: !params.id,
- onSelect: () => {
- const msg = args.activeMessage()
- if (!msg) return
- args.setExpanded(msg.id, (open: boolean | undefined) => !open)
+ if (input.terminal.all().length > 0) input.terminal.new()
+ input.view().terminal.open()
},
}),
])
@@ -208,61 +170,61 @@ export const useSessionCommands = (args: SessionCommandContext) => {
const messageCommands = createMemo(() => [
sessionCommand({
id: "message.previous",
- title: language.t("command.message.previous"),
- description: language.t("command.message.previous.description"),
+ title: input.language.t("command.message.previous"),
+ description: input.language.t("command.message.previous.description"),
keybind: "mod+arrowup",
- disabled: !params.id,
- onSelect: () => args.navigateMessageByOffset(-1),
+ disabled: !input.params.id,
+ onSelect: () => input.navigateMessageByOffset(-1),
}),
sessionCommand({
id: "message.next",
- title: language.t("command.message.next"),
- description: language.t("command.message.next.description"),
+ title: input.language.t("command.message.next"),
+ description: input.language.t("command.message.next.description"),
keybind: "mod+arrowdown",
- disabled: !params.id,
- onSelect: () => args.navigateMessageByOffset(1),
+ disabled: !input.params.id,
+ onSelect: () => input.navigateMessageByOffset(1),
}),
])
const agentCommands = createMemo(() => [
modelCommand({
id: "model.choose",
- title: language.t("command.model.choose"),
- description: language.t("command.model.choose.description"),
+ title: input.language.t("command.model.choose"),
+ description: input.language.t("command.model.choose.description"),
keybind: "mod+'",
slash: "model",
- onSelect: () => dialog.show(() => <DialogSelectModel />),
+ onSelect: () => input.dialog.show(() => <DialogSelectModel />),
}),
mcpCommand({
id: "mcp.toggle",
- title: language.t("command.mcp.toggle"),
- description: language.t("command.mcp.toggle.description"),
+ title: input.language.t("command.mcp.toggle"),
+ description: input.language.t("command.mcp.toggle.description"),
keybind: "mod+;",
slash: "mcp",
- onSelect: () => dialog.show(() => <DialogSelectMcp />),
+ onSelect: () => input.dialog.show(() => <DialogSelectMcp />),
}),
agentCommand({
id: "agent.cycle",
- title: language.t("command.agent.cycle"),
- description: language.t("command.agent.cycle.description"),
+ title: input.language.t("command.agent.cycle"),
+ description: input.language.t("command.agent.cycle.description"),
keybind: "mod+.",
slash: "agent",
- onSelect: () => local.agent.move(1),
+ onSelect: () => input.local.agent.move(1),
}),
agentCommand({
id: "agent.cycle.reverse",
- title: language.t("command.agent.cycle.reverse"),
- description: language.t("command.agent.cycle.reverse.description"),
+ title: input.language.t("command.agent.cycle.reverse"),
+ description: input.language.t("command.agent.cycle.reverse.description"),
keybind: "shift+mod+.",
- onSelect: () => local.agent.move(-1),
+ onSelect: () => input.local.agent.move(-1),
}),
modelCommand({
id: "model.variant.cycle",
- title: language.t("command.model.variant.cycle"),
- description: language.t("command.model.variant.cycle.description"),
+ title: input.language.t("command.model.variant.cycle"),
+ description: input.language.t("command.model.variant.cycle.description"),
keybind: "shift+mod+d",
onSelect: () => {
- local.model.variant.cycle()
+ input.local.model.variant.cycle()
},
}),
])
@@ -271,22 +233,22 @@ export const useSessionCommands = (args: SessionCommandContext) => {
permissionsCommand({
id: "permissions.autoaccept",
title:
- params.id && permission.isAutoAccepting(params.id, sdk.directory)
- ? language.t("command.permissions.autoaccept.disable")
- : language.t("command.permissions.autoaccept.enable"),
+ input.params.id && input.permission.isAutoAccepting(input.params.id, input.sdk.directory)
+ ? input.language.t("command.permissions.autoaccept.disable")
+ : input.language.t("command.permissions.autoaccept.enable"),
keybind: "mod+shift+a",
- disabled: !params.id || !permission.permissionsEnabled(),
+ disabled: !input.params.id || !input.permission.permissionsEnabled(),
onSelect: () => {
- const sessionID = params.id
+ const sessionID = input.params.id
if (!sessionID) return
- permission.toggleAutoAccept(sessionID, sdk.directory)
+ input.permission.toggleAutoAccept(sessionID, input.sdk.directory)
showToast({
- title: permission.isAutoAccepting(sessionID, sdk.directory)
- ? language.t("toast.permissions.autoaccept.on.title")
- : language.t("toast.permissions.autoaccept.off.title"),
- description: permission.isAutoAccepting(sessionID, sdk.directory)
- ? language.t("toast.permissions.autoaccept.on.description")
- : language.t("toast.permissions.autoaccept.off.description"),
+ title: input.permission.isAutoAccepting(sessionID, input.sdk.directory)
+ ? input.language.t("toast.permissions.autoaccept.on.title")
+ : input.language.t("toast.permissions.autoaccept.off.title"),
+ description: input.permission.isAutoAccepting(sessionID, input.sdk.directory)
+ ? input.language.t("toast.permissions.autoaccept.on.description")
+ : input.language.t("toast.permissions.autoaccept.off.description"),
})
},
}),
@@ -295,71 +257,71 @@ export const useSessionCommands = (args: SessionCommandContext) => {
const sessionActionCommands = createMemo(() => [
sessionCommand({
id: "session.undo",
- title: language.t("command.session.undo"),
- description: language.t("command.session.undo.description"),
+ title: input.language.t("command.session.undo"),
+ description: input.language.t("command.session.undo.description"),
slash: "undo",
- disabled: !params.id || visibleUserMessages().length === 0,
+ disabled: !input.params.id || input.visibleUserMessages().length === 0,
onSelect: async () => {
- const sessionID = params.id
+ const sessionID = input.params.id
if (!sessionID) return
- if (status()?.type !== "idle") {
- await sdk.client.session.abort({ sessionID }).catch(() => {})
+ if (input.status()?.type !== "idle") {
+ await input.sdk.client.session.abort({ sessionID }).catch(() => {})
}
- const revert = info()?.revert?.messageID
- const message = findLast(userMessages(), (x) => !revert || x.id < revert)
+ const revert = input.info()?.revert?.messageID
+ const message = findLast(input.userMessages(), (x) => !revert || x.id < revert)
if (!message) return
- await sdk.client.session.revert({ sessionID, messageID: message.id })
- const parts = sync.data.part[message.id]
+ await input.sdk.client.session.revert({ sessionID, messageID: message.id })
+ const parts = input.sync.data.part[message.id]
if (parts) {
- const restored = extractPromptFromParts(parts, { directory: sdk.directory })
- prompt.set(restored)
+ const restored = extractPromptFromParts(parts, { directory: input.sdk.directory })
+ input.prompt.set(restored)
}
- const priorMessage = findLast(userMessages(), (x) => x.id < message.id)
- args.setActiveMessage(priorMessage)
+ const priorMessage = findLast(input.userMessages(), (x) => x.id < message.id)
+ input.setActiveMessage(priorMessage)
},
}),
sessionCommand({
id: "session.redo",
- title: language.t("command.session.redo"),
- description: language.t("command.session.redo.description"),
+ title: input.language.t("command.session.redo"),
+ description: input.language.t("command.session.redo.description"),
slash: "redo",
- disabled: !params.id || !info()?.revert?.messageID,
+ disabled: !input.params.id || !input.info()?.revert?.messageID,
onSelect: async () => {
- const sessionID = params.id
+ const sessionID = input.params.id
if (!sessionID) return
- const revertMessageID = info()?.revert?.messageID
+ const revertMessageID = input.info()?.revert?.messageID
if (!revertMessageID) return
- const nextMessage = userMessages().find((x) => x.id > revertMessageID)
+ const nextMessage = input.userMessages().find((x) => x.id > revertMessageID)
if (!nextMessage) {
- await sdk.client.session.unrevert({ sessionID })
- prompt.reset()
- const lastMsg = findLast(userMessages(), (x) => x.id >= revertMessageID)
- args.setActiveMessage(lastMsg)
+ await input.sdk.client.session.unrevert({ sessionID })
+ input.prompt.reset()
+ const lastMsg = findLast(input.userMessages(), (x) => x.id >= revertMessageID)
+ input.setActiveMessage(lastMsg)
return
}
- await sdk.client.session.revert({ sessionID, messageID: nextMessage.id })
- const priorMsg = findLast(userMessages(), (x) => x.id < nextMessage.id)
- args.setActiveMessage(priorMsg)
+ await input.sdk.client.session.revert({ sessionID, messageID: nextMessage.id })
+ const priorMsg = findLast(input.userMessages(), (x) => x.id < nextMessage.id)
+ input.setActiveMessage(priorMsg)
},
}),
sessionCommand({
id: "session.compact",
- title: language.t("command.session.compact"),
- description: language.t("command.session.compact.description"),
+ title: input.language.t("command.session.compact"),
+ description: input.language.t("command.session.compact.description"),
slash: "compact",
- disabled: !params.id || visibleUserMessages().length === 0,
+ disabled: !input.params.id || input.visibleUserMessages().length === 0,
onSelect: async () => {
- const sessionID = params.id
+ const sessionID = input.params.id
if (!sessionID) return
- const model = local.model.current()
+ const model = input.local.model.current()
if (!model) {
showToast({
- title: language.t("toast.model.none.title"),
- description: language.t("toast.model.none.description"),
+ title: input.language.t("toast.model.none.title"),
+ description: input.language.t("toast.model.none.description"),
})
return
}
- await sdk.client.session.summarize({
+ await input.sdk.client.session.summarize({
sessionID,
modelID: model.id,
providerID: model.provider.id,
@@ -368,27 +330,29 @@ export const useSessionCommands = (args: SessionCommandContext) => {
}),
sessionCommand({
id: "session.fork",
- title: language.t("command.session.fork"),
- description: language.t("command.session.fork.description"),
+ title: input.language.t("command.session.fork"),
+ description: input.language.t("command.session.fork.description"),
slash: "fork",
- disabled: !params.id || visibleUserMessages().length === 0,
- onSelect: () => dialog.show(() => <DialogFork />),
+ disabled: !input.params.id || input.visibleUserMessages().length === 0,
+ onSelect: () => input.dialog.show(() => <DialogFork />),
}),
])
const shareCommands = createMemo(() => {
- if (sync.data.config.share === "disabled") return []
+ if (input.sync.data.config.share === "disabled") return []
return [
sessionCommand({
id: "session.share",
- title: info()?.share?.url ? language.t("session.share.copy.copyLink") : language.t("command.session.share"),
- description: info()?.share?.url
- ? language.t("toast.session.share.success.description")
- : language.t("command.session.share.description"),
+ title: input.info()?.share?.url
+ ? input.language.t("session.share.copy.copyLink")
+ : input.language.t("command.session.share"),
+ description: input.info()?.share?.url
+ ? input.language.t("toast.session.share.success.description")
+ : input.language.t("command.session.share.description"),
slash: "share",
- disabled: !params.id,
+ disabled: !input.params.id,
onSelect: async () => {
- if (!params.id) return
+ if (!input.params.id) return
const write = (value: string) => {
const body = typeof document === "undefined" ? undefined : document.body
@@ -418,7 +382,7 @@ export const useSessionCommands = (args: SessionCommandContext) => {
const ok = await write(url)
if (!ok) {
showToast({
- title: language.t("toast.session.share.copyFailed.title"),
+ title: input.language.t("toast.session.share.copyFailed.title"),
variant: "error",
})
return
@@ -426,27 +390,27 @@ export const useSessionCommands = (args: SessionCommandContext) => {
showToast({
title: existing
- ? language.t("session.share.copy.copied")
- : language.t("toast.session.share.success.title"),
- description: language.t("toast.session.share.success.description"),
+ ? input.language.t("session.share.copy.copied")
+ : input.language.t("toast.session.share.success.title"),
+ description: input.language.t("toast.session.share.success.description"),
variant: "success",
})
}
- const existing = info()?.share?.url
+ const existing = input.info()?.share?.url
if (existing) {
await copy(existing, true)
return
}
- const url = await sdk.client.session
- .share({ sessionID: params.id })
+ const url = await input.sdk.client.session
+ .share({ sessionID: input.params.id })
.then((res) => res.data?.share?.url)
.catch(() => undefined)
if (!url) {
showToast({
- title: language.t("toast.session.share.failed.title"),
- description: language.t("toast.session.share.failed.description"),
+ title: input.language.t("toast.session.share.failed.title"),
+ description: input.language.t("toast.session.share.failed.description"),
variant: "error",
})
return
@@ -457,25 +421,25 @@ export const useSessionCommands = (args: SessionCommandContext) => {
}),
sessionCommand({
id: "session.unshare",
- title: language.t("command.session.unshare"),
- description: language.t("command.session.unshare.description"),
+ title: input.language.t("command.session.unshare"),
+ description: input.language.t("command.session.unshare.description"),
slash: "unshare",
- disabled: !params.id || !info()?.share?.url,
+ disabled: !input.params.id || !input.info()?.share?.url,
onSelect: async () => {
- if (!params.id) return
- await sdk.client.session
- .unshare({ sessionID: params.id })
+ if (!input.params.id) return
+ await input.sdk.client.session
+ .unshare({ sessionID: input.params.id })
.then(() =>
showToast({
- title: language.t("toast.session.unshare.success.title"),
- description: language.t("toast.session.unshare.success.description"),
+ title: input.language.t("toast.session.unshare.success.title"),
+ description: input.language.t("toast.session.unshare.success.description"),
variant: "success",
}),
)
.catch(() =>
showToast({
- title: language.t("toast.session.unshare.failed.title"),
- description: language.t("toast.session.unshare.failed.description"),
+ title: input.language.t("toast.session.unshare.failed.title"),
+ description: input.language.t("toast.session.unshare.failed.description"),
variant: "error",
}),
)
@@ -484,7 +448,7 @@ export const useSessionCommands = (args: SessionCommandContext) => {
]
})
- command.register("session", () =>
+ input.command.register("session", () =>
[
sessionCommands(),
fileCommands(),
@@ -495,6 +459,6 @@ export const useSessionCommands = (args: SessionCommandContext) => {
permissionCommands(),
sessionActionCommands(),
shareCommands(),
- ].flatMap((section) => section),
+ ].flatMap((x) => x),
)
}