generated from nhcarrigan/template
fix: assorted bug fixes for lists, sounds, interrupts, and permissions (#173)
## Summary - **Markdown lists**: Explicitly set `list-style-type: disc` / `decimal` in the Markdown renderer — Tauri's WebView strips browser defaults, leaving bullets and numbers invisible. - **Notification sounds**: Moved all per-task sounds (success, error, permission, task-start) from a global `characterState` subscription into the per-conversation `claude:state` event handler, so background tabs receive their sounds correctly and tab-switching never replays a sound that already fired. Closes #172 - **Draft text**: Persists `inputValue` per conversation tab so a half-typed prompt survives switching to another tab and back. - **Interrupt messages**: Replaced vague "Process interrupted" / "Disconnected" strings with source-specific descriptions (keyboard shortcut, stop button, unexpected crash) so it's clear what actually happened. - **Silent prompt loss**: When Claude Code exits whilst a prompt is in-flight, emits a visible error line telling the user their last prompt was not processed and to reconnect and retry. - **Double disconnect**: Added an `intentional_stop` flag to `WslBridge` so that `stop()` / `interrupt()` — which kill the process themselves — suppress the duplicate "Disconnected unexpectedly" message that `handle_stdout`'s EOF path was also emitting. - **Permission modal**: Fixed two cooperating reactivity bugs — `pendingPermissions` was mutated in-place (`.push()`), causing Svelte's derived-store chain to receive the same array reference and skip re-rendering; `PermissionModal.svelte` also used `$state()` (runes mode) where plain `let` is required for correct store-subscription reactivity. ## Test plan - [ ] Unordered and ordered lists render with visible bullets and numbers in the chat terminal - [ ] Completion sound plays once when a background tab finishes; switching back to that tab does not replay it - [ ] Sounds for error, permission request, and task-start also play for background tabs and do not replay on tab switch - [ ] Typing a prompt, switching tabs, and switching back restores the draft text - [ ] Pressing Ctrl+C shows "keyboard shortcut (Ctrl+C)"; clicking the stop button shows "via stop button" - [ ] If Claude exits mid-request, an error message appears prompting the user to resend - [ ] Clicking stop or pressing Ctrl+C produces exactly one disconnect message (not two) - [ ] When a tool requires permission, the permission modal appears and the user can approve or dismiss it ✨ This PR was created with help from Hikari~ 🌸 Reviewed-on: #173 Co-authored-by: Hikari <hikari@nhcarrigan.com> Co-committed-by: Hikari <hikari@nhcarrigan.com>
This commit was merged in pull request #173.
This commit is contained in:
+59
-1
@@ -21,6 +21,7 @@ import {
|
||||
handleConnectionStatusChange,
|
||||
handleNewUserMessage,
|
||||
} from "$lib/notifications/rules";
|
||||
import { notificationManager } from "$lib/notifications/notificationManager";
|
||||
|
||||
interface StateChangePayload {
|
||||
state: CharacterState;
|
||||
@@ -220,7 +221,7 @@ export async function initializeTauriListeners() {
|
||||
claudeStore.addLineToConversation(
|
||||
targetConversationId,
|
||||
"system",
|
||||
"Disconnected from Claude Code"
|
||||
"Disconnected from Claude Code unexpectedly — the process may have crashed or been stopped by the system"
|
||||
);
|
||||
|
||||
// Clear todos on real disconnect (not on reconnects for permissions)
|
||||
@@ -270,6 +271,63 @@ export async function initializeTauriListeners() {
|
||||
|
||||
const mappedState = stateMap[state.toLowerCase()] || "idle";
|
||||
|
||||
// Per-conversation sound tracking — fires for any tab (active or background).
|
||||
// All sounds are driven from state-change events rather than a global store
|
||||
// subscription, so background tabs receive their sounds correctly and
|
||||
// switching tabs never replays a sound that has already fired.
|
||||
const resolvedConversationId = conversation_id || get(claudeStore.activeConversationId) || null;
|
||||
if (resolvedConversationId) {
|
||||
const conv = get(claudeStore.conversations).get(resolvedConversationId);
|
||||
if (conv) {
|
||||
const previousState = conv.characterState;
|
||||
|
||||
// New response starting — clear all per-task sound flags.
|
||||
if (mappedState === "thinking") {
|
||||
claudeStore.resetSoundState(resolvedConversationId);
|
||||
}
|
||||
|
||||
// Record when a long-running phase begins (used for the 2-second
|
||||
// minimum duration check before playing the completion sound).
|
||||
if (
|
||||
(mappedState === "coding" || mappedState === "searching") &&
|
||||
previousState !== "coding" &&
|
||||
previousState !== "searching"
|
||||
) {
|
||||
claudeStore.setTaskStartTime(resolvedConversationId, Date.now());
|
||||
}
|
||||
|
||||
// Task-start sound — fires once when work enters a long-running phase.
|
||||
if (
|
||||
(mappedState === "coding" || mappedState === "searching") &&
|
||||
previousState !== "coding" &&
|
||||
previousState !== "searching" &&
|
||||
!conv.taskStartSoundFired
|
||||
) {
|
||||
notificationManager.notifyTaskStart();
|
||||
claudeStore.markTaskStartSoundFired(resolvedConversationId);
|
||||
}
|
||||
|
||||
// Error sound — fires each time a new error state is entered.
|
||||
if (mappedState === "error" && previousState !== "error") {
|
||||
notificationManager.notifyError();
|
||||
}
|
||||
|
||||
// Permission sound — fires each time a permission request arrives.
|
||||
if (mappedState === "permission") {
|
||||
notificationManager.notifyPermission();
|
||||
}
|
||||
|
||||
// Completion sound — fires once per task after sufficient duration.
|
||||
if (mappedState === "success" && !conv.successSoundFired) {
|
||||
const duration = conv.taskStartTime ? Date.now() - conv.taskStartTime : 0;
|
||||
if (duration > 2000) {
|
||||
notificationManager.notifySuccess();
|
||||
}
|
||||
claudeStore.markSuccessSoundFired(resolvedConversationId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Always update the conversation's state
|
||||
if (conversation_id) {
|
||||
claudeStore.setCharacterStateForConversation(conversation_id, mappedState);
|
||||
|
||||
Reference in New Issue
Block a user