Files
openchamber/packages/ui/src/lib/sessionTabs.ts
T
Bohdan Triapitsyn f2ec9b1003 feat(ui): add session history navigation, permission keys, and review shortcuts
mod+alt+arrows step through this window's session-open history (or between
neighbouring tabs when session tabs are on), mod+k r renames the current
session inline, and mod+k a toggles permission auto-accept. Pending
permission cards respond to alt+enter / alt+shift+enter / alt+backspace with
the keys printed on the buttons. The commit message box commits on
mod+enter, alt+arrows step the diff review between changed files, and the
command palette gains search-only commands for rare actions so the initial
list stays short.
2026-08-26 15:46:44 +03:00

73 lines
3.3 KiB
TypeScript

import { useSessionTabsStore } from '@/stores/useSessionTabsStore';
import { useGlobalSessionsStore, resolveGlobalSessionDirectory } from '@/stores/useGlobalSessionsStore';
import { useSessionUIStore } from '@/sync/session-ui-store';
/**
* Close one header session tab. Closing the active tab activates its right
* neighbour (falling back left), or opens a new-session draft when it was the
* last tab. Only tabs whose session is present in the loaded session list
* count as neighbours — the same rule the strip uses for rendering. The
* session itself is never touched.
*/
/**
* Activate the nth (0-based) header session tab, counting only tabs whose
* session is present in the loaded session list — the same rule the strip
* uses for rendering, so the digit matches what the user sees.
*/
export const activateSessionTabByIndex = (index: number): boolean => {
const { tabIds } = useSessionTabsStore.getState();
const sessionsById = new Map(
useGlobalSessionsStore.getState().activeSessions.map((session) => [session.id, session] as const),
);
const renderable = tabIds.filter((id) => sessionsById.has(id));
const session = renderable[index] ? sessionsById.get(renderable[index]) : null;
if (!session) return false;
useSessionUIStore.getState().setCurrentSession(session.id, resolveGlobalSessionDirectory(session));
return true;
};
/**
* Activate the tab one step right (+1) or left (-1) of the current session
* in the rendered strip order, wrapping around the ends. Returns false when
* the current session has no tab or there is nothing to move to.
*/
export const activateAdjacentSessionTab = (delta: -1 | 1): boolean => {
const { tabIds } = useSessionTabsStore.getState();
const { currentSessionId, setCurrentSession } = useSessionUIStore.getState();
const sessionsById = new Map(
useGlobalSessionsStore.getState().activeSessions.map((session) => [session.id, session] as const),
);
const renderable = tabIds.filter((id) => sessionsById.has(id));
if (!currentSessionId || renderable.length < 2) return false;
const index = renderable.indexOf(currentSessionId);
if (index === -1) return false;
const nextId = renderable[(index + delta + renderable.length) % renderable.length];
const next = sessionsById.get(nextId);
if (!next) return false;
setCurrentSession(next.id, resolveGlobalSessionDirectory(next));
return true;
};
export const closeSessionTabAndActivateNeighbour = (sessionId: string): void => {
const { tabIds, closeTab } = useSessionTabsStore.getState();
if (!tabIds.includes(sessionId)) return;
const { currentSessionId, setCurrentSession, openNewSessionDraft } = useSessionUIStore.getState();
if (sessionId === currentSessionId) {
const sessionsById = new Map(
useGlobalSessionsStore.getState().activeSessions.map((session) => [session.id, session] as const),
);
const renderable = tabIds.filter((id) => sessionsById.has(id));
const index = renderable.indexOf(sessionId);
const neighbourId = renderable[index + 1] ?? renderable[index - 1] ?? null;
const neighbour = neighbourId ? sessionsById.get(neighbourId) : null;
if (neighbour) {
setCurrentSession(neighbour.id, resolveGlobalSessionDirectory(neighbour));
} else {
openNewSessionDraft();
}
}
closeTab(sessionId);
};