feat: add notification sounds (#44)
Security Scan and Upload / Security & DefectDojo Upload (push) Successful in 54s
CI / Lint & Test (push) Successful in 14m2s
CI / Build Linux (push) Successful in 16m38s
CI / Build Windows (cross-compile) (push) Successful in 26m27s

### Explanation

_No response_

### Issue

_No response_

### Attestations

- [ ] I have read and agree to the [Code of Conduct](https://docs.nhcarrigan.com/community/coc/)
- [ ] I have read and agree to the [Community Guidelines](https://docs.nhcarrigan.com/community/guide/).
- [ ] My contribution complies with the [Contributor Covenant](https://docs.nhcarrigan.com/dev/covenant/).

### Dependencies

- [ ] I have pinned the dependencies to a specific patch version.

### Style

- [ ] I have run the linter and resolved any errors.
- [ ] My pull request uses an appropriate title, matching the conventional commit standards.
- [ ] My scope of feat/fix/chore/etc. correctly matches the nature of changes in my pull request.

### Tests

- [ ] My contribution adds new code, and I have added tests to cover it.
- [ ] My contribution modifies existing code, and I have updated the tests to reflect these changes.
- [ ] All new and existing tests pass locally with my changes.
- [ ] Code coverage remains at or above the configured threshold.

### Documentation

_No response_

### Versioning

_No response_

Reviewed-on: #44
Co-authored-by: Naomi Carrigan <commits@nhcarrigan.com>
Co-committed-by: Naomi Carrigan <commits@nhcarrigan.com>
This commit was merged in pull request #44.
This commit is contained in:
2026-01-19 16:18:25 -08:00
committed by Naomi Carrigan
parent 0065bb4afc
commit a8f98406e1
32 changed files with 1512 additions and 29 deletions
+44 -7
View File
@@ -5,6 +5,12 @@ import { characterState } from "$lib/stores/character";
import { configStore } from "$lib/stores/config";
import type { ConnectionStatus, PermissionPromptEvent } from "$lib/types/messages";
import type { CharacterState } from "$lib/types/states";
import {
initializeNotificationRules,
cleanupNotificationRules,
handleConnectionStatusChange,
handleNewUserMessage,
} from "$lib/notifications/rules";
interface StateChangePayload {
state: CharacterState;
@@ -12,6 +18,7 @@ interface StateChangePayload {
}
let hasConnectedThisSession = false;
let unlisteners: Array<() => void> = [];
function getTimeOfDay(): string {
const hour = new Date().getHours();
@@ -44,6 +51,9 @@ async function sendGreeting() {
// Don't show the system prompt in the UI - just trigger Claude to respond
characterState.setState("thinking");
// Reset notification state for greeting
handleNewUserMessage();
try {
await invoke("send_prompt", { message: greetingPrompt });
} catch (error) {
@@ -60,10 +70,19 @@ interface OutputPayload {
}
export async function initializeTauriListeners() {
await listen<string>("claude:connection", async (event) => {
// Cleanup any existing listeners first
cleanupTauriListeners();
// Initialize notification rules
initializeNotificationRules();
const connectionUnlisten = await listen<string>("claude:connection", async (event) => {
const status = event.payload as ConnectionStatus;
claudeStore.setConnectionStatus(status);
// Handle notification for connection status
handleConnectionStatusChange(status);
if (status === "connected") {
claudeStore.addLine("system", "Connected to Claude Code");
characterState.setState("idle");
@@ -81,8 +100,9 @@ export async function initializeTauriListeners() {
characterState.setTemporaryState("error", 3000);
}
});
unlisteners.push(connectionUnlisten);
await listen<StateChangePayload>("claude:state", (event) => {
const stateUnlisten = await listen<StateChangePayload>("claude:state", (event) => {
const { state } = event.payload;
const stateMap: Record<string, CharacterState> = {
@@ -105,8 +125,9 @@ export async function initializeTauriListeners() {
characterState.setState(mappedState);
}
});
unlisteners.push(stateUnlisten);
await listen<OutputPayload>("claude:output", (event) => {
const outputUnlisten = await listen<OutputPayload>("claude:output", (event) => {
const { line_type, content, tool_name } = event.payload;
claudeStore.addLine(
line_type as "user" | "assistant" | "system" | "tool" | "error",
@@ -114,21 +135,25 @@ export async function initializeTauriListeners() {
tool_name || undefined
);
});
unlisteners.push(outputUnlisten);
await listen<string>("claude:stream", () => {
const streamUnlisten = await listen<string>("claude:stream", () => {
// no-op
});
unlisteners.push(streamUnlisten);
await listen<string>("claude:session", (event) => {
const sessionUnlisten = await listen<string>("claude:session", (event) => {
claudeStore.setSessionId(event.payload);
claudeStore.addLine("system", `Session: ${event.payload.substring(0, 8)}...`);
});
unlisteners.push(sessionUnlisten);
await listen<string>("claude:cwd", (event) => {
const cwdUnlisten = await listen<string>("claude:cwd", (event) => {
claudeStore.setWorkingDirectory(event.payload);
});
unlisteners.push(cwdUnlisten);
await listen<PermissionPromptEvent>("claude:permission", (event) => {
const permissionUnlisten = await listen<PermissionPromptEvent>("claude:permission", (event) => {
const { id, tool_name, tool_input, description } = event.payload;
claudeStore.requestPermission({
id,
@@ -138,6 +163,18 @@ export async function initializeTauriListeners() {
});
claudeStore.addLine("system", `Permission requested for: ${tool_name}`);
});
unlisteners.push(permissionUnlisten);
console.log("Tauri event listeners initialized");
}
export function cleanupTauriListeners() {
// Cleanup all event listeners
unlisteners.forEach((unlisten) => unlisten());
unlisteners = [];
// Cleanup notification rules
cleanupNotificationRules();
console.log("Tauri event listeners cleaned up");
}