Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 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
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
112 changes: 112 additions & 0 deletions .claude/skills/terax-pr-workflow/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
---
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-dash from skill description.

The description field contains an em-dash, which violates the project convention: "Do not use em-dash anywhere, including code, comments, commits, and docs." As per coding guidelines, no em-dashes 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] 111: [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 still uses an em-dash, which violates the repository convention.
Update the description text to remove the em-dash and replace it with plain
ASCII punctuation while preserving the meaning, and ensure the rest of the skill
content remains unchanged. Use the description field in SKILL.md as the target
location for this cleanup.

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

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

Use pnpm check-types instead of npx tsc --noEmit.

The project defines pnpm check-types as the frontend type-check command. Using bare npx tsc bypasses pnpm, the project's package manager, and any project-level TypeScript configuration that check-types may wrap. This is inconsistent with the established workflow and could drift if the check-types script is updated. As per coding guidelines, pnpm only, never npm/npx/yarn.

🧰 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] 111: [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 34, The workflow currently
uses a bare TypeScript check command instead of the project’s pnpm-based script.
Update the SKILL.md instructions to use pnpm check-types in place of npx tsc
--noEmit, and keep the guidance aligned with the established pnpm-only workflow
so it references the project’s standard type-check command.

Source: Coding guidelines

```

### 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. Create PR on the fork (roberto-fernandino/terax-ai-fork)

```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 template.

The PR body template includes a robot emoji, which violates the project convention: "Do not use emojis anywhere." As per coding guidelines, no 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] 111: [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 74, Remove the robot emoji
from the PR body template in the skill documentation so it follows the no-emojis
convention. Update the template text in SKILL.md by editing the PR template
section that includes the “Generated with [Claude Code]” line, and ensure the
final PR body content contains only plain text with no emoji characters.

Source: Coding guidelines

EOF
)"
```

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

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 "..."
```

### 7. Merge the fork PR

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

### 8. Return to main and pull

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

## Rules

