Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
b502645
feat(tabs): select tabs by index within active space
Jun 26, 2026
af93248
Merge pull request #1 from roberto-fernandino/fix-tab-picker
roberto-fernandino Jun 26, 2026
023baa1
feat(shortcuts): add ai toggle mini shortcut
Jun 26, 2026
bda8dfb
Merge pull request #2 from roberto-fernandino/feat/toggle-ai-chat-win…
roberto-fernandino Jun 26, 2026
2b644ff
feat: terminals in command-palette
Jun 26, 2026
1a6b366
Merge pull request #3 from roberto-fernandino/feat/terminals-in-comma…
roberto-fernandino Jun 26, 2026
81e752f
feat(shortcuts): add configurable shortcut to show Files sidebar
Jun 27, 2026
df23232
Merge pull request #4 from roberto-fernandino/feat/files-sidebar-shor…
roberto-fernandino Jun 27, 2026
9bf6a2d
chore: add terax-pr-workflow project skill
Jun 27, 2026
2a5d3b2
Merge remote-tracking branch 'upstream/main'
roberto-fernandino Jun 27, 2026
b679121
feat(sidebar): add Agents tab showing all active AI agent sessions
roberto-fernandino Jun 27, 2026
a22273a
Merge pull request #6 from roberto-fernandino/feat/agents-sidebar-tab…
roberto-fernandino Jun 27, 2026
bf8faf9
fix(agents): show idle status when agent is at prompt, not working
roberto-fernandino Jun 27, 2026
fec2a7d
Merge pull request #7 from roberto-fernandino/fix/agents-idle-status
roberto-fernandino Jun 27, 2026
8a76492
feat(ai): open AI panel by default on startup
roberto-fernandino Jun 27, 2026
74b3f6c
Merge pull request #8 from roberto-fernandino/feat/ai-panel-open-by-d…
roberto-fernandino Jun 27, 2026
497e6b8
fix(agents): clear needs-input when tab focused; finished resets to idle
roberto-fernandino Jun 27, 2026
2f9e17f
Merge pull request #9 from roberto-fernandino/fix/agents-needs-input-…
roberto-fernandino Jun 27, 2026
a893d44
fix(agents): reset working status to idle when terminal tab is focused
roberto-fernandino Jun 28, 2026
1b92a83
Merge pull request #10 from roberto-fernandino/fix/agents-working-stu…
roberto-fernandino Jun 28, 2026
14c1ae1
fix(agents): use tabsRef in focus-reset effect to preserve real-time …
roberto-fernandino Jun 28, 2026
d292c64
feat(terminal): add cursor style setting (bar/block/underline)
roberto-fernandino Jun 30, 2026
f61a0c8
Merge pull request #12 from roberto-fernandino/feat/terminal-cursor-s…
roberto-fernandino Jun 30, 2026
10c67c3
chore: merge upstream/main (sidebar persist, workspace env, Linux cli…
roberto-fernandino Jun 30, 2026
e454866
feat(editor): show git blame for current line in status bar
roberto-fernandino Jun 30, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
130 changes: 130 additions & 0 deletions .claude/skills/terax-pr-workflow/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
---
name: terax-pr-workflow
description: Use this skill whenever making ANY change to this repository — features, fixes, shortcuts, refactors, anything. It enforces the mandatory branch → commit → dual-PR (fork + upstream) → merge-fork → back-to-main workflow. Trigger on phrases like "create a PR", "add a feature", "fix this", "push this", "open a pull request", or any task that involves committing and shipping code changes.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Remove em-dashes from skill description.

The project prohibits em dashes in all files. Replace with hyphens or commas. As per coding guidelines, do not use em dashes anywhere, including code, comments, commits, and docs.

🧰 Tools
🪛 SkillSpector (2.3.7)

[warning] 34: [RP1] null: npx commands without a version suffix (e.g. @1.0.0) create a rug-pull risk if the upstream server is compromised and publishes a malicious update.

Remediation: Pin the version: npx @scope/server@1.2.3

(MCP Rug Pull (RP1))


[warning] 129: [RP1] null: npx commands without a version suffix (e.g. @1.0.0) create a rug-pull risk if the upstream server is compromised and publishes a malicious update.

Remediation: Pin the version: npx @scope/server@1.2.3

(MCP Rug Pull (RP1))

🤖 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 @.claude/skills/terax-pr-workflow/SKILL.md at line 3, The skill description
in SKILL.md contains em dashes, which violate the project’s punctuation rules.
Update the description text to use hyphens or commas instead, and make sure the
wording in the skill’s description remains equivalent while removing every em
dash from that entry.

Source: Coding guidelines

user-invocable: true
---

# Terax PR Workflow

Every change to this repo MUST follow this exact workflow. No exceptions.

## Remotes

- `origin` → `https://github.com/roberto-fernandino/terax-ai-fork.git` (the fork — you have write access)
- `upstream` → `https://github.com/crynta/terax-ai.git` (the original — NO push access, use cross-fork PRs)

## Step-by-step

### 1. Create a branch

```bash
git checkout main
git pull origin main
git checkout -b feat/<descriptive-name>
# or fix/<name>, chore/<name>, etc.
```

Branch name must be kebab-case and describe the change.

### 2. Make the changes

Implement the feature/fix. Type-check before committing:

```bash
npx tsc --noEmit
```

### 3. Commit

Stage only the relevant files (never `git add -A` blindly):

```bash
git add <specific files>
git commit -m "$(cat <<'EOF'
feat/fix/chore(scope): short imperative description

Longer explanation if needed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
EOF
)"
```

### 4. Push to fork

```bash
git push origin <branch-name>
```

### 5. Check for existing open PRs — BEFORE creating new ones

**This step is mandatory.** Before opening any PR, check whether one already exists for the same feature area:

```bash
gh pr list --repo roberto-fernandino/terax-ai-fork --state open
gh pr list --repo crynta/terax-ai --author roberto-fernandino --state open
```

- If an open PR exists for the same feature/area → push commits to that branch and update the existing PR. Do NOT create a new one.
- Only create a new PR if no related open PR exists.

Related means: same feature, same subsystem, or a fix that belongs to an in-progress feature branch. When in doubt, add to the existing PR.

### 6. Create PR on the fork (roberto-fernandino/terax-ai-fork)

Only if no existing open PR covers this change:

```bash
gh pr create \
--repo roberto-fernandino/terax-ai-fork \
--base main \
--head <branch-name> \
--title "<same as commit title>" \
--body "$(cat <<'EOF'
## Summary
- bullet points

## Test plan
- [ ] item

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Remove emoji from PR body template.

The project prohibits emojis in all files. Replace with plain text, e.g., [Generated with Claude Code]. As per coding guidelines, do not use emojis anywhere.

🧰 Tools
🪛 SkillSpector (2.3.7)

[warning] 34: [RP1] null: npx commands without a version suffix (e.g. @1.0.0) create a rug-pull risk if the upstream server is compromised and publishes a malicious update.

Remediation: Pin the version: npx @scope/server@1.2.3

(MCP Rug Pull (RP1))


[warning] 129: [RP1] null: npx commands without a version suffix (e.g. @1.0.0) create a rug-pull risk if the upstream server is compromised and publishes a malicious update.

Remediation: Pin the version: npx @scope/server@1.2.3

(MCP Rug Pull (RP1))

🤖 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 @.claude/skills/terax-pr-workflow/SKILL.md at line 90, The PR body template
contains an emoji, which violates the project’s no-emoji rule. Update the
generated attribution text in SKILL.md to plain text only, using the existing
“Generated with Claude Code” wording without any emoji or special symbols, and
keep the change localized to the template content.

Source: Coding guidelines

EOF
)"
```

### 7. Create cross-fork PR on the upstream (crynta/terax-ai)

Only if no existing open upstream PR covers this change. Push access is denied to upstream, so use the fork branch as head:

```bash
gh pr create \
--repo crynta/terax-ai \
--base main \
--head roberto-fernandino:<branch-name> \
--title "<same as commit title>" \
--body "..."
```

### 8. Merge the fork PR

```bash
gh pr merge <pr-number> --repo roberto-fernandino/terax-ai-fork --merge --auto
```

### 9. Return to main and pull

```bash
git checkout main
git pull origin main
```

## Rules

- ALWAYS create a branch — never commit directly to main.
- ALWAYS check for existing open PRs before creating new ones (step 5). Push to the existing branch/PR when the change belongs to the same feature or subsystem.
- ALWAYS open TWO PRs when creating new ones: one on the fork, one on the upstream (cross-fork).
- ALWAYS merge the fork PR and return to main at the end.
- NEVER push directly to upstream (no access); always use `--head roberto-fernandino:<branch>` for the upstream PR.
- NEVER open a new PR for a fix that belongs to an existing open feature PR — add the commit to that branch instead.
- Type-check (`npx tsc --noEmit`) before committing.
- Use conventional commit prefixes: `feat`, `fix`, `chore`, `refactor`, `docs`.
1 change: 1 addition & 0 deletions src-tauri/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,7 @@ pub fn run() {
git::commands::git_remote_url,
git::commands::git_list_branches,
git::commands::git_checkout_branch,
git::commands::git_blame,
shell::shell_run_command,
shell::shell_session_open,
shell::shell_session_run,
Expand Down
17 changes: 16 additions & 1 deletion src-tauri/src/modules/git/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use tauri::{AppHandle, Manager};

use crate::modules::git::operations;
use crate::modules::git::types::{
DiscardEntry, GitBranchListResult, GitCommitFileChange, GitCommitResult,
DiscardEntry, GitBlameLineInfo, GitBranchListResult, GitCommitFileChange, GitCommitResult,
GitDiffContentResult, GitDiffResult, GitLogEntry, GitPanelSnapshot, GitPushResult,
GitRepoInfo, GitStatusSnapshot,
};
Expand Down Expand Up @@ -309,3 +309,18 @@ pub async fn git_checkout_branch(
})
.await
}

