refactor: improve drag interaction and workspace identity normalization

This commit is contained in:
Mohamed Boudra
2026-03-04 12:25:14 +07:00
parent b5b4e60916
commit 4f1783fc76
12 changed files with 750 additions and 147 deletions
@@ -134,4 +134,24 @@ describe("workspace agent visibility", () => {
"older-activity",
]);
});
it("matches workspace agents when cwd and route workspace differ only by trailing slash", () => {
const sessionAgents = new Map<string, Agent>([
[
"slash-agent",
makeAgent({
id: "slash-agent",
cwd: "/Users/moboudra/.paseo/worktrees/1luy0po7/normal-squid/",
}),
],
]);
const result = deriveWorkspaceAgentVisibility({
sessionAgents,
workspaceId: "/Users/moboudra/.paseo/worktrees/1luy0po7/normal-squid",
});
expect(result.visibleAgents.map((agent) => agent.id)).toEqual(["slash-agent"]);
expect(result.lookupById.has("slash-agent")).toBe(true);
});
});
@@ -1,4 +1,5 @@
import type { Agent } from "@/stores/session-store";
import { normalizeWorkspaceIdentity } from "@/utils/workspace-identity";
function sortAgentsByCreatedAtDescending(agents: Agent[]): Agent[] {
const sorted = [...agents];
@@ -20,6 +21,10 @@ function trimNonEmpty(value: string | null | undefined): string | null {
return trimmed.length > 0 ? trimmed : null;
}
function normalizeWorkspaceId(value: string | null | undefined): string {
return normalizeWorkspaceIdentity(value) ?? "";
}
export function deriveWorkspaceAgentVisibility(input: {
sessionAgents: Map<string, Agent> | undefined;
workspaceId: string;
@@ -28,6 +33,7 @@ export function deriveWorkspaceAgentVisibility(input: {
lookupById: Map<string, Agent>;
} {
const { sessionAgents, workspaceId } = input;
const normalizedWorkspaceId = normalizeWorkspaceId(workspaceId);
if (!sessionAgents || !workspaceId) {
return {
visibleAgents: [],
@@ -38,7 +44,7 @@ export function deriveWorkspaceAgentVisibility(input: {
const lookupById = new Map<string, Agent>();
const visible: Agent[] = [];
for (const agent of sessionAgents.values()) {
if ((trimNonEmpty(agent.cwd) ?? "") !== workspaceId) {
if (normalizeWorkspaceId(agent.cwd) !== normalizedWorkspaceId) {
continue;
}
lookupById.set(agent.id, agent);
@@ -54,6 +54,7 @@ import {
buildHostWorkspaceTabRoute,
decodeWorkspaceIdFromPathSegment,
} from "@/utils/host-routes";
import { normalizeWorkspaceIdentity } from "@/utils/workspace-identity";
import { useHostRuntimeSession } from "@/runtime/host-runtime";
import {
checkoutStatusQueryKey,
@@ -139,7 +140,8 @@ function WorkspaceScreenContent({
UnistylesRuntime.breakpoint === "xs" || UnistylesRuntime.breakpoint === "sm";
const normalizedServerId = trimNonEmpty(decodeSegment(serverId)) ?? "";
const normalizedWorkspaceId = decodeWorkspaceIdFromPathSegment(workspaceId) ?? "";
const normalizedWorkspaceId =
normalizeWorkspaceIdentity(decodeWorkspaceIdFromPathSegment(workspaceId)) ?? "";
const queryClient = useQueryClient();
const { client, isConnected } = useHostRuntimeSession(normalizedServerId);
@@ -298,6 +300,9 @@ function WorkspaceScreenContent({
const hasHydratedWorkspaces = useSessionStore(
(state) => state.sessions[normalizedServerId]?.hasHydratedWorkspaces ?? false
);
const hasHydratedAgents = useSessionStore(
(state) => state.sessions[normalizedServerId]?.hasHydratedAgents ?? false
);
const workspaceHeader = workspaceDescriptor
? resolveWorkspaceHeader({ workspace: workspaceDescriptor })
: null;
@@ -916,15 +921,20 @@ function WorkspaceScreenContent({
) {
return (
<View style={styles.emptyState}>
<Text style={styles.emptyStateText}>
Workspace descriptor missing for this route.
</Text>
<ActivityIndicator color={theme.colors.foregroundMuted} />
</View>
);
}
const target = activeTab?.target ?? null;
if (!target) {
if (!hasHydratedAgents) {
return (
<View style={styles.emptyState}>
<ActivityIndicator color={theme.colors.foregroundMuted} />
</View>
);
}
return (
<View style={styles.emptyState}>
<Text style={styles.emptyStateText}>
@@ -161,4 +161,40 @@ describe("deriveWorkspaceTabModel", () => {
expect(model.tabs.some((tab) => tab.descriptor.tabId === "agent_offender")).toBe(true);
});
it("includes older attention and failure agents in workspace tabs when session data is complete", () => {
const olderPermissionAgent = makeAgent({
id: "agent-permission-old",
title: "Need permission",
createdAt: new Date("2026-01-15T00:00:00.000Z"),
requiresAttention: true,
attentionReason: "permission",
});
const olderFailedAgent = makeAgent({
id: "agent-failed-old",
title: "Failed run",
createdAt: new Date("2026-01-10T00:00:00.000Z"),
requiresAttention: true,
attentionReason: "error",
});
const newerAgent = makeAgent({
id: "agent-recent",
title: "Recent work",
createdAt: new Date("2026-03-04T00:00:00.000Z"),
});
const model = deriveWorkspaceTabModel({
workspaceAgents: [newerAgent, olderPermissionAgent, olderFailedAgent],
terminals: [],
uiTabs: [],
tabOrder: [],
});
expect(model.tabs.some((tab) => tab.descriptor.tabId === "agent_agent-permission-old")).toBe(
true
);
expect(model.tabs.some((tab) => tab.descriptor.tabId === "agent_agent-failed-old")).toBe(
true
);
});
});