Skip to content

feat: AI Chat panel with Claude integration#14

Open
fightbulc wants to merge 6 commits intomainfrom
feat/ai-chat
Open

feat: AI Chat panel with Claude integration#14
fightbulc wants to merge 6 commits intomainfrom
feat/ai-chat

Conversation

@fightbulc
Copy link
Contributor

@fightbulc fightbulc commented Dec 19, 2025

Summary

Adds an integrated AI chat panel for document analysis and Q&A using Claude.

Features

Layout & UI

  • Two-column layout (panel sits beside content, not overlay)
  • Resizable panel (drag separator, 15-60% width)
  • Width persisted in config
  • Purple AI button in header (customizable in settings)

Authentication

  • Token validation via Tauri backend (bypasses CORS)
  • Supports API keys (sk-ant-api03-...) and OAuth tokens
  • Token persisted in localStorage

Chat

  • Streaming responses with real-time updates
  • Markdown rendering (same styles as document)
  • Mermaid diagram support with loading spinner

Commands

Type / for autocomplete menu (arrow keys navigate, Enter selects):

Command Description
/clear Reset conversation
/summarise Generate document summary
/model Select model (submenu)
/export Export chat as markdown in new tab

Models

  • Claude Haiku 4.5 (claude-haiku-4-5)
  • Claude Sonnet 4.5 (claude-sonnet-4-5)
  • Claude Opus 4.5 (claude-opus-4-5)

Keyboard Shortcuts

  • Ctrl+A - Toggle panel
  • Escape - Close panel
  • Enter - Send message
  • Shift+Enter - Newline

Files Added

  • packages/app/src/components/ai-chat-panel.tsx
  • packages/app/src/services/claude-client.ts
  • packages/app/src/stores/ai-chat-store.ts
  • packages/app/src/styles/ai-chat.css

Summary by CodeRabbit

  • New Features

    • AI Chat panel with Claude integration: model switching, streaming replies, token setup/validation, resizable panel with persisted width, Alt/Ctrl+A toggle, chat export to Markdown, and Mermaid-enabled rendering.
  • Documentation

    • Added user-facing AI Chat docs and command menu guidance (commands: /clear, /summarise, /model, /export).
  • Style

    • New AI button, dedicated AI color, panel styles, and improved system-aware theme handling and theme toggle visuals.

- Two-column layout with resizable panel (15-60%, persisted)
- Token validation via Tauri backend (bypasses CORS)
- Streaming responses with real-time updates
- Markdown rendering with Mermaid diagram support

Commands (type / for autocomplete menu):
- /clear - Reset conversation
- /summarise - Generate document summary
- /model - Select model (haiku-4.5, sonnet-4.5, opus-4.5)
- /export - Export chat as markdown in new tab

UI:
- Ctrl+A toggle, Escape close, Enter send
- Purple AI button (customizable in settings)
- Model stored in localStorage with validation
@coderabbitai
Copy link

coderabbitai bot commented Dec 19, 2025

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a Claude-based AI Chat to the desktop app: Tauri backend commands for token validation and SSE-style streaming, a SolidJS AI Chat panel with token handling, streaming UI and command palette, a Claude client, state store, styles, theme/color additions, and persistent panel width.

Changes

Cohort / File(s) Summary
Backend & Tauri
packages/app/src-tauri/src/lib.rs, packages/app/src-tauri/Cargo.toml, packages/app/src-tauri/capabilities/default.json
Adds Tauri commands validate_claude_token and stream_claude_chat (SSE parsing, token handling, event emission). Expands Cargo features (tauri devtools, reqwest stream/json), and adds futures/tokio-stream. Grants core:window:default capability.
Frontend app wiring
packages/app/src/App.tsx, packages/app/index.html
Integrates AI panel into layout, adds Alt/Ctrl+A toggle, persists panel width, and updates initial theme resolution/apply flow to use resolvedTheme/system handling.
AI Chat UI & styles
packages/app/src/components/ai-chat-panel.tsx, packages/app/src/styles/ai-chat.css, packages/app/src/components/file-header.tsx, packages/app/src/components/settings-modal.tsx
New AiChatPanel component (token setup, command palette, model switching, streaming rendering, export, resizable). Adds styles, AI header button, and preview button color in settings.
Client & store
packages/app/src/services/claude-client.ts, packages/app/src/stores/ai-chat-store.ts
New Claude client that invokes Tauri streaming and validates tokens; new SolidJS ai-chat store with token persistence/validation, streaming-aware message list, model catalog, abort controls, UI visibility, and width/resizing state.
Types & theming
packages/app/src/types.ts, packages/app/src/utils.ts, packages/shared/styles/theme.css
Adds btn_ai_chat color and optional ai_chat_width config type; adds default btn colors for light/dark and CSS var --btn-ai-chat.
Markdown/Theme consumers
packages/app/src/components/markdown-viewer.tsx, packages/app/src/components/page-overview-modal.tsx, packages/app/src/components/sidebar.tsx
Replaces config().theme usage with resolvedTheme() and updates theme toggle UI/icons; ensures components react to resolved theme.
Dependencies & manifests
packages/app/package.json, packages/api/go.mod
Adds @anthropic-ai/sdk dependency; bumps Go version and moves chromedp to direct require in go.mod.
Documentation
AGENTS.md
Inserts "AI Chat (Desktop App)" documentation block (commands, command menu pattern, models table) in two places.

Sequence Diagram

sequenceDiagram
    participant User
    participant Frontend as App (UI)
    participant Tauri as Tauri Backend
    participant Claude as Claude API

    rect rgba(220,235,255,0.5)
      Note over User,Frontend: Token validation
      User->>Frontend: Enter token
      Frontend->>Tauri: invoke validate_claude_token(token)
      Tauri->>Claude: auth request
      Claude-->>Tauri: success/error
      Tauri-->>Frontend: validation result
    end

    rect rgba(220,255,225,0.5)
      Note over User,Claude: Streaming chat
      User->>Frontend: Send message + model
      Frontend->>Tauri: invoke stream_claude_chat(request, requestId)
      Tauri->>Claude: POST /messages (streaming)
      Claude-->>Tauri: start event
      Tauri-->>Frontend: emit "ai-chat-start-<id>"
      loop deltas
        Claude-->>Tauri: delta chunk
        Tauri-->>Frontend: emit "ai-chat-delta-<id>"
        Frontend->>Frontend: update assistant message progressively
      end
      Claude-->>Tauri: done event
      Tauri-->>Frontend: emit "ai-chat-done-<id>"
      Frontend->>Frontend: finalize message
    end

    rect rgba(255,240,220,0.5)
      Note over User,Frontend: Abort / errors
      User->>Frontend: Stop / Close
      Frontend->>Tauri: signal abort
      Tauri->>Claude: cancel request
      Tauri-->>Frontend: emit error/cleanup
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