- ALWAYS create a branch — never commit directly to main.
- ALWAYS open TWO PRs: 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.
- Type-check (`npx tsc --noEmit`) before committing.
- Use conventional commit prefixes: `feat`, `fix`, `chore`, `refactor`, `docs`.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"version": "0.8.2",
"license": "Apache-2.0",
"type": "module",
"packageManager": "pnpm@11.5.0+sha512.dbfcc4f81cf48597afd4bc391ffdf12c11f1a9fb83a395bfa6b0a2d9cc2fd8ffebafdb1ccbd529632153f793904c2615b7f09fe1a345473fd1c35845172a8eb1",
"packageManager": "pnpm@11.9.0+sha512.bd682d5d03fe525ef7c9fd6780c6884d1e756ac4c9c9fe00c538782824310dcf90e3ddc4f53835f06dfaebd5085e41855e0bcbb3b60de2ac5bbab89e5036f03b",
"engines": {
"node": ">=22"
},
Expand Down
70 changes: 68 additions & 2 deletions src/app/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import { quoteShellArg } from "@/lib/shellQuote";
import { useZoom } from "@/lib/useZoom";
import { getCurrentWindow } from "@tauri-apps/api/window";
import { AgentNotificationsBridge, nextAttentionTarget } from "@/modules/agents";
import { useAgentStore } from "@/modules/agents/store/agentStore";
import { AgentsPanel } from "@/modules/agents-panel";
import {
AgentRunBridge,
AiMiniWindow,
Expand Down Expand Up @@ -258,6 +260,21 @@ export default function App() {
[tabs, activeSpaceId],
);

const agentStoreSessions = useAgentStore((s) => s.sessions);
const agentSessions = useMemo(
() => Object.values(agentStoreSessions),
[agentStoreSessions],
);
const agentsByTabId = useMemo(() => {
const map = new Map<number, string>();
for (const s of agentSessions) {
map.set(s.tabId, s.agent);
}
return map;
}, [agentSessions]);

const showAgentsTab = usePreferencesStore((s) => s.showAgentsTab);

const {
sidebarRef,
sidebarWidthRef,
Expand All @@ -267,7 +284,7 @@ export default function App() {
cycleSidebarView,
persistSidebarWidth,
toggleExplorerFocus,
} = useSidebarPanel(explorerRef);
} = useSidebarPanel(explorerRef, showAgentsTab);

const [newEditorOpen, setNewEditorOpen] = useState(false);
const [commandPaletteOpen, setCommandPaletteOpen] = useState(false);
Expand All @@ -284,6 +301,7 @@ export default function App() {
const miniOpen = useChatStore((s) => s.mini.open);
const miniPresence = usePresence(miniOpen, 200);
const openMini = useChatStore((s) => s.openMini);
const toggleMini = useChatStore((s) => s.toggleMini);
const focusInput = useChatStore((s) => s.focusInput);
const openPanel = useChatStore((s) => s.openPanel);
const panelOpen = useChatStore((s) => s.panelOpen);
Expand Down Expand Up @@ -378,6 +396,23 @@ export default function App() {
useEffect(() => {
mruRef.current = [activeId, ...mruRef.current.filter((id) => id !== activeId)];
}, [activeId]);

// When the user NAVIGATES to a terminal tab, reset any non-idle agent status
// back to idle — they are looking at it so notifications are moot, and
// Ctrl+C won't fire a finished signal so "working" would otherwise get stuck.
// Uses tabsRef (not tabs) so this only fires on activeId change, not on every
// tab title update (which would kill real-time "working" display).
useEffect(() => {
const tab = tabsRef.current.find((t) => t.id === activeId);
if (!tab || tab.kind !== "terminal") return;
const store = useAgentStore.getState();
for (const s of Object.values(store.sessions)) {
if (s.tabId === activeId && s.status !== "idle") {
store.setStatus(s.leafId, "idle");
}
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [activeId]);
Comment on lines +399 to +415

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Do not clear agent status on tab switch.

This rewrites real lifecycle state as a visibility side effect: a running or waiting agent in the active tab becomes idle, which drops it from agentCount and the panel, and split tabs clear sibling leaves too. Keep acknowledgement separate, or at least scope this to the focused leaf and waiting. As per path instructions, focus on regressions from local fixes and the right abstraction boundary.

🤖 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/app/App.tsx` around lines 399 - 415, Do not reset agent lifecycle state
in the App tab-switch effect: the logic in App.tsx useEffect that iterates over
useAgentStore sessions and calls setStatus(..., "idle") on activeId change is
clearing real running/waiting state. Remove or narrow this behavior so tab
visibility only acknowledges notifications, and if any status update is still
needed, scope it to the focused leaf and only for waiting states rather than all
non-idle sessions.

Source: Path instructions

useEffect(() => {
const live = new Set(tabs.map((t) => t.id));
mruRef.current = mruRef.current.filter((id) => live.has(id));
Expand Down Expand Up @@ -653,7 +688,11 @@ export default function App() {
"tab.close": handleCloseTabOrPane,
"tab.next": () => stepSwitcher(1),
"tab.prev": () => stepSwitcher(-1),
"tab.selectByIndex": (e) => selectByIndex(parseInt(e.key, 10) - 1),
"tab.selectByIndex": (e) =>
selectByIndex(
parseInt(e.key, 10) - 1,
activeSpaceId ?? DEFAULT_SPACE_ID,
),
"space.next": () => cycleSpace(1),
"space.prev": () => cycleSpace(-1),
"space.overview": () => setSwitcherOpen(true),
Expand All @@ -662,6 +701,7 @@ export default function App() {
"pane.focusNext": () => focusNextPaneInTab(activeId, 1),
"pane.focusPrev": () => focusNextPaneInTab(activeId, -1),
"pane.source": toggleSourceControl,
"sidebar.files": () => cycleSidebarView("explorer"),
"terminal.clear": () => {
clearFocusedTerminal();
},
Expand All @@ -671,6 +711,13 @@ export default function App() {
"blocks.next": () => navigateFocusedBlocks(1),
"search.focus": () => searchInlineRef.current?.focus(),
"ai.toggle": togglePanelAndFocus,
"ai.toggleMini": () => {
if (!hasComposer) {
void openSettingsWindow("models");
return;
}
toggleMini();
},
"ai.askSelection": askFromSelection,
"agent.focusAttention": () => {
const t = nextAttentionTarget();
Expand Down Expand Up @@ -700,7 +747,9 @@ export default function App() {
splitActivePaneInActiveTab,
focusNextPaneInTab,
toggleSourceControl,
hasComposer,
togglePanelAndFocus,
toggleMini,
askFromSelection,
toggleSidebar,
toggleExplorerFocus,
Expand Down Expand Up @@ -979,6 +1028,7 @@ export default function App() {
openNewPreview: () => openPreviewTab(""),
openGitGraph: openGitGraphFromContext,
toggleSourceControl,
toggleFilesExplorer: () => cycleSidebarView("explorer"),
closeActiveTabOrPane: handleCloseTabOrPane,
splitPaneRight: () => splitActivePaneInActiveTab("row"),
splitPaneDown: () => splitActivePaneInActiveTab("col"),
Expand All @@ -994,6 +1044,12 @@ export default function App() {
openSpacesOverview: () => setSwitcherOpen(true),
newSpace: () => void handleNewSpace(),
switchSpace: (id) => useSpaces.getState().setActive(id),
terminalTabs: tabs.filter(
(t) =>
t.kind === "terminal" &&
t.spaceId === (activeSpaceId ?? DEFAULT_SPACE_ID),
),
switchTab: (id) => setActiveId(id),
})
: [],
[
Expand All @@ -1009,6 +1065,7 @@ export default function App() {
openPreviewTab,
openGitGraphFromContext,
toggleSourceControl,
cycleSidebarView,
handleCloseTabOrPane,
splitActivePaneInActiveTab,
toggleSidebar,
Expand Down Expand Up @@ -1082,6 +1139,7 @@ export default function App() {
searchTarget={searchTarget}
searchRef={searchInlineRef}
onOverrideLanguage={setOverrideLanguage}
agentsByTabId={agentsByTabId}
/>
)}

Expand Down Expand Up @@ -1118,6 +1176,12 @@ export default function App() {
onRevealInTerminal={cdInNewTab}
onAttachToAgent={handleAttachFileToAgent}
/>
) : sidebarView === "agents" ? (
<AgentsPanel
sessions={agentSessions}
tabs={tabs}
onSelectTab={setActiveId}
/>
Comment on lines +1179 to +1184

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Use activateAgentTarget for panel row clicks.

agentSessions spans all spaces and multiple leaves per tab, but these rows only call setActiveId, so cross-space clicks leave the header on the wrong space and split-pane clicks cannot focus the owning leaf. Pass the existing leaf-aware activation callback through the panel instead. As per path instructions, focus on blast radius and global regressions from local UI wiring.

🤖 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/app/App.tsx` around lines 1179 - 1184, The panel row click wiring in
App’s AgentsPanel is using setActiveId directly, but these rows need the
leaf-aware activation path to keep the header and split-pane state in sync
across spaces. Update the AgentsPanel usage so row clicks call
activateAgentTarget instead of setActiveId, and make sure the panel receives the
existing callback that knows how to focus the owning leaf for an agent session.
Keep the change localized to the App/AgentsPanel wiring to avoid broader
regressions in global navigation state.

Source: Path instructions

) : (
<SourceControlPanel
open
Expand All @@ -1133,6 +1197,8 @@ export default function App() {
activeView={sidebarView}
onSelectView={persistSidebarView}
changedCount={sourceControl.changedCount}
showAgentsTab={showAgentsTab}
agentCount={agentSessions.filter((s) => s.status !== "idle").length}
/>
</div>
</ResizablePanel>
Expand Down
Loading