Skip to content

feat: middle-click close tab, git status colors & diff preview#117

Open
ajianaz wants to merge 5 commits into
gnoviawan:devfrom
ajianaz:feat/middle-click-git-explorer-diff
Open

feat: middle-click close tab, git status colors & diff preview#117
ajianaz wants to merge 5 commits into
gnoviawan:devfrom
ajianaz:feat/middle-click-git-explorer-diff

Conversation

@ajianaz
Copy link
Copy Markdown
Contributor

@ajianaz ajianaz commented May 7, 2026

New Features

1. Middle-click to close tab

onAuxClick handler on all tab types (terminal, editor, browser) — standard browser behavior, no need to find the X button.

2. Git file status colors in explorer

  • Rust command project_git_file_statuses parses git status --porcelain into per-file entries
  • FileTreeNode: icon + filename change color based on git status (like VS Code)
  • Parent folders inherit child status color (visible from root)
  • Polls every 5s, auto-starts/stops with project
Status Color Indicator
Modified Amber M
Added/Staged Emerald A
Deleted Red D
Untracked Emerald-400 U
Renamed Blue R
Conflicted Orange C

3. Git diff preview

  • Rust command project_git_diff_file returns unified diff for a single file
  • DiffViewer component renders with color-coded lines (red/green)
  • Diff button in EditorToolbar (next to TOC/Source/Preview)
  • Shows for ALL files with git changes (not just markdown)
  • Source/Preview disabled in diff mode

Files Changed

  • src-tauri/src/commands.rs — 2 new commands
  • src-tauri/src/lib.rs — command registration
  • src-tauri/src/trackers/git_tracker.rs — file status parser + diff method
  • src/renderer/lib/tauri-git-api.ts — new API module
  • src/renderer/stores/git-file-status-store.ts — new store with folder propagation
  • src/renderer/components/file-explorer/FileExplorer.tsx — polling setup
  • src/renderer/components/file-explorer/FileTreeNode.tsx — color indicators
  • src/renderer/components/editor/DiffViewer.tsx — new diff viewer component
  • src/renderer/components/editor/EditorToolbar.tsx — diff toggle button
  • src/renderer/components/editor/EditorPanel.tsx — diff state management
  • src/renderer/components/workspace/WorkspaceTabBar.tsx — middle-click handlers

Testing

  • 64 test files, 920 tests passing
  • TypeScript clean, ESLint clean, cargo check clean

Summary by CodeRabbit

Release Notes

  • New Features
    • File explorer now displays Git status indicators (modified, added, deleted, untracked, renamed, conflicted) for each file with real-time updates.
    • Editor includes a new Diff viewer to display unified diffs with syntax highlighting.
    • Added Diff toggle button in the editor toolbar to view file changes.
    • Middle-click on workspace tabs now closes them.

ajianaz added 5 commits May 7, 2026 15:05
Add onAuxClick handler (button === 1) to all three tab types:
- TerminalTabInline: with !isClosing guard
- EditorTabWrapper: closes editor tab
- BrowserTabInline: closes browser tab

Standard browser middle-click behavior — no need to find the X button.
- Rust: new command project_git_file_statuses parses git status --porcelain
  into per-file entries with status (modified/added/deleted/untracked/renamed/conflicted)
  and staged flag
- Frontend: git-file-status-store polls every 5s, maps absolute paths to status
- FileTreeNode: colored status indicator (M/A/D/U/R/C) appears on changed files
- FileExplorer: starts/stops polling based on active project rootPath

Colors: Modified=amber, Added=emerald, Deleted=red, Untracked=emerald-400,
Renamed=blue, Conflicted=orange
Icon and filename now change color based on git status (like VS Code),
not just the small indicator letter. The indicator letter is now
slightly transparent to not compete with the colored name.
- Rust: project_git_diff_file command (unstaged then staged fallback)
- DiffViewer: unified diff renderer with color-coded lines
- EditorToolbar: added Diff button next to TOC/Source/Preview
  - Shows for ALL files with git changes (not just markdown)
  - Source/Preview disabled in diff mode
  - TOC still accessible in diff mode
- EditorPanel: manages diff state, toolbar visibility, and content switching
Folders now show git status color when they contain changed files,
so deeply nested changes are visible from the root of the explorer.

- git-file-status-store: propagate file status up to parent directories
  with priority ordering (conflicted > modified > added > untracked)
- FileTreeNode: git status color takes precedence over default folder
  blue color
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 7, 2026

Review Change Stack

📝 Walkthrough

Walkthrough

This PR introduces Git file status tracking and unified diff viewing across the application. Changes span from Rust backend methods (get_file_statuses, get_file_diff), through Tauri IPC commands, to a TypeScript API layer, Zustand store with 5-second polling, a diff parser/viewer component, and integration into the editor, file explorer, and toolbar.

Changes

Git File Status and Diff Viewing Feature