🐰 I hopped in with tokens and a curious grin,
Streams of thought unfurl, small deltas spin.
Commands tap like tambourines—/summarise, /model, /clear,
Mermaids sketch diagrams while I nibble on a pear.
✨ A joyful rabbit cheers: Claude, let's pair!

🚥 Pre-merge checks | ✅ 3 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 71.70% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'feat: AI Chat panel with Claude integration' accurately summarizes the main change: adding an integrated AI chat panel with Claude support. It is concise, clear, and directly reflects the primary objective of the changeset.
Merge Conflict Detection ✅ Passed ✅ No merge conflicts detected when merging into main

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/ai-chat

Tip

Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs).
Share your feedback on Discord.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (5)
AGENTS.md (1)

241-241: Capitalize "Markdown" as a proper noun.

Line 241 uses lowercase "markdown"; per documentation style conventions, it should be capitalized as "Markdown".

-| `/export` | Export chat as markdown in new tab |
+| `/export` | Export chat as Markdown in new tab |
packages/app/src-tauri/src/lib.rs (1)

527-531: Consider adding a timeout for the streaming HTTP request.

The streaming request has no timeout configured. If the API becomes unresponsive, the request could hang indefinitely, degrading user experience.

🔎 Proposed fix to add a timeout
     let client = reqwest::Client::new();
-    let mut http_request = client
+    let client = reqwest::Client::builder()
+        .timeout(std::time::Duration::from_secs(120))
+        .build()
+        .map_err(|e| format!("Failed to create HTTP client: {}", e))?;
+    
+    let mut http_request = client
         .post("https://api.anthropic.com/v1/messages")
         .header("Content-Type", "application/json")
         .header("anthropic-version", "2023-06-01");
packages/app/src/App.tsx (1)

474-477: Consider potential conflict with standard "Select All" shortcut.

Ctrl+A is commonly used for "Select All" in most applications. Intercepting it for AI chat toggle may surprise users trying to select text. The PR description mentions "Alt/Ctrl + A" but only Ctrl is checked here.

Consider using Alt+A instead, or checking for e.altKey:

🔎 Proposed alternative using Alt key
-        case "a":
-          e.preventDefault();
-          toggleAiChat();
-          break;
+        case "a":
+          if (e.altKey) {
+            e.preventDefault();
+            toggleAiChat();
+          }
+          break;

Note: If keeping Ctrl+A, you may want to only intercept it when not in an input/textarea context.

packages/app/src/stores/ai-chat-store.ts (2)

123-132: Potential ID collision with Date.now().

Using Date.now() for message IDs can cause collisions if multiple messages are created within the same millisecond (e.g., rapid user input or programmatic batch creation).

🔎 Proposed fix using crypto.randomUUID()
 function addUserMessage(content: string): AiMessage {
   const message: AiMessage = {
-    id: `user-${Date.now()}`,
+    id: `user-${crypto.randomUUID()}`,
     role: "user",
     content,
     timestamp: Date.now(),
   };
   setAiMessages([...aiMessages(), message]);
   return message;
 }

 function addAssistantMessage(content: string = "", isStreaming: boolean = false): AiMessage {
   const message: AiMessage = {
-    id: `assistant-${Date.now()}`,
+    id: `assistant-${crypto.randomUUID()}`,
     role: "assistant",
     content,
     timestamp: Date.now(),
     isStreaming,
   };
   setAiMessages([...aiMessages(), message]);
   return message;
 }

Also applies to: 137-147


48-51: localStorage access at module initialization.

Accessing localStorage synchronously during module load can throw in restricted contexts (SSR, sandboxed iframes, or when storage is disabled). Consider wrapping in a try-catch or deferring initialization.

🔎 Proposed defensive approach
+function getStoredToken(): string | null {
+  try {
+    return localStorage.getItem(TOKEN_STORAGE_KEY);
+  } catch {
+    return null;
+  }
+}
+
 /** API token (null if not set) */
 const [aiToken, setAiTokenInternal] = createSignal<string | null>(
-  localStorage.getItem(TOKEN_STORAGE_KEY)
+  getStoredToken()
 );
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 54bb5d0 and c4c9f33.

⛔ Files ignored due to path filters (2)
  • bun.lock is excluded by !**/*.lock
  • packages/app/src-tauri/Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (15)
  • AGENTS.md (1 hunks)
  • packages/app/package.json (1 hunks)
  • packages/app/src-tauri/Cargo.toml (1 hunks)
  • packages/app/src-tauri/src/lib.rs (3 hunks)
  • packages/app/src/App.tsx (5 hunks)
  • packages/app/src/components/ai-chat-panel.tsx (1 hunks)
  • packages/app/src/components/file-header.tsx (3 hunks)
  • packages/app/src/components/settings-modal.tsx (2 hunks)
  • packages/app/src/services/claude-client.ts (1 hunks)
  • packages/app/src/stores/ai-chat-store.ts (1 hunks)
  • packages/app/src/stores/app-store.ts (1 hunks)
  • packages/app/src/styles/ai-chat.css (1 hunks)
  • packages/app/src/types.ts (2 hunks)
  • packages/app/src/utils.ts (2 hunks)
  • packages/shared/styles/theme.css (2 hunks)
🧰 Additional context used
📓 Path-based instructions (6)
packages/app/{package.json,src-tauri/Cargo.toml,src-tauri/tauri.conf.json}

📄 CodeRabbit inference engine (AGENTS.md)

Desktop app version must be updated in: package.json, src-tauri/Cargo.toml, and src-tauri/tauri.conf.json

Files:

  • packages/app/package.json
  • packages/app/src-tauri/Cargo.toml
packages/app/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

packages/app/src/**/*.{ts,tsx}: Use SolidJS signals for state management, NOT React hooks (useState, useEffect, etc.)
Import SolidJS primitives (createSignal, createEffect, Show, For) for reactivity, not React patterns

Files:

  • packages/app/src/components/file-header.tsx
  • packages/app/src/stores/app-store.ts
  • packages/app/src/utils.ts
  • packages/app/src/services/claude-client.ts
  • packages/app/src/App.tsx
  • packages/app/src/types.ts
  • packages/app/src/components/ai-chat-panel.tsx
  • packages/app/src/stores/ai-chat-store.ts
  • packages/app/src/components/settings-modal.tsx
packages/{app,extension}/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Shared styles must be imported from @md/shared/styles workspace package (theme.css, markdown.css, print.css)

Files:

  • packages/app/src/components/file-header.tsx
  • packages/app/src/stores/app-store.ts
  • packages/app/src/utils.ts
  • packages/app/src/services/claude-client.ts
  • packages/app/src/App.tsx
  • packages/app/src/types.ts
  • packages/app/src/components/ai-chat-panel.tsx
  • packages/app/src/stores/ai-chat-store.ts
  • packages/app/src/components/settings-modal.tsx
packages/app/src/components/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Desktop app components must be created in packages/app/src/components/ directory and use SolidJS patterns

Files:

  • packages/app/src/components/file-header.tsx
  • packages/app/src/components/ai-chat-panel.tsx
  • packages/app/src/components/settings-modal.tsx
