ccmanager 4.1.20 → 4.1.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/Dashboard.js +1 -0
- package/dist/components/Menu.js +4 -5
- package/dist/utils/filterByQuery.d.ts +11 -0
- package/dist/utils/filterByQuery.js +16 -0
- package/dist/utils/filterByQuery.test.d.ts +1 -0
- package/dist/utils/filterByQuery.test.js +81 -0
- package/dist/utils/worktreeUtils.d.ts +1 -0
- package/dist/utils/worktreeUtils.js +4 -0
- package/dist/utils/worktreeUtils.test.js +2 -0
- package/package.json +6 -6
|
@@ -245,6 +245,7 @@ const Dashboard = ({ projectsDir, onSelectSession, onSelectProject, onSessionAct
|
|
|
245
245
|
worktree: wt,
|
|
246
246
|
session: entry.session,
|
|
247
247
|
baseLabel,
|
|
248
|
+
searchableName: `${entry.projectName} :: ${fullBranchName}${isMain}`,
|
|
248
249
|
fileChanges,
|
|
249
250
|
aheadBehind,
|
|
250
251
|
parentBranch,
|
package/dist/components/Menu.js
CHANGED
|
@@ -10,7 +10,7 @@ import { prepareSessionItems, calculateColumnPositions, assembleSessionLabel, }
|
|
|
10
10
|
import { projectManager } from '../services/projectManager.js';
|
|
11
11
|
import { useSearchMode } from '../hooks/useSearchMode.js';
|
|
12
12
|
import { useDynamicLimit } from '../hooks/useDynamicLimit.js';
|
|
13
|
-
import {
|
|
13
|
+
import { filterSessionItemsByQuery } from '../utils/filterByQuery.js';
|
|
14
14
|
import SearchableList from './SearchableList.js';
|
|
15
15
|
import { globalSessionOrchestrator } from '../services/globalSessionOrchestrator.js';
|
|
16
16
|
import { configReader } from '../services/config/configReader.js';
|
|
@@ -124,10 +124,9 @@ const Menu = ({ sessionManager, worktreeService, onMenuAction, onSelectRecentPro
|
|
|
124
124
|
sortByLastSession: worktreeConfig.sortByLastSession,
|
|
125
125
|
});
|
|
126
126
|
const columnPositions = calculateColumnPositions(items);
|
|
127
|
-
// Filter
|
|
128
|
-
|
|
129
|
-
const
|
|
130
|
-
const filteredItems = items.filter(item => filteredWorktreeSet.has(item.worktree));
|
|
127
|
+
// Filter session items based on search query, matching the name shown in
|
|
128
|
+
// the menu (branch name, " (main)", and session name) plus the path.
|
|
129
|
+
const filteredItems = filterSessionItemsByQuery(items, searchQuery);
|
|
131
130
|
// Build menu items with proper alignment
|
|
132
131
|
const menuItems = filteredItems.map((item, index) => {
|
|
133
132
|
const baseLabel = assembleSessionLabel(item, columnPositions);
|
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
import { Worktree } from '../types/index.js';
|
|
2
|
+
import { SessionItem } from './worktreeUtils.js';
|
|
2
3
|
/**
|
|
3
4
|
* Filter worktrees by matching search query against branch name and path.
|
|
4
5
|
*/
|
|
5
6
|
export declare function filterWorktreesByQuery(worktrees: Worktree[], query: string): Worktree[];
|
|
7
|
+
/**
|
|
8
|
+
* Filter session items by matching the search query against the name shown in
|
|
9
|
+
* the menu (branch name, " (main)" indicator, and session name) and the
|
|
10
|
+
* worktree path. Status icons and git status columns are not matched.
|
|
11
|
+
*
|
|
12
|
+
* Filtering happens per session item (not per worktree) so that a query can
|
|
13
|
+
* match an individual session name within a worktree that has multiple
|
|
14
|
+
* sessions.
|
|
15
|
+
*/
|
|
16
|
+
export declare function filterSessionItemsByQuery(items: SessionItem[], query: string): SessionItem[];
|
|
@@ -11,3 +11,19 @@ export function filterWorktreesByQuery(worktrees, query) {
|
|
|
11
11
|
worktree.path.toLowerCase().includes(searchLower));
|
|
12
12
|
});
|
|
13
13
|
}
|
|
14
|
+
/**
|
|
15
|
+
* Filter session items by matching the search query against the name shown in
|
|
16
|
+
* the menu (branch name, " (main)" indicator, and session name) and the
|
|
17
|
+
* worktree path. Status icons and git status columns are not matched.
|
|
18
|
+
*
|
|
19
|
+
* Filtering happens per session item (not per worktree) so that a query can
|
|
20
|
+
* match an individual session name within a worktree that has multiple
|
|
21
|
+
* sessions.
|
|
22
|
+
*/
|
|
23
|
+
export function filterSessionItemsByQuery(items, query) {
|
|
24
|
+
if (!query)
|
|
25
|
+
return items;
|
|
26
|
+
const searchLower = query.toLowerCase();
|
|
27
|
+
return items.filter(item => item.searchableName.toLowerCase().includes(searchLower) ||
|
|
28
|
+
item.worktree.path.toLowerCase().includes(searchLower));
|
|
29
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { filterWorktreesByQuery, filterSessionItemsByQuery, } from './filterByQuery.js';
|
|
3
|
+
const makeItem = (searchableName, path) => ({
|
|
4
|
+
worktree: {
|
|
5
|
+
path,
|
|
6
|
+
isMainWorktree: false,
|
|
7
|
+
hasSession: false,
|
|
8
|
+
},
|
|
9
|
+
baseLabel: searchableName,
|
|
10
|
+
searchableName,
|
|
11
|
+
fileChanges: '',
|
|
12
|
+
aheadBehind: '',
|
|
13
|
+
parentBranch: '',
|
|
14
|
+
lastCommitDate: '',
|
|
15
|
+
lengths: {
|
|
16
|
+
base: 0,
|
|
17
|
+
fileChanges: 0,
|
|
18
|
+
aheadBehind: 0,
|
|
19
|
+
parentBranch: 0,
|
|
20
|
+
lastCommitDate: 0,
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
describe('filterWorktreesByQuery', () => {
|
|
24
|
+
const worktrees = [
|
|
25
|
+
{
|
|
26
|
+
path: '/repo/feature-a',
|
|
27
|
+
branch: 'feature/a',
|
|
28
|
+
isMainWorktree: false,
|
|
29
|
+
hasSession: false,
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
path: '/repo/main',
|
|
33
|
+
branch: 'main',
|
|
34
|
+
isMainWorktree: true,
|
|
35
|
+
hasSession: false,
|
|
36
|
+
},
|
|
37
|
+
];
|
|
38
|
+
it('returns all worktrees when query is empty', () => {
|
|
39
|
+
expect(filterWorktreesByQuery(worktrees, '')).toEqual(worktrees);
|
|
40
|
+
});
|
|
41
|
+
it('matches branch name case-insensitively', () => {
|
|
42
|
+
const result = filterWorktreesByQuery(worktrees, 'FEATURE');
|
|
43
|
+
expect(result).toHaveLength(1);
|
|
44
|
+
expect(result[0]?.branch).toBe('feature/a');
|
|
45
|
+
});
|
|
46
|
+
it('matches path', () => {
|
|
47
|
+
const result = filterWorktreesByQuery(worktrees, '/repo/main');
|
|
48
|
+
expect(result).toHaveLength(1);
|
|
49
|
+
expect(result[0]?.path).toBe('/repo/main');
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
describe('filterSessionItemsByQuery', () => {
|
|
53
|
+
const items = [
|
|
54
|
+
makeItem('feature/a', '/repo/feature-a'),
|
|
55
|
+
makeItem('main (main)', '/repo/main'),
|
|
56
|
+
makeItem('feature/b: my-session', '/repo/feature-b'),
|
|
57
|
+
makeItem('feature/b: other', '/repo/feature-b'),
|
|
58
|
+
];
|
|
59
|
+
it('returns all items when query is empty', () => {
|
|
60
|
+
expect(filterSessionItemsByQuery(items, '')).toEqual(items);
|
|
61
|
+
});
|
|
62
|
+
it('matches the session name within a worktree', () => {
|
|
63
|
+
const result = filterSessionItemsByQuery(items, 'my-session');
|
|
64
|
+
expect(result).toHaveLength(1);
|
|
65
|
+
expect(result[0]?.searchableName).toBe('feature/b: my-session');
|
|
66
|
+
});
|
|
67
|
+
it('matches the (main) indicator', () => {
|
|
68
|
+
const result = filterSessionItemsByQuery(items, '(main)');
|
|
69
|
+
expect(result).toHaveLength(1);
|
|
70
|
+
expect(result[0]?.searchableName).toBe('main (main)');
|
|
71
|
+
});
|
|
72
|
+
it('matches branch name case-insensitively', () => {
|
|
73
|
+
const result = filterSessionItemsByQuery(items, 'FEATURE/A');
|
|
74
|
+
expect(result).toHaveLength(1);
|
|
75
|
+
expect(result[0]?.searchableName).toBe('feature/a');
|
|
76
|
+
});
|
|
77
|
+
it('matches path', () => {
|
|
78
|
+
const result = filterSessionItemsByQuery(items, '/repo/feature-b');
|
|
79
|
+
expect(result).toHaveLength(2);
|
|
80
|
+
});
|
|
81
|
+
});
|
|
@@ -166,6 +166,9 @@ function buildSessionItem(wt, session, sessionSuffix) {
|
|
|
166
166
|
const branchName = truncateString(fullBranchName, MAX_BRANCH_NAME_LENGTH);
|
|
167
167
|
const isMain = wt.isMainWorktree ? ' (main)' : '';
|
|
168
168
|
const baseLabel = `${branchName}${isMain}${sessionSuffix}${status}`;
|
|
169
|
+
// Use the full (untruncated) branch name so search still matches the tail
|
|
170
|
+
// of long branch names; status icons are excluded so they don't match.
|
|
171
|
+
const searchableName = `${fullBranchName}${isMain}${sessionSuffix}`;
|
|
169
172
|
const { fileChanges, aheadBehind, parentBranch, error } = gitStatusColumns(wt, fullBranchName);
|
|
170
173
|
const lastCommitDate = wt.lastCommitDate
|
|
171
174
|
? `\x1b[90m${formatRelativeDate(wt.lastCommitDate)}\x1b[0m`
|
|
@@ -174,6 +177,7 @@ function buildSessionItem(wt, session, sessionSuffix) {
|
|
|
174
177
|
worktree: wt,
|
|
175
178
|
session,
|
|
176
179
|
baseLabel,
|
|
180
|
+
searchableName,
|
|
177
181
|
fileChanges,
|
|
178
182
|
aheadBehind,
|
|
179
183
|
parentBranch,
|
|
@@ -173,6 +173,7 @@ describe('column alignment', () => {
|
|
|
173
173
|
{
|
|
174
174
|
worktree: {},
|
|
175
175
|
baseLabel: 'feature/test-branch',
|
|
176
|
+
searchableName: 'feature/test-branch',
|
|
176
177
|
fileChanges: '\x1b[32m+10\x1b[0m \x1b[31m-5\x1b[0m',
|
|
177
178
|
aheadBehind: '\x1b[33m↑2 ↓3\x1b[0m',
|
|
178
179
|
parentBranch: '',
|
|
@@ -188,6 +189,7 @@ describe('column alignment', () => {
|
|
|
188
189
|
{
|
|
189
190
|
worktree: {},
|
|
190
191
|
baseLabel: 'main',
|
|
192
|
+
searchableName: 'main',
|
|
191
193
|
fileChanges: '\x1b[32m+2\x1b[0m \x1b[31m-1\x1b[0m',
|
|
192
194
|
aheadBehind: '\x1b[33m↑1\x1b[0m',
|
|
193
195
|
parentBranch: '',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ccmanager",
|
|
3
|
-
"version": "4.1.
|
|
3
|
+
"version": "4.1.21",
|
|
4
4
|
"description": "TUI application for managing multiple Claude Code sessions across Git worktrees",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Kodai Kabasawa",
|
|
@@ -41,11 +41,11 @@
|
|
|
41
41
|
"bin"
|
|
42
42
|
],
|
|
43
43
|
"optionalDependencies": {
|
|
44
|
-
"@kodaikabasawa/ccmanager-darwin-arm64": "4.1.
|
|
45
|
-
"@kodaikabasawa/ccmanager-darwin-x64": "4.1.
|
|
46
|
-
"@kodaikabasawa/ccmanager-linux-arm64": "4.1.
|
|
47
|
-
"@kodaikabasawa/ccmanager-linux-x64": "4.1.
|
|
48
|
-
"@kodaikabasawa/ccmanager-win32-x64": "4.1.
|
|
44
|
+
"@kodaikabasawa/ccmanager-darwin-arm64": "4.1.21",
|
|
45
|
+
"@kodaikabasawa/ccmanager-darwin-x64": "4.1.21",
|
|
46
|
+
"@kodaikabasawa/ccmanager-linux-arm64": "4.1.21",
|
|
47
|
+
"@kodaikabasawa/ccmanager-linux-x64": "4.1.21",
|
|
48
|
+
"@kodaikabasawa/ccmanager-win32-x64": "4.1.21"
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|
|
51
51
|
"@eslint/js": "^9.28.0",
|