Layer / File(s) Summary
Data Types and Contracts
src-tauri/src/commands.rs, src/renderer/lib/tauri-git-api.ts
Introduces GitFileStatus enum (Modified, Added, Deleted, Untracked, Renamed, Conflicted) and GitFileStatusEntry struct in Rust with serde camelCase; mirrors as TypeScript types with camelCase fields.
Backend Git Implementation
src-tauri/src/trackers/git_tracker.rs
Implements get_file_statuses() parsing git status --porcelain with XY codes, rename/copy handling, and absolute path derivation; implements get_file_diff() attempting unstaged diff first, then cached diff fallback.
Tauri IPC Commands
src-tauri/src/commands.rs, src-tauri/src/lib.rs
Adds project_git_file_statuses and project_git_diff_file commands that spawn blocking Tokio tasks; registers both commands in Tauri invoke handler.
Frontend Tauri API Wrapper
src/renderer/lib/tauri-git-api.ts
Implements projectGitFileStatuses() and projectGitDiffFile() as async IPC wrappers; returns parsed entries or empty defaults when not in Tauri context or on IPC failure.
Frontend State Store and Polling
src/renderer/stores/git-file-status-store.ts
Creates Zustand store with statusMap for file caching and isLoaded hydration flag; implements parent directory status propagation using priority-based override; adds startGitFileStatusPolling() for 5-second interval polling with disposal cleanup.
Diff Viewer Component
src/renderer/components/editor/DiffViewer.tsx
Parses unified diff into typed line entries (meta, header, hunk, added, removed, context); memoizes parsed output; renders scrollable, monospace, line-by-line color-coded view with per-line prefix indicator.
Editor Panel Diff Integration
src/renderer/components/editor/EditorPanel.tsx
Adds showDiff, diffContent, diffLoading state; fetches diff on toggle via gitApi.projectGitDiffFile(); reads hasGitChanges from status store; conditionally shows EditorToolbar (markdown or git changes) and renders DiffViewer or markdown/code editors based on showDiff.
Editor Toolbar Diff Button
src/renderer/components/editor/EditorToolbar.tsx
Extends props with optional hasGitChanges, showDiff, onToggleDiff; adds conditional diff button (visible only when changes exist); disables source/preview toggle and reduces opacity when in diff mode.
File Explorer Polling Setup
src/renderer/components/file-explorer/FileExplorer.tsx
Calls startGitFileStatusPolling(rootPath) in useEffect when rootPath is available; stores and returns cleanup function to stop polling on unmount or path change.
File Tree Status Display
src/renderer/components/file-explorer/FileTreeNode.tsx
Reads git status from store for current entry path; maps status to single-character label and Tailwind color class via gitStatusStyle() helper; renders right-aligned status badge with tooltip showing staged/unstaged state.
Tab Bar Middle-Click Close
src/renderer/components/workspace/WorkspaceTabBar.tsx
Adds onAuxClick handlers to terminal, editor, and browser tab containers to close tabs on middle-click (respecting isClosing state for terminal tabs).

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • gnoviawan/termul#37: Related prior work on Git tracking backend; shares modifications to src-tauri/src/commands.rs and src-tauri/src/trackers/git_tracker.rs for status and diff APIs.

Poem

🐰 A diff unfolds like carrots in a row,
Each line a garden, green and red to show,
Git status blooms where changes dare to grow,
The frontend reads, the backend starts to flow,
We hops through files with colors all aglow! 🥕✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the three main features added: middle-click tab closing, git status colors in file explorer, and a diff preview viewer in the editor.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@gnoviawan
Copy link
Copy Markdown
Owner

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 7, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown
Contributor

@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: 7

🧹 Nitpick comments (2)
src/renderer/lib/tauri-git-api.ts (1)

43-46: 💤 Low value

projectGitDiffFile is declared after the gitApi object that references it.

Function declarations are hoisted so this is not a runtime bug, but it is surprising — a reader scanning top-to-bottom will see projectGitDiffFile used in the exported object before its implementation. Moving the declaration above gitApi (matching projectGitFileStatuses) removes the ambiguity.

♻️ Suggested reorder
+async function projectGitDiffFile(
+	projectPath: string,
+	filePath: string,
+): Promise<string> {
+	// ... implementation ...
+}
+
 export const gitApi = {
 	projectGitFileStatuses,
 	projectGitDiffFile,
 }
-
-async function projectGitDiffFile(
-	projectPath: string,
-	filePath: string,
-): Promise<string> {
-	// ... implementation ...
-}

