Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 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
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.
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. 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)
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
69 changes: 67 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,22 @@ 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 the terminal so notifications are moot,
// and a Ctrl+C interrupt won't fire a finished signal so "working" would
// otherwise get stuck. If the agent is genuinely still working the backend
// will fire the next "working" signal and promote it back immediately.
useEffect(() => {
const tab = tabs.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");
}
}
}, [activeId, tabs]);
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 +687,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 +700,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 +710,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 +746,9 @@ export default function App() {
splitActivePaneInActiveTab,
focusNextPaneInTab,
toggleSourceControl,
hasComposer,
togglePanelAndFocus,
toggleMini,
askFromSelection,
toggleSidebar,
toggleExplorerFocus,
Expand Down Expand Up @@ -979,6 +1027,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 +1043,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 +1064,7 @@ export default function App() {
openPreviewTab,
openGitGraphFromContext,
toggleSourceControl,
cycleSidebarView,
handleCloseTabOrPane,
splitActivePaneInActiveTab,
toggleSidebar,
Expand Down Expand Up @@ -1082,6 +1138,7 @@ export default function App() {
searchTarget={searchTarget}
searchRef={searchInlineRef}
onOverrideLanguage={setOverrideLanguage}
agentsByTabId={agentsByTabId}
/>
)}

Expand Down Expand Up @@ -1118,6 +1175,12 @@ export default function App() {
onRevealInTerminal={cdInNewTab}
onAttachToAgent={handleAttachFileToAgent}
/>
) : sidebarView === "agents" ? (
<AgentsPanel
sessions={agentSessions}
tabs={tabs}
onSelectTab={setActiveId}
/>
) : (
<SourceControlPanel
open
Expand All @@ -1133,6 +1196,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
Loading