refactor: improve drag interaction and workspace identity normalization
This commit is contained in:
@@ -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
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user