Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions apps/mobile/src/features/threads/NewTaskCheckoutScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@ export function NewTaskCheckoutScreen() {
flow.selectPreparedCheckout({
branch: result.value.branch,
worktreePath: result.value.worktreePath,
changeRequest: result.value.changeRequest,
});
flow.setBranchQuery("");
navigation.goBack();
Expand Down
3 changes: 3 additions & 0 deletions apps/mobile/src/features/threads/NewTaskDraftScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -879,6 +879,9 @@ export function NewTaskDraftScreen(props: {
envMode: workspaceMode,
branch: selectedBranchName,
worktreePath: workspaceMode === "worktree" ? null : selectedWorktreePath,
...(draft.workspaceSelection?.changeRequest
? { changeRequest: draft.workspaceSelection.changeRequest }
: {}),
startFromOrigin,
runtimeMode,
interactionMode,
Expand Down
41 changes: 37 additions & 4 deletions apps/mobile/src/features/threads/new-task-flow-provider.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";

import type {
ChangeRequestAssociation,
EnvironmentId,
ModelSelection,
ProviderInteractionMode,
Expand Down Expand Up @@ -147,6 +148,7 @@ type NewTaskFlowContextValue = {
readonly selectPreparedCheckout: (input: {
readonly branch: string;
readonly worktreePath: string | null;
readonly changeRequest: ChangeRequestAssociation;
}) => void;
readonly setStartFromOrigin: (value: boolean) => void;
readonly beginEditingPendingTask: (messageId: string) => boolean;
Expand Down Expand Up @@ -355,6 +357,7 @@ export function NewTaskFlowProvider(props: React.PropsWithChildren) {
const selectedBranchName = selectedProjectDraft.workspaceSelection?.branch ?? null;
const selectedWorktreePath = selectedProjectDraft.workspaceSelection?.worktreePath ?? null;
const startFromOrigin = selectedProjectDraft.workspaceSelection?.startFromOrigin ?? false;
const selectedChangeRequest = selectedProjectDraft.workspaceSelection?.changeRequest;
const runtimeMode = selectedProjectDraft.runtimeMode ?? DEFAULT_RUNTIME_MODE;
const interactionMode = selectedProjectDraft.interactionMode ?? DEFAULT_PROVIDER_INTERACTION_MODE;

Expand Down Expand Up @@ -554,10 +557,17 @@ export function NewTaskFlowProvider(props: React.PropsWithChildren) {
branch: selectedBranchName,
worktreePath: selectedWorktreePath,
startFromOrigin,
...(selectedChangeRequest ? { changeRequest: selectedChangeRequest } : {}),
},
});
},
[selectedBranchName, selectedProjectDraftKey, selectedWorktreePath, startFromOrigin],
[
selectedBranchName,
selectedChangeRequest,
selectedProjectDraftKey,
selectedWorktreePath,
startFromOrigin,
],
);

const selectBranch = useCallback(
Expand All @@ -578,7 +588,11 @@ export function NewTaskFlowProvider(props: React.PropsWithChildren) {
);

const selectPreparedCheckout = useCallback(
(input: { readonly branch: string; readonly worktreePath: string | null }) => {
(input: {
readonly branch: string;
readonly worktreePath: string | null;
readonly changeRequest: ChangeRequestAssociation;
}) => {
if (!selectedProjectDraftKey) return;
updateComposerDraftSettings(selectedProjectDraftKey, {
workspaceSelection: {
Expand All @@ -588,10 +602,16 @@ export function NewTaskFlowProvider(props: React.PropsWithChildren) {
branch: input.branch,
worktreePath: input.worktreePath,
startFromOrigin: false,
...(selectedEnvironmentServerConfig?.settings.enableDurableChangeRequestStatus
? { changeRequest: input.changeRequest }
: {}),
},
});
},
[selectedProjectDraftKey],
[
selectedEnvironmentServerConfig?.settings.enableDurableChangeRequestStatus,
selectedProjectDraftKey,
],
);

const setStartFromOrigin = useCallback(
Expand All @@ -605,10 +625,17 @@ export function NewTaskFlowProvider(props: React.PropsWithChildren) {
branch: selectedBranchName,
worktreePath: selectedWorktreePath,
startFromOrigin: value,
...(selectedChangeRequest && !value ? { changeRequest: selectedChangeRequest } : {}),
},
});
},
[selectedBranchName, selectedProjectDraftKey, selectedWorktreePath, workspaceMode],
[
selectedBranchName,
selectedChangeRequest,
selectedProjectDraftKey,
selectedWorktreePath,
workspaceMode,
],
);

const refreshBranches = branchState.refresh;
Expand Down Expand Up @@ -669,6 +696,9 @@ export function NewTaskFlowProvider(props: React.PropsWithChildren) {
branch: message.creation.branch,
worktreePath: message.creation.worktreePath,
startFromOrigin: message.creation.startFromOrigin ?? false,
...(message.creation.changeRequest
? { changeRequest: message.creation.changeRequest }
: {}),
},
});
}
Expand Down Expand Up @@ -724,6 +754,9 @@ export function NewTaskFlowProvider(props: React.PropsWithChildren) {
branch: workspaceSelection?.branch ?? null,
worktreePath: mode === "worktree" ? null : (workspaceSelection?.worktreePath ?? null),
...(workspaceSelection?.startFromOrigin ? { startFromOrigin: true } : {}),
...(workspaceSelection?.changeRequest
? { changeRequest: workspaceSelection.changeRequest }
: {}),
},
createdAt: metadata.createdAt,
};
Expand Down
3 changes: 3 additions & 0 deletions apps/mobile/src/features/threads/use-project-actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { EnvironmentProject } from "@t3tools/client-runtime/state/shell";
import { mapAtomCommandResult } from "@t3tools/client-runtime/state/runtime";
import {
ThreadId,
type ChangeRequestAssociation,
type ModelSelection,
type ProviderInteractionMode,
type RuntimeMode,
Expand Down Expand Up @@ -32,6 +33,7 @@ export function useCreateProjectThread() {
readonly envMode: "local" | "worktree";
readonly branch: string | null;
readonly worktreePath: string | null;
readonly changeRequest?: ChangeRequestAssociation;
readonly startFromOrigin?: boolean;
readonly runtimeMode: RuntimeMode;
readonly interactionMode: ProviderInteractionMode;
Expand Down Expand Up @@ -73,6 +75,7 @@ export function useCreateProjectThread() {
workspaceMode: input.envMode,
branch: input.branch,
worktreePath: input.worktreePath,
...(input.changeRequest ? { changeRequest: input.changeRequest } : {}),
startFromOrigin: input.startFromOrigin ?? false,
worktreeBranchName: buildTemporaryWorktreeBranchName(randomHex),
}),
Expand Down
3 changes: 3 additions & 0 deletions apps/mobile/src/lib/projectThreadStartTurn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
MessageId,
ThreadId,
type ModelSelection,
type ChangeRequestAssociation,
type ProjectId,
type ProviderInteractionMode,
type RuntimeMode,
Expand Down Expand Up @@ -35,6 +36,7 @@ export interface ProjectThreadStartTurnSpec {
readonly workspaceMode: "local" | "worktree";
readonly branch: string | null;
readonly worktreePath: string | null;
readonly changeRequest?: ChangeRequestAssociation;
readonly startFromOrigin: boolean;
/** Generated temp branch for worktree mode; unused for local mode. */
readonly worktreeBranchName: string;
Expand Down Expand Up @@ -70,6 +72,7 @@ export function buildProjectThreadStartTurnInput(spec: ProjectThreadStartTurnSpe
interactionMode: spec.interactionMode,
branch: spec.branch,
worktreePath: isWorktree ? null : spec.worktreePath,
...(spec.changeRequest ? { changeRequest: spec.changeRequest } : {}),
createdAt: spec.createdAt,
},
...(isWorktree
Expand Down
4 changes: 4 additions & 0 deletions apps/mobile/src/state/thread-outbox-model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { isTransportConnectionErrorMessage } from "@t3tools/client-runtime/error
import type { EnvironmentShellStatus } from "@t3tools/client-runtime/state/shell";
import {
CommandId,
ChangeRequestAssociation,
EnvironmentId,
IsoDateTime,
MessageId,
Expand All @@ -11,6 +12,7 @@ import {
RuntimeMode,
ThreadId,
type ModelSelection as ModelSelectionType,
type ChangeRequestAssociation as ChangeRequestAssociationType,
type ProjectId as ProjectIdType,
type ProviderInteractionMode as ProviderInteractionModeType,
type RuntimeMode as RuntimeModeType,
Expand All @@ -34,6 +36,7 @@ const QueuedThreadCreationSchema = Schema.Struct({
branch: Schema.NullOr(Schema.String),
worktreePath: Schema.NullOr(Schema.String),
startFromOrigin: Schema.optional(Schema.Boolean),
changeRequest: Schema.optional(ChangeRequestAssociation),
});

export const QueuedThreadMessageSchema = Schema.Struct({
Expand Down Expand Up @@ -64,6 +67,7 @@ export interface QueuedThreadCreation {
readonly branch: string | null;
readonly worktreePath: string | null;
readonly startFromOrigin?: boolean;
readonly changeRequest?: ChangeRequestAssociationType;
}

export interface QueuedThreadMessage {
Expand Down
8 changes: 6 additions & 2 deletions apps/mobile/src/state/thread-pr-presentation.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import type { VcsStatusResult } from "@t3tools/contracts";
import type {
SourceControlProviderInfo,
SourceControlProviderKind,
VcsStatusResult,
} from "@t3tools/contracts";
import { resolveChangeRequestPresentation } from "@t3tools/shared/sourceControl";

export type ThreadPr = NonNullable<VcsStatusResult["pr"]>;
Expand All @@ -22,7 +26,7 @@ const PR_STATE_TEXT_CLASS: Record<ThreadPr["state"], string> = {

export function presentThreadPr(
pr: ThreadPr,
provider: VcsStatusResult["sourceControlProvider"] | null | undefined,
provider: SourceControlProviderInfo | SourceControlProviderKind | null | undefined,
): ThreadPrPresentation {
const presentation = resolveChangeRequestPresentation(provider);
return {
Expand Down
4 changes: 4 additions & 0 deletions apps/mobile/src/state/use-composer-drafts.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { useAtomValue } from "@effect/atom-react";
import {
ChangeRequestAssociation,
ModelSelection as ModelSelectionSchema,
PROVIDER_SEND_TURN_MAX_ATTACHMENTS,
ProviderInteractionMode as ProviderInteractionModeSchema,
RuntimeMode as RuntimeModeSchema,
type EnvironmentId,
type ChangeRequestAssociation as ChangeRequestAssociationType,
type ModelSelection,
type ProviderInteractionMode,
type RuntimeMode,
Expand Down Expand Up @@ -58,6 +60,7 @@ export interface ComposerDraftWorkspaceSelection {
readonly branch: string | null;
readonly worktreePath: string | null;
readonly startFromOrigin?: boolean;
readonly changeRequest?: ChangeRequestAssociationType;
}

export type ComposerDraftSettingsUpdate = Pick<
Expand All @@ -70,6 +73,7 @@ const ComposerDraftWorkspaceSelectionSchema = Schema.Struct({
branch: Schema.NullOr(Schema.String),
worktreePath: Schema.NullOr(Schema.String),
startFromOrigin: Schema.optional(Schema.Boolean),
changeRequest: Schema.optional(ChangeRequestAssociation),
});

const ComposerDraftSchema = Schema.Struct({
Expand Down
1 change: 1 addition & 0 deletions apps/mobile/src/state/use-thread-outbox-drain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,7 @@ export function useThreadOutboxDrain(): void {
workspaceMode: creation.workspaceMode,
branch: creation.branch,
worktreePath: creation.worktreePath,
...(creation.changeRequest ? { changeRequest: creation.changeRequest } : {}),
startFromOrigin: creation.startFromOrigin ?? false,
worktreeBranchName: buildTemporaryWorktreeBranchName(randomHex),
}),
Expand Down
47 changes: 36 additions & 11 deletions apps/mobile/src/state/use-thread-pr.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import type { EnvironmentThreadShell } from "@t3tools/client-runtime/state/shell";
import {
resolveThreadChangeRequestProviderKind,
resolveThreadChangeRequestStatus,
shouldQueryThreadVcsStatus,
} from "@t3tools/shared/sourceControl";

import { useEnvironmentServerConfig } from "./entities";
import { useEnvironmentQuery } from "./query";
import { presentThreadPr, type ThreadPrPresentation } from "./thread-pr-presentation";
import { vcsEnvironment } from "./vcs";
Expand All @@ -11,31 +17,50 @@ export {
} from "./thread-pr-presentation";

/**
* Live PR status for a thread's branch. Subscriptions are deduplicated per
* (environmentId, cwd) by the atom family, so many rows on the same worktree
* or project root share one stream — and virtualization means only visible
* rows subscribe at all.
* Live PR status for a thread's branch. Known PR identities remain distinct;
* otherwise subscriptions are deduplicated per (environmentId, cwd). List
* virtualization means only visible rows subscribe.
*/
export function useThreadPr(
thread: EnvironmentThreadShell,
projectCwd: string | null,
): ThreadPrPresentation | null {
const cwd = thread.worktreePath ?? projectCwd;
const durableChangeRequestStatusEnabled =
useEnvironmentServerConfig(thread.environmentId)?.settings.enableDurableChangeRequestStatus ??
false;
const changeRequest = durableChangeRequestStatusEnabled ? thread.changeRequest : undefined;
const gitStatus = useEnvironmentQuery(
thread.branch !== null && cwd !== null
cwd !== null &&
shouldQueryThreadVcsStatus({
threadBranch: thread.branch,
...(changeRequest ? { changeRequest } : {}),
durableChangeRequestStatusEnabled,
})
? vcsEnvironment.status({
environmentId: thread.environmentId,
input: { cwd },
input: {
cwd,
...(changeRequest ? { changeRequest } : {}),
},
})
: null,
);

const status = gitStatus.data;
if (status === null || thread.branch === null || status.refName !== thread.branch) {
const pr = resolveThreadChangeRequestStatus({
threadBranch: thread.branch,
...(changeRequest ? { changeRequest } : {}),
gitStatus: status,
durableChangeRequestStatusEnabled,
});
if (!pr) {
return null;
}
if (!status.pr) {
return null;
}
return presentThreadPr(status.pr, status.sourceControlProvider);
const providerKind = resolveThreadChangeRequestProviderKind({
...(changeRequest ? { changeRequest } : {}),
gitStatus: status,
durableChangeRequestStatusEnabled,
});
return presentThreadPr(pr, providerKind);
}
Loading
Loading