From 267b296f13bdde1f14b15f99fc0f2bf1fa5869f9 Mon Sep 17 00:00:00 2001
From: adamelmore <2363879+adamdottv@users.noreply.github.com>
Date: Sat, 14 Feb 2026 19:25:55 -0600
Subject: [PATCH] feat(app): clear notifications action
---
packages/app/e2e/selectors.ts | 3 +
packages/app/src/i18n/ar.ts | 1 +
packages/app/src/i18n/br.ts | 1 +
packages/app/src/i18n/bs.ts | 1 +
packages/app/src/i18n/da.ts | 1 +
packages/app/src/i18n/de.ts | 1 +
packages/app/src/i18n/en.ts | 1 +
packages/app/src/i18n/es.ts | 1 +
packages/app/src/i18n/fr.ts | 1 +
packages/app/src/i18n/ja.ts | 1 +
packages/app/src/i18n/ko.ts | 1 +
packages/app/src/i18n/no.ts | 1 +
packages/app/src/i18n/pl.ts | 1 +
packages/app/src/i18n/ru.ts | 1 +
packages/app/src/i18n/th.ts | 1 +
packages/app/src/i18n/zh.ts | 1 +
packages/app/src/i18n/zht.ts | 1 +
packages/app/src/pages/layout.tsx | 17 ++
.../app/src/pages/layout/sidebar-project.tsx | 155 ++++++++++--------
19 files changed, 126 insertions(+), 65 deletions(-)
diff --git a/packages/app/e2e/selectors.ts b/packages/app/e2e/selectors.ts
index 52c9007ea19..1a0afbab102 100644
--- a/packages/app/e2e/selectors.ts
+++ b/packages/app/e2e/selectors.ts
@@ -30,6 +30,9 @@ export const projectMenuTriggerSelector = (slug: string) =>
export const projectCloseMenuSelector = (slug: string) => `[data-action="project-close-menu"][data-project="${slug}"]`
+export const projectClearNotificationsSelector = (slug: string) =>
+ `[data-action="project-clear-notifications"][data-project="${slug}"]`
+
export const projectWorkspacesToggleSelector = (slug: string) =>
`[data-action="project-workspaces-toggle"][data-project="${slug}"]`
diff --git a/packages/app/src/i18n/ar.ts b/packages/app/src/i18n/ar.ts
index e3792a3c3cc..81cc92bf6de 100644
--- a/packages/app/src/i18n/ar.ts
+++ b/packages/app/src/i18n/ar.ts
@@ -509,6 +509,7 @@ export const dict = {
"sidebar.gettingStarted.line2": "قم بتوصيل أي موفر لاستخدام النماذج، بما في ذلك Claude و GPT و Gemini وما إلى ذلك.",
"sidebar.project.recentSessions": "الجلسات الحديثة",
"sidebar.project.viewAllSessions": "عرض جميع الجلسات",
+ "sidebar.project.clearNotifications": "مسح الإشعارات",
"app.name.desktop": "OpenCode Desktop",
"settings.section.desktop": "سطح المكتب",
"settings.section.server": "الخادم",
diff --git a/packages/app/src/i18n/br.ts b/packages/app/src/i18n/br.ts
index 07d6ce467ae..9ed3a9fc6fc 100644
--- a/packages/app/src/i18n/br.ts
+++ b/packages/app/src/i18n/br.ts
@@ -515,6 +515,7 @@ export const dict = {
"sidebar.gettingStarted.line2": "Conecte qualquer provedor para usar modelos, incluindo Claude, GPT, Gemini etc.",
"sidebar.project.recentSessions": "Sessões recentes",
"sidebar.project.viewAllSessions": "Ver todas as sessões",
+ "sidebar.project.clearNotifications": "Limpar notificações",
"app.name.desktop": "OpenCode Desktop",
"settings.section.desktop": "Desktop",
"settings.section.server": "Servidor",
diff --git a/packages/app/src/i18n/bs.ts b/packages/app/src/i18n/bs.ts
index 7d10da6ed87..206aae3729d 100644
--- a/packages/app/src/i18n/bs.ts
+++ b/packages/app/src/i18n/bs.ts
@@ -576,6 +576,7 @@ export const dict = {
"sidebar.gettingStarted.line2": "Poveži bilo kojeg provajdera da koristiš modele, npr. Claude, GPT, Gemini itd.",
"sidebar.project.recentSessions": "Nedavne sesije",
"sidebar.project.viewAllSessions": "Prikaži sve sesije",
+ "sidebar.project.clearNotifications": "Očisti obavijesti",
"app.name.desktop": "OpenCode Desktop",
diff --git a/packages/app/src/i18n/da.ts b/packages/app/src/i18n/da.ts
index ac5c4d494b1..6bf67168fb0 100644
--- a/packages/app/src/i18n/da.ts
+++ b/packages/app/src/i18n/da.ts
@@ -572,6 +572,7 @@ export const dict = {
"sidebar.gettingStarted.line2": "Forbind enhver udbyder for at bruge modeller, inkl. Claude, GPT, Gemini osv.",
"sidebar.project.recentSessions": "Seneste sessioner",
"sidebar.project.viewAllSessions": "Vis alle sessioner",
+ "sidebar.project.clearNotifications": "Ryd notifikationer",
"app.name.desktop": "OpenCode Desktop",
"settings.section.desktop": "Desktop",
diff --git a/packages/app/src/i18n/de.ts b/packages/app/src/i18n/de.ts
index 99a95063107..4b6b43a57c0 100644
--- a/packages/app/src/i18n/de.ts
+++ b/packages/app/src/i18n/de.ts
@@ -524,6 +524,7 @@ export const dict = {
"Verbinden Sie einen beliebigen Anbieter, um Modelle wie Claude, GPT, Gemini usw. zu nutzen.",
"sidebar.project.recentSessions": "Letzte Sitzungen",
"sidebar.project.viewAllSessions": "Alle Sitzungen anzeigen",
+ "sidebar.project.clearNotifications": "Benachrichtigungen löschen",
"app.name.desktop": "OpenCode Desktop",
"settings.section.desktop": "Desktop",
"settings.section.server": "Server",
diff --git a/packages/app/src/i18n/en.ts b/packages/app/src/i18n/en.ts
index 99513edaa17..fd70f389ecf 100644
--- a/packages/app/src/i18n/en.ts
+++ b/packages/app/src/i18n/en.ts
@@ -577,6 +577,7 @@ export const dict = {
"sidebar.gettingStarted.line2": "Connect any provider to use models, inc. Claude, GPT, Gemini etc.",
"sidebar.project.recentSessions": "Recent sessions",
"sidebar.project.viewAllSessions": "View all sessions",
+ "sidebar.project.clearNotifications": "Clear notifications",
"app.name.desktop": "OpenCode Desktop",
diff --git a/packages/app/src/i18n/es.ts b/packages/app/src/i18n/es.ts
index 7a6c4974e09..135a63fef74 100644
--- a/packages/app/src/i18n/es.ts
+++ b/packages/app/src/i18n/es.ts
@@ -579,6 +579,7 @@ export const dict = {
"sidebar.gettingStarted.line2": "Conecta cualquier proveedor para usar modelos, inc. Claude, GPT, Gemini etc.",
"sidebar.project.recentSessions": "Sesiones recientes",
"sidebar.project.viewAllSessions": "Ver todas las sesiones",
+ "sidebar.project.clearNotifications": "Borrar notificaciones",
"app.name.desktop": "OpenCode Desktop",
diff --git a/packages/app/src/i18n/fr.ts b/packages/app/src/i18n/fr.ts
index fc3bf266794..1ab0c72d53a 100644
--- a/packages/app/src/i18n/fr.ts
+++ b/packages/app/src/i18n/fr.ts
@@ -523,6 +523,7 @@ export const dict = {
"Connectez n'importe quel fournisseur pour utiliser des modèles, y compris Claude, GPT, Gemini etc.",
"sidebar.project.recentSessions": "Sessions récentes",
"sidebar.project.viewAllSessions": "Voir toutes les sessions",
+ "sidebar.project.clearNotifications": "Effacer les notifications",
"app.name.desktop": "OpenCode Desktop",
"settings.section.desktop": "Bureau",
"settings.section.server": "Serveur",
diff --git a/packages/app/src/i18n/ja.ts b/packages/app/src/i18n/ja.ts
index b597db02a58..6f092a60f68 100644
--- a/packages/app/src/i18n/ja.ts
+++ b/packages/app/src/i18n/ja.ts
@@ -513,6 +513,7 @@ export const dict = {
"sidebar.gettingStarted.line2": "プロバイダーを接続して、Claude、GPT、Geminiなどのモデルを使用できます。",
"sidebar.project.recentSessions": "最近のセッション",
"sidebar.project.viewAllSessions": "すべてのセッションを表示",
+ "sidebar.project.clearNotifications": "通知をクリア",
"app.name.desktop": "OpenCode Desktop",
"settings.section.desktop": "デスクトップ",
"settings.section.server": "サーバー",
diff --git a/packages/app/src/i18n/ko.ts b/packages/app/src/i18n/ko.ts
index 525bd035651..4d814d43d0a 100644
--- a/packages/app/src/i18n/ko.ts
+++ b/packages/app/src/i18n/ko.ts
@@ -514,6 +514,7 @@ export const dict = {
"sidebar.gettingStarted.line2": "Claude, GPT, Gemini 등을 포함한 모델을 사용하려면 공급자를 연결하세요.",
"sidebar.project.recentSessions": "최근 세션",
"sidebar.project.viewAllSessions": "모든 세션 보기",
+ "sidebar.project.clearNotifications": "알림 지우기",
"app.name.desktop": "OpenCode Desktop",
"settings.section.desktop": "데스크톱",
"settings.section.server": "서버",
diff --git a/packages/app/src/i18n/no.ts b/packages/app/src/i18n/no.ts
index 98e79e1896a..63bc66acfcd 100644
--- a/packages/app/src/i18n/no.ts
+++ b/packages/app/src/i18n/no.ts
@@ -579,6 +579,7 @@ export const dict = {
"sidebar.gettingStarted.line2": "Koble til en leverandør for å bruke modeller, inkl. Claude, GPT, Gemini osv.",
"sidebar.project.recentSessions": "Nylige sesjoner",
"sidebar.project.viewAllSessions": "Vis alle sesjoner",
+ "sidebar.project.clearNotifications": "Fjern varsler",
"app.name.desktop": "OpenCode Desktop",
diff --git a/packages/app/src/i18n/pl.ts b/packages/app/src/i18n/pl.ts
index 983c9c14ac1..2a3ea7bfb18 100644
--- a/packages/app/src/i18n/pl.ts
+++ b/packages/app/src/i18n/pl.ts
@@ -514,6 +514,7 @@ export const dict = {
"sidebar.gettingStarted.line2": "Połącz dowolnego dostawcę, aby używać modeli, w tym Claude, GPT, Gemini itp.",
"sidebar.project.recentSessions": "Ostatnie sesje",
"sidebar.project.viewAllSessions": "Zobacz wszystkie sesje",
+ "sidebar.project.clearNotifications": "Wyczyść powiadomienia",
"app.name.desktop": "OpenCode Desktop",
"settings.section.desktop": "Pulpit",
"settings.section.server": "Serwer",
diff --git a/packages/app/src/i18n/ru.ts b/packages/app/src/i18n/ru.ts
index f2c87fe0f1e..93e5b274253 100644
--- a/packages/app/src/i18n/ru.ts
+++ b/packages/app/src/i18n/ru.ts
@@ -578,6 +578,7 @@ export const dict = {
"Подключите любого провайдера для использования моделей, включая Claude, GPT, Gemini и др.",
"sidebar.project.recentSessions": "Недавние сессии",
"sidebar.project.viewAllSessions": "Посмотреть все сессии",
+ "sidebar.project.clearNotifications": "Очистить уведомления",
"app.name.desktop": "OpenCode Desktop",
"settings.section.desktop": "Приложение",
diff --git a/packages/app/src/i18n/th.ts b/packages/app/src/i18n/th.ts
index 689e8211896..3b3486b5c70 100644
--- a/packages/app/src/i18n/th.ts
+++ b/packages/app/src/i18n/th.ts
@@ -571,6 +571,7 @@ export const dict = {
"sidebar.gettingStarted.line2": "เชื่อมต่อผู้ให้บริการใด ๆ เพื่อใช้โมเดล รวมถึง Claude, GPT, Gemini ฯลฯ",
"sidebar.project.recentSessions": "เซสชันล่าสุด",
"sidebar.project.viewAllSessions": "ดูเซสชันทั้งหมด",
+ "sidebar.project.clearNotifications": "ล้างการแจ้งเตือน",
"app.name.desktop": "OpenCode Desktop",
diff --git a/packages/app/src/i18n/zh.ts b/packages/app/src/i18n/zh.ts
index 1b40013b60d..6489b702542 100644
--- a/packages/app/src/i18n/zh.ts
+++ b/packages/app/src/i18n/zh.ts
@@ -569,6 +569,7 @@ export const dict = {
"sidebar.gettingStarted.line2": "连接任意提供商即可使用更多模型,如 Claude、GPT、Gemini 等。",
"sidebar.project.recentSessions": "最近会话",
"sidebar.project.viewAllSessions": "查看全部会话",
+ "sidebar.project.clearNotifications": "清除通知",
"app.name.desktop": "OpenCode Desktop",
diff --git a/packages/app/src/i18n/zht.ts b/packages/app/src/i18n/zht.ts
index 34aec01b9cb..a01b76c0521 100644
--- a/packages/app/src/i18n/zht.ts
+++ b/packages/app/src/i18n/zht.ts
@@ -567,6 +567,7 @@ export const dict = {
"sidebar.gettingStarted.line2": "連線任意提供者即可使用更多模型,如 Claude、GPT、Gemini 等。",
"sidebar.project.recentSessions": "最近工作階段",
"sidebar.project.viewAllSessions": "查看全部工作階段",
+ "sidebar.project.clearNotifications": "清除通知",
"app.name.desktop": "OpenCode Desktop",
"settings.section.desktop": "桌面",
diff --git a/packages/app/src/pages/layout.tsx b/packages/app/src/pages/layout.tsx
index 7eb064f425d..7d4a5c0cb81 100644
--- a/packages/app/src/pages/layout.tsx
+++ b/packages/app/src/pages/layout.tsx
@@ -1692,6 +1692,13 @@ export default function Layout(props: ParentProps) {
})
const projectId = createMemo(() => panelProps.project?.id ?? "")
const workspaces = createMemo(() => workspaceIds(panelProps.project))
+ const unseenCount = createMemo(() =>
+ workspaces().reduce((total, directory) => total + notification.project.unseenCount(directory), 0),
+ )
+ const clearNotifications = () =>
+ workspaces()
+ .filter((directory) => notification.project.unseenCount(directory) > 0)
+ .forEach((directory) => notification.project.markViewed(directory))
const workspacesEnabled = createMemo(() => {
const project = panelProps.project
if (!project) return false
@@ -1769,6 +1776,16 @@ export default function Layout(props: ParentProps) {
: language.t("sidebar.workspaces.enable")}
+
+
+ {language.t("sidebar.project.clearNotifications")}
+
+
active: Accessor
overlay: Accessor
+ dirs: Accessor
onProjectMouseEnter: (worktree: string, event: MouseEvent) => void
onProjectMouseLeave: (worktree: string) => void
onProjectFocus: (worktree: string) => void
@@ -70,73 +72,94 @@ const ProjectTile = (props: {
setMenu: (value: boolean) => void
setOpen: (value: boolean) => void
language: ReturnType
-}): JSX.Element => (
- {
- props.setMenu(value)
- if (value) props.setOpen(false)
- }}
- >
- {
- if (!props.overlay()) return
- props.onProjectMouseEnter(props.project.worktree, event)
- }}
- onMouseLeave={() => {
- if (!props.overlay()) return
- props.onProjectMouseLeave(props.project.worktree)
- }}
- onFocus={() => {
- if (!props.overlay()) return
- props.onProjectFocus(props.project.worktree)
+}): JSX.Element => {
+ const notification = useNotification()
+ const unseenCount = createMemo(() =>
+ props.dirs().reduce((total, directory) => total + notification.project.unseenCount(directory), 0),
+ )
+
+ const clear = () =>
+ props
+ .dirs()
+ .filter((directory) => notification.project.unseenCount(directory) > 0)
+ .forEach((directory) => notification.project.markViewed(directory))
+
+ return (
+ {
+ props.setMenu(value)
+ if (value) props.setOpen(false)
}}
- onClick={() => props.navigateToProject(props.project.worktree)}
- onBlur={() => props.setOpen(false)}
>
-
-
-
-
- props.showEditProjectDialog(props.project)}>
- {props.language.t("common.edit")}
-
- props.toggleProjectWorkspaces(props.project)}
- >
-
- {props.workspacesEnabled(props.project)
- ? props.language.t("sidebar.workspaces.disable")
- : props.language.t("sidebar.workspaces.enable")}
-
-
-
- props.closeProject(props.project.worktree)}
- >
- {props.language.t("common.close")}
-
-
-
-
-)
+ {
+ if (!props.overlay()) return
+ props.onProjectMouseEnter(props.project.worktree, event)
+ }}
+ onMouseLeave={() => {
+ if (!props.overlay()) return
+ props.onProjectMouseLeave(props.project.worktree)
+ }}
+ onFocus={() => {
+ if (!props.overlay()) return
+ props.onProjectFocus(props.project.worktree)
+ }}
+ onClick={() => props.navigateToProject(props.project.worktree)}
+ onBlur={() => props.setOpen(false)}
+ >
+
+
+
+
+ props.showEditProjectDialog(props.project)}>
+ {props.language.t("common.edit")}
+
+ props.toggleProjectWorkspaces(props.project)}
+ >
+
+ {props.workspacesEnabled(props.project)
+ ? props.language.t("sidebar.workspaces.disable")
+ : props.language.t("sidebar.workspaces.enable")}
+
+
+
+ {props.language.t("sidebar.project.clearNotifications")}
+
+
+ props.closeProject(props.project.worktree)}
+ >
+ {props.language.t("common.close")}
+
+
+
+
+ )
+}
const ProjectPreviewPanel = (props: {
project: LocalProject
@@ -254,6 +277,7 @@ export const SortableProject = (props: {
)
const workspaces = createMemo(() => props.ctx.workspaceIds(props.project).slice(0, 2))
const workspaceEnabled = createMemo(() => props.ctx.workspacesEnabled(props.project))
+ const dirs = createMemo(() => props.ctx.workspaceIds(props.project))
const [open, setOpen] = createSignal(false)
const [menu, setMenu] = createSignal(false)
@@ -304,6 +328,7 @@ export const SortableProject = (props: {
selected={selected}
active={active}
overlay={overlay}
+ dirs={dirs}
onProjectMouseEnter={props.ctx.onProjectMouseEnter}
onProjectMouseLeave={props.ctx.onProjectMouseLeave}
onProjectFocus={props.ctx.onProjectFocus}