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:
@@ -135,7 +135,7 @@
|
||||
setSkipNextGreeting(true);
|
||||
|
||||
await invoke("interrupt_claude", { conversationId });
|
||||
claudeStore.addLine("system", "Interrupted");
|
||||
claudeStore.addLine("system", "Process interrupted via stop button");
|
||||
characterState.setState("idle");
|
||||
} catch (error) {
|
||||
console.error("Failed to interrupt:", error);
|
||||
|
||||
@@ -164,6 +164,17 @@
|
||||
attachments = storedAttachments;
|
||||
});
|
||||
|
||||
// Per-tab draft persistence — restore the draft text whenever the active
|
||||
// conversation changes, and save it back on every keystroke.
|
||||
claudeStore.activeConversationId.subscribe((conversationId) => {
|
||||
if (conversationId) {
|
||||
const conv = get(claudeStore.conversations).get(conversationId);
|
||||
inputValue = conv?.draftText ?? "";
|
||||
} else {
|
||||
inputValue = "";
|
||||
}
|
||||
});
|
||||
|
||||
function handleInputChange() {
|
||||
// If input is empty, allow history navigation again
|
||||
// Otherwise, mark that user has manually typed
|
||||
@@ -176,6 +187,12 @@
|
||||
historyIndex = -1;
|
||||
tempInput = "";
|
||||
|
||||
// Save the current draft so it persists if the user switches tabs.
|
||||
const activeId = get(claudeStore.activeConversationId);
|
||||
if (activeId) {
|
||||
claudeStore.setDraftText(activeId, inputValue);
|
||||
}
|
||||
|
||||
if (isSlashCommand(inputValue)) {
|
||||
matchingCommands = getMatchingCommands(inputValue);
|
||||
showCommandMenu = matchingCommands.length > 0;
|
||||
@@ -326,7 +343,7 @@ User: ${formattedMessage}`;
|
||||
throw new Error("No active conversation");
|
||||
}
|
||||
await invoke("interrupt_claude", { conversationId });
|
||||
claudeStore.addLine("system", "Process interrupted - reconnecting...");
|
||||
claudeStore.addLine("system", "Process interrupted via stop button — reconnecting...");
|
||||
characterState.setState("idle");
|
||||
|
||||
// Show connecting status while we reconnect
|
||||
|
||||
@@ -281,10 +281,16 @@
|
||||
font-family: "JetBrains Mono", "Fira Code", monospace;
|
||||
}
|
||||
|
||||
.markdown-content :global(ul),
|
||||
.markdown-content :global(ul) {
|
||||
margin: 0.5em 0;
|
||||
padding-left: 1.5em;
|
||||
list-style-type: disc;
|
||||
}
|
||||
|
||||
.markdown-content :global(ol) {
|
||||
margin: 0.5em 0;
|
||||
padding-left: 1.5em;
|
||||
list-style-type: decimal;
|
||||
}
|
||||
|
||||
.markdown-content :global(li) {
|
||||
|
||||
@@ -9,10 +9,10 @@
|
||||
import { conversationsStore } from "$lib/stores/conversations";
|
||||
import { configStore } from "$lib/stores/config";
|
||||
|
||||
let permissions: PermissionRequest[] = $state([]);
|
||||
let permissions: PermissionRequest[] = [];
|
||||
let selectedPermissions = new SvelteSet<string>();
|
||||
let grantedToolsList: string[] = $state([]);
|
||||
let workingDirectory = $state("");
|
||||
let grantedToolsList: string[] = [];
|
||||
let workingDirectory = "";
|
||||
|
||||
conversationsStore.pendingPermissions.subscribe((perms) => {
|
||||
permissions = perms;
|
||||
|
||||
Reference in New Issue
Block a user