From 52eb8a7a8c2ceefa0de8a1a37d5f8754f08cfcff Mon Sep 17 00:00:00 2001 From: Brendan Allan Date: Mon, 2 Feb 2026 19:05:47 +0800 Subject: [PATCH] feat(app): unread session navigation keybinds (#11750) --- packages/app/src/i18n/ar.ts | 2 ++ packages/app/src/i18n/br.ts | 2 ++ packages/app/src/i18n/da.ts | 2 ++ packages/app/src/i18n/de.ts | 2 ++ packages/app/src/i18n/en.ts | 2 ++ packages/app/src/i18n/es.ts | 2 ++ packages/app/src/i18n/fr.ts | 2 ++ packages/app/src/i18n/ja.ts | 2 ++ packages/app/src/i18n/ko.ts | 2 ++ packages/app/src/i18n/no.ts | 2 ++ packages/app/src/i18n/pl.ts | 2 ++ packages/app/src/i18n/ru.ts | 2 ++ packages/app/src/i18n/th.ts | 2 ++ packages/app/src/i18n/zh.ts | 2 ++ packages/app/src/i18n/zht.ts | 2 ++ packages/app/src/pages/layout.tsx | 60 +++++++++++++++++++++++++++++++ 16 files changed, 90 insertions(+) diff --git a/packages/app/src/i18n/ar.ts b/packages/app/src/i18n/ar.ts index e3831e23c..3718303e5 100644 --- a/packages/app/src/i18n/ar.ts +++ b/packages/app/src/i18n/ar.ts @@ -28,6 +28,8 @@ export const dict = { "command.settings.open": "فتح الإعدادات", "command.session.previous": "الجلسة السابقة", "command.session.next": "الجلسة التالية", + "command.session.previous.unseen": "Previous unread session", + "command.session.next.unseen": "Next unread session", "command.session.archive": "أرشفة الجلسة", "command.palette": "لوحة الأوامر", diff --git a/packages/app/src/i18n/br.ts b/packages/app/src/i18n/br.ts index f930a66af..43336f8d6 100644 --- a/packages/app/src/i18n/br.ts +++ b/packages/app/src/i18n/br.ts @@ -28,6 +28,8 @@ export const dict = { "command.settings.open": "Abrir configurações", "command.session.previous": "Sessão anterior", "command.session.next": "Próxima sessão", + "command.session.previous.unseen": "Previous unread session", + "command.session.next.unseen": "Next unread session", "command.session.archive": "Arquivar sessão", "command.palette": "Paleta de comandos", diff --git a/packages/app/src/i18n/da.ts b/packages/app/src/i18n/da.ts index 2b7d77456..69e8e8114 100644 --- a/packages/app/src/i18n/da.ts +++ b/packages/app/src/i18n/da.ts @@ -28,6 +28,8 @@ export const dict = { "command.settings.open": "Åbn indstillinger", "command.session.previous": "Forrige session", "command.session.next": "Næste session", + "command.session.previous.unseen": "Previous unread session", + "command.session.next.unseen": "Next unread session", "command.session.archive": "Arkivér session", "command.palette": "Kommandopalette", diff --git a/packages/app/src/i18n/de.ts b/packages/app/src/i18n/de.ts index 4648ad9c4..1c28e4a16 100644 --- a/packages/app/src/i18n/de.ts +++ b/packages/app/src/i18n/de.ts @@ -32,6 +32,8 @@ export const dict = { "command.settings.open": "Einstellungen öffnen", "command.session.previous": "Vorherige Sitzung", "command.session.next": "Nächste Sitzung", + "command.session.previous.unseen": "Previous unread session", + "command.session.next.unseen": "Next unread session", "command.session.archive": "Sitzung archivieren", "command.palette": "Befehlspalette", diff --git a/packages/app/src/i18n/en.ts b/packages/app/src/i18n/en.ts index 12ddcb4cd..5589337e5 100644 --- a/packages/app/src/i18n/en.ts +++ b/packages/app/src/i18n/en.ts @@ -28,6 +28,8 @@ export const dict = { "command.settings.open": "Open settings", "command.session.previous": "Previous session", "command.session.next": "Next session", + "command.session.previous.unseen": "Previous unread session", + "command.session.next.unseen": "Next unread session", "command.session.archive": "Archive session", "command.palette": "Command palette", diff --git a/packages/app/src/i18n/es.ts b/packages/app/src/i18n/es.ts index 5d396f0b4..6e3eac0dd 100644 --- a/packages/app/src/i18n/es.ts +++ b/packages/app/src/i18n/es.ts @@ -28,6 +28,8 @@ export const dict = { "command.settings.open": "Abrir ajustes", "command.session.previous": "Sesión anterior", "command.session.next": "Siguiente sesión", + "command.session.previous.unseen": "Previous unread session", + "command.session.next.unseen": "Next unread session", "command.session.archive": "Archivar sesión", "command.palette": "Paleta de comandos", diff --git a/packages/app/src/i18n/fr.ts b/packages/app/src/i18n/fr.ts index 4226d0c7e..fa3dccd9a 100644 --- a/packages/app/src/i18n/fr.ts +++ b/packages/app/src/i18n/fr.ts @@ -28,6 +28,8 @@ export const dict = { "command.settings.open": "Ouvrir les paramètres", "command.session.previous": "Session précédente", "command.session.next": "Session suivante", + "command.session.previous.unseen": "Previous unread session", + "command.session.next.unseen": "Next unread session", "command.session.archive": "Archiver la session", "command.palette": "Palette de commandes", diff --git a/packages/app/src/i18n/ja.ts b/packages/app/src/i18n/ja.ts index 28a925a0d..4fccbd77e 100644 --- a/packages/app/src/i18n/ja.ts +++ b/packages/app/src/i18n/ja.ts @@ -28,6 +28,8 @@ export const dict = { "command.settings.open": "設定を開く", "command.session.previous": "前のセッション", "command.session.next": "次のセッション", + "command.session.previous.unseen": "Previous unread session", + "command.session.next.unseen": "Next unread session", "command.session.archive": "セッションをアーカイブ", "command.palette": "コマンドパレット", diff --git a/packages/app/src/i18n/ko.ts b/packages/app/src/i18n/ko.ts index 1be4e1eb4..5b5d29c0e 100644 --- a/packages/app/src/i18n/ko.ts +++ b/packages/app/src/i18n/ko.ts @@ -32,6 +32,8 @@ export const dict = { "command.settings.open": "설정 열기", "command.session.previous": "이전 세션", "command.session.next": "다음 세션", + "command.session.previous.unseen": "Previous unread session", + "command.session.next.unseen": "Next unread session", "command.session.archive": "세션 보관", "command.palette": "명령 팔레트", diff --git a/packages/app/src/i18n/no.ts b/packages/app/src/i18n/no.ts index 0a3b39885..89614ce85 100644 --- a/packages/app/src/i18n/no.ts +++ b/packages/app/src/i18n/no.ts @@ -31,6 +31,8 @@ export const dict = { "command.settings.open": "Åpne innstillinger", "command.session.previous": "Forrige sesjon", "command.session.next": "Neste sesjon", + "command.session.previous.unseen": "Previous unread session", + "command.session.next.unseen": "Next unread session", "command.session.archive": "Arkiver sesjon", "command.palette": "Kommandopalett", diff --git a/packages/app/src/i18n/pl.ts b/packages/app/src/i18n/pl.ts index f4457c6ac..b89921a9b 100644 --- a/packages/app/src/i18n/pl.ts +++ b/packages/app/src/i18n/pl.ts @@ -28,6 +28,8 @@ export const dict = { "command.settings.open": "Otwórz ustawienia", "command.session.previous": "Poprzednia sesja", "command.session.next": "Następna sesja", + "command.session.previous.unseen": "Previous unread session", + "command.session.next.unseen": "Next unread session", "command.session.archive": "Zarchiwizuj sesję", "command.palette": "Paleta poleceń", diff --git a/packages/app/src/i18n/ru.ts b/packages/app/src/i18n/ru.ts index d5a4014d3..e99abbd08 100644 --- a/packages/app/src/i18n/ru.ts +++ b/packages/app/src/i18n/ru.ts @@ -28,6 +28,8 @@ export const dict = { "command.settings.open": "Открыть настройки", "command.session.previous": "Предыдущая сессия", "command.session.next": "Следующая сессия", + "command.session.previous.unseen": "Previous unread session", + "command.session.next.unseen": "Next unread session", "command.session.archive": "Архивировать сессию", "command.palette": "Палитра команд", diff --git a/packages/app/src/i18n/th.ts b/packages/app/src/i18n/th.ts index 1914b8e5b..0da6f9acc 100644 --- a/packages/app/src/i18n/th.ts +++ b/packages/app/src/i18n/th.ts @@ -28,6 +28,8 @@ export const dict = { "command.settings.open": "เปิดการตั้งค่า", "command.session.previous": "เซสชันก่อนหน้า", "command.session.next": "เซสชันถัดไป", + "command.session.previous.unseen": "Previous unread session", + "command.session.next.unseen": "Next unread session", "command.session.archive": "จัดเก็บเซสชัน", "command.palette": "คำสั่งค้นหา", diff --git a/packages/app/src/i18n/zh.ts b/packages/app/src/i18n/zh.ts index b9d539573..a7e1659ec 100644 --- a/packages/app/src/i18n/zh.ts +++ b/packages/app/src/i18n/zh.ts @@ -32,6 +32,8 @@ export const dict = { "command.settings.open": "打开设置", "command.session.previous": "上一个会话", "command.session.next": "下一个会话", + "command.session.previous.unseen": "Previous unread session", + "command.session.next.unseen": "Next unread session", "command.session.archive": "归档会话", "command.palette": "命令面板", diff --git a/packages/app/src/i18n/zht.ts b/packages/app/src/i18n/zht.ts index 23d3d80e1..7b8849b9a 100644 --- a/packages/app/src/i18n/zht.ts +++ b/packages/app/src/i18n/zht.ts @@ -32,6 +32,8 @@ export const dict = { "command.settings.open": "開啟設定", "command.session.previous": "上一個工作階段", "command.session.next": "下一個工作階段", + "command.session.previous.unseen": "Previous unread session", + "command.session.next.unseen": "Next unread session", "command.session.archive": "封存工作階段", "command.palette": "命令面板", diff --git a/packages/app/src/pages/layout.tsx b/packages/app/src/pages/layout.tsx index 845a4fc83..a970bf667 100644 --- a/packages/app/src/pages/layout.tsx +++ b/packages/app/src/pages/layout.tsx @@ -886,6 +886,52 @@ export default function Layout(props: ParentProps) { queueMicrotask(() => scrollToSession(session.id, `${session.directory}:${session.id}`)) } + function navigateSessionByUnseen(offset: number) { + const sessions = currentSessions() + if (sessions.length === 0) return + + const hasUnseen = sessions.some((session) => notification.session.unseen(session.id).length > 0) + if (!hasUnseen) return + + const activeIndex = params.id ? sessions.findIndex((s) => s.id === params.id) : -1 + const start = activeIndex === -1 ? (offset > 0 ? -1 : 0) : activeIndex + + for (let i = 1; i <= sessions.length; i++) { + const index = offset > 0 ? (start + i) % sessions.length : (start - i + sessions.length) % sessions.length + const session = sessions[index] + if (!session) continue + if (notification.session.unseen(session.id).length === 0) continue + + prefetchSession(session, "high") + + const next = sessions[(index + 1) % sessions.length] + const prev = sessions[(index - 1 + sessions.length) % sessions.length] + + if (offset > 0) { + if (next) prefetchSession(next, "high") + if (prev) prefetchSession(prev) + } + + if (offset < 0) { + if (prev) prefetchSession(prev, "high") + if (next) prefetchSession(next) + } + + if (import.meta.env.DEV) { + navStart({ + dir: base64Encode(session.directory), + from: params.id, + to: session.id, + trigger: offset > 0 ? "shift+alt+arrowdown" : "shift+alt+arrowup", + }) + } + + navigateToSession(session) + queueMicrotask(() => scrollToSession(session.id, `${session.directory}:${session.id}`)) + return + } + } + async function archiveSession(session: Session) { const [store, setStore] = globalSync.child(session.directory) const sessions = store.session ?? [] @@ -1024,6 +1070,20 @@ export default function Layout(props: ParentProps) { keybind: "alt+arrowdown", onSelect: () => navigateSessionByOffset(1), }, + { + id: "session.previous.unseen", + title: language.t("command.session.previous.unseen"), + category: language.t("command.category.session"), + keybind: "shift+alt+arrowup", + onSelect: () => navigateSessionByUnseen(-1), + }, + { + id: "session.next.unseen", + title: language.t("command.session.next.unseen"), + category: language.t("command.category.session"), + keybind: "shift+alt+arrowdown", + onSelect: () => navigateSessionByUnseen(1), + }, { id: "session.archive", title: language.t("command.session.archive"),