packages/app/src/stores/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

State management must be defined in packages/app/src/stores/app-store.ts and exported as signals with setters

Files:

  • packages/app/src/stores/app-store.ts
  • packages/app/src/stores/ai-chat-store.ts
packages/app/src-tauri/src/**/*.rs

📄 CodeRabbit inference engine (AGENTS.md)

packages/app/src-tauri/src/**/*.rs: Tauri commands must be defined in packages/app/src-tauri/src/lib.rs with #[tauri::command] decorator and registered in invoke_handler![]
Tauri resource paths with ../prefix in tauri.conf.json must be accessed in Rust code with up/ directory prefix

Files:

  • packages/app/src-tauri/src/lib.rs
🧠 Learnings (6)
📓 Common learnings
Learnt from: CR
Repo: dnl-fm/md PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-18T13:58:12.139Z
Learning: Desktop app flow is: SolidJS UI → Tauri invoke() → Rust backend → File I/O
📚 Learning: 2025-12-18T13:58:12.139Z
Learnt from: CR
Repo: dnl-fm/md PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-18T13:58:12.139Z
Learning: Applies to packages/{app,extension}/src/**/*.{ts,tsx} : Shared styles must be imported from md/shared/styles workspace package (theme.css, markdown.css, print.css)

Applied to files:

  • packages/app/src/stores/app-store.ts
📚 Learning: 2025-12-18T13:58:12.139Z
Learnt from: CR
Repo: dnl-fm/md PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-18T13:58:12.139Z
Learning: Applies to packages/app/{package.json,src-tauri/Cargo.toml,src-tauri/tauri.conf.json} : Desktop app version must be updated in: package.json, src-tauri/Cargo.toml, and src-tauri/tauri.conf.json

Applied to files:

  • packages/app/src-tauri/Cargo.toml
📚 Learning: 2025-12-18T13:58:12.139Z
Learnt from: CR
Repo: dnl-fm/md PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-18T13:58:12.139Z
Learning: Markdown rendering in extension uses markdown-it (bundled), Prism.js for syntax highlighting, API for Mermaid/ASCII diagrams

Applied to files:

  • packages/app/src/App.tsx
📚 Learning: 2025-12-18T13:58:12.139Z
Learnt from: CR
Repo: dnl-fm/md PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-18T13:58:12.139Z
Learning: Applies to packages/app/src/stores/**/*.{ts,tsx} : State management must be defined in packages/app/src/stores/app-store.ts and exported as signals with setters

Applied to files:

  • packages/app/src/stores/ai-chat-store.ts
📚 Learning: 2025-12-18T13:58:12.139Z
Learnt from: CR
Repo: dnl-fm/md PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-18T13:58:12.139Z
Learning: Applies to packages/app/src/**/*.{ts,tsx} : Use SolidJS signals for state management, NOT React hooks (useState, useEffect, etc.)

Applied to files:

  • packages/app/src/stores/ai-chat-store.ts
🧬 Code graph analysis (3)
packages/app/src/components/file-header.tsx (1)
packages/app/src/stores/ai-chat-store.ts (1)
  • toggleAiChat (250-250)
packages/app/src/services/claude-client.ts (1)
packages/app/src/stores/ai-chat-store.ts (2)
  • AiMessage (17-23)
  • AiModel (25-25)
packages/app/src/components/ai-chat-panel.tsx (3)
packages/app/src/utils.ts (2)
  • escapeHtml (121-130)
  • getFilename (105-107)
packages/app/src/stores/ai-chat-store.ts (2)
  • AI_MODELS (27-31)
  • AiModel (25-25)
packages/app/src/services/claude-client.ts (2)
  • validateToken (81-101)
  • streamChat (118-179)
🪛 LanguageTool
AGENTS.md

[uncategorized] ~241-~241: Did you mean the formatting language “Markdown” (= proper noun)?
Context: ...submenu) | | /export | Export chat as markdown in new tab | ### Command Menu Pattern ...

(MARKDOWN_NNP)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: test
  • GitHub Check: build-check
🔇 Additional comments (27)
AGENTS.md (1)

228-274: Documentation for AI Chat feature is comprehensive and well-structured.

The new section clearly outlines the command interface, menu patterns, and available models with appropriate TypeScript interface examples and persistence details. It integrates naturally with the existing AGENTS.md style and provides users with actionable information for using the Claude integration.

packages/app/src/utils.ts (1)

39-39: LGTM!

