Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e7f4d851f6 |
@@ -349,25 +349,3 @@ test("session model restore across workspaces", async ({ page, withProject }) =>
|
|||||||
await waitFooter(page, firstState)
|
await waitFooter(page, firstState)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
test("variant preserved when switching agent modes", async ({ page, withProject }) => {
|
|
||||||
await page.setViewportSize({ width: 1440, height: 900 })
|
|
||||||
|
|
||||||
await withProject(async ({ directory, gotoSession }) => {
|
|
||||||
await gotoSession()
|
|
||||||
|
|
||||||
await ensureVariant(page, directory)
|
|
||||||
const updated = await chooseDifferentVariant(page)
|
|
||||||
|
|
||||||
const available = await agents(page)
|
|
||||||
const other = available.find((name) => name !== updated.agent)
|
|
||||||
test.skip(!other, "only one agent available")
|
|
||||||
if (!other) return
|
|
||||||
|
|
||||||
await choose(page, promptAgentSelector, other)
|
|
||||||
await waitFooter(page, { agent: other, variant: updated.variant })
|
|
||||||
|
|
||||||
await choose(page, promptAgentSelector, updated.agent)
|
|
||||||
await waitFooter(page, { agent: updated.agent, variant: updated.variant })
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|||||||
@@ -58,7 +58,6 @@ export function Titlebar() {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const path = () => `${location.pathname}${location.search}${location.hash}`
|
const path = () => `${location.pathname}${location.search}${location.hash}`
|
||||||
const home = createMemo(() => !params.dir)
|
|
||||||
const creating = createMemo(() => {
|
const creating = createMemo(() => {
|
||||||
if (!params.dir) return false
|
if (!params.dir) return false
|
||||||
if (params.id) return false
|
if (params.id) return false
|
||||||
@@ -176,119 +175,113 @@ export function Titlebar() {
|
|||||||
>
|
>
|
||||||
<Show when={mac()}>
|
<Show when={mac()}>
|
||||||
<div class="h-full shrink-0" style={{ width: `${72 / zoom()}px` }} />
|
<div class="h-full shrink-0" style={{ width: `${72 / zoom()}px` }} />
|
||||||
<Show when={!home()}>
|
<div class="xl:hidden w-10 shrink-0 flex items-center justify-center">
|
||||||
<div class="xl:hidden w-10 shrink-0 flex items-center justify-center">
|
<IconButton
|
||||||
<IconButton
|
icon="menu"
|
||||||
icon="menu"
|
variant="ghost"
|
||||||
variant="ghost"
|
class="titlebar-icon rounded-md"
|
||||||
class="titlebar-icon rounded-md"
|
onClick={layout.mobileSidebar.toggle}
|
||||||
onClick={layout.mobileSidebar.toggle}
|
aria-label={language.t("sidebar.menu.toggle")}
|
||||||
aria-label={language.t("sidebar.menu.toggle")}
|
aria-expanded={layout.mobileSidebar.opened()}
|
||||||
aria-expanded={layout.mobileSidebar.opened()}
|
/>
|
||||||
/>
|
</div>
|
||||||
</div>
|
|
||||||
</Show>
|
|
||||||
</Show>
|
</Show>
|
||||||
<Show when={!mac()}>
|
<Show when={!mac()}>
|
||||||
<Show when={!home()}>
|
<div class="xl:hidden w-[48px] shrink-0 flex items-center justify-center">
|
||||||
<div class="xl:hidden w-[48px] shrink-0 flex items-center justify-center">
|
<IconButton
|
||||||
<IconButton
|
icon="menu"
|
||||||
icon="menu"
|
variant="ghost"
|
||||||
variant="ghost"
|
class="titlebar-icon rounded-md"
|
||||||
class="titlebar-icon rounded-md"
|
onClick={layout.mobileSidebar.toggle}
|
||||||
onClick={layout.mobileSidebar.toggle}
|
aria-label={language.t("sidebar.menu.toggle")}
|
||||||
aria-label={language.t("sidebar.menu.toggle")}
|
aria-expanded={layout.mobileSidebar.opened()}
|
||||||
aria-expanded={layout.mobileSidebar.opened()}
|
/>
|
||||||
/>
|
</div>
|
||||||
</div>
|
|
||||||
</Show>
|
|
||||||
</Show>
|
</Show>
|
||||||
<div class="flex items-center gap-1 shrink-0">
|
<div class="flex items-center gap-1 shrink-0">
|
||||||
<Show when={!home()}>
|
<TooltipKeybind
|
||||||
<TooltipKeybind
|
class={web() ? "hidden xl:flex shrink-0 ml-14" : "hidden xl:flex shrink-0 ml-2"}
|
||||||
class={web() ? "hidden xl:flex shrink-0 ml-14" : "hidden xl:flex shrink-0 ml-2"}
|
placement="bottom"
|
||||||
placement="bottom"
|
title={language.t("command.sidebar.toggle")}
|
||||||
title={language.t("command.sidebar.toggle")}
|
keybind={command.keybind("sidebar.toggle")}
|
||||||
keybind={command.keybind("sidebar.toggle")}
|
>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
class="group/sidebar-toggle titlebar-icon w-8 h-6 p-0 box-border"
|
||||||
|
onClick={layout.sidebar.toggle}
|
||||||
|
aria-label={language.t("command.sidebar.toggle")}
|
||||||
|
aria-expanded={layout.sidebar.opened()}
|
||||||
>
|
>
|
||||||
<Button
|
<Icon size="small" name={layout.sidebar.opened() ? "sidebar-active" : "sidebar"} />
|
||||||
variant="ghost"
|
</Button>
|
||||||
class="group/sidebar-toggle titlebar-icon w-8 h-6 p-0 box-border"
|
</TooltipKeybind>
|
||||||
onClick={layout.sidebar.toggle}
|
<div class="hidden xl:flex items-center shrink-0">
|
||||||
aria-label={language.t("command.sidebar.toggle")}
|
<Show when={params.dir}>
|
||||||
aria-expanded={layout.sidebar.opened()}
|
|
||||||
>
|
|
||||||
<Icon size="small" name={layout.sidebar.opened() ? "sidebar-active" : "sidebar"} />
|
|
||||||
</Button>
|
|
||||||
</TooltipKeybind>
|
|
||||||
<div class="hidden xl:flex items-center shrink-0">
|
|
||||||
<Show when={params.dir}>
|
|
||||||
<div
|
|
||||||
class="flex items-center shrink-0 w-8 mr-1"
|
|
||||||
aria-hidden={layout.sidebar.opened() ? "true" : undefined}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="transition-opacity"
|
|
||||||
classList={{
|
|
||||||
"opacity-100 duration-120 ease-out": !layout.sidebar.opened(),
|
|
||||||
"opacity-0 duration-120 ease-in delay-0 pointer-events-none": layout.sidebar.opened(),
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<TooltipKeybind
|
|
||||||
placement="bottom"
|
|
||||||
title={language.t("command.session.new")}
|
|
||||||
keybind={command.keybind("session.new")}
|
|
||||||
openDelay={2000}
|
|
||||||
>
|
|
||||||
<Button
|
|
||||||
variant="ghost"
|
|
||||||
icon={creating() ? "new-session-active" : "new-session"}
|
|
||||||
class="titlebar-icon w-8 h-6 p-0 box-border"
|
|
||||||
disabled={layout.sidebar.opened()}
|
|
||||||
tabIndex={layout.sidebar.opened() ? -1 : undefined}
|
|
||||||
onClick={() => {
|
|
||||||
if (!params.dir) return
|
|
||||||
navigate(`/${params.dir}/session`)
|
|
||||||
}}
|
|
||||||
aria-label={language.t("command.session.new")}
|
|
||||||
aria-current={creating() ? "page" : undefined}
|
|
||||||
/>
|
|
||||||
</TooltipKeybind>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Show>
|
|
||||||
<div
|
<div
|
||||||
class="flex items-center gap-0 transition-transform"
|
class="flex items-center shrink-0 w-8 mr-1"
|
||||||
classList={{
|
aria-hidden={layout.sidebar.opened() ? "true" : undefined}
|
||||||
"translate-x-0": !layout.sidebar.opened(),
|
|
||||||
"-translate-x-[36px]": layout.sidebar.opened(),
|
|
||||||
"duration-180 ease-out": !layout.sidebar.opened(),
|
|
||||||
"duration-180 ease-in": layout.sidebar.opened(),
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<Tooltip placement="bottom" value={language.t("common.goBack")} openDelay={2000}>
|
<div
|
||||||
<Button
|
class="transition-opacity"
|
||||||
variant="ghost"
|
classList={{
|
||||||
icon="chevron-left"
|
"opacity-100 duration-120 ease-out": !layout.sidebar.opened(),
|
||||||
class="titlebar-icon w-6 h-6 p-0 box-border"
|
"opacity-0 duration-120 ease-in delay-0 pointer-events-none": layout.sidebar.opened(),
|
||||||
disabled={!canBack()}
|
}}
|
||||||
onClick={back}
|
>
|
||||||
aria-label={language.t("common.goBack")}
|
<TooltipKeybind
|
||||||
/>
|
placement="bottom"
|
||||||
</Tooltip>
|
title={language.t("command.session.new")}
|
||||||
<Tooltip placement="bottom" value={language.t("common.goForward")} openDelay={2000}>
|
keybind={command.keybind("session.new")}
|
||||||
<Button
|
openDelay={2000}
|
||||||
variant="ghost"
|
>
|
||||||
icon="chevron-right"
|
<Button
|
||||||
class="titlebar-icon w-6 h-6 p-0 box-border"
|
variant="ghost"
|
||||||
disabled={!canForward()}
|
icon={creating() ? "new-session-active" : "new-session"}
|
||||||
onClick={forward}
|
class="titlebar-icon w-8 h-6 p-0 box-border"
|
||||||
aria-label={language.t("common.goForward")}
|
disabled={layout.sidebar.opened()}
|
||||||
/>
|
tabIndex={layout.sidebar.opened() ? -1 : undefined}
|
||||||
</Tooltip>
|
onClick={() => {
|
||||||
|
if (!params.dir) return
|
||||||
|
navigate(`/${params.dir}/session`)
|
||||||
|
}}
|
||||||
|
aria-label={language.t("command.session.new")}
|
||||||
|
aria-current={creating() ? "page" : undefined}
|
||||||
|
/>
|
||||||
|
</TooltipKeybind>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</Show>
|
||||||
|
<div
|
||||||
|
class="flex items-center gap-0 transition-transform"
|
||||||
|
classList={{
|
||||||
|
"translate-x-0": !layout.sidebar.opened(),
|
||||||
|
"-translate-x-[36px]": layout.sidebar.opened(),
|
||||||
|
"duration-180 ease-out": !layout.sidebar.opened(),
|
||||||
|
"duration-180 ease-in": layout.sidebar.opened(),
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Tooltip placement="bottom" value={language.t("common.goBack")} openDelay={2000}>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
icon="chevron-left"
|
||||||
|
class="titlebar-icon w-6 h-6 p-0 box-border"
|
||||||
|
disabled={!canBack()}
|
||||||
|
onClick={back}
|
||||||
|
aria-label={language.t("common.goBack")}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip placement="bottom" value={language.t("common.goForward")} openDelay={2000}>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
icon="chevron-right"
|
||||||
|
class="titlebar-icon w-6 h-6 p-0 box-border"
|
||||||
|
disabled={!canForward()}
|
||||||
|
onClick={forward}
|
||||||
|
aria-label={language.t("common.goForward")}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
</Show>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="opencode-titlebar-left" class="flex items-center gap-3 min-w-0 px-2" />
|
<div id="opencode-titlebar-left" class="flex items-center gap-3 min-w-0 px-2" />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -192,11 +192,10 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
|
|||||||
model: item.model,
|
model: item.model,
|
||||||
variant: item.variant ?? null,
|
variant: item.variant ?? null,
|
||||||
})
|
})
|
||||||
const prev = scope()
|
|
||||||
const next = {
|
const next = {
|
||||||
agent: item.name,
|
agent: item.name,
|
||||||
model: item.model ?? prev?.model,
|
model: item.model,
|
||||||
variant: item.variant ?? prev?.variant,
|
variant: item.variant,
|
||||||
} satisfies State
|
} satisfies State
|
||||||
const session = id()
|
const session = id()
|
||||||
if (session) {
|
if (session) {
|
||||||
|
|||||||
@@ -111,7 +111,6 @@ export default function Layout(props: ParentProps) {
|
|||||||
let scrollContainerRef: HTMLDivElement | undefined
|
let scrollContainerRef: HTMLDivElement | undefined
|
||||||
|
|
||||||
const params = useParams()
|
const params = useParams()
|
||||||
const home = createMemo(() => !params.dir)
|
|
||||||
const globalSDK = useGlobalSDK()
|
const globalSDK = useGlobalSDK()
|
||||||
const globalSync = useGlobalSync()
|
const globalSync = useGlobalSync()
|
||||||
const layout = useLayout()
|
const layout = useLayout()
|
||||||
@@ -2242,7 +2241,7 @@ export default function Layout(props: ParentProps) {
|
|||||||
const sidebarContent = (mobile?: boolean) => (
|
const sidebarContent = (mobile?: boolean) => (
|
||||||
<SidebarContent
|
<SidebarContent
|
||||||
mobile={mobile}
|
mobile={mobile}
|
||||||
opened={() => (home() ? false : layout.sidebar.opened())}
|
opened={() => layout.sidebar.opened()}
|
||||||
aimMove={aim.move}
|
aimMove={aim.move}
|
||||||
projects={projects}
|
projects={projects}
|
||||||
renderProject={(project) => (
|
renderProject={(project) => (
|
||||||
@@ -2286,7 +2285,7 @@ export default function Layout(props: ParentProps) {
|
|||||||
"absolute inset-y-0 left-0": true,
|
"absolute inset-y-0 left-0": true,
|
||||||
"z-10": true,
|
"z-10": true,
|
||||||
}}
|
}}
|
||||||
style={{ width: home() ? "4rem" : `${Math.max(layout.sidebar.width(), 244)}px` }}
|
style={{ width: `${Math.max(layout.sidebar.width(), 244)}px` }}
|
||||||
ref={(el) => {
|
ref={(el) => {
|
||||||
setState("nav", el)
|
setState("nav", el)
|
||||||
}}
|
}}
|
||||||
@@ -2295,14 +2294,13 @@ export default function Layout(props: ParentProps) {
|
|||||||
}}
|
}}
|
||||||
onMouseLeave={() => {
|
onMouseLeave={() => {
|
||||||
aim.reset()
|
aim.reset()
|
||||||
if (home()) return
|
|
||||||
if (!sidebarHovering()) return
|
if (!sidebarHovering()) return
|
||||||
|
|
||||||
arm()
|
arm()
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div class="@container w-full h-full contain-strict">{sidebarContent()}</div>
|
<div class="@container w-full h-full contain-strict">{sidebarContent()}</div>
|
||||||
<Show when={!home() && layout.sidebar.opened()}>
|
<Show when={layout.sidebar.opened()}>
|
||||||
<div onPointerDown={() => setState("sizing", true)}>
|
<div onPointerDown={() => setState("sizing", true)}>
|
||||||
<ResizeHandle
|
<ResizeHandle
|
||||||
direction="horizontal"
|
direction="horizontal"
|
||||||
@@ -2327,54 +2325,46 @@ export default function Layout(props: ParentProps) {
|
|||||||
style={{ left: "calc(4rem + 12px)" }}
|
style={{ left: "calc(4rem + 12px)" }}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Show when={!home()}>
|
<div class="xl:hidden">
|
||||||
<div class="xl:hidden">
|
<div
|
||||||
<div
|
classList={{
|
||||||
classList={{
|
"fixed inset-x-0 top-10 bottom-0 z-40 transition-opacity duration-200": true,
|
||||||
"fixed inset-x-0 top-10 bottom-0 z-40 transition-opacity duration-200": true,
|
"opacity-100 pointer-events-auto": layout.mobileSidebar.opened(),
|
||||||
"opacity-100 pointer-events-auto": layout.mobileSidebar.opened(),
|
"opacity-0 pointer-events-none": !layout.mobileSidebar.opened(),
|
||||||
"opacity-0 pointer-events-none": !layout.mobileSidebar.opened(),
|
}}
|
||||||
}}
|
onClick={(e) => {
|
||||||
onClick={(e) => {
|
if (e.target === e.currentTarget) layout.mobileSidebar.hide()
|
||||||
if (e.target === e.currentTarget) layout.mobileSidebar.hide()
|
}}
|
||||||
}}
|
/>
|
||||||
/>
|
<nav
|
||||||
<nav
|
aria-label={language.t("sidebar.nav.projectsAndSessions")}
|
||||||
aria-label={language.t("sidebar.nav.projectsAndSessions")}
|
data-component="sidebar-nav-mobile"
|
||||||
data-component="sidebar-nav-mobile"
|
classList={{
|
||||||
classList={{
|
"@container fixed top-10 bottom-0 left-0 z-50 w-full max-w-[400px] overflow-hidden border-r border-border-weaker-base bg-background-base transition-transform duration-200 ease-out": true,
|
||||||
"@container fixed top-10 bottom-0 left-0 z-50 w-full max-w-[400px] overflow-hidden border-r border-border-weaker-base bg-background-base transition-transform duration-200 ease-out": true,
|
"translate-x-0": layout.mobileSidebar.opened(),
|
||||||
"translate-x-0": layout.mobileSidebar.opened(),
|
"-translate-x-full": !layout.mobileSidebar.opened(),
|
||||||
"-translate-x-full": !layout.mobileSidebar.opened(),
|
}}
|
||||||
}}
|
onClick={(e) => e.stopPropagation()}
|
||||||
onClick={(e) => e.stopPropagation()}
|
>
|
||||||
>
|
{sidebarContent(true)}
|
||||||
{sidebarContent(true)}
|
</nav>
|
||||||
</nav>
|
</div>
|
||||||
</div>
|
|
||||||
</Show>
|
|
||||||
|
|
||||||
<div
|
<div
|
||||||
classList={{
|
classList={{
|
||||||
"absolute inset-0": true,
|
"absolute inset-0": true,
|
||||||
"xl:inset-y-0 xl:right-0": true,
|
"xl:inset-y-0 xl:right-0 xl:left-[var(--main-left)]": true,
|
||||||
"xl:left-[var(--main-left)]": true,
|
|
||||||
"z-20": true,
|
"z-20": true,
|
||||||
"transition-[left] duration-200 ease-[cubic-bezier(0.22,1,0.36,1)] will-change-[left] motion-reduce:transition-none":
|
"transition-[left] duration-200 ease-[cubic-bezier(0.22,1,0.36,1)] will-change-[left] motion-reduce:transition-none":
|
||||||
!state.sizing,
|
!state.sizing,
|
||||||
}}
|
}}
|
||||||
style={{
|
style={{
|
||||||
"--main-left": home()
|
"--main-left": layout.sidebar.opened() ? `${Math.max(layout.sidebar.width(), 244)}px` : "4rem",
|
||||||
? "4rem"
|
|
||||||
: layout.sidebar.opened()
|
|
||||||
? `${Math.max(layout.sidebar.width(), 244)}px`
|
|
||||||
: "4rem",
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<main
|
<main
|
||||||
classList={{
|
classList={{
|
||||||
"size-full overflow-x-hidden flex flex-col items-start contain-strict border-t border-border-weak-base bg-background-base": true,
|
"size-full overflow-x-hidden flex flex-col items-start contain-strict border-t border-border-weak-base bg-background-base xl:border-l xl:rounded-tl-[12px]": true,
|
||||||
"xl:border-l xl:rounded-tl-[12px]": true,
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Show when={!autoselecting()} fallback={<div class="size-full" />}>
|
<Show when={!autoselecting()} fallback={<div class="size-full" />}>
|
||||||
@@ -2383,47 +2373,43 @@ export default function Layout(props: ParentProps) {
|
|||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Show when={!home()}>
|
<div
|
||||||
<div
|
classList={{
|
||||||
classList={{
|
"hidden xl:flex absolute inset-y-0 left-16 z-30": true,
|
||||||
"hidden xl:flex absolute inset-y-0 left-16 z-30": true,
|
"opacity-100 translate-x-0 pointer-events-auto": state.peeked && !layout.sidebar.opened(),
|
||||||
"opacity-100 translate-x-0 pointer-events-auto": state.peeked && !layout.sidebar.opened(),
|
"opacity-0 -translate-x-2 pointer-events-none": !state.peeked || layout.sidebar.opened(),
|
||||||
"opacity-0 -translate-x-2 pointer-events-none": !state.peeked || layout.sidebar.opened(),
|
"transition-[opacity,transform] motion-reduce:transition-none": true,
|
||||||
"transition-[opacity,transform] motion-reduce:transition-none": true,
|
"duration-180 ease-out": state.peeked && !layout.sidebar.opened(),
|
||||||
"duration-180 ease-out": state.peeked && !layout.sidebar.opened(),
|
"duration-120 ease-in": !state.peeked || layout.sidebar.opened(),
|
||||||
"duration-120 ease-in": !state.peeked || layout.sidebar.opened(),
|
}}
|
||||||
}}
|
onMouseMove={disarm}
|
||||||
onMouseMove={disarm}
|
onMouseEnter={() => {
|
||||||
onMouseEnter={() => {
|
disarm()
|
||||||
disarm()
|
aim.reset()
|
||||||
aim.reset()
|
}}
|
||||||
}}
|
onPointerDown={disarm}
|
||||||
onPointerDown={disarm}
|
onMouseLeave={() => {
|
||||||
onMouseLeave={() => {
|
arm()
|
||||||
arm()
|
}}
|
||||||
}}
|
>
|
||||||
>
|
<Show when={peekProject()}>
|
||||||
<Show when={peekProject()}>
|
<SidebarPanel project={peekProject} merged={false} />
|
||||||
<SidebarPanel project={peekProject} merged={false} />
|
</Show>
|
||||||
</Show>
|
</div>
|
||||||
</div>
|
|
||||||
</Show>
|
|
||||||
|
|
||||||
<Show when={!home()}>
|
<div
|
||||||
<div
|
classList={{
|
||||||
classList={{
|
"hidden xl:block pointer-events-none absolute inset-y-0 right-0 z-25 overflow-hidden": true,
|
||||||
"hidden xl:block pointer-events-none absolute inset-y-0 right-0 z-25 overflow-hidden": true,
|
"opacity-100 translate-x-0": state.peeked && !layout.sidebar.opened(),
|
||||||
"opacity-100 translate-x-0": state.peeked && !layout.sidebar.opened(),
|
"opacity-0 -translate-x-2": !state.peeked || layout.sidebar.opened(),
|
||||||
"opacity-0 -translate-x-2": !state.peeked || layout.sidebar.opened(),
|
"transition-[opacity,transform] motion-reduce:transition-none": true,
|
||||||
"transition-[opacity,transform] motion-reduce:transition-none": true,
|
"duration-180 ease-out": state.peeked && !layout.sidebar.opened(),
|
||||||
"duration-180 ease-out": state.peeked && !layout.sidebar.opened(),
|
"duration-120 ease-in": !state.peeked || layout.sidebar.opened(),
|
||||||
"duration-120 ease-in": !state.peeked || layout.sidebar.opened(),
|
}}
|
||||||
}}
|
style={{ left: `calc(4rem + ${Math.max(Math.max(layout.sidebar.width(), 244) - 64, 0)}px)` }}
|
||||||
style={{ left: `calc(4rem + ${Math.max(Math.max(layout.sidebar.width(), 244) - 64, 0)}px)` }}
|
>
|
||||||
>
|
<div class="h-full w-px" style={{ "box-shadow": "var(--shadow-sidebar-overlay)" }} />
|
||||||
<div class="h-full w-px" style={{ "box-shadow": "var(--shadow-sidebar-overlay)" }} />
|
</div>
|
||||||
</div>
|
|
||||||
</Show>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{import.meta.env.DEV && <DebugBar />}
|
{import.meta.env.DEV && <DebugBar />}
|
||||||
|
|||||||
@@ -11,11 +11,6 @@ const openBrowser = (url: string) => Effect.promise(() => open(url).catch(() =>
|
|||||||
|
|
||||||
const println = (msg: string) => Effect.sync(() => UI.println(msg))
|
const println = (msg: string) => Effect.sync(() => UI.println(msg))
|
||||||
|
|
||||||
const isActiveOrgChoice = (
|
|
||||||
active: Option.Option<{ id: AccountID; active_org_id: OrgID | null }>,
|
|
||||||
choice: { accountID: AccountID; orgID: OrgID },
|
|
||||||
) => Option.isSome(active) && active.value.id === choice.accountID && active.value.active_org_id === choice.orgID
|
|
||||||
|
|
||||||
const loginEffect = Effect.fn("login")(function* (url: string) {
|
const loginEffect = Effect.fn("login")(function* (url: string) {
|
||||||
const service = yield* AccountService
|
const service = yield* AccountService
|
||||||
|
|
||||||
@@ -104,10 +99,11 @@ const switchEffect = Effect.fn("switch")(function* () {
|
|||||||
if (groups.length === 0) return yield* println("Not logged in")
|
if (groups.length === 0) return yield* println("Not logged in")
|
||||||
|
|
||||||
const active = yield* service.active()
|
const active = yield* service.active()
|
||||||
|
const activeOrgID = Option.flatMap(active, (a) => Option.fromNullishOr(a.active_org_id))
|
||||||
|
|
||||||
const opts = groups.flatMap((group) =>
|
const opts = groups.flatMap((group) =>
|
||||||
group.orgs.map((org) => {
|
group.orgs.map((org) => {
|
||||||
const isActive = isActiveOrgChoice(active, { accountID: group.account.id, orgID: org.id })
|
const isActive = Option.isSome(activeOrgID) && activeOrgID.value === org.id
|
||||||
return {
|
return {
|
||||||
value: { orgID: org.id, accountID: group.account.id, label: org.name },
|
value: { orgID: org.id, accountID: group.account.id, label: org.name },
|
||||||
label: isActive
|
label: isActive
|
||||||
@@ -136,10 +132,11 @@ const orgsEffect = Effect.fn("orgs")(function* () {
|
|||||||
if (!groups.some((group) => group.orgs.length > 0)) return yield* println("No orgs found")
|
if (!groups.some((group) => group.orgs.length > 0)) return yield* println("No orgs found")
|
||||||
|
|
||||||
const active = yield* service.active()
|
const active = yield* service.active()
|
||||||
|
const activeOrgID = Option.flatMap(active, (a) => Option.fromNullishOr(a.active_org_id))
|
||||||
|
|
||||||
for (const group of groups) {
|
for (const group of groups) {
|
||||||
for (const org of group.orgs) {
|
for (const org of group.orgs) {
|
||||||
const isActive = isActiveOrgChoice(active, { accountID: group.account.id, orgID: org.id })
|
const isActive = Option.isSome(activeOrgID) && activeOrgID.value === org.id
|
||||||
const dot = isActive ? UI.Style.TEXT_SUCCESS + "●" + UI.Style.TEXT_NORMAL : " "
|
const dot = isActive ? UI.Style.TEXT_SUCCESS + "●" + UI.Style.TEXT_NORMAL : " "
|
||||||
const name = isActive ? UI.Style.TEXT_HIGHLIGHT_BOLD + org.name + UI.Style.TEXT_NORMAL : org.name
|
const name = isActive ? UI.Style.TEXT_HIGHLIGHT_BOLD + org.name + UI.Style.TEXT_NORMAL : org.name
|
||||||
const email = UI.Style.TEXT_DIM + group.account.email + UI.Style.TEXT_NORMAL
|
const email = UI.Style.TEXT_DIM + group.account.email + UI.Style.TEXT_NORMAL
|
||||||
|
|||||||
@@ -167,8 +167,7 @@ export namespace ProviderError {
|
|||||||
|
|
||||||
export function parseAPICallError(input: { providerID: ProviderID; error: APICallError }): ParsedAPICallError {
|
export function parseAPICallError(input: { providerID: ProviderID; error: APICallError }): ParsedAPICallError {
|
||||||
const m = message(input.providerID, input.error)
|
const m = message(input.providerID, input.error)
|
||||||
const body = json(input.error.responseBody)
|
if (isOverflow(m) || input.error.statusCode === 413) {
|
||||||
if (isOverflow(m) || input.error.statusCode === 413 || body?.error?.code === "context_length_exceeded") {
|
|
||||||
return {
|
return {
|
||||||
type: "context_overflow",
|
type: "context_overflow",
|
||||||
message: m,
|
message: m,
|
||||||
|
|||||||
@@ -869,26 +869,6 @@ describe("session.message-v2.fromError", () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
test("detects context overflow from context_length_exceeded code in response body", () => {
|
|
||||||
const error = new APICallError({
|
|
||||||
message: "Request failed",
|
|
||||||
url: "https://example.com",
|
|
||||||
requestBodyValues: {},
|
|
||||||
statusCode: 422,
|
|
||||||
responseHeaders: { "content-type": "application/json" },
|
|
||||||
responseBody: JSON.stringify({
|
|
||||||
error: {
|
|
||||||
message: "Some message",
|
|
||||||
type: "invalid_request_error",
|
|
||||||
code: "context_length_exceeded",
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
isRetryable: false,
|
|
||||||
})
|
|
||||||
const result = MessageV2.fromError(error, { providerID })
|
|
||||||
expect(MessageV2.ContextOverflowError.isInstance(result)).toBe(true)
|
|
||||||
})
|
|
||||||
|
|
||||||
test("does not classify 429 no body as context overflow", () => {
|
test("does not classify 429 no body as context overflow", () => {
|
||||||
const result = MessageV2.fromError(
|
const result = MessageV2.fromError(
|
||||||
new APICallError({
|
new APICallError({
|
||||||
|
|||||||
@@ -6,13 +6,7 @@ import { useWorkerPool } from "../context/worker-pool"
|
|||||||
import { createDefaultOptions, styleVariables } from "../pierre"
|
import { createDefaultOptions, styleVariables } from "../pierre"
|
||||||
import { markCommentedDiffLines } from "../pierre/commented-lines"
|
import { markCommentedDiffLines } from "../pierre/commented-lines"
|
||||||
import { fixDiffSelection } from "../pierre/diff-selection"
|
import { fixDiffSelection } from "../pierre/diff-selection"
|
||||||
import {
|
import { clearReadyWatcher, createReadyWatcher, notifyShadowReady } from "../pierre/file-runtime"
|
||||||
applyViewerScheme,
|
|
||||||
clearReadyWatcher,
|
|
||||||
createReadyWatcher,
|
|
||||||
notifyShadowReady,
|
|
||||||
observeViewerScheme,
|
|
||||||
} from "../pierre/file-runtime"
|
|
||||||
import { acquireVirtualizer, virtualMetrics } from "../pierre/virtualizer"
|
import { acquireVirtualizer, virtualMetrics } from "../pierre/virtualizer"
|
||||||
import { File, type DiffFileProps, type FileProps } from "./file"
|
import { File, type DiffFileProps, type FileProps } from "./file"
|
||||||
|
|
||||||
@@ -87,8 +81,6 @@ function DiffSSRViewer<T>(props: SSRDiffFileProps<T>) {
|
|||||||
onMount(() => {
|
onMount(() => {
|
||||||
if (isServer) return
|
if (isServer) return
|
||||||
|
|
||||||
onCleanup(observeViewerScheme(() => fileDiffRef))
|
|
||||||
|
|
||||||
const virtualizer = getVirtualizer()
|
const virtualizer = getVirtualizer()
|
||||||
fileDiffInstance = virtualizer
|
fileDiffInstance = virtualizer
|
||||||
? new VirtualizedFileDiff<T>(
|
? new VirtualizedFileDiff<T>(
|
||||||
@@ -110,8 +102,6 @@ function DiffSSRViewer<T>(props: SSRDiffFileProps<T>) {
|
|||||||
workerPool,
|
workerPool,
|
||||||
)
|
)
|
||||||
|
|
||||||
applyViewerScheme(fileDiffRef)
|
|
||||||
|
|
||||||
// @ts-expect-error private field required for hydration
|
// @ts-expect-error private field required for hydration
|
||||||
fileDiffInstance.fileContainer = fileDiffRef
|
fileDiffInstance.fileContainer = fileDiffRef
|
||||||
fileDiffInstance.hydrate({
|
fileDiffInstance.hydrate({
|
||||||
|
|||||||
@@ -16,20 +16,12 @@ import {
|
|||||||
} from "@pierre/diffs"
|
} from "@pierre/diffs"
|
||||||
import { type PreloadMultiFileDiffResult } from "@pierre/diffs/ssr"
|
import { type PreloadMultiFileDiffResult } from "@pierre/diffs/ssr"
|
||||||
import { createMediaQuery } from "@solid-primitives/media"
|
import { createMediaQuery } from "@solid-primitives/media"
|
||||||
import { ComponentProps, createEffect, createMemo, createSignal, onCleanup, onMount, Show, splitProps } from "solid-js"
|
import { ComponentProps, createEffect, createMemo, createSignal, onCleanup, Show, splitProps } from "solid-js"
|
||||||
import { createDefaultOptions, styleVariables } from "../pierre"
|
import { createDefaultOptions, styleVariables } from "../pierre"
|
||||||
import { markCommentedDiffLines, markCommentedFileLines } from "../pierre/commented-lines"
|
import { markCommentedDiffLines, markCommentedFileLines } from "../pierre/commented-lines"
|
||||||
import { fixDiffSelection, findDiffSide, type DiffSelectionSide } from "../pierre/diff-selection"
|
import { fixDiffSelection, findDiffSide, type DiffSelectionSide } from "../pierre/diff-selection"
|
||||||
import { createFileFind } from "../pierre/file-find"
|
import { createFileFind } from "../pierre/file-find"
|
||||||
import {
|
import { clearReadyWatcher, createReadyWatcher, getViewerRoot, notifyShadowReady } from "../pierre/file-runtime"
|
||||||
applyViewerScheme,
|
|
||||||
clearReadyWatcher,
|
|
||||||
createReadyWatcher,
|
|
||||||
getViewerHost,
|
|
||||||
getViewerRoot,
|
|
||||||
notifyShadowReady,
|
|
||||||
observeViewerScheme,
|
|
||||||
} from "../pierre/file-runtime"
|
|
||||||
import {
|
import {
|
||||||
findCodeSelectionSide,
|
findCodeSelectionSide,
|
||||||
findDiffLineNumber,
|
findDiffLineNumber,
|
||||||
@@ -154,7 +146,6 @@ function useFileViewer(config: ViewerConfig) {
|
|||||||
const [rendered, setRendered] = createSignal(0)
|
const [rendered, setRendered] = createSignal(0)
|
||||||
|
|
||||||
const getRoot = () => getViewerRoot(container)
|
const getRoot = () => getViewerRoot(container)
|
||||||
const getHost = () => getViewerHost(container)
|
|
||||||
|
|
||||||
const find = createFileFind({
|
const find = createFileFind({
|
||||||
wrapper: () => wrapper,
|
wrapper: () => wrapper,
|
||||||
@@ -265,10 +256,6 @@ function useFileViewer(config: ViewerConfig) {
|
|||||||
|
|
||||||
// -- shared effects --
|
// -- shared effects --
|
||||||
|
|
||||||
onMount(() => {
|
|
||||||
onCleanup(observeViewerScheme(getHost))
|
|
||||||
})
|
|
||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
rendered()
|
rendered()
|
||||||
const ranges = config.commentedLines()
|
const ranges = config.commentedLines()
|
||||||
@@ -351,7 +338,6 @@ function useFileViewer(config: ViewerConfig) {
|
|||||||
rendered,
|
rendered,
|
||||||
setRendered,
|
setRendered,
|
||||||
getRoot,
|
getRoot,
|
||||||
getHost,
|
|
||||||
find,
|
find,
|
||||||
scheduleSelectionUpdate,
|
scheduleSelectionUpdate,
|
||||||
}
|
}
|
||||||
@@ -486,7 +472,6 @@ function renderViewer<I extends RenderTarget>(opts: {
|
|||||||
opts.viewer.container.innerHTML = ""
|
opts.viewer.container.innerHTML = ""
|
||||||
opts.draw(next)
|
opts.draw(next)
|
||||||
|
|
||||||
applyViewerScheme(opts.viewer.getHost())
|
|
||||||
opts.viewer.setRendered((value) => value + 1)
|
opts.viewer.setRendered((value) => value + 1)
|
||||||
opts.onReady()
|
opts.onReady()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1050,8 +1050,18 @@
|
|||||||
line-height: var(--line-height-large);
|
line-height: var(--line-height-large);
|
||||||
color: var(--text-base);
|
color: var(--text-base);
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
overflow-wrap: anywhere;
|
overflow: hidden;
|
||||||
white-space: normal;
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-slot="question-option"][data-custom="true"] {
|
||||||
|
[data-slot="option-description"] {
|
||||||
|
overflow: visible;
|
||||||
|
text-overflow: clip;
|
||||||
|
white-space: normal;
|
||||||
|
overflow-wrap: anywhere;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-slot="question-custom"] {
|
[data-slot="question-custom"] {
|
||||||
|
|||||||
@@ -23,31 +23,6 @@ export function getViewerRoot(container: HTMLElement | undefined) {
|
|||||||
return getViewerHost(container)?.shadowRoot ?? undefined
|
return getViewerHost(container)?.shadowRoot ?? undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
export function applyViewerScheme(host: HTMLElement | undefined) {
|
|
||||||
if (!host) return
|
|
||||||
if (typeof document === "undefined") return
|
|
||||||
|
|
||||||
const scheme = document.documentElement.dataset.colorScheme
|
|
||||||
if (scheme === "dark" || scheme === "light") {
|
|
||||||
host.dataset.colorScheme = scheme
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
host.removeAttribute("data-color-scheme")
|
|
||||||
}
|
|
||||||
|
|
||||||
export function observeViewerScheme(getHost: () => HTMLElement | undefined) {
|
|
||||||
if (typeof document === "undefined") return () => {}
|
|
||||||
|
|
||||||
applyViewerScheme(getHost())
|
|
||||||
if (typeof MutationObserver === "undefined") return () => {}
|
|
||||||
|
|
||||||
const root = document.documentElement
|
|
||||||
const monitor = new MutationObserver(() => applyViewerScheme(getHost()))
|
|
||||||
monitor.observe(root, { attributes: true, attributeFilter: ["data-color-scheme"] })
|
|
||||||
return () => monitor.disconnect()
|
|
||||||
}
|
|
||||||
|
|
||||||
export function notifyShadowReady(opts: {
|
export function notifyShadowReady(opts: {
|
||||||
state: ReadyWatcher
|
state: ReadyWatcher
|
||||||
container: HTMLElement
|
container: HTMLElement
|
||||||
|
|||||||
@@ -37,26 +37,19 @@ const unsafeCSS = `
|
|||||||
--diffs-bg-addition-emphasis: var(--diffs-bg-addition-emphasis-override, light-dark(rgb(from var(--diffs-addition-base) r g b / 0.07), rgb(from var(--diffs-addition-base) r g b / 0.1)));
|
--diffs-bg-addition-emphasis: var(--diffs-bg-addition-emphasis-override, light-dark(rgb(from var(--diffs-addition-base) r g b / 0.07), rgb(from var(--diffs-addition-base) r g b / 0.1)));
|
||||||
--diffs-selection-base: var(--surface-warning-strong);
|
--diffs-selection-base: var(--surface-warning-strong);
|
||||||
--diffs-selection-border: var(--border-warning-base);
|
--diffs-selection-border: var(--border-warning-base);
|
||||||
--diffs-selection-number-fg: #1c1917;
|
--diffs-selection-number-fg: light-dark(#1c1917, #fdfbfb);
|
||||||
/* Use explicit alpha instead of color-mix(..., transparent) to avoid Safari's non-premultiplied interpolation bugs. */
|
/* Use explicit alpha instead of color-mix(..., transparent) to avoid Safari's non-premultiplied interpolation bugs. */
|
||||||
--diffs-bg-selection: var(--diffs-bg-selection-override, rgb(from var(--surface-warning-base) r g b / 0.65));
|
--diffs-bg-selection: var(
|
||||||
|
--diffs-bg-selection-override,
|
||||||
|
light-dark(rgb(from var(--surface-warning-base) r g b / 0.65), rgb(from var(--solaris-dark-6) r g b / 0.65))
|
||||||
|
);
|
||||||
--diffs-bg-selection-number: var(
|
--diffs-bg-selection-number: var(
|
||||||
--diffs-bg-selection-number-override,
|
--diffs-bg-selection-number-override,
|
||||||
rgb(from var(--surface-warning-base) r g b / 0.85)
|
light-dark(rgb(from var(--surface-warning-base) r g b / 0.85), rgb(from var(--solaris-dark-6) r g b / 0.85))
|
||||||
);
|
);
|
||||||
--diffs-bg-selection-text: rgb(from var(--surface-warning-strong) r g b / 0.2);
|
--diffs-bg-selection-text: rgb(from var(--surface-warning-strong) r g b / 0.2);
|
||||||
}
|
}
|
||||||
|
|
||||||
:host([data-color-scheme='dark']) [data-diff],
|
|
||||||
:host([data-color-scheme='dark']) [data-file] {
|
|
||||||
--diffs-selection-number-fg: #fdfbfb;
|
|
||||||
--diffs-bg-selection: var(--diffs-bg-selection-override, rgb(from var(--solaris-dark-6) r g b / 0.65));
|
|
||||||
--diffs-bg-selection-number: var(
|
|
||||||
--diffs-bg-selection-number-override,
|
|
||||||
rgb(from var(--solaris-dark-6) r g b / 0.85)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-diff] ::selection,
|
[data-diff] ::selection,
|
||||||
[data-file] ::selection {
|
[data-file] ::selection {
|
||||||
background-color: var(--diffs-bg-selection-text);
|
background-color: var(--diffs-bg-selection-text);
|
||||||
|
|||||||
Reference in New Issue
Block a user