diff --git a/packages/app/src/home/sessions/controller.tsx b/packages/app/src/home/sessions/controller.tsx index 7f8099e0d21..571beeeffd2 100644 --- a/packages/app/src/home/sessions/controller.tsx +++ b/packages/app/src/home/sessions/controller.tsx @@ -231,9 +231,16 @@ export function createHomeSessionsController(home: HomeController) { if (!conn || !ctx) return false const ids = sessionTreeIDs(ctx.data.session.list(), session.id) + const rows = homeRowTops() + // session.deleted clears the local hide mutation before the refetch lands, + // so the cached index must drop the row now or it briefly reappears. + queryClient.setQueryData(["home-sessions", conn], (current) => + current?.filter((item) => item.id !== session.id), + ) + const request = ctx.data.session.remove(session.id) + animateHomeRowsUp(rows) - return ctx.data.session - .remove(session.id) + return request .then(() => { notifySessionTabsRemoved({ server: ServerConnection.key(conn), @@ -260,8 +267,8 @@ export function createHomeSessionsController(home: HomeController) { const name = () => sessionTitle(props.session.title) ?? language.t("command.session.new") const confirm = async () => { - await remove(props.server, props.session) dialog.close() + await remove(props.server, props.session) } return ( @@ -381,6 +388,31 @@ function directories(project: LocalProject) { return [project.worktree, ...(project.sandboxes ?? [])] } +const HOME_ROW_SELECTOR = '[data-component="home-session-row-container"]' + +function homeRowTops() { + return new Map( + Array.from(document.querySelectorAll(HOME_ROW_SELECTOR), (row) => { + return [row.dataset.sessionId, row.getBoundingClientRect().top] as const + }), + ) +} + +// FLIP: rows below a removed row jump up in layout; play the difference back as a short transform. +function animateHomeRowsUp(tops: Map) { + if (matchMedia("(prefers-reduced-motion: reduce)").matches) return + + document.querySelectorAll(HOME_ROW_SELECTOR).forEach((row) => { + const before = tops.get(row.dataset.sessionId) + const delta = before === undefined ? 0 : before - row.getBoundingClientRect().top + if (delta === 0) return + row.animate([{ transform: `translateY(${delta}px)` }, { transform: "translateY(0)" }], { + duration: 180, + easing: "cubic-bezier(0.645, 0.045, 0.355, 1)", + }) + }) +} + export function homeSessionSearchKey(record: HomeSessionRecord) { return `${pathKey(record.session.location.directory)}:${record.session.id}` } diff --git a/packages/app/src/home/sessions/view.tsx b/packages/app/src/home/sessions/view.tsx index adb7f0a06c4..26fc5b57fef 100644 --- a/packages/app/src/home/sessions/view.tsx +++ b/packages/app/src/home/sessions/view.tsx @@ -620,8 +620,8 @@ function HomeSessionRow( fallback={
@@ -678,11 +678,11 @@ function HomeSessionRow( aria-expanded={!!menu()} class={` flex h-10 min-w-0 w-full flex-1 shrink-0 cursor-default items-center rounded-[6px] border-0 - bg-transparent py-3 ps-1.5 pe-3 md:ps-3 md:pe-10 text-start text-v2-text-text-muted [font-weight:530] + bg-transparent py-3 ps-1.5 pe-3 md:ps-3 text-start text-v2-text-text-muted [font-weight:530] transition-[background-color,color,box-shadow] duration-[120ms] ease-in-out hover:bg-v2-overlay-simple-overlay-hover focus-visible:bg-v2-overlay-simple-overlay-hover focus-visible:outline-none `} - classList={{ "gap-1.5": props.desktop, "gap-2": !props.desktop }} + classList={{ "gap-1.5": props.desktop, "gap-2": !props.desktop, "md:pe-10": SHOW_HOME_SESSION_ARCHIVE }} onMouseDown={(event) => { if (event.button === 1) event.preventDefault() }}