fix(coding-agent): sort threaded sessions by latest activity in the subtree

This commit is contained in:
Sviatoslav Abakumov 2026-06-16 00:20:04 +04:00
parent 431d88f4fc
commit ebdf9b65e7
No known key found for this signature in database
2 changed files with 56 additions and 3 deletions

View file

@ -190,6 +190,7 @@ class SessionSelectorHeader implements Component {
interface SessionTreeNode {
session: SessionInfo;
children: SessionTreeNode[];
latestActivity: number;
}
/** Flattened node for display with tree structure info */
@ -210,7 +211,7 @@ function buildSessionTree(sessions: SessionInfo[]): SessionTreeNode[] {
for (const session of sessions) {
const sessionPath = canonicalizePath(session.path) ?? session.path;
byPath.set(sessionPath, { session, children: [] });
byPath.set(sessionPath, { session, children: [], latestActivity: session.modified.getTime() });
}
const roots: SessionTreeNode[] = [];
@ -227,9 +228,22 @@ function buildSessionTree(sessions: SessionInfo[]): SessionTreeNode[] {
}
}
// Sort children and roots by modified date (descending)
const updateLatestActivity = (node: SessionTreeNode): number => {
let latestActivity = node.session.modified.getTime();
for (const child of node.children) {
latestActivity = Math.max(latestActivity, updateLatestActivity(child));
}
node.latestActivity = latestActivity;
return latestActivity;
};
for (const root of roots) {
updateLatestActivity(root);
}
// Sort children and roots by latest activity in each subtree (descending)
const sortNodes = (nodes: SessionTreeNode[]): void => {
nodes.sort((a, b) => b.session.modified.getTime() - a.session.modified.getTime());
nodes.sort((a, b) => b.latestActivity - a.latestActivity);
for (const node of nodes) {
sortNodes(node.children);
}

View file

@ -282,6 +282,45 @@ describe("session selector path/delete interactions", () => {
expect(output).toContain("└─ Child");
});
it("sorts threaded sessions by latest activity in their subtree", async () => {
const parentOne = makeSession({
id: "parent-one",
name: "Parent one",
modified: new Date("2026-01-02T00:00:00.000Z"),
});
const parentTwo = makeSession({
id: "parent-two",
name: "Parent two",
modified: new Date("2026-01-01T00:00:00.000Z"),
});
const childTwo = makeSession({
id: "child-two",
name: "Child two",
parentSessionPath: parentTwo.path,
modified: new Date("2026-01-03T00:00:00.000Z"),
});
const selector = new SessionSelectorComponent(
async () => [parentOne, parentTwo, childTwo],
async () => [],
() => {},
() => {},
() => {},
() => {},
{ keybindings },
);
await flushPromises();
const output = stripAnsi(selector.render(120).join("\n"));
const parentTwoIndex = output.indexOf("Parent two");
const childTwoIndex = output.indexOf("└─ Child two");
const parentOneIndex = output.indexOf("Parent one");
expect(parentTwoIndex).toBeGreaterThanOrEqual(0);
expect(childTwoIndex).toBeGreaterThan(parentTwoIndex);
expect(parentOneIndex).toBeGreaterThan(childTwoIndex);
});
it("treats the current session as active across symlink aliases", async () => {
const paths = createSymlinkedSessionPaths();
tempDirs.push(paths.baseDir);