diff --git a/app/src/features/account/AlertsSettings.tsx b/app/src/features/account/AlertsSettings.tsx
index 6f734e1a..a95ddfd4 100644
--- a/app/src/features/account/AlertsSettings.tsx
+++ b/app/src/features/account/AlertsSettings.tsx
@@ -12,6 +12,7 @@ export function AlertsSettings() {
const needs = usePref(prefs.notifyNeeds);
const done = usePref(prefs.notifyDone);
const sound = usePref(prefs.sound);
+ const replies = usePref(prefs.markReplies);
const tray = usePref(prefs.tray);
const keep = usePref(prefs.whenClosed) === "keep";
const nearClock = tray && keep;
@@ -29,6 +30,15 @@ export function AlertsSettings() {
checked={done}
onChange={prefs.notifyDone.set}
/>
+ {/* The mark on the icon is a taskbar overlay, which only Windows has. */}
+ {SYSTEM === "Windows" && (
+
+ )}
{!nearClock &&
{s.alertsNeedTray}
}
diff --git a/app/src/features/bots/botActions.tsx b/app/src/features/bots/botActions.tsx
index 58524ce5..f2a879cc 100644
--- a/app/src/features/bots/botActions.tsx
+++ b/app/src/features/bots/botActions.tsx
@@ -1,8 +1,18 @@
-import { Archive, Crown, FolderOpen, Pencil, RefreshCcw, Trash2 } from "lucide-react";
+import {
+ Archive,
+ Crown,
+ FolderOpen,
+ Mail,
+ MailOpen,
+ Pencil,
+ RefreshCcw,
+ Trash2,
+} from "lucide-react";
import { type ReactNode, useState } from "react";
import { useT } from "../../i18n";
import type { Bot, Crew } from "../../lib/protocol.gen";
import { useApi, useApp, useHost } from "../../store/context";
+import { isUnread } from "../../store/seen";
import { Confirm } from "../../ui/Confirm";
import type { MenuItem } from "../../ui/Menu";
import { attempt } from "../../ui/toast";
@@ -27,6 +37,9 @@ export function useBotActions(
const host = useHost();
const putBot = useApp((state) => state.putBot);
const putCrew = useApp((state) => state.putCrew);
+ const unread = useApp((state) => isUnread(state, bot.id));
+ const markSeen = useApp((state) => state.markSeen);
+ const markUnread = useApp((state) => state.markUnread);
const chief = isChief(bot, crew);
const [open, setOpen] = useState(null);
const close = () => setOpen(null);
@@ -54,6 +67,9 @@ export function useBotActions(
icon: FolderOpen,
onSelect: () => attempt(words.failed.openFolder, () => host.openPath(bot.workspace)),
},
+ unread
+ ? { label: words.markRead, icon: MailOpen, onSelect: () => markSeen(bot.id) }
+ : { label: words.markUnread, icon: Mail, onSelect: () => markUnread(bot.id) },
{ label: words.archive, icon: Archive, danger: true, onSelect: () => setOpen("archive") },
{ label: words.delete, icon: Trash2, danger: true, onSelect: () => setOpen("delete") },
];
diff --git a/app/src/features/crews/Sidebar.tsx b/app/src/features/crews/Sidebar.tsx
index 7c2c7a8b..58a066cf 100644
--- a/app/src/features/crews/Sidebar.tsx
+++ b/app/src/features/crews/Sidebar.tsx
@@ -7,7 +7,7 @@ import { when } from "../../lib/format";
import type { Activity, Bot, Crew } from "../../lib/protocol.gen";
import { activityOf, botsOf, crewList } from "../../store/app";
import { useApp } from "../../store/context";
-import { hasUnreadReply } from "../../store/seen";
+import { isUnread, unreadIn } from "../../store/seen";
import { Button } from "../../ui/Button";
import { ContextMenu, menuPoint, type Point } from "../../ui/ContextMenu";
import { APP_OPENED } from "../../ui/motion";
@@ -58,6 +58,7 @@ function CrewEntry({ crew }: { crew: Crew }) {
const t = useT();
const bots = useApp(useShallow((state) => botsOf(state, crew.id)));
const selected = useApp((state) => state.selectedCrewId === crew.id && !state.selectedBotId);
+ const unread = useApp((state) => unreadIn(state, crew.id));
const selectCrew = useApp((state) => state.selectCrew);
return (
@@ -69,6 +70,12 @@ function CrewEntry({ crew }: { crew: Crew }) {
className={`${row} h-8 gap-2 px-2.5 font-semibold text-sm ${selected ? "bg-sunken" : ""}`}
>
{crew.name}
+ {unread > 0 && (
+
+ {unread}
+ {t.crews.sidebar.unreadCount(unread)}
+
+ )}
{crew.paused && (
@@ -95,7 +102,7 @@ const Conversation = memo(function Conversation({ bot, crew }: { bot: Bot; crew:
const [menuAt, setMenuAt] = useState(null);
const closeMenu = useCallback(() => setMenuAt(null), []);
const activity = useApp((state) => activityOf(state, bot.id));
- const unread = useApp((state) => hasUnreadReply(state, bot.id));
+ const unread = useApp((state) => isUnread(state, bot.id));
return (
APP_OPENED ? "animate-rise" : undefined}>