Compare commits

...

2 Commits

Author SHA1 Message Date
jared a9787ef041 feat: Windows taskbar notification badge counter
CI / Build & Quality Checks (push) Successful in 10m31s
Trigger Desktop Build / trigger (push) Successful in 13s
Adds a Tauri command (set_badge_count) that draws a red circle badge
with a highlight count onto the Windows taskbar button overlay icon,
matching Discord's behavior. Badge shows @mention/highlight count only
(not total messages), clears to zero when all highlights are read.

Frontend: useTauriNotificationBadge hook reads roomToUnreadAtom and
calls set_badge_count via window.__TAURI_INTERNALS__.invoke whenever
the unread map changes. No-ops silently in the browser (non-Tauri).
Mounted as TauriEffects inside JotaiProvider in App.tsx.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-10 17:32:38 -04:00
jared fcf16fd654 refactor(settings): replace composer toolbar toggle rows with chip grid
8 individual SettingTile rows collapsed into a single wrapped chip grid.
Active chips show as Primary+outlined; inactive as Secondary. Clicking
any chip toggles it. Drops from ~83 lines to ~25 and reads at a glance.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-10 17:15:34 -04:00
3 changed files with 67 additions and 81 deletions
+35 -81
View File
@@ -933,6 +933,17 @@ function Editor() {
setComposerToolbarButtons({ ...composerToolbarButtons, [key]: !composerToolbarButtons[key] });
};
const TOOLBAR_CHIPS: Array<{ key: keyof ComposerToolbarSettings; label: string }> = [
{ key: 'showFormat', label: 'Format' },
{ key: 'showEmoji', label: 'Emoji' },
{ key: 'showSticker', label: 'Sticker' },
{ key: 'showGif', label: 'GIF' },
{ key: 'showLocation', label: 'Location' },
{ key: 'showPoll', label: 'Poll' },
{ key: 'showVoice', label: 'Voice' },
{ key: 'showSchedule', label: 'Schedule' },
];
return (
<Box direction="Column" gap="100">
<Text size="L400">Editor</Text>
@@ -958,89 +969,32 @@ function Editor() {
after={<Switch variant="Primary" value={editorToolbar} onChange={setEditorToolbar} />}
/>
</SequenceCard>
<Text size="L400" style={{ marginTop: '8px' }}>Composer Toolbar Buttons</Text>
<SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column">
<SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column" gap="300">
<SettingTile
title="Format Toggle"
description="Button to show/hide the text formatting toolbar."
after={
<Switch
variant="Primary"
value={composerToolbarButtons?.showFormat ?? true}
onChange={() => toggleToolbarButton('showFormat')}
/>
}
/>
<SettingTile
title="Emoji"
after={
<Switch
variant="Primary"
value={composerToolbarButtons?.showEmoji ?? true}
onChange={() => toggleToolbarButton('showEmoji')}
/>
}
/>
<SettingTile
title="Sticker"
after={
<Switch
variant="Primary"
value={composerToolbarButtons?.showSticker ?? true}
onChange={() => toggleToolbarButton('showSticker')}
/>
}
/>
<SettingTile
title="GIF"
after={
<Switch
variant="Primary"
value={composerToolbarButtons?.showGif ?? true}
onChange={() => toggleToolbarButton('showGif')}
/>
}
/>
<SettingTile
title="Location"
after={
<Switch
variant="Primary"
value={composerToolbarButtons?.showLocation ?? true}
onChange={() => toggleToolbarButton('showLocation')}
/>
}
/>
<SettingTile
title="Poll"
after={
<Switch
variant="Primary"
value={composerToolbarButtons?.showPoll ?? true}
onChange={() => toggleToolbarButton('showPoll')}
/>
}
/>
<SettingTile
title="Voice Message"
after={
<Switch
variant="Primary"
value={composerToolbarButtons?.showVoice ?? true}
onChange={() => toggleToolbarButton('showVoice')}
/>
}
/>
<SettingTile
title="Schedule Message"
after={
<Switch
variant="Primary"
value={composerToolbarButtons?.showSchedule ?? true}
onChange={() => toggleToolbarButton('showSchedule')}
/>
}
title="Composer Toolbar"
description="Tap a button to show or hide it in the message composer."
/>
<Box
wrap="Wrap"
gap="200"
style={{ padding: `0 ${config.space.S400} ${config.space.S300}` }}
>
{TOOLBAR_CHIPS.map(({ key, label }) => {
const active = composerToolbarButtons?.[key] ?? true;
return (
<Chip
key={key}
variant={active ? 'Primary' : 'Secondary'}
outlined={active}
radii="Pill"
onClick={() => toggleToolbarButton(key)}
aria-pressed={active}
>
<Text size="T300">{label}</Text>
</Chip>
);
})}
</Box>
</SequenceCard>
</Box>
);
@@ -0,0 +1,25 @@
import { useEffect } from 'react';
import { useAtomValue } from 'jotai';
import { roomToUnreadAtom } from '../state/room/roomToUnread';
// Tauri v2 injects __TAURI_INTERNALS__ into the webview at runtime.
// We use it directly so cinny doesn't need @tauri-apps/api as a dependency.
type TauriInternals = { invoke: (cmd: string, args?: Record<string, unknown>) => Promise<unknown> };
const tauriInvoke = (): TauriInternals['invoke'] | undefined =>
(window as unknown as { __TAURI_INTERNALS__?: TauriInternals }).__TAURI_INTERNALS__?.invoke;
export function useTauriNotificationBadge() {
const roomToUnread = useAtomValue(roomToUnreadAtom);
useEffect(() => {
const invoke = tauriInvoke();
if (!invoke) return;
let totalHighlights = 0;
roomToUnread.forEach((unread) => {
totalHighlights += unread.highlight;
});
invoke('set_badge_count', { count: totalHighlights }).catch(() => {});
}, [roomToUnread]);
}
+7
View File
@@ -15,6 +15,7 @@ import { ScreenSizeProvider, useScreenSize } from '../hooks/useScreenSize';
import { useCompositionEndTracking } from '../hooks/useComposingCheck';
import { settingsAtom } from '../state/settings';
import { LotusToastContainer } from '../features/toast/LotusToastContainer';
import { useTauriNotificationBadge } from '../hooks/useTauriNotificationBadge';
const FONT_MAP: Record<string, string> = {
system: "system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif",
@@ -52,6 +53,11 @@ function AppearanceEffects() {
return null;
}
function TauriEffects() {
useTauriNotificationBadge();
return null;
}
function NightLightOverlay() {
const settings = useAtomValue(settingsAtom);
if (!settings.nightLightEnabled) return null;
@@ -131,6 +137,7 @@ function App() {
<QueryClientProvider client={queryClient}>
<JotaiProvider>
<AppearanceEffects />
<TauriEffects />
<RouterProvider router={createRouter(clientConfig, screenSize)} />
<NightLightOverlay />
<LotusToastContainer />