The AI chat button colors are well-chosen and consistent with the existing color scheme. The purple shades (#8b5cf6 for dark, #7c3aed for light) provide good contrast and align with typical AI feature styling.

Also applies to: 78-78

packages/app/src/services/claude-client.ts (3)

106-113: LGTM!

The message filtering logic correctly excludes system messages (which are handled separately via buildSystemPrompt) and transforms user/assistant messages to the API format.


118-179: LGTM - Well-structured streaming implementation!

The streaming implementation follows good practices:

  • Event listener is set up before invoking the command (prevents race conditions)
  • Abort signal is properly checked to prevent callbacks after cancellation
  • Cleanup is guaranteed via finally block
  • Text accumulation correctly builds fullText from deltas

37-72: LGTM!

The system prompt is well-structured and provides clear context about the application, capabilities, and constraints. The mention of /tabs as a "future feature" is appropriately documented.

packages/app/src/stores/app-store.ts (1)

262-262: LGTM!

The CSS custom property for the AI chat button follows the existing pattern and correctly integrates with the theme color system using the getColor helper.

packages/shared/styles/theme.css (1)

31-31: LGTM!

The CSS custom properties for the AI chat button are correctly defined in both dark and light themes, matching the default color values in packages/app/src/utils.ts.

Also applies to: 74-74

packages/app/src/types.ts (1)

74-75: LGTM!

The type additions are clean, well-documented, and follow the existing patterns. Making ai_chat_width optional is appropriate for backward compatibility with existing configurations.

Also applies to: 106-107

packages/app/src-tauri/Cargo.toml (1)

34-36: Dependencies are current with no identified security issues.

The versions used enable proper caret ranges: reqwest 0.12 resolves to latest 0.12.x (0.12.25+), futures 0.3 resolves to latest 0.3.x (0.3.31+), and tokio-stream 0.1 resolves to latest 0.1.x (0.1.17+). All three crates are actively maintained. No CVEs have been identified for these specific crates. Note: CVE-2025-62518 (TARmageddon) affects tokio-tar (archive handling), not tokio-stream (stream utilities).

packages/app/package.json (1)

20-20: @anthropic-ai/sdk version 0.71.2 is current and secure. This is the latest stable version as of now, last published 12 days ago. No known security vulnerabilities have been identified in this release.

packages/app/src/components/file-header.tsx (1)

69-75: The Ctrl+A keyboard shortcut is properly implemented in the app's global keyboard event handler (App.tsx, lines 424-476). The handler is attached to the document and correctly calls toggleAiChat() when Ctrl/Cmd+A is pressed.

packages/app/src/components/settings-modal.tsx (1)

68-68: LGTM!

The new btn_ai_chat color key is properly integrated into the color groups and preview section, following the same pattern as existing button colors.

Also applies to: 199-199

packages/app/src/styles/ai-chat.css (1)

1-56: Well-structured CSS with consistent theming.

The stylesheet follows good practices with:

  • CSS variables for theme consistency
  • Clear section organization with comments
  • Appropriate use of flexbox for layout
  • Smooth animations for UX polish
  • Focus states for accessibility

Minor note: Error colors (e.g., #ef4444) are hardcoded, which is acceptable for semantic error states, but consider defining a --error-color variable for future consistency.

Also applies to: 57-139, 140-238, 239-357, 358-393, 394-536

packages/app/src-tauri/src/lib.rs (3)

478-516: Token validation implementation looks good.

The validation correctly:

  • Distinguishes OAuth vs API key tokens
  • Makes a minimal API call to verify validity
  • Returns descriptive error messages with status code and body

541-649: Streaming implementation is well-structured.

The SSE parsing logic correctly handles:

  • Buffer-based chunked parsing with \n\n delimiters
  • JSON extraction from data: prefixed lines
  • Content delta accumulation
  • Error propagation via events
  • Proper lifecycle events (start/delta/done/error)

695-696: Commands properly registered.

Both validate_claude_token and stream_claude_chat are correctly added to the invoke handler. As per coding guidelines, Tauri commands are properly defined with #[tauri::command] and registered in invoke_handler.

packages/app/src/App.tsx (1)

95-104: AI chat integration follows existing patterns well.

The implementation correctly:

  • Uses SolidJS signals and stores (per coding guidelines)
  • Follows existing config persistence pattern in saveAiChatWidth
  • Properly integrates escape key handling in LIFO order
  • Uses conditional flex sizing for the two-column layout
  • Imports shared styles from the appropriate location

Also applies to: 194-194, 632-633, 1256-1261, 1265-1265, 1273-1285

packages/app/src/components/ai-chat-panel.tsx (4)

115-204: TokenSetup component handles validation flow correctly.

The component:

  • Validates stored token on mount with proper loading state
  • Shows appropriate UI for validating vs input states
  • Clears invalid stored tokens automatically
  • Provides clear user feedback for errors

Good use of SolidJS patterns with Show for conditional rendering.


214-291: Message component handles streaming and mermaid rendering well.

The implementation correctly:

  • Uses effects that track streaming state changes
  • Avoids unnecessary re-renders with content comparison
  • Processes mermaid diagrams only after streaming completes
  • Handles render errors gracefully with fallback display

297-666: ChatView component is well-implemented.

The command palette, keyboard navigation, and streaming chat integration are well-structured. Good separation between command execution, message sending, and UI state management.


672-795: Resize handling with proper cleanup.

The resize implementation:

  • Uses proper mouse event handling pattern
  • Cleans up listeners in onCleanup hook
  • Clamps width to reasonable bounds (15-60%)
  • Persists width via callback

Double-click auto-fit is a nice UX touch.

packages/app/src/stores/ai-chat-store.ts (6)

1-11: Coding guideline: State management should be in app-store.ts.

Per the coding guidelines, state management must be defined in packages/app/src/stores/app-store.ts. This file creates a separate store which deviates from that convention. Consider either:

  1. Moving these signals to app-store.ts, or
  2. Confirming this separation is intentional for modularity (AI feature isolation).

The use of SolidJS createSignal is correct. Based on learnings, this follows the expected pattern.


37-38: Security consideration: API token in localStorage.

Storing the API token in localStorage makes it accessible to any JavaScript running on the page (XSS risk). For a Tauri desktop app this is lower risk than a web app, but worth noting. The current implementation is acceptable for a desktop context where the trust boundary is different.

Also applies to: 101-110


152-172: LGTM: Streaming message update logic.

The updateLastAssistantMessage function correctly finds and updates the last assistant message immutably, which is appropriate for SolidJS reactivity.


184-197: Abort controller management is sound.

The abort controller pattern for canceling in-flight requests is correctly implemented. The module-level currentAbortController variable appropriately manages the singleton abort state.


220-252: Exports are well-organized.

Clean separation between state signals and action functions in exports. The API surface is comprehensive for the chat panel's needs.


25-31: The model identifiers are correct. The Claude API uses the model identifiers claude-sonnet-4-5 and claude-opus-4-5, and claude-haiku-4-5 is used via the Claude API. These are the current and official identifiers for Claude 4.5 models as of late 2025. No changes required.

Comment on lines +81 to +101
export async function validateToken(token: string): Promise<{ valid: boolean; error?: string }> {
try {
await invoke<boolean>("validate_claude_token", { token });
return { valid: true };
} catch (error) {
const message = error instanceof Error ? error.message : String(error);

// Parse common error cases
if (message.includes("401") || message.includes("authentication") || message.includes("Unauthorized")) {
return { valid: false, error: "Invalid API key or token" };
}
if (message.includes("403") || message.includes("permission")) {
return { valid: false, error: "Token lacks required permissions" };
}
if (message.includes("Network") || message.includes("network")) {
return { valid: false, error: "Network error. Check your connection." };
}

return { valid: false, error: message };
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find Rust source files and search for the validate_claude_token command
find . -type f -name "*.rs" | head -20

Repository: dnl-fm/md

Length of output: 194


🏁 Script executed:

#!/bin/bash
# Search for validate_claude_token in Rust files
rg "validate_claude_token" --type rust -A 20

Repository: dnl-fm/md

Length of output: 1913


🏁 Script executed:

#!/bin/bash
# Search for tauri::command pattern related to claude or token
rg "tauri::command" --type rust -A 15 | grep -A 15 -E "(token|claude|validate)" || echo "No matches found with filter"

Repository: dnl-fm/md

Length of output: 2204


🏁 Script executed:

#!/bin/bash
# Get the full validate_claude_token function implementation
rg "async fn validate_claude_token" --type rust -A 50

Repository: dnl-fm/md

Length of output: 3362


Remove redundant lowercase "network" check and consider structured error handling.

The network error check should only look for "Network" (capital N) since the backend always uses "Network error:" format. The redundant lowercase check adds no value. Additionally, the error parsing for 401/403 depends on Anthropic API response bodies containing specific keywords—consider requesting structured error types from the backend instead of relying on string pattern matching.

if (message.includes("Network")) {  // Remove includes("network")
  return { valid: false, error: "Network error. Check your connection." };
}
🤖 Prompt for AI Agents
packages/app/src/services/claude-client.ts around lines 81-101: remove the
redundant message.includes("network") check so only message.includes("Network")
is used, and replace brittle string-based permission/auth checks by attempting
to read a structured error from the thrown object (e.g., error.code or
error.type) and branch on that first; if no structured fields exist, fall back
to the existing string checks and final message return.

- Clear mermaidCodeMap on /clear to prevent memory leak
- Use TextEncoder for mermaid hash (fixes btoa on non-ASCII)
- Remove redundant lowercase network check
- Use CSS variables for dark mode support in chat messages
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (4)
packages/app/src/services/claude-client.ts (1)

81-101: Error handling looks correct.

The previous review comment about redundant lowercase "network" check has been addressed—the code now only checks for "Network" (capital N). The error parsing for common HTTP status codes and network errors is reasonable.

packages/app/src/styles/ai-chat.css (1)

301-320: Previous hardcoded color issue has been addressed.

The user and assistant message backgrounds now correctly use CSS variables with color-mix() and var(--bg-primary), properly supporting theme/dark mode.

packages/app/src/components/ai-chat-panel.tsx (2)

82-88: Previous memory leak concern has been addressed.

The clearMermaidCache() function is now exported and called in executeCommand when clearing the chat (line 414). This prevents unbounded growth of the mermaid code map.


96-104: Previous btoa() issue has been addressed.

The code now uses TextEncoder to safely handle Unicode content when generating mermaid diagram IDs, preventing DOMException on non-ASCII characters.

🧹 Nitpick comments (6)
packages/app/src/services/claude-client.ts (1)

37-72: System prompt embeds full document content without size limits.

The document content is embedded directly in the system prompt. For very large documents, this could exceed Claude's context window limits or cause performance issues.

Consider adding a character/token limit with truncation and informing the user when content is truncated:

const MAX_CONTENT_LENGTH = 100000; // ~25k tokens rough estimate
const truncatedContent = context.content.length > MAX_CONTENT_LENGTH 
  ? context.content.slice(0, MAX_CONTENT_LENGTH) + "\n\n[Document truncated due to size...]"
  : context.content;
packages/app/src/styles/ai-chat.css (2)

57-130: Consider reducing !important usage.

Multiple !important declarations on .ai-logout-btn (lines 103-115) suggest specificity conflicts with base button styles. While functional, this could make future style maintenance harder.


197-204: Error color is hardcoded instead of using theme variables.

The error states use hardcoded #ef4444 which won't adapt to theme changes. Consider introducing an error color CSS variable for consistency.

🔎 Proposed fix
 .ai-token-error {
-  color: #ef4444;
+  color: var(--color-error, #ef4444);
   font-size: 0.85em;
   margin-bottom: 12px;
   padding: 8px 12px;
-  background: rgba(239, 68, 68, 0.1);
+  background: color-mix(in srgb, var(--color-error, #ef4444) 10%, transparent);
   border-radius: 6px;
 }

 .ai-stop-btn {
-  background: #ef4444 !important;
-  border-color: #ef4444 !important;
+  background: var(--color-error, #ef4444) !important;
+  border-color: var(--color-error, #ef4444) !important;
   color: white !important;
   font-size: 0.7em;
 }

 .ai-stop-btn:hover {
-  background: #dc2626 !important;
-  border-color: #dc2626 !important;
+  background: color-mix(in srgb, var(--color-error, #ef4444) 85%, black) !important;
+  border-color: color-mix(in srgb, var(--color-error, #ef4444) 85%, black) !important;
 }

Also applies to: 526-535

packages/app/src/components/ai-chat-panel.tsx (3)

123-141: Token validation on mount doesn't handle concurrent navigation.

If the user closes and reopens the panel quickly while validation is in progress, or if the component unmounts during validation, the async operation continues and may update state on an unmounted component. SolidJS handles this better than React, but consider adding cleanup:

🔎 Proposed fix with abort handling
 function TokenSetup() {
   const [inputToken, setInputToken] = createSignal("");
+  let isMounted = true;
   
+  onCleanup(() => {
+    isMounted = false;
+  });
+
   // Validate stored token on mount
   onMount(async () => {
     const storedToken = aiToken();
     if (storedToken && !aiTokenValid()) {
       setAiTokenValidating(true);
       const result = await validateToken(storedToken);
+      if (!isMounted) return;
       setAiTokenValidating(false);

250-281: Mermaid rendering uses setTimeout without cleanup.

The setTimeout for mermaid diagram processing doesn't get cancelled if the component unmounts, which could cause issues if the user navigates away quickly. Also, diagrams.forEach with async callback doesn't await—errors in individual diagram renders won't prevent others from rendering (which is good), but there's no aggregate error handling.

🔎 Proposed fix with timeout cleanup
 function Message(props: MessageProps) {
   let containerRef: HTMLDivElement | undefined;
   let contentRef: HTMLDivElement | undefined;
   let lastRenderedContent = "";
   let hasMermaidRendered = false;
+  let mermaidTimeout: number | undefined;
+
+  onCleanup(() => {
+    if (mermaidTimeout) clearTimeout(mermaidTimeout);
+  });

   // ... existing code ...

   // Process mermaid diagrams after render
   createEffect(() => {
     const msgContent = props.message.content;
     const isStreaming = props.message.isStreaming;
     
     if (!msgContent || isStreaming || !containerRef) return;
     
-    setTimeout(() => {
+    mermaidTimeout = window.setTimeout(() => {
       // ... existing code ...
     }, 100);
   });

432-467: Export creates draft without error handling.

createDraft and updateDraft calls don't handle potential errors. If draft creation fails, the subsequent operations would still proceed, potentially leaving the app in an inconsistent state.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c4c9f33 and 427ea55.

📒 Files selected for processing (3)
  • packages/app/src/components/ai-chat-panel.tsx (1 hunks)
  • packages/app/src/services/claude-client.ts (1 hunks)
  • packages/app/src/styles/ai-chat.css (1 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
packages/app/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

packages/app/src/**/*.{ts,tsx}: Use SolidJS signals for state management, NOT React hooks (useState, useEffect, etc.)
Import SolidJS primitives (createSignal, createEffect, Show, For) for reactivity, not React patterns

Files:

  • packages/app/src/services/claude-client.ts
  • packages/app/src/components/ai-chat-panel.tsx
packages/{app,extension}/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Shared styles must be imported from @md/shared/styles workspace package (theme.css, markdown.css, print.css)

Files:

  • packages/app/src/services/claude-client.ts
  • packages/app/src/components/ai-chat-panel.tsx
packages/app/src/components/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Desktop app components must be created in packages/app/src/components/ directory and use SolidJS patterns

Files:

  • packages/app/src/components/ai-chat-panel.tsx
🧬 Code graph analysis (2)
packages/app/src/services/claude-client.ts (1)
packages/app/src/stores/ai-chat-store.ts (2)
  • AiMessage (17-23)
  • AiModel (25-25)
packages/app/src/components/ai-chat-panel.tsx (4)
packages/app/src/utils.ts (2)
  • escapeHtml (121-130)
  • getFilename (105-107)
packages/app/src/stores/ai-chat-store.ts (20)
  • aiToken (224-224)
  • aiTokenValid (226-226)
  • setAiTokenValidating (229-229)
  • setAiTokenValid (227-227)
  • setAiToken (225-225)
  • aiTokenValidating (228-228)
  • aiTokenError (230-230)
  • aiMessages (232-232)
  • setAiLoading (235-235)
  • addAssistantMessage (245-245)
  • getAbortSignal (248-248)
  • AI_MODELS (27-31)
  • AiModel (25-25)
  • abortCurrentRequest (249-249)
  • aiChatWidth (238-238)
  • setAiChatResizing (241-241)
  • setAiChatWidth (239-239)
  • clearAiToken (251-251)
  • showAiChat (222-222)
  • setShowAiChat (223-223)
packages/app/src/services/claude-client.ts (2)
  • validateToken (81-101)
  • streamChat (118-179)
packages/app/src/stores/app-store.ts (9)
  • content (358-358)
  • currentFile (356-356)
  • currentDraftId (413-413)
  • getDraft (418-418)
  • createDraft (415-415)
  • updateDraft (416-416)
  • setCurrentFile (357-357)
  • setCurrentDraftId (414-414)
  • setContent (359-359)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: test
  • GitHub Check: build-check
🔇 Additional comments (8)
packages/app/src/services/claude-client.ts (3)

1-9: LGTM!

Clean module header with appropriate imports. Using Tauri's invoke and listen APIs for IPC is the correct approach for a desktop app needing to bypass CORS.


14-31: LGTM!

Well-defined interfaces for streaming callbacks and document context. The ChatStreamEvent internal type properly models the expected event payload structure from the backend.


118-179: Race condition and stream completion issues with event-based streaming pattern.

The current implementation has two architectural problems:

  1. Race condition risk: The listen() and invoke() calls are concurrent rather than sequential. According to Tauri patterns, the listener should be registered (awaited) before the backend command is invoked. Early events can be missed if the backend starts emitting before the JavaScript listener is fully registered.

  2. Wrong pattern for streaming: Tauri channels are the recommended mechanism for streaming data like HTTP responses, not events. Events are inefficient for continuous streaming and lose delivery guarantees.

  3. Incomplete stream awaiting: The function resolves after invoke() completes, not when streaming finishes. If the backend spawns an async task to emit events after the command returns, the caller won't wait for the actual stream to complete.

Consider refactoring to use Tauri Channels instead of events for the streaming response, or ensure the backend blocks the invoke command until streaming is actually complete.

packages/app/src/styles/ai-chat.css (1)

1-56: LGTM!

Clean layout styles with proper use of CSS variables for theming. The resize handle implementation with transition and hover states is well done.

packages/app/src/components/ai-chat-panel.tsx (4)

13-51: LGTM!

Correct use of SolidJS primitives (createSignal, createEffect, Show, For, onMount, onCleanup) as required by coding guidelines. Clean import organization with proper separation of concerns.


686-723: LGTM! Resize logic is well implemented.

The resize handling correctly clamps width between 15-60%, uses proper cleanup in onCleanup, and persists the width via the callback. The double-click auto-fit feature is a nice UX touch.


746-750: Good defensive cleanup on unmount.

The onCleanup handler properly removes global event listeners, preventing memory leaks if the component unmounts during a resize operation.


752-804: LGTM!

The main panel component correctly uses SolidJS's Show for conditional rendering, properly wires up the resize handle, and cleanly switches between TokenSetup and ChatView based on token validity.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (2)
packages/app/src/components/ai-chat-panel.tsx (2)

263-279: Consider the async forEach pattern.

Line 263 uses forEach with an async callback, which doesn't await the promises. While this works for independent diagrams, it could theoretically lead to race conditions if the same diagram ID appears multiple times.

This is likely fine given the hash-based ID generation, but for robustness you could use for...of or Promise.all():

🔎 Alternative approach with Promise.all
-      diagrams.forEach(async (el) => {
+      await Promise.all(Array.from(diagrams).map(async (el) => {
         if (el.querySelector('svg')) return; // Already rendered
         
         const code = mermaidCodeMap.get(el.id);
         if (!code) return;
         
         try {
           const { svg } = await mermaid.render(el.id + '-svg', code);
           el.innerHTML = svg;
           el.classList.remove('mermaid-loading');
           hasMermaidRendered = true;
         } catch (err) {
           console.error('Mermaid render error:', err);
           el.innerHTML = `<pre class="mermaid-error">Diagram error: ${err}</pre>`;
           el.classList.remove('mermaid-loading');
         }
-      });
+      }));

728-743: Misleading comment about measuring content width.

The comment at line 729 states "measure content and set width accordingly," but the implementation sets a fixed width of 35% (line 737) without measuring the actual content.

🔎 Suggested fix

Either update the comment to reflect the actual behavior:

 function handleResizeDoubleClick() {
-  // Auto-fit: measure content and set width accordingly
+  // Auto-fit: reset to default width for comfortable reading
   if (panelRef) {

Or remove the unused measurement code:

 function handleResizeDoubleClick() {
-  // Auto-fit: measure content and set width accordingly
-  if (panelRef) {
-    const messagesContainer = panelRef.querySelector(".ai-messages");
-    if (messagesContainer) {
-      // Get the natural content width
-      const container = panelRef.parentElement;
-      if (container) {
-        // Set to a reasonable auto-fit width (40% is good for reading)
-        const autoWidth = 35;
-        setAiChatWidth(autoWidth);
-        props.onSaveWidth(autoWidth);
-      }
-    }
-  }
+  // Reset to default width for comfortable reading
+  const autoWidth = 35;
+  setAiChatWidth(autoWidth);
+  props.onSaveWidth(autoWidth);
 }
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 427ea55 and acf52b1.

📒 Files selected for processing (1)
  • packages/app/src/components/ai-chat-panel.tsx (1 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
packages/app/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

packages/app/src/**/*.{ts,tsx}: Use SolidJS signals for state management, NOT React hooks (useState, useEffect, etc.)
Import SolidJS primitives (createSignal, createEffect, Show, For) for reactivity, not React patterns

Files:

  • packages/app/src/components/ai-chat-panel.tsx
packages/{app,extension}/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Shared styles must be imported from @md/shared/styles workspace package (theme.css, markdown.css, print.css)

Files:

  • packages/app/src/components/ai-chat-panel.tsx
packages/app/src/components/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Desktop app components must be created in packages/app/src/components/ directory and use SolidJS patterns

Files:

  • packages/app/src/components/ai-chat-panel.tsx
🧬 Code graph analysis (1)
packages/app/src/components/ai-chat-panel.tsx (4)
packages/app/src/utils.ts (2)
  • escapeHtml (121-130)
  • getFilename (105-107)
packages/app/src/stores/ai-chat-store.ts (24)
  • aiToken (224-224)
  • aiTokenValid (226-226)
  • setAiTokenValidating (229-229)
  • setAiTokenValid (227-227)
  • setAiToken (225-225)
  • setAiTokenError (231-231)
  • aiTokenValidating (228-228)
  • aiTokenError (230-230)
  • aiMessages (232-232)
  • addAssistantMessage (245-245)
  • addUserMessage (244-244)
  • setAiLoading (235-235)
  • aiModel (236-236)
  • updateLastAssistantMessage (246-246)
  • getAbortSignal (248-248)
  • AiModel (25-25)
  • setAiModel (237-237)
  • abortCurrentRequest (249-249)
  • aiChatWidth (238-238)
  • setAiChatResizing (241-241)
  • setAiChatWidth (239-239)
  • clearAiToken (251-251)
  • showAiChat (222-222)
  • setShowAiChat (223-223)
packages/app/src/services/claude-client.ts (2)
  • validateToken (81-101)
  • streamChat (118-179)
packages/app/src/stores/app-store.ts (9)
  • content (358-358)
  • currentFile (356-356)
  • currentDraftId (413-413)
  • getDraft (418-418)
  • createDraft (415-415)
  • updateDraft (416-416)
  • setCurrentFile (357-357)
  • setCurrentDraftId (414-414)
  • setContent (359-359)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: test
  • GitHub Check: build-check
🔇 Additional comments (11)
packages/app/src/components/ai-chat-panel.tsx (11)

13-13: Verify SolidJS imports are complete.

The imports look correct for SolidJS patterns. Good adherence to the coding guidelines requiring SolidJS (not React hooks).


82-88: Memory leak addressed with clearMermaidCache.

The exported clearMermaidCache function properly addresses the previous concern about unbounded memory growth in mermaidCodeMap. This is called from the clear command at line 417.


96-104: Non-ASCII handling improved with TextEncoder.

The hash generation now uses TextEncoder instead of btoa(), which properly handles Unicode characters in Mermaid diagrams. This addresses the previous concern about DOMException with non-ASCII content.


123-212: TokenSetup component looks solid.

The token validation flow is well-structured:

  • Validates stored tokens on mount
  • Clears invalid tokens for security
  • Provides clear error messaging
  • Uses SolidJS patterns correctly

228-281: Mermaid rendering logic is well-structured.

The two-effect pattern (markdown rendering + Mermaid processing) correctly handles streaming updates and diagram rendering. The optimization to skip updates when content hasn't changed (line 236) is good.


380-411: Token validation improved.

The token check now occurs before adding the user message (line 381-385), which addresses the previous concern. If no token exists, an error message is shown to the user without adding their message to the chat. This is a reasonable UX approach.


435-470: Chat export functionality is well-implemented.

The export flow properly validates messages, formats content as Markdown, creates a new draft, and switches the view. The integration with the app store is correct.


689-726: Resize drag logic is well-implemented.

The resize handlers properly track mouse movement, clamp width to 15-60%, and persist the width via the callback. Event cleanup is handled correctly.


750-753: Proper cleanup on unmount.

The onCleanup hook correctly removes event listeners to prevent memory leaks. Good practice.


684-807: Main panel component structure is solid.

The AiChatPanel component properly:

  • Manages resize state and persistence
  • Cleans up event listeners
  • Conditionally renders based on token validity
  • Uses SolidJS patterns throughout

51-51: No action required—ai-chat.css is component-specific.

The file correctly contains only AI Chat Panel-specific styles (all selectors use the .ai-* prefix) and appropriately uses shared theme variables without redefining them. The reference to markdown-body at line 314 is a proper dependency on shared styles, not a redefinition.

- Add 'system' theme option that follows OS preference
- Poll Tauri theme API for production builds (matchMedia doesn't fire)
- Add core:window:default capability for theme detection
- Add devtools feature for debugging
- Update splash screen to handle system theme on load
- Show current theme state in sidebar (not next state)
- Prevent flash by waiting for theme before rendering content
- Use SVG icons for theme toggle (moon/sun/monitor)
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@packages/app/src-tauri/capabilities/default.json`:
- Line 8: Remove the unnecessary "core:window:default" permission from the
capabilities list in default.json: locate the entry "core:window:default" and
delete it so the AI chat feature no longer requests window-management permission
(leave the rest of the JSON array intact and ensure valid JSON punctuation after
removal).

In `@packages/app/src/App.tsx`:
- Around line 478-481: The global Ctrl+A branch (case "a") is intercepting
keydown and preventing the wasm editor's Select All; update the handler so it
first checks whether the editor is focused (e.g., inspect document.activeElement
or an existing editor focus state exposed by the WasmEditor
component/wasm-editor.tsx) and only call e.preventDefault() and toggleAiChat()
when the editor is NOT focused; alternatively bind toggleAiChat() to a different
shortcut. Specifically, guard the case "a" branch with a focus check before
calling toggleAiChat().
🧹 Nitpick comments (5)
packages/app/src-tauri/Cargo.toml (1)

21-21: Consider conditionally enabling devtools feature.

The devtools feature is useful for debugging but adds overhead in release builds. Consider using Cargo features to enable it only in debug builds, or verify this is intentional for production.

packages/app/src/components/sidebar.tsx (1)

203-234: Consider extracting SVG icons to reduce duplication.

The theme SVG icons (moon, sun, monitor) are duplicated between the expanded sidebar (lines 203-234) and collapsed sidebar (lines 259-281). Extracting these to constants or small components would improve maintainability.

♻️ Example refactor to extract icons
// Add at the top of the file or in a shared icons file
const MoonIcon = () => (
  <svg style="width: 14px; height: 14px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
    <path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z" />
  </svg>
);

const SunIcon = () => (
  <svg style="width: 14px; height: 14px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
    <circle cx="12" cy="12" r="5" />
    <line x1="12" y1="1" x2="12" y2="3" />
    {/* ... remaining lines */}
  </svg>
);

const MonitorIcon = () => (
  <svg style="width: 14px; height: 14px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
    <rect x="2" y="3" width="20" height="14" rx="2" />
    <line x1="8" y1="21" x2="16" y2="21" />
    <line x1="12" y1="17" x2="12" y2="21" />
  </svg>
);
packages/app/src/stores/app-store.ts (3)

29-38: Module-level mutable state may cause reactivity issues.

systemThemeCache is a mutable module-level variable that won't trigger SolidJS reactivity when updated. This is likely intentional since it's only used to derive resolvedTheme signal value, but worth noting for maintainability.


355-367: Consider increasing the theme polling interval.

A 500ms polling interval may be too aggressive for checking system theme changes. Consider increasing to 1000-2000ms to reduce CPU usage, especially since system theme changes are infrequent user actions.

♻️ Suggested change
-    const THEME_POLL_INTERVAL_MS = 500;
+    const THEME_POLL_INTERVAL_MS = 1000;

363-370: Empty catch blocks silently swallow errors.

The empty catch blocks provide no visibility into failures. Consider at minimum logging at debug level for troubleshooting purposes.

♻️ Add minimal logging
       } catch {
-        // Expected if Tauri API temporarily unavailable
+        // Expected if Tauri API temporarily unavailable - no action needed
       }
     }, THEME_POLL_INTERVAL_MS);
   } catch {
-    // Expected in browser dev mode
+    // Expected in browser dev mode - Tauri APIs not available
   }

"windows": ["main"],
"permissions": [
"core:default",
"core:window:default",
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# Find files related to AI chat feature
fd -type f -name "*.ts" -o -name "*.tsx" | xargs grep -l "chat\|claude\|ai" | head -20

Repository: dnl-fm/md

Length of output: 223


🏁 Script executed:

# Search for Tauri window API imports in TypeScript/TSX files
rg -n "@tauri-apps/api/window|getCurrentWindow|appWindow|getAll|create|physicalSize|Monitor" packages/app/src --type ts --type tsx -A 2 -B 2

Repository: dnl-fm/md

Length of output: 80


🏁 Script executed:

# Search for window-related operations in Rust backend
rg -n "WindowBuilder|create_window|show_window|focus_window|resize|Window" packages/app/src-tauri/src --type rust -A 1 -B 1 | head -50

Repository: dnl-fm/md

Length of output: 270


🏁 Script executed:

# Check the actual changes in the PR - list all modified files
git diff --name-only HEAD^ HEAD 2>/dev/null || echo "Git not available, checking file structure"

Repository: dnl-fm/md

Length of output: 95


🏁 Script executed:

# Look for any window-specific code in new or modified files related to streaming/AI
find packages/app/src -type f \( -name "*stream*" -o -name "*chat*" -o -name "*claude*" -o -name "*panel*" \) 2>/dev/null | xargs wc -l 2>/dev/null

Repository: dnl-fm/md

Length of output: 254


🏁 Script executed:

# Search for window API imports and usage
rg "@tauri-apps/api/window|getCurrentWindow|appWindow|createTauriWindow" packages/app/src

Repository: dnl-fm/md

Length of output: 403


🏁 Script executed:

# Check the ai-chat-panel component for window operations
cat -n packages/app/src/components/ai-chat-panel.tsx | head -80

Repository: dnl-fm/md

Length of output: 3240


🏁 Script executed:

# Search for any Tauri command invocations in AI chat files
rg "invoke|tauri|window" packages/app/src/components/ai-chat-panel.tsx -i

Repository: dnl-fm/md

Length of output: 35


🏁 Script executed:

# Check the claude client for Tauri API usage
cat -n packages/app/src/services/claude-client.ts

Repository: dnl-fm/md

Length of output: 6718


🏁 Script executed:

# Search for Tauri imports across the entire src directory
rg "@tauri-apps" packages/app/src

Repository: dnl-fm/md

Length of output: 1010


Remove the unnecessary core:window:default permission.

The AI chat feature does not use any window management APIs. The panel is implemented as a resizable UI component using CSS and state management. The only window API usage in the codebase is in app-store.ts for theme detection, which is unrelated to the new AI chat feature. The new code uses only invoke() for Tauri commands and listen() for event streaming.

🤖 Prompt for AI Agents
In `@packages/app/src-tauri/capabilities/default.json` at line 8, Remove the
unnecessary "core:window:default" permission from the capabilities list in
default.json: locate the entry "core:window:default" and delete it so the AI
chat feature no longer requests window-management permission (leave the rest of
the JSON array intact and ensure valid JSON punctuation after removal).

Comment on lines +478 to +481
case "a":
e.preventDefault();
toggleAiChat();
break;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if Ctrl+A is used elsewhere or if there's select-all functionality
echo "=== Searching for select-all references ==="
rg -n "select.*all|selectAll" --type=ts --type=tsx packages/app/src/ || echo "No select-all references found"
echo ""
echo "=== Searching for other Ctrl+A references ==="
rg -n "Ctrl\+A|ctrl.*a" packages/app/src/ || echo "No other Ctrl+A references found"

Repository: dnl-fm/md

Length of output: 711


🏁 Script executed:

#!/bin/bash
# Examine the keyboard binding context around lines 478-481
echo "=== Context around the Ctrl+A binding (lines 470-490) ==="
sed -n '470,490p' packages/app/src/App.tsx

Repository: dnl-fm/md

Length of output: 628


🏁 Script executed:

#!/bin/bash
# Search for select functionality and editor context
echo "=== Searching for 'select' references (broader context) ==="
rg -n "case \"[a-z]\":" packages/app/src/App.tsx | head -20

Repository: dnl-fm/md

Length of output: 404


🏁 Script executed:

#!/bin/bash
# Examine the Select All implementation in wasm-editor.tsx
echo "=== wasm-editor.tsx around line 625 (Select All context) ==="
sed -n '620,630p' packages/app/src/components/wasm-editor.tsx

Repository: dnl-fm/md

Length of output: 349


🏁 Script executed:

#!/bin/bash
# Check the broader keyboard event handler in App.tsx to understand event flow
echo "=== Full keyboard event handler in App.tsx ==="
sed -n '420,510p' packages/app/src/App.tsx

Repository: dnl-fm/md

Length of output: 2815


🏁 Script executed:

#!/bin/bash
# Find where handleKeyDown is attached
echo "=== Where handleKeyDown is registered ==="
rg -n "handleKeyDown|keydown" packages/app/src/App.tsx | head -20

Repository: dnl-fm/md

Length of output: 212


🏁 Script executed:

#!/bin/bash
# Check if there's any focus-aware handling or component isolation
echo "=== Event listener attachment in App.tsx ==="
sed -n '1,100p' packages/app/src/App.tsx | tail -50

Repository: dnl-fm/md

Length of output: 1503


🏁 Script executed:

#!/bin/bash
# Check if handleKeyDown has any focus checks to prevent this conflict
echo "=== Full handleKeyDown function with context ==="
sed -n '428,550p' packages/app/src/App.tsx

Repository: dnl-fm/md

Length of output: 3724


🏁 Script executed:

#!/bin/bash
# Check wasm-editor to see how it attaches its keydown listener
echo "=== wasm-editor keydown listener attachment ==="
rg -n "addEventListener.*keydown|onkeydown" packages/app/src/components/wasm-editor.tsx -A 2 -B 2

Repository: dnl-fm/md

Length of output: 322


Ctrl+A conflict breaks editor Select All functionality.

The document-level Ctrl+A handler (line 478) intercepts the keydown event before the wasm-editor receives it. This prevents the "Select All" functionality in the editor (wasm-editor.tsx:625-628) from working. Users cannot select all text in the editor when this shortcut is active. Add a focus check to prevent the app handler from firing when the editor is focused, or choose a different shortcut for AI chat toggle.

🤖 Prompt for AI Agents
In `@packages/app/src/App.tsx` around lines 478 - 481, The global Ctrl+A branch
(case "a") is intercepting keydown and preventing the wasm editor's Select All;
update the handler so it first checks whether the editor is focused (e.g.,
inspect document.activeElement or an existing editor focus state exposed by the
WasmEditor component/wasm-editor.tsx) and only call e.preventDefault() and
toggleAiChat() when the editor is NOT focused; alternatively bind toggleAiChat()
to a different shortcut. Specifically, guard the case "a" branch with a focus
check before calling toggleAiChat().

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant