fix: use numeric workspace ID for worktree setup tracking and improve service list UI
This commit is contained in:
@@ -365,15 +365,26 @@ function WorkspaceHoverCardContent({
|
|||||||
backgroundColor:
|
backgroundColor:
|
||||||
service.status === "running"
|
service.status === "running"
|
||||||
? theme.colors.palette.green[500]
|
? theme.colors.palette.green[500]
|
||||||
: theme.colors.palette.red[500],
|
: theme.colors.foregroundMuted,
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
<Text style={styles.serviceName} numberOfLines={1}>
|
<Text
|
||||||
|
style={[
|
||||||
|
styles.serviceName,
|
||||||
|
{ color: service.status === "running" ? theme.colors.foreground : theme.colors.foregroundMuted },
|
||||||
|
]}
|
||||||
|
numberOfLines={1}
|
||||||
|
>
|
||||||
{service.serviceName}
|
{service.serviceName}
|
||||||
</Text>
|
</Text>
|
||||||
{service.url ? (
|
{service.url ? (
|
||||||
<ExternalLink size={12} color={theme.colors.foregroundMuted} />
|
<Text style={styles.serviceUrl} numberOfLines={1}>
|
||||||
|
{service.url.replace(/^https?:\/\//, "")}
|
||||||
|
</Text>
|
||||||
|
) : null}
|
||||||
|
{service.url ? (
|
||||||
|
<ExternalLink size={11} color={theme.colors.foregroundMuted} />
|
||||||
) : null}
|
) : null}
|
||||||
</Pressable>
|
</Pressable>
|
||||||
))}
|
))}
|
||||||
@@ -482,8 +493,12 @@ const styles = StyleSheet.create((theme) => ({
|
|||||||
flexShrink: 0,
|
flexShrink: 0,
|
||||||
},
|
},
|
||||||
serviceName: {
|
serviceName: {
|
||||||
color: theme.colors.foreground,
|
|
||||||
fontSize: theme.fontSize.sm,
|
fontSize: theme.fontSize.sm,
|
||||||
|
flexShrink: 0,
|
||||||
|
},
|
||||||
|
serviceUrl: {
|
||||||
|
color: theme.colors.foregroundMuted,
|
||||||
|
fontSize: theme.fontSize.xs,
|
||||||
flex: 1,
|
flex: 1,
|
||||||
minWidth: 0,
|
minWidth: 0,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import {
|
|||||||
EllipsisVertical,
|
EllipsisVertical,
|
||||||
PanelRight,
|
PanelRight,
|
||||||
RotateCw,
|
RotateCw,
|
||||||
|
Settings,
|
||||||
SquarePen,
|
SquarePen,
|
||||||
SquareTerminal,
|
SquareTerminal,
|
||||||
X,
|
X,
|
||||||
@@ -1363,6 +1364,23 @@ function WorkspaceScreenContent({ serverId, workspaceId }: WorkspaceScreenProps)
|
|||||||
}
|
}
|
||||||
}, [currentBranchName, toast]);
|
}, [currentBranchName, toast]);
|
||||||
|
|
||||||
|
const handleOpenSetupTab = useCallback(() => {
|
||||||
|
if (!persistenceKey) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const target = normalizeWorkspaceTabTarget({
|
||||||
|
kind: "setup",
|
||||||
|
workspaceId: normalizedWorkspaceId,
|
||||||
|
});
|
||||||
|
if (!target) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const tabId = openWorkspaceTab(persistenceKey, target);
|
||||||
|
if (tabId) {
|
||||||
|
focusWorkspaceTab(persistenceKey, tabId);
|
||||||
|
}
|
||||||
|
}, [focusWorkspaceTab, normalizedWorkspaceId, openWorkspaceTab, persistenceKey]);
|
||||||
|
|
||||||
const handleBulkCloseTabs = useCallback(
|
const handleBulkCloseTabs = useCallback(
|
||||||
async (input: { tabsToClose: WorkspaceTabDescriptor[]; title: string; logLabel: string }) => {
|
async (input: { tabsToClose: WorkspaceTabDescriptor[]; title: string; logLabel: string }) => {
|
||||||
const { tabsToClose, title, logLabel } = input;
|
const { tabsToClose, title, logLabel } = input;
|
||||||
@@ -2021,6 +2039,14 @@ function WorkspaceScreenContent({ serverId, workspaceId }: WorkspaceScreenProps)
|
|||||||
Copy branch name
|
Copy branch name
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
) : null}
|
) : null}
|
||||||
|
<DropdownMenuSeparator />
|
||||||
|
<DropdownMenuItem
|
||||||
|
testID="workspace-header-show-setup"
|
||||||
|
leading={<Settings size={16} color={theme.colors.foregroundMuted} />}
|
||||||
|
onSelect={handleOpenSetupTab}
|
||||||
|
>
|
||||||
|
Show setup
|
||||||
|
</DropdownMenuItem>
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
@@ -146,6 +146,7 @@ describe("createPaseoWorktreeInBackground", () => {
|
|||||||
{
|
{
|
||||||
requestCwd: repoDir,
|
requestCwd: repoDir,
|
||||||
repoRoot: repoDir,
|
repoRoot: repoDir,
|
||||||
|
workspaceId: 42,
|
||||||
worktree: {
|
worktree: {
|
||||||
branchName: "feature-no-setup",
|
branchName: "feature-no-setup",
|
||||||
worktreePath,
|
worktreePath,
|
||||||
@@ -160,7 +161,7 @@ describe("createPaseoWorktreeInBackground", () => {
|
|||||||
);
|
);
|
||||||
expect(progressMessages).toHaveLength(2);
|
expect(progressMessages).toHaveLength(2);
|
||||||
expect(progressMessages[0]?.payload).toMatchObject({
|
expect(progressMessages[0]?.payload).toMatchObject({
|
||||||
workspaceId: worktreePath,
|
workspaceId: "42",
|
||||||
status: "running",
|
status: "running",
|
||||||
error: null,
|
error: null,
|
||||||
detail: {
|
detail: {
|
||||||
@@ -172,7 +173,7 @@ describe("createPaseoWorktreeInBackground", () => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
expect(progressMessages[1]?.payload).toMatchObject({
|
expect(progressMessages[1]?.payload).toMatchObject({
|
||||||
workspaceId: worktreePath,
|
workspaceId: "42",
|
||||||
status: "completed",
|
status: "completed",
|
||||||
error: null,
|
error: null,
|
||||||
detail: {
|
detail: {
|
||||||
@@ -183,7 +184,7 @@ describe("createPaseoWorktreeInBackground", () => {
|
|||||||
commands: [],
|
commands: [],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
expect(snapshots.get(worktreePath)).toMatchObject({
|
expect(snapshots.get("42")).toMatchObject({
|
||||||
status: "completed",
|
status: "completed",
|
||||||
error: null,
|
error: null,
|
||||||
detail: {
|
detail: {
|
||||||
@@ -274,7 +275,7 @@ describe("createPaseoWorktreeInBackground", () => {
|
|||||||
expect(progressMessages[1]?.payload.status).toBe("failed");
|
expect(progressMessages[1]?.payload.status).toBe("failed");
|
||||||
expect(progressMessages[1]?.payload.error).toContain("Failed to parse paseo.json");
|
expect(progressMessages[1]?.payload.error).toContain("Failed to parse paseo.json");
|
||||||
expect(progressMessages[1]?.payload.detail.commands).toEqual([]);
|
expect(progressMessages[1]?.payload.detail.commands).toEqual([]);
|
||||||
expect(snapshots.get(worktreePath)).toMatchObject({
|
expect(snapshots.get("101")).toMatchObject({
|
||||||
status: "failed",
|
status: "failed",
|
||||||
error: expect.stringContaining("Failed to parse paseo.json"),
|
error: expect.stringContaining("Failed to parse paseo.json"),
|
||||||
});
|
});
|
||||||
@@ -323,6 +324,7 @@ describe("createPaseoWorktreeInBackground", () => {
|
|||||||
{
|
{
|
||||||
requestCwd: repoDir,
|
requestCwd: repoDir,
|
||||||
repoRoot: repoDir,
|
repoRoot: repoDir,
|
||||||
|
workspaceId: 43,
|
||||||
worktree: {
|
worktree: {
|
||||||
branchName: "feature-running-setup",
|
branchName: "feature-running-setup",
|
||||||
worktreePath,
|
worktreePath,
|
||||||
@@ -337,7 +339,7 @@ describe("createPaseoWorktreeInBackground", () => {
|
|||||||
);
|
);
|
||||||
expect(progressMessages.length).toBeGreaterThan(1);
|
expect(progressMessages.length).toBeGreaterThan(1);
|
||||||
expect(progressMessages[0]?.payload).toMatchObject({
|
expect(progressMessages[0]?.payload).toMatchObject({
|
||||||
workspaceId: worktreePath,
|
workspaceId: "43",
|
||||||
status: "running",
|
status: "running",
|
||||||
error: null,
|
error: null,
|
||||||
detail: {
|
detail: {
|
||||||
@@ -368,7 +370,7 @@ describe("createPaseoWorktreeInBackground", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
expect(progressMessages.at(-1)?.payload).toMatchObject({
|
expect(progressMessages.at(-1)?.payload).toMatchObject({
|
||||||
workspaceId: worktreePath,
|
workspaceId: "43",
|
||||||
status: "completed",
|
status: "completed",
|
||||||
error: null,
|
error: null,
|
||||||
detail: {
|
detail: {
|
||||||
@@ -385,7 +387,7 @@ describe("createPaseoWorktreeInBackground", () => {
|
|||||||
status: "completed",
|
status: "completed",
|
||||||
exitCode: 0,
|
exitCode: 0,
|
||||||
});
|
});
|
||||||
expect(snapshots.get(worktreePath)).toMatchObject({
|
expect(snapshots.get("43")).toMatchObject({
|
||||||
status: "completed",
|
status: "completed",
|
||||||
error: null,
|
error: null,
|
||||||
});
|
});
|
||||||
@@ -439,6 +441,7 @@ describe("createPaseoWorktreeInBackground", () => {
|
|||||||
{
|
{
|
||||||
requestCwd: repoDir,
|
requestCwd: repoDir,
|
||||||
repoRoot: repoDir,
|
repoRoot: repoDir,
|
||||||
|
workspaceId: 44,
|
||||||
worktree: {
|
worktree: {
|
||||||
branchName: "reused-worktree",
|
branchName: "reused-worktree",
|
||||||
worktreePath: existingWorktree.worktreePath,
|
worktreePath: existingWorktree.worktreePath,
|
||||||
@@ -453,12 +456,12 @@ describe("createPaseoWorktreeInBackground", () => {
|
|||||||
);
|
);
|
||||||
expect(progressMessages).toHaveLength(2);
|
expect(progressMessages).toHaveLength(2);
|
||||||
expect(progressMessages[0]?.payload).toMatchObject({
|
expect(progressMessages[0]?.payload).toMatchObject({
|
||||||
workspaceId: existingWorktree.worktreePath,
|
workspaceId: "44",
|
||||||
status: "running",
|
status: "running",
|
||||||
error: null,
|
error: null,
|
||||||
});
|
});
|
||||||
expect(progressMessages[1]?.payload).toMatchObject({
|
expect(progressMessages[1]?.payload).toMatchObject({
|
||||||
workspaceId: existingWorktree.worktreePath,
|
workspaceId: "44",
|
||||||
status: "completed",
|
status: "completed",
|
||||||
error: null,
|
error: null,
|
||||||
detail: {
|
detail: {
|
||||||
@@ -487,7 +490,7 @@ describe("createPaseoWorktreeInBackground", () => {
|
|||||||
readFileSync(path.join(existingWorktree.worktreePath, "README.md"), "utf8"),
|
readFileSync(path.join(existingWorktree.worktreePath, "README.md"), "utf8"),
|
||||||
).toContain("hello");
|
).toContain("hello");
|
||||||
expect(() => readFileSync(path.join(existingWorktree.worktreePath, "setup-ran.txt"), "utf8")).toThrow();
|
expect(() => readFileSync(path.join(existingWorktree.worktreePath, "setup-ran.txt"), "utf8")).toThrow();
|
||||||
expect(snapshots.get(existingWorktree.worktreePath)).toMatchObject({
|
expect(snapshots.get("44")).toMatchObject({
|
||||||
status: "completed",
|
status: "completed",
|
||||||
error: null,
|
error: null,
|
||||||
});
|
});
|
||||||
@@ -544,6 +547,7 @@ describe("createPaseoWorktreeInBackground", () => {
|
|||||||
{
|
{
|
||||||
requestCwd: repoDir,
|
requestCwd: repoDir,
|
||||||
repoRoot: repoDir,
|
repoRoot: repoDir,
|
||||||
|
workspaceId: 45,
|
||||||
worktree: {
|
worktree: {
|
||||||
branchName: "feature-service-failure",
|
branchName: "feature-service-failure",
|
||||||
worktreePath,
|
worktreePath,
|
||||||
@@ -572,7 +576,7 @@ describe("createPaseoWorktreeInBackground", () => {
|
|||||||
}),
|
}),
|
||||||
"Failed to spawn worktree services after workspace setup completed",
|
"Failed to spawn worktree services after workspace setup completed",
|
||||||
);
|
);
|
||||||
expect(snapshots.get(worktreePath)).toMatchObject({
|
expect(snapshots.get("45")).toMatchObject({
|
||||||
status: "completed",
|
status: "completed",
|
||||||
error: null,
|
error: null,
|
||||||
});
|
});
|
||||||
@@ -625,6 +629,7 @@ describe("createPaseoWorktreeInBackground", () => {
|
|||||||
{
|
{
|
||||||
requestCwd: repoDir,
|
requestCwd: repoDir,
|
||||||
repoRoot: repoDir,
|
repoRoot: repoDir,
|
||||||
|
workspaceId: 46,
|
||||||
worktree: {
|
worktree: {
|
||||||
branchName: "feature-socket-mode",
|
branchName: "feature-socket-mode",
|
||||||
worktreePath,
|
worktreePath,
|
||||||
@@ -646,7 +651,7 @@ describe("createPaseoWorktreeInBackground", () => {
|
|||||||
expect(terminalManager.terminals[0]?.env?.PORT).toEqual(expect.any(String));
|
expect(terminalManager.terminals[0]?.env?.PORT).toEqual(expect.any(String));
|
||||||
expect(terminalManager.terminals[0]?.env?.PASEO_SERVICE_URL).toBeUndefined();
|
expect(terminalManager.terminals[0]?.env?.PASEO_SERVICE_URL).toBeUndefined();
|
||||||
expect(terminalManager.terminals[0]?.sent).toEqual(["npm run dev\r"]);
|
expect(terminalManager.terminals[0]?.sent).toEqual(["npm run dev\r"]);
|
||||||
expect(snapshots.get(worktreePath)).toMatchObject({
|
expect(snapshots.get("46")).toMatchObject({
|
||||||
status: "completed",
|
status: "completed",
|
||||||
error: null,
|
error: null,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -644,7 +644,7 @@ export async function handleWorkspaceSetupStatusRequest(
|
|||||||
dependencies: HandleWorkspaceSetupStatusRequestDependencies,
|
dependencies: HandleWorkspaceSetupStatusRequestDependencies,
|
||||||
request: Extract<SessionInboundMessage, { type: "workspace_setup_status_request" }>,
|
request: Extract<SessionInboundMessage, { type: "workspace_setup_status_request" }>,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const workspaceId = normalizePersistedWorkspaceId(request.workspaceId);
|
const workspaceId = request.workspaceId;
|
||||||
dependencies.emit({
|
dependencies.emit({
|
||||||
type: "workspace_setup_status_response",
|
type: "workspace_setup_status_response",
|
||||||
payload: {
|
payload: {
|
||||||
@@ -669,7 +669,7 @@ export async function createPaseoWorktreeInBackground(
|
|||||||
let setupResults: WorktreeSetupCommandResult[] = [];
|
let setupResults: WorktreeSetupCommandResult[] = [];
|
||||||
let setupStarted = false;
|
let setupStarted = false;
|
||||||
const progressAccumulator = createWorktreeSetupProgressAccumulator();
|
const progressAccumulator = createWorktreeSetupProgressAccumulator();
|
||||||
const workspaceId = normalizePersistedWorkspaceId(worktree.worktreePath);
|
const workspaceId = String(options.workspaceId);
|
||||||
|
|
||||||
const emitSetupProgress = (status: "running" | "completed" | "failed", error: string | null) => {
|
const emitSetupProgress = (status: "running" | "completed" | "failed", error: string | null) => {
|
||||||
const snapshot: WorkspaceSetupSnapshot = {
|
const snapshot: WorkspaceSetupSnapshot = {
|
||||||
|
|||||||
Reference in New Issue
Block a user