#[tauri::command]
pub async fn git_blame(
cwd: String,
path: String,
line: u32,
workspace: Option<WorkspaceEnv>,
app: AppHandle,
) -> Result<Option<GitBlameLineInfo>, String> {
let workspace = WorkspaceEnv::from_option(workspace);
blocking(app, move |r| {
operations::blame_line(r, &cwd, &path, line, &workspace).map_err(Into::into)
})
.await
}
57 changes: 55 additions & 2 deletions src-tauri/src/modules/git/operations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ use crate::modules::git::process::{
read_text_file, run_git,
};
use crate::modules::git::types::{
DiscardEntry, GitBranchEntry, GitBranchListResult, GitCommitFileChange, GitCommitResult,
GitDiffContentResult, GitDiffResult, GitLogEntry, GitOutput, GitPanelSnapshot,
DiscardEntry, GitBlameLineInfo, GitBranchEntry, GitBranchListResult, GitCommitFileChange,
GitCommitResult, GitDiffContentResult, GitDiffResult, GitLogEntry, GitOutput, GitPanelSnapshot,
GitPushResult, GitRepoInfo, GitStatusSnapshot, TextSource, DEFAULT_TIMEOUT_SECS,
NETWORK_TIMEOUT_SECS,
};
Expand Down Expand Up @@ -1147,6 +1147,59 @@ pub fn checkout_branch(
ensure_success(&output, "git checkout failed")
}

pub fn blame_line(
registry: &WorkspaceRegistry,
cwd: &str,
path: &str,
line: u32,
workspace: &WorkspaceEnv,
) -> Result<Option<GitBlameLineInfo>> {
if line == 0 {
return Ok(None);
}
let dir = canonical_dir(registry, cwd, workspace)?;
if !registry.is_authorized(&dir.local_path) {
return Err(GitError::PathOutsideWorkspace(dir.local_path));
}
ensure_git_available(&dir.workspace)?;

let line_spec = format!("{},{}", line, line);
let output = run_git(
&dir.workspace,
Some(&dir.git_path),
["blame", "-L", &line_spec, "--porcelain", "--", path],
DEFAULT_TIMEOUT_SECS,
)?;

if output.exit_code != Some(0) {
return Ok(None);
}

let text = String::from_utf8_lossy(&output.stdout);
let mut author: Option<String> = None;
let mut timestamp: Option<i64> = None;

for line_text in text.lines() {
if let Some(name) = line_text.strip_prefix("author ") {
author = Some(name.to_string());
} else if let Some(ts) = line_text.strip_prefix("author-time ") {
if let Ok(t) = ts.trim().parse::<i64>() {
timestamp = Some(t);
}
}
if author.is_some() && timestamp.is_some() {
break;
}
}

Ok(match (author, timestamp) {
(Some(a), Some(t)) if a != "Not Committed Yet" => {
Some(GitBlameLineInfo { author: a, timestamp: t })
}
_ => None,
})
}
Comment on lines +1150 to +1201

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Extract the porcelain parser into a pure function and add a test for it.

The parsing logic (lines 1178-1200) is sound, but it's fused to the run_git IO call, so it can't be unit-tested without shelling out to a real git process. TERAX.md's quality bar requires a test that locks the invariant for any git-subsystem change, and none is included for this parser (e.g. malformed porcelain output, "Not Committed Yet" filtering, missing author-time).

Splitting the text.lines() loop into a standalone fn parse_blame_porcelain(text: &str) -> Option<GitBlameLineInfo> keeps blame_line as the thin IO orchestrator and makes the parsing trivially testable with fixture strings.

As per coding guidelines, "A change to a core subsystem (terminal/shell spawn, workspace auth, git, fs, IPC or AI tool surface) needs a test that locks the invariant," and "new or changed logic lives in pure, dependency-light functions (functional core)."

♻️ Suggested split
fn parse_blame_porcelain(text: &str) -> Option<GitBlameLineInfo> {
    let mut author: Option<String> = None;
    let mut timestamp: Option<i64> = None;
    for line_text in text.lines() {
        if let Some(name) = line_text.strip_prefix("author ") {
            author = Some(name.to_string());
        } else if let Some(ts) = line_text.strip_prefix("author-time ") {
            if let Ok(t) = ts.trim().parse::<i64>() {
                timestamp = Some(t);
            }
        }
        if author.is_some() && timestamp.is_some() {
            break;
        }
    }
    match (author, timestamp) {
        (Some(a), Some(t)) if a != "Not Committed Yet" => Some(GitBlameLineInfo { author: a, timestamp: t }),
        _ => None,
    }
}

Then blame_line just calls Ok(output.exit_code == Some(0)).then(|| parse_blame_porcelain(&String::from_utf8_lossy(&output.stdout))).flatten()) (or an equivalent if), and a #[cfg(test)] module can exercise parse_blame_porcelain directly with porcelain fixtures.

🤖 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/modules/git/operations.rs` around lines 1150 - 1201, Refactor
blame_line so the porcelain parsing is moved into a pure helper, such as
parse_blame_porcelain, and keep blame_line focused on the run_git I/O and
exit-code handling. Extract the text.lines() author/timestamp loop into that
helper using GitBlameLineInfo as the return value, then add unit tests directly
against the parser with fixture strings covering valid output, malformed output,
missing author-time, and the "Not Committed Yet" filter.

Source: Coding guidelines


#[cfg(test)]
mod tests {
use super::*;
Expand Down
7 changes: 7 additions & 0 deletions src-tauri/src/modules/git/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,13 @@ pub struct GitBranchListResult {
pub branches: Vec<GitBranchEntry>,
}

#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
pub struct GitBlameLineInfo {
pub author: String,
pub timestamp: i64,
}

pub(crate) struct GitOutput {
pub(crate) stdout: Vec<u8>,
pub(crate) stderr: Vec<u8>,
Expand Down
Loading
Loading