mirror of
https://github.com/anomalyco/opencode.git
synced 2026-11-14 03:56:55 +03:00
fix(app): keep inbox scroll position when opening a session (#60356)
This commit is contained in:
@@ -4,6 +4,7 @@ import {
|
||||
NO_PROVIDER,
|
||||
REMOTE_SERVER,
|
||||
SERVER,
|
||||
T0,
|
||||
draftHref,
|
||||
expectPath,
|
||||
holdRoute,
|
||||
@@ -1356,3 +1357,43 @@ async function emptySidePanel(page: Page) {
|
||||
|
||||
return { beta, alphaTab: page.locator(`[data-titlebar-tab-slot]:has(a[href="${sessionHref(alpha.id)}"])`) }
|
||||
}
|
||||
|
||||
test("opening a session from the inbox keeps the list's scroll position and rows", async ({ page }) => {
|
||||
await page.setViewportSize({ width: 1280, height: 480 })
|
||||
|
||||
const sessions = Array.from({ length: 24 }, (_, index) => ({
|
||||
id: `ses_inbox_${String(index).padStart(2, "0")}`,
|
||||
title: `Inbox session ${index}`,
|
||||
time: { created: T0, updated: T0 + index, idle: T0 + index },
|
||||
}))
|
||||
|
||||
await mockWorkspace(page, {
|
||||
name: "Inbox",
|
||||
sessions,
|
||||
seed: {
|
||||
settings: { appearance: { tabLayout: "inbox" } },
|
||||
tabs: [],
|
||||
// Every seeded session finished a run after the inbox was first used, so Recent lists them all.
|
||||
storage: { "opencode.global.dat:sidebar.inbox": { workingExpanded: true, since: 0 } },
|
||||
},
|
||||
})
|
||||
await page.goto("/")
|
||||
|
||||
const list = page.locator('[data-slot="sidebar-inbox-scroll"]')
|
||||
const items = list.locator('[data-slot="sidebar-inbox-item"]')
|
||||
const target = items.filter({ hasText: "Inbox session 0" })
|
||||
await expect(items).toHaveCount(sessions.length)
|
||||
await list.evaluate((element) => element.scrollTo(0, element.scrollHeight))
|
||||
await expect(target).toBeInViewport({ ratio: 1 })
|
||||
const offset = await list.evaluate((element) => element.scrollTop)
|
||||
expect(offset).toBeGreaterThan(0)
|
||||
const row = await target.elementHandle()
|
||||
|
||||
await target.click()
|
||||
await expectPath(page, sessionHref("ses_inbox_00"))
|
||||
await expect(target).toHaveAttribute("data-state", "active")
|
||||
// Opening a session loads it into the client, which updates every section.
|
||||
await expect(page.locator('[data-component="composer-editor"]')).toBeEditable()
|
||||
expect(await target.evaluate((element, before) => element === before, row)).toBe(true)
|
||||
expect(await list.evaluate((element) => element.scrollTop)).toBe(offset)
|
||||
})
|
||||
|
||||
@@ -321,6 +321,12 @@ const Group = HttpApiGroup.make("mock")
|
||||
success: NoContent,
|
||||
}),
|
||||
)
|
||||
.add(
|
||||
HttpApiEndpoint.post("sessionView", "/api/session/:sessionID/view", {
|
||||
params: SessionParams,
|
||||
success: NoContent,
|
||||
}),
|
||||
)
|
||||
.add(
|
||||
HttpApiEndpoint.post("sessionShell", "/api/session/:sessionID/shell", {
|
||||
params: SessionParams,
|
||||
|
||||
@@ -1315,6 +1315,7 @@ function mockHandlers(
|
||||
}),
|
||||
sessionRemove: () => noContent,
|
||||
sessionShell: () => noContent,
|
||||
sessionView: () => noContent,
|
||||
sessionForm: (ctx) =>
|
||||
Effect.succeed({
|
||||
data: resolve(config.forms ?? []).filter(
|
||||
|
||||
@@ -103,8 +103,10 @@ export function SidebarInbox() {
|
||||
)
|
||||
}
|
||||
|
||||
const rows = (list: InboxEntry[]) => (
|
||||
<For each={list.map((entry) => entry.key)}>
|
||||
// Takes an accessor so only the For tracks the section. Reading it here would recreate the For, remounting every
|
||||
// row on any session change and resetting the list's scroll.
|
||||
const rows = (list: () => InboxEntry[]) => (
|
||||
<For each={list().map((entry) => entry.key)}>
|
||||
{(key) => (
|
||||
<Show when={inbox.byKey().get(key)}>
|
||||
{(entry) => (
|
||||
@@ -133,7 +135,7 @@ export function SidebarInbox() {
|
||||
<Show when={sections().needs.length > 0}>
|
||||
<div class="flex shrink-0 flex-col gap-px pb-3">
|
||||
<SectionHeader label={language.t("sidebar.inbox.section.needsAttention")} count={sections().needs.length} />
|
||||
{rows(sections().needs)}
|
||||
{rows(() => sections().needs)}
|
||||
</div>
|
||||
</Show>
|
||||
<Show
|
||||
@@ -153,12 +155,12 @@ export function SidebarInbox() {
|
||||
<Show when={sections().needs.length > 0}>
|
||||
<SectionHeader label={language.t("sidebar.inbox.section.recent")} />
|
||||
</Show>
|
||||
{rows(sections().inbox)}
|
||||
{rows(() => sections().inbox)}
|
||||
</Show>
|
||||
<Show when={sections().archived.length > 0}>
|
||||
<div class="flex shrink-0 flex-col gap-px pt-3">
|
||||
<SectionHeader label={language.t("sidebar.inbox.section.archived")} />
|
||||
{rows(sections().archived)}
|
||||
{rows(() => sections().archived)}
|
||||
</div>
|
||||
</Show>
|
||||
</div>
|
||||
@@ -176,7 +178,7 @@ export function SidebarInbox() {
|
||||
/>
|
||||
<div class="flex min-h-0 flex-col gap-px overflow-y-auto">
|
||||
{/* A collapsed shelf still shows the session that is open. */}
|
||||
{rows(sections().working.filter((entry) => state.workingExpanded || inbox.selected(entry)))}
|
||||
{rows(() => sections().working.filter((entry) => state.workingExpanded || inbox.selected(entry)))}
|
||||
</div>
|
||||
</div>
|
||||
</Show>
|
||||
|
||||
Reference in New Issue
Block a user