Also applies to: 48-65

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/renderer/lib/tauri-git-api.ts` around lines 43 - 46, The exported object
gitApi references projectGitDiffFile before its implementation, which is
confusing; reorder the file so projectGitDiffFile is declared above the gitApi
export (matching how projectGitFileStatuses is ordered) and ensure any related
helper functions used by projectGitDiffFile are also declared before gitApi to
keep top-to-bottom readability consistent.
src-tauri/src/commands.rs (1)

172-182: ⚡ Quick win

Consider making project_git_file_statuses and project_git_diff_file accept State<'_, Arc<GitTracker>> and call instance methods instead of static methods.

terminal_get_git_branch and terminal_get_git_status both use State<'_, Arc<GitTracker>> and call instance methods (get_branch, get_status). However, project_git_file_statuses and project_git_diff_file call GitTracker::get_file_statuses and get_file_diff as static methods without accessing the singleton. This creates an inconsistent pattern: if GitTracker gains internal caching or state management in the future, these commands would bypass it. Since only one call site exists for each method, converting them to instance methods is low-effort and would align with the existing pattern.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src-tauri/src/commands.rs` around lines 172 - 182, project_git_file_statuses
(and similarly project_git_diff_file) currently call
GitTracker::get_file_statuses/get_file_diff as static functions; change their
signatures to accept State<'_, Arc<GitTracker>> and call the instance methods
(e.g. git_tracker.get_file_statuses(...) / git_tracker.get_file_diff(...))
instead, mirroring terminal_get_git_branch and terminal_get_git_status; update
any tokio::task::spawn_blocking closures to capture the Arc<GitTracker> clone
and invoke the instance method so the singleton's internal state/caching is used
consistently.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src-tauri/src/commands.rs`:
- Around line 172-197: The commands project_git_file_statuses and
project_git_diff_file currently wrap whatever GitTracker::get_file_statuses /
GitTracker::get_file_diff return as IpcResult::success even when those tracker
helpers swallow errors and return empty values; change the handlers to detect
failures and return IpcResult::error instead of always success: either
(preferred) update GitTracker::get_file_statuses and get_file_diff to return
Result<T, String> (propagate the actual git error) and then in
project_git_file_statuses / project_git_diff_file map Err to IpcResult::error
with that message, or (if changing trackers is infeasible) have the command
functions inspect the returned value (empty Vec or empty String) and treat that
as an error case by returning IpcResult::error("git unavailable or not a repo")
so the UI can distinguish real empty results from git failures.

In `@src-tauri/src/trackers/git_tracker.rs`:
- Around line 982-984: get_file_diff currently uses string strip_prefix and
trim_start_matches which breaks when cwd has a trailing slash; change
get_file_diff to work with std::path::Path (use Path::new for cwd and
file_path), normalize both paths (trim trailing separators or
canonicalize/clean) and compute the relative path with Path::strip_prefix (or
attempt strip_prefix both with and without a trailing separator) so the
resulting relative path is correct before passing to git; update the code paths
that reference cwd and file_path to use the Path-based relative result and
convert it to a String (e.g., via to_string_lossy) for the git diff call.
- Around line 939-966: The match over (index_status, work_tree_status) in the
git status parsing currently falls through for common combos like ('A','M'),
('M','D') and ('A','D'); add explicit arms in that match (referencing
index_status, work_tree_status and the GitFileStatus enum) so those states are
not skipped: map ('A','M') -> (GitFileStatus::Modified, true) (file staged then
changed in worktree), map ('M','D') -> (GitFileStatus::Deleted, true) (staged
change with worktree deletion), and map ('A','D') -> (GitFileStatus::Deleted,
true) (staged add then removed from disk); insert these arms alongside the other
patterns in the existing match block to ensure these common states are handled.
- Around line 932-936: The constructed absolute path in git_tracker.rs (variable
abs_path built from cwd and file_path) can mix backslashes from cwd with forward
slashes from git output; after joining (in the block that sets abs_path)
normalize the path separators to forward slashes (or use Path/PathBuf
normalization) so the resulting string uses '/' consistently; apply the same
normalization where cwd is used in get_file_diff() (the cwd-based path strip at
lines around get_file_diff) to ensure downstream path matching works on Windows.

In `@src/renderer/components/editor/EditorPanel.tsx`:
- Around line 108-124: The toolbar hiding logic can remove the Diff toggle while
in diff mode; update the showToolbar condition to keep the toolbar visible when
showDiff is true. Specifically, change the computed showToolbar (currently using
isMarkdownFile || hasGitChanges) to include showDiff so that the EditorToolbar
(props: viewMode via fileState.viewMode, onToggleViewMode handleToggleViewMode,
filePath, hasGitChanges, showDiff, onToggleDiff handleToggleDiff) remains
rendered whenever showDiff is active, ensuring users can exit diff mode even if
gitStatus/hasGitChanges becomes false.

In `@src/renderer/lib/tauri-git-api.ts`:
- Around line 30-41: Wrap each call to invoke in project_git_file_statuses (and
the other invoke at lines 54-64) inside a try/catch so IPC-level exceptions
don't propagate during polling: catch any thrown error from
invoke('project_git_file_statuses', { projectPath }) (and the other invoke
call), log or swallow the error as appropriate, and return an empty array or
fallback value when an exception occurs; update the functions in
src/renderer/lib/tauri-git-api.ts that call invoke to use try/catch around the
invoke(...) calls (refer to the invoke call site and the function that returns
result.data) so unhandled rejections are prevented.

In `@src/renderer/stores/git-file-status-store.ts`:
- Around line 46-58: The code uses entry.path.split('/') when walking parent
directories which fails for Windows paths with backslashes; normalize entry.path
to use forward slashes (or split on both separators) before computing parts so
folder statuses propagate correctly (update the place that constructs parts from
entry.path used in the parent-directory walk, which currently calls
entry.path.split('/'), and ensure the rest of the logic that updates map entries
and compares against priority and isStaged remains unchanged).

---

Nitpick comments:
In `@src-tauri/src/commands.rs`:
- Around line 172-182: project_git_file_statuses (and similarly
project_git_diff_file) currently call
GitTracker::get_file_statuses/get_file_diff as static functions; change their
signatures to accept State<'_, Arc<GitTracker>> and call the instance methods
(e.g. git_tracker.get_file_statuses(...) / git_tracker.get_file_diff(...))
instead, mirroring terminal_get_git_branch and terminal_get_git_status; update
any tokio::task::spawn_blocking closures to capture the Arc<GitTracker> clone
and invoke the instance method so the singleton's internal state/caching is used
consistently.

In `@src/renderer/lib/tauri-git-api.ts`:
- Around line 43-46: The exported object gitApi references projectGitDiffFile
before its implementation, which is confusing; reorder the file so
projectGitDiffFile is declared above the gitApi export (matching how
projectGitFileStatuses is ordered) and ensure any related helper functions used
by projectGitDiffFile are also declared before gitApi to keep top-to-bottom
readability consistent.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: be1190ff-12af-4c90-b8da-1f06cea50702

📥 Commits

Reviewing files that changed from the base of the PR and between 592ddeb and e145e52.

📒 Files selected for processing (11)
  • src-tauri/src/commands.rs
  • src-tauri/src/lib.rs
  • src-tauri/src/trackers/git_tracker.rs
  • src/renderer/components/editor/DiffViewer.tsx
  • src/renderer/components/editor/EditorPanel.tsx
  • src/renderer/components/editor/EditorToolbar.tsx
  • src/renderer/components/file-explorer/FileExplorer.tsx
  • src/renderer/components/file-explorer/FileTreeNode.tsx
  • src/renderer/components/workspace/WorkspaceTabBar.tsx
  • src/renderer/lib/tauri-git-api.ts
  • src/renderer/stores/git-file-status-store.ts

Comment thread src-tauri/src/commands.rs
Comment on lines +172 to +197
pub async fn project_git_file_statuses(
project_path: String,
) -> Result<IpcResult<Vec<GitFileStatusEntry>>, String> {
let entries = tokio::task::spawn_blocking(move || {
crate::trackers::git_tracker::GitTracker::get_file_statuses(&project_path)
})
.await
.map_err(|e| format!("Task join error: {}", e))?;

Ok(IpcResult::success(entries))
}

/// Get unified diff for a single file
#[tauri::command]
pub async fn project_git_diff_file(
project_path: String,
file_path: String,
) -> Result<IpcResult<String>, String> {
let diff = tokio::task::spawn_blocking(move || {
crate::trackers::git_tracker::GitTracker::get_file_diff(&project_path, &file_path)
})
.await
.map_err(|e| format!("Task join error: {}", e))?;

Ok(IpcResult::success(diff))
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check signatures of get_file_statuses and get_file_diff in git_tracker.rs
rg -n "fn get_file_statuses\|fn get_file_diff" --type rust -A 5

Repository: gnoviawan/termul

Length of output: 42


🏁 Script executed:

# Find git_tracker.rs file
fd -type f "git_tracker" --extension rs

Repository: gnoviawan/termul

Length of output: 231


🏁 Script executed:

# Also search for the file in the src-tauri directory
find . -path "*src-tauri*" -name "*git*" -type f

Repository: gnoviawan/termul

Length of output: 100


🏁 Script executed:

# List the structure of src-tauri/src to understand the layout
ls -la src-tauri/src/ 2>/dev/null || echo "Directory not found, searching for src-tauri"
fd -type d "src-tauri" | head -5

Repository: gnoviawan/termul

Length of output: 855


🏁 Script executed:

cat -n ./src-tauri/src/trackers/git_tracker.rs

Repository: gnoviawan/termul

Length of output: 50372


🏁 Script executed:

sed -n '172,197p' ./src-tauri/src/commands.rs

Repository: gnoviawan/termul

Length of output: 870


Both commands unconditionally return IpcResult::success, making it impossible for callers to distinguish git unavailability from an empty result.

When GitTracker::get_file_statuses (line 907: _ => return Vec::new()) or get_file_diff (line 1016: output.unwrap_or_default()) fail—whether because git is not installed, the path is not a repository, or permissions are denied—they return empty results without any error signal. The commands then wrap these empty results as success: true unconditionally, so the frontend incorrectly treats unavailable git as "no changes."

Return IpcResult::error when the underlying git calls fail, so the UI can handle missing or invalid repositories appropriately.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src-tauri/src/commands.rs` around lines 172 - 197, The commands
project_git_file_statuses and project_git_diff_file currently wrap whatever
GitTracker::get_file_statuses / GitTracker::get_file_diff return as
IpcResult::success even when those tracker helpers swallow errors and return
empty values; change the handlers to detect failures and return IpcResult::error
instead of always success: either (preferred) update
GitTracker::get_file_statuses and get_file_diff to return Result<T, String>
(propagate the actual git error) and then in project_git_file_statuses /
project_git_diff_file map Err to IpcResult::error with that message, or (if
changing trackers is infeasible) have the command functions inspect the returned
value (empty Vec or empty String) and treat that as an error case by returning
IpcResult::error("git unavailable or not a repo") so the UI can distinguish real
empty results from git failures.

Comment on lines +932 to +936
let abs_path = if file_path.starts_with('/') {
file_path.to_string()
} else {
format!("{}/{}", cwd.trim_end_matches('/'), file_path)
};
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

# First, let's examine the git_tracker.rs file around the mentioned lines
cd src-tauri && wc -l src/trackers/git_tracker.rs

Repository: gnoviawan/termul

Length of output: 93


🏁 Script executed:

# Read the relevant section (lines 920-950 to get full context)
sed -n '920,950p' src/trackers/git_tracker.rs

Repository: gnoviawan/termul

Length of output: 131


🏁 Script executed:

# Look at get_file_diff function around line 983-984
sed -n '980,1020p' src/trackers/git_tracker.rs

Repository: gnoviawan/termul

Length of output: 131


🏁 Script executed:

# Find where cwd is obtained and how it's constructed
rg 'let cwd' src/trackers/git_tracker.rs -A 5 -B 2

Repository: gnoviawan/termul

Length of output: 128


🏁 Script executed:

# Search for how get_file_statuses is called and what cwd value is passed
rg 'get_file_statuses' src/trackers/git_tracker.rs -B 5 -A 2

Repository: gnoviawan/termul

Length of output: 128


🏁 Script executed:

# Look for any path normalization or separator handling
rg -i 'replace.*\\|normalize|separator|windows' src/trackers/git_tracker.rs

Repository: gnoviawan/termul

Length of output: 128


🏁 Script executed:

# Check Tauri commands that might receive these paths
fd -e rs -e ts -e tsx | xargs grep -l 'entry\.path' | head -10

Repository: gnoviawan/termul

Length of output: 320


🏁 Script executed:

# Find git_tracker.rs
find . -name "git_tracker.rs" -type f

Repository: gnoviawan/termul

Length of output: 100


🏁 Script executed:

# Also check the directory structure
ls -la src-tauri/src/trackers/

Repository: gnoviawan/termul

Length of output: 432


🏁 Script executed:

# Read the relevant section around lines 932-936
sed -n '920,950p' ./src-tauri/src/trackers/git_tracker.rs

Repository: gnoviawan/termul

Length of output: 1326


🏁 Script executed:

# Look at the full get_file_statuses function to understand cwd source
sed -n '898,945p' ./src-tauri/src/trackers/git_tracker.rs

Repository: gnoviawan/termul

Length of output: 1948


🏁 Script executed:

# Look at get_file_diff function around line 983-984
sed -n '980,1020p' ./src-tauri/src/trackers/git_tracker.rs

Repository: gnoviawan/termul

Length of output: 1680


🏁 Script executed:

# Find where cwd is obtained - check the function signature and initial part
sed -n '898,920p' ./src-tauri/src/trackers/git_tracker.rs

Repository: gnoviawan/termul

Length of output: 949


🏁 Script executed:

# Search for how this function is called from elsewhere
rg 'get_file_statuses' ./src-tauri/src/trackers/git_tracker.rs -B 3 -A 3

Repository: gnoviawan/termul

Length of output: 448


🏁 Script executed:

# Check how git commands are run
sed -n '1,50p' ./src-tauri/src/trackers/git_tracker.rs

Repository: gnoviawan/termul

Length of output: 1488


🏁 Script executed:

# Look for run_git_command implementation
rg 'fn run_git_command' ./src-tauri/src/trackers/git_tracker.rs -A 20

Repository: gnoviawan/termul

Length of output: 911


🏁 Script executed:

# Find where get_file_statuses is called from
rg 'get_file_statuses' ./src-tauri/src/ -B 2 -A 2

Repository: gnoviawan/termul

Length of output: 914


🏁 Script executed:

# Check how cwd is obtained in the commands module
rg -i 'cwd' ./src-tauri/src/commands.rs | head -20

Repository: gnoviawan/termul

Length of output: 389


🏁 Script executed:

# Look at commands.rs to see if there's path normalization
head -100 ./src-tauri/src/commands.rs

Repository: gnoviawan/termul

Length of output: 2743


🏁 Script executed:

# Find CwdTracker to see how cwd is determined
sed -n '1,100p' ./src-tauri/src/trackers/cwd_tracker.rs

Repository: gnoviawan/termul

Length of output: 3362


🏁 Script executed:

# Check the frontend file-explorer-store to see what entry.path looks like
cat ./src/renderer/stores/file-explorer-store.ts | head -150

Repository: gnoviawan/termul

Length of output: 5021


🏁 Script executed:

# Look for entry.path usage in frontend
rg 'entry\.path' ./src/renderer/ -B 2 -A 2 | head -50

Repository: gnoviawan/termul

Length of output: 3618


🏁 Script executed:

# Check if there's any path normalization in the frontend
rg -i 'normalize|replace.*\\|separator' ./src/renderer/stores/ -A 2 -B 2

Repository: gnoviawan/termul

Length of output: 28671


🏁 Script executed:

# Look at the full context of how get_file_statuses is called in commands.rs
rg -B 10 'get_file_statuses' ./src-tauri/src/commands.rs | head -40

Repository: gnoviawan/termul

Length of output: 486


🏁 Script executed:

# Search for where project_path comes from
rg 'project_path' ./src-tauri/src/commands.rs -B 3 -A 3 | head -60

Repository: gnoviawan/termul

Length of output: 867


🏁 Script executed:

# Check if there's any filesystem API that reads directory entries
rg 'readDirectory' ./src-tauri/src/ -B 2 -A 5

Repository: gnoviawan/termul

Length of output: 42


🏁 Script executed:

# Look for the filesystem API implementation to see what entry.path looks like
find ./src-tauri/src -name "*.rs" -type f | xargs grep -l 'entry\.path\|entry\["path"\]' | head -5

Repository: gnoviawan/termul

Length of output: 42


🏁 Script executed:

# Check git-file-status-store to see how paths are matched
cat ./src/renderer/stores/git-file-status-store.ts

Repository: gnoviawan/termul

Length of output: 2818


🏁 Script executed:

# Check the filesystem API implementation
find ./src-tauri/src -name "*.rs" -type f | xargs grep -l 'DirectoryEntry\|readDirectory' | head -5

Repository: gnoviawan/termul

Length of output: 42


🏁 Script executed:

# Look at the complete Tauri command for filesystem operations
rg 'pub async fn.*read.*directory\|pub async fn.*get.*file' ./src-tauri/src/commands.rs -A 10 | head -60

Repository: gnoviawan/termul

Length of output: 42


🏁 Script executed:

# Check what the filesystem API module returns
fd -e rs -name '*filesystem*' ./src-tauri/src/

Repository: gnoviawan/termul

Length of output: 291


🏁 Script executed:

# Look at the filesystem types
rg 'DirectoryEntry' ./src-tauri/src/ -B 2 -A 5

Repository: gnoviawan/termul

Length of output: 42


🏁 Script executed:

# Confirm how paths flow from backend through API
rg 'GitFileStatusEntry' ./src-tauri/src/commands.rs -B 5 -A 5

Repository: gnoviawan/termul

Length of output: 671


🏁 Script executed:

# Check how project_path flows into get_file_statuses
# and whether it's already normalized somewhere before reaching git_tracker
rg 'project_git_file_statuses\|project_path' ./src-tauri/src/ -B 5 -A 2

Repository: gnoviawan/termul

Length of output: 42


🏁 Script executed:

# Check if there's any normalization in the Tauri/IPC layer
rg -i 'normalize|to_string_lossy|canonicalize' ./src-tauri/src/commands.rs | head -20

Repository: gnoviawan/termul

Length of output: 42


🏁 Script executed:

# Look at the full git_tracker.rs implementation to see all path handling
sed -n '930,1000p' ./src-tauri/src/trackers/git_tracker.rs

Repository: gnoviawan/termul

Length of output: 2749


🏁 Script executed:

# Check how paths are constructed and if they're normalized anywhere
rg 'abs_path' ./src-tauri/src/trackers/git_tracker.rs -B 2 -A 2

Repository: gnoviawan/termul

Length of output: 346


🏁 Script executed:

# Test: on the frontend, see if entry.path uses forward slashes in split
cat ./src/renderer/stores/git-file-status-store.ts | grep -A 5 -B 5 'split.*/'

Repository: gnoviawan/termul

Length of output: 336


🏁 Script executed:

# Check get_file_diff for the same path issue
sed -n '977,1010p' ./src-tauri/src/trackers/git_tracker.rs

Repository: gnoviawan/termul

Length of output: 1490


🏁 Script executed:

# Look for any normalization of project_path before it reaches git_tracker
rg 'project_git_file_statuses' ./src -B 10 | head -40

Repository: gnoviawan/termul

Length of output: 735


🏁 Script executed:

# Check on what cwd value is expected - is it system native or normalized?
rg 'get_cwd\|detect_cwd' ./src-tauri/src/trackers/cwd_tracker.rs -A 15 | head -50

Repository: gnoviawan/termul

Length of output: 42


🏁 Script executed:

# Look at complete get_file_diff to see full scope of path issue
sed -n '977,1030p' ./src-tauri/src/trackers/git_tracker.rs

Repository: gnoviawan/termul

Length of output: 2055


🏁 Script executed:

# Verify git outputs forward slashes on Windows
# Check the actual git command structure
sed -n '898,925p' ./src-tauri/src/trackers/git_tracker.rs

Repository: gnoviawan/termul

Length of output: 1203


🏁 Script executed:

# Check if CwdTracker returns system-native paths (backslashes on Windows)
sed -n '60,100p' ./src-tauri/src/trackers/cwd_tracker.rs

Repository: gnoviawan/termul

Length of output: 1543


🏁 Script executed:

# Check if there's any path normalization happening before project_path is used
rg 'project_path' ./src-tauri/src/commands.rs -B 10 -A 10

Repository: gnoviawan/termul

Length of output: 1248


🏁 Script executed:

# Verify git truly outputs forward slashes on Windows by checking git documentation
# or looking at test cases in the code
rg -i 'git.*status.*porcelain\|windows.*path\|forward.*slash' ./src-tauri/src/trackers/git_tracker.rs

Repository: gnoviawan/termul

Length of output: 42


🏁 Script executed:

# Check what the filesystem API (Tauri's built-in) returns on Windows
# Look for any Tauri API calls
rg -i 'tauri.*fs\|read.*dir' ./src-tauri/src/commands.rs | head -20

Repository: gnoviawan/termul

Length of output: 42


🏁 Script executed:

# Verify if project_path is the same path that the frontend normalizes
rg 'normalizePath' ./src/renderer -A 2 -B 2 | head -40

Repository: gnoviawan/termul

Length of output: 2890


🏁 Script executed:

# Check if project_path parameter is the frontend-normalized path or system path
# Look at where project_git_file_statuses is called from
rg 'project_git_file_statuses' ./src/renderer -B 5 -A 2

Repository: gnoviawan/termul

Length of output: 535


🏁 Script executed:

# Double-check: verify git status --porcelain always outputs forward slashes
# by looking for any test or documentation
rg -i 'porcelain\|git.*status' ./src-tauri/src/trackers/git_tracker.rs | head -10

Repository: gnoviawan/termul

Length of output: 42


🏁 Script executed:

# Check where projectPath comes from in the frontend before calling project_git_file_statuses
rg 'projectGitFileStatuses\|project_git_file_statuses' ./src/renderer -B 10 | head -60

Repository: gnoviawan/termul

Length of output: 42


🏁 Script executed:

# Look at how the project path is determined/stored
fd -type f -name "*.ts" -o -name "*.tsx" ./src/renderer | xargs grep -l 'projectPath\|project.*path' | head -5

Repository: gnoviawan/termul

Length of output: 231


🏁 Script executed:

# Search for projectGitFileStatuses usage
rg 'projectGitFileStatuses' ./src/renderer/ -B 15 -A 5

Repository: gnoviawan/termul

Length of output: 3829


🏁 Script executed:

# Look at the git API file
cat ./src/renderer/lib/tauri-git-api.ts

Repository: gnoviawan/termul

Length of output: 1291


🏁 Script executed:

# Find where startGitFileStatusPolling is called to see what projectPath is
rg 'startGitFileStatusPolling' ./src/renderer -B 5 -A 5

Repository: gnoviawan/termul

Length of output: 3074


🏁 Script executed:

# Check if projectPath comes from a normalized source
rg 'startGitFileStatusPolling\|projectPath' ./src/renderer/components -B 10 -A 2 | head -100

Repository: gnoviawan/termul

Length of output: 42


🏁 Script executed:

# Check if there are any other sources of cwd or project_path that might not be normalized
rg 'get_file_statuses\|get_file_diff' ./src-tauri/src -B 5 | grep -E 'get_file_|cwd|project'

Repository: gnoviawan/termul

Length of output: 42


🏁 Script executed:

# Specifically check what cwd sources exist in git_tracker calls
sed -n '1,100p' ./src-tauri/src/commands.rs

Repository: gnoviawan/termul

Length of output: 2743


🏁 Script executed:

# Final verification: look at all callers of GitTracker methods
rg 'GitTracker::' ./src-tauri/src/ -B 3 -A 1

Repository: gnoviawan/termul

Length of output: 4713


Normalize path separators in git status path construction on Windows.

On Windows, cwd may contain backslashes while git's porcelain output uses forward slashes (e.g., src/main.rs). Although the frontend currently normalizes paths before sending them to the backend, the backend code should not rely on this precondition. The simple concatenation format!("{}/{}", cwd.trim_end_matches('/'), file_path) could produce a mixed-separator path if cwd contains backslashes, breaking path matching downstream.

Add separator normalization after joining to make the code defensive and self-contained:

Proposed fix
  let abs_path = if file_path.starts_with('/') {
      file_path.to_string()
  } else {
-     format!("{}/{}", cwd.trim_end_matches('/'), file_path)
+     format!("{}/{}", cwd.trim_end_matches('/').trim_end_matches('\\'), file_path)
+         .replace('\\', "/")
  };

The same normalization should be applied in get_file_diff() at line 983-984 where cwd is used for path stripping.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
let abs_path = if file_path.starts_with('/') {
file_path.to_string()
} else {
format!("{}/{}", cwd.trim_end_matches('/'), file_path)
};
let abs_path = if file_path.starts_with('/') {
file_path.to_string()
} else {
format!("{}/{}", cwd.trim_end_matches('/').trim_end_matches('\\'), file_path)
.replace('\\', "/")
};
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src-tauri/src/trackers/git_tracker.rs` around lines 932 - 936, The
constructed absolute path in git_tracker.rs (variable abs_path built from cwd
and file_path) can mix backslashes from cwd with forward slashes from git
output; after joining (in the block that sets abs_path) normalize the path
separators to forward slashes (or use Path/PathBuf normalization) so the
resulting string uses '/' consistently; apply the same normalization where cwd
is used in get_file_diff() (the cwd-based path strip at lines around
get_file_diff) to ensure downstream path matching works on Windows.

Comment on lines +939 to +966
let (status, is_staged) = match (index_status, work_tree_status) {
// Untracked
('?', '?') => (GitFileStatus::Untracked, false),
// Conflicted (unmerged)
('U', _) | (_, 'U') | ('D', 'D') | ('A', 'A') => {
(GitFileStatus::Conflicted, true)
}
// Renamed
('R', _) => (GitFileStatus::Renamed, true),
// Copied
('C', _) => (GitFileStatus::Added, true),
// Both index and worktree changed
('M', 'M') => (GitFileStatus::Modified, false),
// Only index changed (staged)
('M', ' ') | ('A', ' ') | ('D', ' ') => {
let s = match index_status {
'A' => GitFileStatus::Added,
'D' => GitFileStatus::Deleted,
_ => GitFileStatus::Modified,
};
(s, true)
}
// Only worktree changed
(' ', 'M') => (GitFileStatus::Modified, false),
(' ', 'D') => (GitFileStatus::Deleted, false),
// Fallback
_ => continue,
};
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Missing XY status combinations leave common file states untracked.

The match block falls to _ => continue for several valid and common combinations:

XY Scenario Result
('A', 'M') File staged with git add, then modified again in the working tree Silently skipped
('M', 'D') Staged modification, then the file is deleted in the working tree Silently skipped
('A', 'D') Staged new file then deleted from disk Silently skipped

('A', 'M') in particular is extremely common: create a new file, git add it, then edit it again before committing.

🛠️ Proposed additions to the match block
  // Only worktree changed
  (' ', 'M') => (GitFileStatus::Modified, false),
  (' ', 'D') => (GitFileStatus::Deleted, false),
+ // Staged + additional worktree change
+ ('A', 'M') => (GitFileStatus::Added, false),    // staged add + modified
+ ('M', 'D') => (GitFileStatus::Modified, false),  // staged modification + deleted
+ ('A', 'D') => (GitFileStatus::Added, false),     // staged add + deleted
+ ('D', 'M') => (GitFileStatus::Deleted, false),   // staged delete + modified
  // Fallback
  _ => continue,
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src-tauri/src/trackers/git_tracker.rs` around lines 939 - 966, The match over
(index_status, work_tree_status) in the git status parsing currently falls
through for common combos like ('A','M'), ('M','D') and ('A','D'); add explicit
arms in that match (referencing index_status, work_tree_status and the
GitFileStatus enum) so those states are not skipped: map ('A','M') ->
(GitFileStatus::Modified, true) (file staged then changed in worktree), map
('M','D') -> (GitFileStatus::Deleted, true) (staged change with worktree
deletion), and map ('A','D') -> (GitFileStatus::Deleted, true) (staged add then
removed from disk); insert these arms alongside the other patterns in the
existing match block to ensure these common states are handled.

Comment on lines +982 to +984
pub fn get_file_diff(cwd: &str, file_path: &str) -> String {
let relative = file_path.strip_prefix(cwd).unwrap_or(file_path);
let relative = relative.trim_start_matches('/');
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

get_file_diff path stripping is fragile under trailing-slash cwd.

str::strip_prefix is a byte-exact string comparison. If cwd arrives with a trailing slash (e.g., "/home/user/project/") while file_path starts with "/home/user/project/src/...", strip_prefix returns None, the fallback gives the full absolute path, and trim_start_matches('/') produces "home/user/project/src/file.rs". Passed to git diff --, this relative path won't resolve correctly from the repo root, yielding a silent empty diff.

🛠️ Proposed fix
- let relative = file_path.strip_prefix(cwd).unwrap_or(file_path);
- let relative = relative.trim_start_matches('/');
+ let cwd_norm = cwd.trim_end_matches('/').trim_end_matches('\\');
+ let relative = file_path
+     .strip_prefix(cwd_norm)
+     .unwrap_or(file_path)
+     .trim_start_matches('/')
+     .trim_start_matches('\\');
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
pub fn get_file_diff(cwd: &str, file_path: &str) -> String {
let relative = file_path.strip_prefix(cwd).unwrap_or(file_path);
let relative = relative.trim_start_matches('/');
pub fn get_file_diff(cwd: &str, file_path: &str) -> String {
let cwd_norm = cwd.trim_end_matches('/').trim_end_matches('\\');
let relative = file_path
.strip_prefix(cwd_norm)
.unwrap_or(file_path)
.trim_start_matches('/')
.trim_start_matches('\\');
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src-tauri/src/trackers/git_tracker.rs` around lines 982 - 984, get_file_diff
currently uses string strip_prefix and trim_start_matches which breaks when cwd
has a trailing slash; change get_file_diff to work with std::path::Path (use
Path::new for cwd and file_path), normalize both paths (trim trailing separators
or canonicalize/clean) and compute the relative path with Path::strip_prefix (or
attempt strip_prefix both with and without a trailing separator) so the
resulting relative path is correct before passing to git; update the code paths
that reference cwd and file_path to use the Path-based relative result and
convert it to a String (e.g., via to_string_lossy) for the git diff call.

Comment on lines +108 to 124
const hasGitChanges = !!gitStatus

// Show toolbar if: markdown file (always) OR any file with git changes
const showToolbar = isMarkdownFile || hasGitChanges

return (
<div className="w-full h-full flex flex-col">
{isMarkdownFile && (
{showToolbar && (
<EditorToolbar
viewMode={fileState.viewMode}
onToggleViewMode={handleToggleViewMode}
filePath={filePath}
hasGitChanges={hasGitChanges}
showDiff={showDiff}
onToggleDiff={handleToggleDiff}
/>
)}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Toolbar disappears mid-diff for non-markdown files, trapping the user.

When a non-markdown file is in diff mode and all git changes are committed (or the next poll clears gitStatus), hasGitChanges becomes false, showToolbar becomes false, and the Diff toggle button vanishes — leaving the user stuck viewing "No changes to display" with no UI to exit diff mode. Markdown files are unaffected because isMarkdownFile always keeps the toolbar.

Fix: include showDiff in the toolbar guard so the toggle remains reachable while diff mode is active:

🛠️ Proposed fix
- const showToolbar = isMarkdownFile || hasGitChanges
+ const showToolbar = isMarkdownFile || hasGitChanges || showDiff
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const hasGitChanges = !!gitStatus
// Show toolbar if: markdown file (always) OR any file with git changes
const showToolbar = isMarkdownFile || hasGitChanges
return (
<div className="w-full h-full flex flex-col">
{isMarkdownFile && (
{showToolbar && (
<EditorToolbar
viewMode={fileState.viewMode}
onToggleViewMode={handleToggleViewMode}
filePath={filePath}
hasGitChanges={hasGitChanges}
showDiff={showDiff}
onToggleDiff={handleToggleDiff}
/>
)}
const hasGitChanges = !!gitStatus
// Show toolbar if: markdown file (always) OR any file with git changes OR diff is open
const showToolbar = isMarkdownFile || hasGitChanges || showDiff
return (
<div className="w-full h-full flex flex-col">
{showToolbar && (
<EditorToolbar
viewMode={fileState.viewMode}
onToggleViewMode={handleToggleViewMode}
filePath={filePath}
hasGitChanges={hasGitChanges}
showDiff={showDiff}
onToggleDiff={handleToggleDiff}
/>
)}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/renderer/components/editor/EditorPanel.tsx` around lines 108 - 124, The
toolbar hiding logic can remove the Diff toggle while in diff mode; update the
showToolbar condition to keep the toolbar visible when showDiff is true.
Specifically, change the computed showToolbar (currently using isMarkdownFile ||
hasGitChanges) to include showDiff so that the EditorToolbar (props: viewMode
via fileState.viewMode, onToggleViewMode handleToggleViewMode, filePath,
hasGitChanges, showDiff, onToggleDiff handleToggleDiff) remains rendered
whenever showDiff is active, ensuring users can exit diff mode even if
gitStatus/hasGitChanges becomes false.

Comment on lines +30 to +41
const result = await invoke<{
success: boolean
data?: GitFileStatusEntry[]
error?: string
}>('project_git_file_statuses', { projectPath })

if (result.success && result.data) {
return result.data
}

return []
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Add try/catch around every invoke call to prevent unhandled rejections during polling.

invoke throws on IPC-level failures (command not registered, serialization error, Tauri channel closed). Because projectGitFileStatuses is called on a 5-second poll loop, every tick will surface an unhandled rejection and pollute the console (or worse, crash an error boundary) until the condition is resolved. The existing !isTauriContext() guard does not cover runtime IPC errors.

🛡️ Proposed fix — wrap both `invoke` calls
 async function projectGitFileStatuses(
 	projectPath: string,
 ): Promise<GitFileStatusEntry[]> {
 	if (!isTauriContext()) return []

-	const result = await invoke<{
-		success: boolean
-		data?: GitFileStatusEntry[]
-		error?: string
-	}>('project_git_file_statuses', { projectPath })
-
-	if (result.success && result.data) {
-		return result.data
+	try {
+		const result = await invoke<{
+			success: boolean
+			data?: GitFileStatusEntry[]
+			error?: string
+		}>('project_git_file_statuses', { projectPath })
+
+		if (result.success && result.data) {
+			return result.data
+		}
+	} catch (e) {
+		console.error('[gitApi] project_git_file_statuses failed:', e)
 	}

 	return []
 }
 async function projectGitDiffFile(
 	projectPath: string,
 	filePath: string,
 ): Promise<string> {
 	if (!isTauriContext()) return ''

-	const result = await invoke<{
-		success: boolean
-		data?: string
-		error?: string
-	}>('project_git_diff_file', { projectPath, filePath })
-
-	if (result.success && result.data) {
-		return result.data
+	try {
+		const result = await invoke<{
+			success: boolean
+			data?: string
+			error?: string
+		}>('project_git_diff_file', { projectPath, filePath })
+
+		if (result.success && result.data) {
+			return result.data
+		}
+	} catch (e) {
+		console.error('[gitApi] project_git_diff_file failed:', e)
 	}

 	return ''
 }

Also applies to: 54-64

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/renderer/lib/tauri-git-api.ts` around lines 30 - 41, Wrap each call to
invoke in project_git_file_statuses (and the other invoke at lines 54-64) inside
a try/catch so IPC-level exceptions don't propagate during polling: catch any
thrown error from invoke('project_git_file_statuses', { projectPath }) (and the
other invoke call), log or swallow the error as appropriate, and return an empty
array or fallback value when an exception occurs; update the functions in
src/renderer/lib/tauri-git-api.ts that call invoke to use try/catch around the
invoke(...) calls (refer to the invoke call site and the function that returns
result.data) so unhandled rejections are prevented.

Comment on lines +46 to +58
const parts = entry.path.split('/')
// Walk up parent directories
for (let i = parts.length - 1; i >= 2; i--) {
const dirPath = parts.slice(0, i).join('/')
const existing = map.get(dirPath)

if (!existing) {
map.set(dirPath, { path: dirPath, status: entry.status, isStaged: entry.isStaged })
} else if ((priority[entry.status] ?? 0) > (priority[existing.status] ?? 0)) {
// Higher-priority status wins
map.set(dirPath, { path: dirPath, status: entry.status, isStaged: entry.isStaged })
}
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Path split with '/' only will mis-propagate folder status on Windows.

entry.path.split('/') assumes forward-slash-only paths. If the backend produces mixed separators (e.g., C:\Users\user\project/src/file.ts — see the related issue in git_tracker.rs), the split leaves the entire C:\Users\user\project segment as one part at index 0, so only C:\Users\user\project/src gets a propagated status entry. The project root folder never receives any status colour on Windows.

This issue is downstream of the path-separator construction in get_file_statuses; normalizing to forward slashes on the Rust side (suggested above) will fix this automatically. No independent change needed here unless the Rust fix is deferred.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/renderer/stores/git-file-status-store.ts` around lines 46 - 58, The code
uses entry.path.split('/') when walking parent directories which fails for
Windows paths with backslashes; normalize entry.path to use forward slashes (or
split on both separators) before computing parts so folder statuses propagate
correctly (update the place that constructs parts from entry.path used in the
parent-directory walk, which currently calls entry.path.split('/'), and ensure
the rest of the logic that updates map entries and compares against priority and
isStaged remains unchanged).

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants