fix(app): keep session routes within layouts (#35842)
This commit is contained in:
parent
14a5529793
commit
95013d2936
5 changed files with 69 additions and 32 deletions
|
|
@ -9,7 +9,7 @@ import { Font } from "@opencode-ai/ui/font"
|
||||||
import { Splash } from "@opencode-ai/ui/logo"
|
import { Splash } from "@opencode-ai/ui/logo"
|
||||||
import { ThemeProvider } from "@opencode-ai/ui/theme/context"
|
import { ThemeProvider } from "@opencode-ai/ui/theme/context"
|
||||||
import { MetaProvider } from "@solidjs/meta"
|
import { MetaProvider } from "@solidjs/meta"
|
||||||
import { type BaseRouterProps, Navigate, Route, Router, useParams, useSearchParams } from "@solidjs/router"
|
import { type BaseRouterProps, Navigate, Route, Router, useNavigate, useParams, useSearchParams } from "@solidjs/router"
|
||||||
import { QueryClient, QueryClientProvider } from "@tanstack/solid-query"
|
import { QueryClient, QueryClientProvider } from "@tanstack/solid-query"
|
||||||
import { Effect } from "effect"
|
import { Effect } from "effect"
|
||||||
import {
|
import {
|
||||||
|
|
@ -32,7 +32,7 @@ import { CommandProvider, useCommand, type CommandOption } from "@/context/comma
|
||||||
import { CommentsProvider } from "@/context/comments"
|
import { CommentsProvider } from "@/context/comments"
|
||||||
import { FileProvider } from "@/context/file"
|
import { FileProvider } from "@/context/file"
|
||||||
import { ServerSDKProvider } from "@/context/server-sdk"
|
import { ServerSDKProvider } from "@/context/server-sdk"
|
||||||
import { ServerSyncProvider } from "@/context/server-sync"
|
import { ServerSyncProvider, useServerSync } from "@/context/server-sync"
|
||||||
import { GlobalProvider, useGlobal } from "@/context/global"
|
import { GlobalProvider, useGlobal } from "@/context/global"
|
||||||
import { HighlightsProvider } from "@/context/highlights"
|
import { HighlightsProvider } from "@/context/highlights"
|
||||||
import { LanguageProvider, type Locale, useLanguage } from "@/context/language"
|
import { LanguageProvider, type Locale, useLanguage } from "@/context/language"
|
||||||
|
|
@ -52,9 +52,10 @@ import LegacyLayout from "@/pages/layout"
|
||||||
import NewLayout from "@/pages/layout-new"
|
import NewLayout from "@/pages/layout-new"
|
||||||
import { ErrorPage } from "./pages/error"
|
import { ErrorPage } from "./pages/error"
|
||||||
import { useCheckServerHealth } from "./utils/server-health"
|
import { useCheckServerHealth } from "./utils/server-health"
|
||||||
import { legacySessionServer, requireServerKey, sessionHref } from "./utils/session-route"
|
import { legacySessionHref, legacySessionServer, requireServerKey, sessionHref } from "./utils/session-route"
|
||||||
|
import { createSessionLineage } from "@/pages/session/session-lineage"
|
||||||
|
|
||||||
import { SessionPage, TargetSessionRouteContent } from "@/pages/session"
|
import { SessionPage, SessionRouteErrorBoundary, TargetSessionRouteContent } from "@/pages/session"
|
||||||
import { NewHome, LegacyHome } from "@/pages/home"
|
import { NewHome, LegacyHome } from "@/pages/home"
|
||||||
|
|
||||||
const NewSession = lazy(() => import("@/pages/new-session"))
|
const NewSession = lazy(() => import("@/pages/new-session"))
|
||||||
|
|
@ -88,10 +89,14 @@ const SessionRoute = () => {
|
||||||
tabs.newDraft({ server: server.key, directory: sdk().directory }, search.prompt)
|
tabs.newDraft({ server: server.key, directory: sdk().directory }, search.prompt)
|
||||||
})
|
})
|
||||||
|
|
||||||
return <SessionPage />
|
return (
|
||||||
|
<SessionRouteErrorBoundary sessionID={params.id}>
|
||||||
|
<SessionPage />
|
||||||
|
</SessionRouteErrorBoundary>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const TargetSessionRoute = () => {
|
function TargetServerRoute(props: ParentProps) {
|
||||||
const params = useParams<{ serverKey: string; id: string }>()
|
const params = useParams<{ serverKey: string; id: string }>()
|
||||||
const global = useGlobal()
|
const global = useGlobal()
|
||||||
const conn = createMemo(() => {
|
const conn = createMemo(() => {
|
||||||
|
|
@ -105,14 +110,47 @@ const TargetSessionRoute = () => {
|
||||||
// re-resolves reactively instead); both rely on this key for server changes.
|
// re-resolves reactively instead); both rely on this key for server changes.
|
||||||
<Show when={requireServerKey(params.serverKey)} keyed>
|
<Show when={requireServerKey(params.serverKey)} keyed>
|
||||||
<ServerSDKProvider server={conn}>
|
<ServerSDKProvider server={conn}>
|
||||||
<ServerSyncProvider server={conn}>
|
<ServerSyncProvider server={conn}>{props.children}</ServerSyncProvider>
|
||||||
<TargetSessionRouteContent />
|
|
||||||
</ServerSyncProvider>
|
|
||||||
</ServerSDKProvider>
|
</ServerSDKProvider>
|
||||||
</Show>
|
</Show>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const TargetSessionRoute = () => (
|
||||||
|
<TargetServerRoute>
|
||||||
|
<TargetSessionRouteContent />
|
||||||
|
</TargetServerRoute>
|
||||||
|
)
|
||||||
|
|
||||||
|
function LegacyTargetSessionRoute() {
|
||||||
|
const params = useParams<{ serverKey: string; id: string }>()
|
||||||
|
return (
|
||||||
|
<TargetServerRoute>
|
||||||
|
<SessionRouteErrorBoundary sessionID={params.id} serverKey={requireServerKey(params.serverKey)}>
|
||||||
|
<LegacyTargetSessionRedirect />
|
||||||
|
</SessionRouteErrorBoundary>
|
||||||
|
</TargetServerRoute>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function LegacyTargetSessionRedirect() {
|
||||||
|
const params = useParams<{ id: string }>()
|
||||||
|
const navigate = useNavigate()
|
||||||
|
const sync = useServerSync()
|
||||||
|
const current = createSessionLineage(
|
||||||
|
() => params.id,
|
||||||
|
() => sync().session.lineage,
|
||||||
|
)
|
||||||
|
|
||||||
|
createEffect(() => {
|
||||||
|
const directory = current()?.session.directory
|
||||||
|
if (!directory) return
|
||||||
|
navigate(legacySessionHref(directory, params.id), { replace: true })
|
||||||
|
})
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
// Wraps the non-draft routes. They are gated on (and keyed to) the globally selected
|
// Wraps the non-draft routes. They are gated on (and keyed to) the globally selected
|
||||||
// server via ServerKey, then provide the server-scoped shell (Permission/Layout/
|
// server via ServerKey, then provide the server-scoped shell (Permission/Layout/
|
||||||
// Notification/Models + the visual Layout) for that server.
|
// Notification/Models + the visual Layout) for that server.
|
||||||
|
|
@ -542,7 +580,14 @@ function Routes(props: { serverScoped?: JSX.Element }) {
|
||||||
<LegacyServerLayout serverScoped={props.serverScoped}>{routeProps.children}</LegacyServerLayout>
|
<LegacyServerLayout serverScoped={props.serverScoped}>{routeProps.children}</LegacyServerLayout>
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Show when={!settings.general.newLayoutDesigns()}>{<Route path="/" component={LegacyHome} />}</Show>
|
<Show when={!settings.general.newLayoutDesigns()}>
|
||||||
|
{
|
||||||
|
<>
|
||||||
|
<Route path="/" component={LegacyHome} />
|
||||||
|
<Route path="/server/:serverKey/session/:id" component={LegacyTargetSessionRoute} />
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
</Show>
|
||||||
<Route path="/:dir" component={DirectoryLayout}>
|
<Route path="/:dir" component={DirectoryLayout}>
|
||||||
<Route path="/" component={() => <Navigate href="session" />} />
|
<Route path="/" component={() => <Navigate href="session" />} />
|
||||||
<Route path="/session/:id?" component={SessionRoute} />
|
<Route path="/session/:id?" component={SessionRoute} />
|
||||||
|
|
@ -550,15 +595,15 @@ function Routes(props: { serverScoped?: JSX.Element }) {
|
||||||
</Route>
|
</Route>
|
||||||
<Show when={settings.general.newLayoutDesigns()}>
|
<Show when={settings.general.newLayoutDesigns()}>
|
||||||
<Route path="/" component={NewHome} />
|
<Route path="/" component={NewHome} />
|
||||||
<Route path="/:dir/session/:id" component={LegacyTargetSessionRoute} />
|
<Route path="/:dir/session/:id" component={NewLayoutLegacySessionRedirect} />
|
||||||
|
<Route path="/server/:serverKey/session/:id" component={TargetSessionRoute} />
|
||||||
</Show>
|
</Show>
|
||||||
<Route path="/new-session" component={DraftRoute} />
|
<Route path="/new-session" component={DraftRoute} />
|
||||||
<Route path="/server/:serverKey/session/:id" component={TargetSessionRoute} />
|
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function LegacyTargetSessionRoute() {
|
function NewLayoutLegacySessionRedirect() {
|
||||||
const server = useServer()
|
const server = useServer()
|
||||||
const tabs = useTabs()
|
const tabs = useTabs()
|
||||||
const params = useParams<{ id: string }>()
|
const params = useParams<{ id: string }>()
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,7 @@ import { usePlatform } from "@/context/platform"
|
||||||
import { DateTime } from "luxon"
|
import { DateTime } from "luxon"
|
||||||
import { useDialog } from "@opencode-ai/ui/context/dialog"
|
import { useDialog } from "@opencode-ai/ui/context/dialog"
|
||||||
import { useDirectoryPicker } from "@/components/directory-picker"
|
import { useDirectoryPicker } from "@/components/directory-picker"
|
||||||
import { useSettingsCommand } from "@/components/settings-dialog"
|
import { useSettingsDialog } from "@/components/settings-dialog"
|
||||||
import { DialogSelectServer, useServerManagementController } from "@/components/dialog-select-server"
|
import { DialogSelectServer, useServerManagementController } from "@/components/dialog-select-server"
|
||||||
import { DialogServerV2 } from "@/components/settings-v2/dialog-server-v2"
|
import { DialogServerV2 } from "@/components/settings-v2/dialog-server-v2"
|
||||||
import { ServerConnection, serverName, useServer } from "@/context/server"
|
import { ServerConnection, serverName, useServer } from "@/context/server"
|
||||||
|
|
@ -267,7 +267,7 @@ export function NewHome() {
|
||||||
const command = useCommand()
|
const command = useCommand()
|
||||||
const notification = useNotification()
|
const notification = useNotification()
|
||||||
const marked = useMarked()
|
const marked = useMarked()
|
||||||
const openSettings = useSettingsCommand()
|
const openSettings = useSettingsDialog()
|
||||||
let focusSessionSearch: (() => void) | undefined
|
let focusSessionSearch: (() => void) | undefined
|
||||||
const [state, setState] = createStore({
|
const [state, setState] = createStore({
|
||||||
search: "",
|
search: "",
|
||||||
|
|
|
||||||
|
|
@ -6,11 +6,13 @@ import { Titlebar, type TitlebarUpdate } from "@/components/titlebar"
|
||||||
import { usePlatform } from "@/context/platform"
|
import { usePlatform } from "@/context/platform"
|
||||||
import { setNavigate } from "@/utils/notification-click"
|
import { setNavigate } from "@/utils/notification-click"
|
||||||
import { setV2Toast, ToastRegion } from "@/utils/toast"
|
import { setV2Toast, ToastRegion } from "@/utils/toast"
|
||||||
|
import { useSettingsCommand } from "@/components/settings-dialog"
|
||||||
|
|
||||||
export default function NewLayout(props: ParentProps) {
|
export default function NewLayout(props: ParentProps) {
|
||||||
const platform = usePlatform()
|
const platform = usePlatform()
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
setNavigate(navigate)
|
setNavigate(navigate)
|
||||||
|
useSettingsCommand()
|
||||||
|
|
||||||
createEffect(() => setV2Toast(true))
|
createEffect(() => setV2Toast(true))
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@ import { Tooltip } from "@opencode-ai/ui/tooltip"
|
||||||
import { NewSessionDesignView } from "@/components/session"
|
import { NewSessionDesignView } from "@/components/session"
|
||||||
import { PromptInput } from "@/components/prompt-input"
|
import { PromptInput } from "@/components/prompt-input"
|
||||||
import { StatusPopoverV2 } from "@/components/status-popover"
|
import { StatusPopoverV2 } from "@/components/status-popover"
|
||||||
import { useSettingsCommand } from "@/components/settings-dialog"
|
|
||||||
import {
|
import {
|
||||||
PromptProjectAddButton,
|
PromptProjectAddButton,
|
||||||
PromptProjectSelector,
|
PromptProjectSelector,
|
||||||
|
|
@ -45,7 +44,6 @@ export default function NewSessionPage() {
|
||||||
const [searchParams, setSearchParams] = useSearchParams<{ draftId?: string; prompt?: string }>()
|
const [searchParams, setSearchParams] = useSearchParams<{ draftId?: string; prompt?: string }>()
|
||||||
|
|
||||||
useComposerCommands()
|
useComposerCommands()
|
||||||
useSettingsCommand()
|
|
||||||
|
|
||||||
let inputRef: HTMLDivElement | undefined
|
let inputRef: HTMLDivElement | undefined
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,7 @@ import { previewSelectedLines } from "@opencode-ai/session-ui/pierre/selection-b
|
||||||
import { Button } from "@opencode-ai/ui/button"
|
import { Button } from "@opencode-ai/ui/button"
|
||||||
import { showToast } from "@/utils/toast"
|
import { showToast } from "@/utils/toast"
|
||||||
import { base64Encode, checksum } from "@opencode-ai/core/util/encode"
|
import { base64Encode, checksum } from "@opencode-ai/core/util/encode"
|
||||||
import { Navigate, useLocation, useNavigate, useParams, useSearchParams } from "@solidjs/router"
|
import { useLocation, useNavigate, useParams, useSearchParams } from "@solidjs/router"
|
||||||
import { NewSessionView, SessionHeader } from "@/components/session"
|
import { NewSessionView, SessionHeader } from "@/components/session"
|
||||||
import { ErrorPage } from "@/pages/error"
|
import { ErrorPage } from "@/pages/error"
|
||||||
import { CommentsProvider, useComments } from "@/context/comments"
|
import { CommentsProvider, useComments } from "@/context/comments"
|
||||||
|
|
@ -56,7 +56,6 @@ import { useSync } from "@/context/sync"
|
||||||
import { useTabs } from "@/context/tabs"
|
import { useTabs } from "@/context/tabs"
|
||||||
import { TerminalProvider, useTerminal } from "@/context/terminal"
|
import { TerminalProvider, useTerminal } from "@/context/terminal"
|
||||||
import { PromptInput } from "@/components/prompt-input"
|
import { PromptInput } from "@/components/prompt-input"
|
||||||
import { useSettingsCommand } from "@/components/settings-dialog"
|
|
||||||
import { type FollowupDraft, sendFollowupDraft } from "@/components/prompt-input/submit"
|
import { type FollowupDraft, sendFollowupDraft } from "@/components/prompt-input/submit"
|
||||||
import {
|
import {
|
||||||
createPromptInputController,
|
createPromptInputController,
|
||||||
|
|
@ -153,7 +152,7 @@ export function TargetSessionRouteContent() {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function SessionRouteErrorBoundary(
|
export function SessionRouteErrorBoundary(
|
||||||
props: ParentProps<{ sessionID?: string; serverKey?: ServerConnection.Key; padded?: boolean }>,
|
props: ParentProps<{ sessionID?: string; serverKey?: ServerConnection.Key; padded?: boolean }>,
|
||||||
) {
|
) {
|
||||||
const settings = useSettings()
|
const settings = useSettings()
|
||||||
|
|
@ -221,7 +220,6 @@ function SessionErrorFallback(props: { error: unknown; sessionID?: string; serve
|
||||||
|
|
||||||
function ResolvedTargetSessionRoute() {
|
function ResolvedTargetSessionRoute() {
|
||||||
const params = useParams<{ serverKey: string; id: string }>()
|
const params = useParams<{ serverKey: string; id: string }>()
|
||||||
const settings = useSettings()
|
|
||||||
const tabs = useTabs()
|
const tabs = useTabs()
|
||||||
const sync = useServerSync()
|
const sync = useServerSync()
|
||||||
const serverKey = createMemo(() => requireServerKey(params.serverKey))
|
const serverKey = createMemo(() => requireServerKey(params.serverKey))
|
||||||
|
|
@ -248,16 +246,11 @@ function ResolvedTargetSessionRoute() {
|
||||||
the terminal. Same-workspace tab switches keep it open because warm
|
the terminal. Same-workspace tab switches keep it open because warm
|
||||||
targets resolve synchronously from the sync cache. */}
|
targets resolve synchronously from the sync cache. */}
|
||||||
<Show when={directory()}>
|
<Show when={directory()}>
|
||||||
<Show
|
<SDKProvider directory={targetDirectory}>
|
||||||
when={settings.general.newLayoutDesigns()}
|
<DirectoryDataProvider directory={targetDirectory} server={serverKey}>
|
||||||
fallback={<Navigate href={legacySessionHref(directory()!, params.id)} />}
|
<TargetSessionPage />
|
||||||
>
|
</DirectoryDataProvider>
|
||||||
<SDKProvider directory={targetDirectory}>
|
</SDKProvider>
|
||||||
<DirectoryDataProvider directory={targetDirectory} server={serverKey}>
|
|
||||||
<TargetSessionPage />
|
|
||||||
</DirectoryDataProvider>
|
|
||||||
</SDKProvider>
|
|
||||||
</Show>
|
|
||||||
</Show>
|
</Show>
|
||||||
</TargetServerScopedProviders>
|
</TargetServerScopedProviders>
|
||||||
)
|
)
|
||||||
|
|
@ -1021,7 +1014,6 @@ export default function Page() {
|
||||||
}
|
}
|
||||||
|
|
||||||
useComposerCommands()
|
useComposerCommands()
|
||||||
useSettingsCommand()
|
|
||||||
useSessionCommands({
|
useSessionCommands({
|
||||||
navigateMessageByOffset,
|
navigateMessageByOffset,
|
||||||
setActiveMessage,
|
setActiveMessage,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue