Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
78 commits
Select commit Hold shift + click to select a range
f2edf45
chore: add Conductor setup and run scripts (#1)
connorch Aug 6, 2026
4dbe93a
Merge branch 'main' of https://github.com/connorch/t3code
connorch Aug 6, 2026
b59515a
add install desktop local script
connorch Aug 6, 2026
143684b
feat: add Connor Stack sidebar mode with worktree grouping (#3)
connorch Aug 6, 2026
3e3af0e
fix(server): avoid worktree force-remove timeouts on large trees (#4)
connorch Aug 6, 2026
af599db
feat: show gitignored files in workspace search and file browser (#5)
connorch Aug 6, 2026
96b2c9a
feat: add GitHub PR automerge support (#6)
connorch Aug 6, 2026
d27d7b3
fix(server): sweep stale worktree trash dirs via rm -rf subprocess (#7)
connorch Aug 7, 2026
1bff525
refactor(web): unify Connor local-checkout threads into a Current Che…
connorch Aug 7, 2026
a1fe9e3
feat(web): treat merged PRs as viewable, add merged-state tint
connorch Aug 7, 2026
7fc6387
feat(server): namespace generated worktree branches under GitHub logi…
connorch Aug 7, 2026
f4aeeda
fix(web): add success-strong token for open-PR button tint
connorch Aug 7, 2026
7d48f83
fix(web): relabel merged-PR quick action as "Merged" (#10)
connorch Aug 7, 2026
390fdd0
feat(web): add chat.newInWorkspace shortcut for current workspace (#11)
connorch Aug 7, 2026
a2232af
Merge branch 'main' of https://github.com/pingdotgg/t3code
connorch Aug 7, 2026
bce6904
feat(web): show remote environment glyph for non-primary worktree gro…
connorch Aug 8, 2026
98698d8
Merge branch 'main' of https://github.com/connorch/t3code
connorch Aug 8, 2026
21c0604
feat(scripts): install desktop app on Linux via AppImage
Aug 8, 2026
3317082
feat: archive and restore whole worktrees with their threads (#13)
connorch Aug 12, 2026
9aa4363
fix(web): stack row action buttons over status indicators on hover (#14)
connorch Aug 12, 2026
2ac46eb
fix(web): let stacked sidebar action buttons receive clicks
connorch Aug 12, 2026
e809793
fix(web): mark threads visited when viewing them, not just on completion
connorch Aug 12, 2026
05d27c7
fix(web): show empty-project tooltip instead of "No threads yet" row …
connorch Aug 12, 2026
762cc06
feat(web): let users drag-resize the composer prompt input (#16)
connorch Aug 12, 2026
ab65c66
feat(web): open right-panel surfaces from ghost tabs in the tab bar (…
connorch Aug 12, 2026
3a608fe
feat(web): add "Mark unread" action to worktree group menu (#18)
connorch Aug 12, 2026
2756206
feat(server): provision a git-ignored .context scratch dir in new wor…
connorch Aug 12, 2026
e065698
fix(web): show pointer cursor on right-panel action buttons (#20)
connorch Aug 12, 2026
6e52d6f
feat(web): open matching links in the integrated browser
connorch Aug 12, 2026
0f887ad
feat(web): let users drag-reorder right-panel tabs (#21)
connorch Aug 12, 2026
5026dfe
fix(web): keep composer content sizing when drag-resize settles (#22)
connorch Aug 12, 2026
0d8ed7c
Merge branch 'main' of https://github.com/connorch/t3code
connorch Aug 12, 2026
e19489c
feat(server,web): support disabling automerge, not just enabling it (…
connorch Aug 12, 2026
4e33863
Merge branch 'main' of https://github.com/connorch/t3code
connorch Aug 12, 2026
df26e15
feat(web): show worktree count badge on collapsed project rows (#24)
connorch Aug 12, 2026
c75c309
feat(web): archive thread and close right-panel tab on mod+w (#25)
connorch Aug 12, 2026
e2246b3
Merge remote-tracking branch 'upstream/main' into connorch/sync-upstr…
connorch Aug 12, 2026
9b535be
fix(web): show Connor worktree chevron only on hover (#26)
connorch Aug 13, 2026
ad6aedb
feat(web): launch right-panel surfaces from ghost tabs (#27)
connorch Aug 13, 2026
3913cd0
fix(web): reorder Browser tab after Pull request in right panel (#28)
connorch Aug 13, 2026
7f01422
feat(web): drag-to-reorder right-panel tabs (#29)
connorch Aug 13, 2026
1e86619
fix(web): flatten worktree row layout and inline branch on expand (#30)
connorch Aug 13, 2026
3170e1e
fix(web): let worktree titles use the idle hover-button space
connorch Aug 13, 2026
14105a3
fix(web): align worktree rows with project rows and mute their titles
connorch Aug 13, 2026
d1002ee
fix(web): indent expanded thread rows under the worktree title
connorch Aug 13, 2026
afe66d7
feat(web): animate worktree card expand and collapse
connorch Aug 13, 2026
1c8c4ab
feat(web): paste and copy thread transcripts into composer (#31)
connorch Aug 16, 2026
9ee6d10
Merge remote-tracking branch 'upstream/main' into connorch/pull-upstr…
connorch Aug 16, 2026
7a5d2bd
fix(web): drop unused useState import after favicon merge
connorch Aug 16, 2026
ef71c3a
feat(web): copy thread transcript from sidebar context menu
connorch Aug 16, 2026
560846e
feat(web): add thread completion sound notification (#32)
connorch Aug 18, 2026
695b26b
Merge remote-tracking branch 'upstream/main' into merge/upstream-2026…
connorch Aug 18, 2026
f8a3c7b
refactor(web): move open-links-in-browser pattern to Settings > Integ…
connorch Aug 18, 2026
8dcf761
fix(web): migrate fork native title tooltips to styled Tooltip
connorch Aug 18, 2026
50b545b
test: adapt fork tests to upstream signature and chime fixture key
connorch Aug 18, 2026
a84ea9a
Merge remote-tracking branch 'upstream/main' into merge/upstream-2026…
connorch Aug 21, 2026
7e4e46c
Merge remote-tracking branch 'upstream/main' into merge/upstream-2026…
connorch Sep 1, 2026
60c080e
Merge remote-tracking branch 'upstream/main' into merge/upstream-2026…
connorch Sep 1, 2026
a9bc582
chore: merge upstream v0.0.42 into fork
connorch Sep 16, 2026
e768d04
fix(web): reuse browser tabs for matching links (#34)
connorch Sep 17, 2026
6133edf
feat(web): group worktree threads into sidebar cards (#35)
connorch Sep 18, 2026
39c7595
feat(web): tint worktree card outlines per worktree (#36)
connorch Sep 21, 2026
78f51d1
chore: merge upstream a493946bb4 into fork
connorch Sep 23, 2026
918fa3b
chore: drop Conductor workspace scripts
connorch Sep 28, 2026
a04b100
revert(web): drop the drag-resizable composer prompt input
connorch Sep 28, 2026
1ff7b79
revert(web): stop stamping thread visits on view
connorch Sep 28, 2026
9a48607
revert: drop quick-action GitHub automerge in favor of upstream's PR …
connorch Sep 28, 2026
8d89c7d
revert(server): restore upstream force worktree removal
connorch Sep 28, 2026
9da1214
revert(web): drop the Connor Mode sidebar and the sidebar mode setting
connorch Sep 28, 2026
94b1a64
revert: drop whole-worktree archive and restore
connorch Sep 28, 2026
f2bda1f
Merge pull request #38 from connorch/connorch/catalog-fork-changes
connorch Sep 28, 2026
8a8ce0e
chore: merge upstream main (adfc9240ea) into fork
connorch Sep 29, 2026
f35dfc8
fix(server): release the fork's stale migration slot 54 before runnin…
connorch Sep 29, 2026
72b8534
feat(web): add a collapse-viewed default for diff files
connorch Sep 29, 2026
2dfff04
Merge pull request #39 from connorch/connorch/collapse-viewed-files-1
connorch Sep 29, 2026
9c38404
feat(web): scope browser empty-state servers to the current worktree
connorch Oct 1, 2026
eeb097e
Merge pull request #40 from connorch/connorch/worktree-scoped-local-s…
connorch Oct 1, 2026
ceb6b56
feat: add a setting to anchor sent messages at the top of the timeline
connorch Oct 2, 2026
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
3 changes: 2 additions & 1 deletion apps/desktop/src/settings/DesktopClientSettings.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ const clientSettings: ClientSettings = {
contextWindowMeterEnabled: false,
composerCollapseOnScroll: true,
dismissedProviderUpdateNotificationKeys: [],
diffFilesCollapsed: true,
diffFilesCollapsed: "viewed",
diffIgnoreWhitespace: true,
diffLayout: "stacked",
environmentIdentificationMode: "artwork",
Expand All @@ -51,6 +51,7 @@ const clientSettings: ClientSettings = {
fontSizeTerminal: 12,
fontSmoothing: true,
glassOpacity: 80,
openLinksInPreviewPattern: "",
onboardingCompletedAt: null,
panelAnimationDurationMs: 0,
planModeEnabled: false,
Expand Down
28 changes: 28 additions & 0 deletions apps/mobile/src/features/settings/SettingsThreadsRouteScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ export function SettingsThreadsRouteScreen() {
contentContainerStyle={{ paddingBottom: Math.max(insets.bottom, 18) + 18 }}
>
<AutoSettleSettingsRows />
<TimelineSettingsSection />
<LegacySettingsSection />
</ScrollView>
</SettingsScreen>
Expand Down Expand Up @@ -215,6 +216,33 @@ function AutoSettleSettingsRows() {
);
}

/**
* Device-local counterpart of web's "Sent message position" client setting.
*/
function TimelineSettingsSection() {
const savePreferences = useAtomSet(updateMobilePreferencesAtom);
const preferences = useAtomValue(mobilePreferencesAtom);
const anchorsAtTop =
AsyncResult.isSuccess(preferences) && preferences.value.chatTurnAnchor === "top";

return (
<View className="gap-3">
<SettingsSection title="Timeline">
<SettingsSwitchRow
icon="arrow.up"
label="Sent messages at top"
value={anchorsAtTop}
onValueChange={(value) => savePreferences({ chatTurnAnchor: value ? "top" : "bottom" })}
/>
</SettingsSection>
<Text className="px-2 text-sm text-foreground-muted">
Holds each message you send at the top of the screen so the reply reads downward. Off keeps
the conversation pinned to the latest message.
</Text>
</View>
);
}

/**
* Device-local legacy toggles. Mobile has no client-settings sync, so this is
* the counterpart of web's Settings → General → Legacy features backed by
Expand Down
9 changes: 9 additions & 0 deletions apps/mobile/src/features/threads/ThreadDetailScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import type {
CodexFeedbackSubmission,
EnvironmentThreadStatus,
} from "@t3tools/client-runtime/state/threads";
import { useAtomValue } from "@effect/atom-react";
import { AsyncResult } from "effect/unstable/reactivity";
import { useKeyboardChatComposerInset, useKeyboardScrollToEnd } from "@legendapp/list/keyboard";
import { resolveProviderSkillsForCwd } from "@t3tools/client-runtime/providerSkills";
import type { LegendListRef } from "@legendapp/list/react-native";
Expand Down Expand Up @@ -75,6 +77,7 @@ import { CHAT_CONTENT_MAX_WIDTH, type LayoutVariant } from "../../lib/layout";
import { IOS_NAV_BAR_HEIGHT } from "../../lib/layoutMetrics";
import { editPendingThreadMessage } from "../../state/edit-pending-thread-message";
import { deviceEnvironment } from "../../state/device";
import { mobilePreferencesAtom } from "../../state/preferences";
import { useEnvironmentQuery } from "../../state/query";
import { threadDevicePreviews } from "../devices/threadDevicePreviews";
import type { QueuedThreadMessage } from "../../state/thread-outbox-model";
Expand Down Expand Up @@ -337,6 +340,10 @@ export const ThreadDetailScreen = memo(function ThreadDetailScreen(props: Thread
);
const [anchorMessageId, setAnchorMessageId] = useState<MessageId | null>(null);
const [submittedMessageId, setSubmittedMessageId] = useState<MessageId | null>(null);
const preferencesResult = useAtomValue(mobilePreferencesAtom);
const chatTurnAnchor =
(AsyncResult.isSuccess(preferencesResult) ? preferencesResult.value.chatTurnAnchor : null) ??
"bottom";
const [endFollowEnabled, setEndFollowEnabled] = useState(true);
// Android keys the safe-area padding on keyboard visibility (#5988): the
// back gesture closes the keyboard while the editor stays focused, and a
Expand Down Expand Up @@ -790,12 +797,14 @@ export const ThreadDetailScreen = memo(function ThreadDetailScreen(props: Thread
hasStartedTurn: props.selectedThread.latestTurn !== null,
hasUserMessage,
queuedMessageCount: props.selectedThreadQueueCount,
turnAnchor: chatTurnAnchor,
}),
);
composerEditorRef.current?.blur();
return messageId;
}, [
anchorMessageId,
chatTurnAnchor,
clearUsageLimitsFor,
props.onSendMessage,
props.selectedThread.latestTurn,
Expand Down
31 changes: 31 additions & 0 deletions apps/mobile/src/features/threads/thread-feed-live-follow.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ describe("resolveThreadFeedSubmissionAnchor", () => {
hasStartedTurn: false,
hasUserMessage: false,
queuedMessageCount: 0,
turnAnchor: "bottom",
}),
).toBe("first-message");
});
Expand All @@ -103,6 +104,7 @@ describe("resolveThreadFeedSubmissionAnchor", () => {
hasStartedTurn: false,
hasUserMessage: false,
queuedMessageCount: 1,
turnAnchor: "bottom",
}),
).toBe("first-message");
});
Expand All @@ -115,6 +117,7 @@ describe("resolveThreadFeedSubmissionAnchor", () => {
hasStartedTurn: false,
hasUserMessage: false,
queuedMessageCount: 0,
turnAnchor: "bottom",
}),
).toBe("first-message");
});
Expand All @@ -127,6 +130,7 @@ describe("resolveThreadFeedSubmissionAnchor", () => {
hasStartedTurn: false,
hasUserMessage: true,
queuedMessageCount: 0,
turnAnchor: "bottom",
}),
).toBeNull();
});
Expand All @@ -139,9 +143,36 @@ describe("resolveThreadFeedSubmissionAnchor", () => {
hasStartedTurn: true,
hasUserMessage: false,
queuedMessageCount: 0,
turnAnchor: "bottom",
}),
).toBeNull();
});

it("re-anchors every direct send when messages anchor at the top", () => {
expect(
resolveThreadFeedSubmissionAnchor({
currentAnchorMessageId: "first-message",
submittedMessageId: "second-message",
hasStartedTurn: true,
hasUserMessage: true,
queuedMessageCount: 0,
turnAnchor: "top",
}),
).toBe("second-message");
});

it("keeps the current framing for a send that queues behind others", () => {
expect(
resolveThreadFeedSubmissionAnchor({
currentAnchorMessageId: "first-message",
submittedMessageId: "second-message",
hasStartedTurn: true,
hasUserMessage: true,
queuedMessageCount: 1,
turnAnchor: "top",
}),
).toBe("first-message");
});
});

describe("resolveThreadFeedLiveFollow", () => {
Expand Down
13 changes: 13 additions & 0 deletions apps/mobile/src/features/threads/thread-feed-live-follow.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import type { ChatTurnAnchor } from "@t3tools/contracts";

export type ThreadFeedLiveFollowEvent =
| { readonly type: "reset" }
| { readonly type: "user-scroll-begin" }
Expand Down Expand Up @@ -47,13 +49,24 @@ export function shouldFollowThreadWorkGroupAppend(input: {
);
}

/**
* Which sent message the feed should hold near the top while its turn streams.
* `bottom` anchors only the first message of a thread; `top` re-anchors every
* direct send. A message that queues behind others is not the live turn, so it
* leaves the current framing alone either way.
*/
export function resolveThreadFeedSubmissionAnchor<AnchorId>(input: {
readonly currentAnchorMessageId: AnchorId | null;
readonly submittedMessageId: AnchorId;
readonly hasStartedTurn: boolean;
readonly hasUserMessage: boolean;
readonly queuedMessageCount: number;
readonly turnAnchor: ChatTurnAnchor;
}): AnchorId | null {
if (input.turnAnchor === "top") {
return input.queuedMessageCount > 0 ? input.currentAnchorMessageId : input.submittedMessageId;
}

if (input.hasStartedTurn || input.hasUserMessage) {
return null;
}
Expand Down
12 changes: 11 additions & 1 deletion apps/mobile/src/persistence/mobile-preferences.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@ import * as Option from "effect/Option";
import * as Ref from "effect/Ref";
import * as Schema from "effect/Schema";
import * as Semaphore from "effect/Semaphore";
import type { ProviderInstanceId, SidebarProjectGroupingMode } from "@t3tools/contracts";
import type {
ChatTurnAnchor,
ProviderInstanceId,
SidebarProjectGroupingMode,
} from "@t3tools/contracts";
import type { ComposerEnterBehavior } from "../lib/composerEnterBehavior";
import { MOBILE_THEME_IDS, type MobileThemeId, type MobileThemeMode } from "../lib/mobileTheme";
import * as MobileDatabase from "./mobile-database";
Expand All @@ -30,6 +34,8 @@ export interface Preferences {
readonly collapsedProjectGroups?: readonly string[];
/** What the Return key does in the composer on a hardware keyboard. iOS only. */
readonly composerEnterBehavior?: ComposerEnterBehavior;
/** Device-local counterpart of the web client's `chatTurnAnchor` setting. */
readonly chatTurnAnchor?: ChatTurnAnchor;
/** @deprecated Kept temporarily so older OTA bundles retain the selected mode. */
readonly projectGroupingEnabled?: boolean;
readonly projectGroupingMode?: SidebarProjectGroupingMode;
Expand Down Expand Up @@ -97,6 +103,7 @@ function sanitizePreferences(parsed: Preferences): Preferences {
connectOnboardingOptOutAccounts?: ReadonlyArray<string>;
collapsedProjectGroups?: readonly string[];
composerEnterBehavior?: ComposerEnterBehavior;
chatTurnAnchor?: ChatTurnAnchor;
projectGroupingEnabled?: boolean;
projectGroupingMode?: SidebarProjectGroupingMode;
planModeEnabled?: boolean;
Expand Down Expand Up @@ -157,6 +164,9 @@ function sanitizePreferences(parsed: Preferences): Preferences {
if (parsed.composerEnterBehavior === "send" || parsed.composerEnterBehavior === "newline") {
preferences.composerEnterBehavior = parsed.composerEnterBehavior;
}
if (parsed.chatTurnAnchor === "bottom" || parsed.chatTurnAnchor === "top") {
preferences.chatTurnAnchor = parsed.chatTurnAnchor;
}
if (typeof parsed.projectGroupingEnabled === "boolean") {
preferences.projectGroupingEnabled = parsed.projectGroupingEnabled;
}
Expand Down
13 changes: 13 additions & 0 deletions apps/server/integration/OrchestrationEngineHarness.integration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,8 @@ import * as WorkspacePaths from "../src/workspace/WorkspacePaths.ts";
import * as VcsDriverRegistry from "../src/vcs/VcsDriverRegistry.ts";
import { VcsStatusBroadcaster } from "../src/vcs/VcsStatusBroadcaster.ts";
import { GitWorkflowService } from "../src/git/GitWorkflowService.ts";
import * as GitHubCli from "../src/sourceControl/GitHubCli.ts";
import { ChildProcessSpawner } from "effect/unstable/process";
import * as VcsProcess from "../src/vcs/VcsProcess.ts";
import * as AgentAwarenessRelay from "../src/relay/AgentAwarenessRelay.ts";
import * as PullRequestService from "../src/pullRequest/PullRequestService.ts";
Expand Down Expand Up @@ -335,6 +337,16 @@ export const makeOrchestrationIntegrationHarness = (
generateBranchName: () => Effect.succeed({ branch: "update" }),
generateThreadTitle: () => Effect.succeed({ title: "New thread" }),
} as unknown as TextGeneration["Service"]);
const gitHubCliLayer = Layer.mock(GitHubCli.GitHubCli)({
execute: () =>
Effect.succeed({
exitCode: ChildProcessSpawner.ExitCode(1),
stdout: "",
stderr: "",
stdoutTruncated: false,
stderrTruncated: false,
}),
} satisfies Partial<GitHubCli.GitHubCli["Service"]>);
const providerCommandReactorLayer = ProviderCommandReactorLive.pipe(
Layer.provide(
Layer.mock(ProviderAuthService)({
Expand All @@ -345,6 +357,7 @@ export const makeOrchestrationIntegrationHarness = (
Layer.provideMerge(runtimeServicesLayer),
Layer.provideMerge(gitWorkflowLayer),
Layer.provideMerge(textGenerationLayer),
Layer.provideMerge(gitHubCliLayer),
Layer.provideMerge(serverSettingsLayer),
);
const checkpointReactorLayer = CheckpointReactorLive.pipe(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ import { ServerSettingsService } from "../../serverSettings.ts";
import { ServerActivation } from "../../serverActivation.ts";
import { VcsStatusBroadcaster } from "../../vcs/VcsStatusBroadcaster.ts";
import * as GitWorkflowService from "../../git/GitWorkflowService.ts";
import * as GitHubCli from "../../sourceControl/GitHubCli.ts";
import { ChildProcessSpawner } from "effect/unstable/process";

const asProjectId = (value: string): ProjectId => ProjectId.make(value);
const asApprovalRequestId = (value: string): ApprovalRequestId => ApprovalRequestId.make(value);
Expand Down Expand Up @@ -347,6 +349,21 @@ describe("ProviderCommandReactor", () => {
}),
),
);
const executeGitHubCli = vi.fn<GitHubCli.GitHubCli["Service"]["execute"]>((_) =>
Effect.succeed({
exitCode: ChildProcessSpawner.ExitCode(0),
stdout: JSON.stringify({
hosts: {
"github.com": [
{ state: "success", active: true, host: "github.com", login: "octocat" },
],
},
}),
stderr: "",
stdoutTruncated: false,
stderrTruncated: false,
}),
);
const providerSnapshots = [
{
instanceId: modelSelection.instanceId,
Expand Down Expand Up @@ -475,6 +492,11 @@ describe("ProviderCommandReactor", () => {
createWorktree,
} satisfies Partial<GitWorkflowService.GitWorkflowService["Service"]>),
),
Layer.provideMerge(
Layer.mock(GitHubCli.GitHubCli)({
execute: executeGitHubCli,
} satisfies Partial<GitHubCli.GitHubCli["Service"]>),
),
Layer.provideMerge(
Layer.succeed(VcsStatusBroadcaster, {
getStatus: () => Effect.die("getStatus should not be called in this test"),
Expand Down Expand Up @@ -628,6 +650,7 @@ describe("ProviderCommandReactor", () => {
refreshStatus,
generateBranchName,
generateThreadTitle,
executeGitHubCli,
runtimeSessions,
stateDir,
drain,
Expand Down Expand Up @@ -2682,6 +2705,9 @@ describe("ProviderCommandReactor", () => {
`Add a safer reconnect backoff. ${assistantQuoteText}`,
);
expect(harness.generateBranchName.mock.calls[0]?.[0].message).not.toContain("t3-citation://");
expect(harness.renameBranch.mock.calls[0]?.[0]).toMatchObject({
newBranch: expect.stringMatching(/^octocat\//),
});
expect(harness.refreshStatus.mock.calls[0]?.[0]).toBe("/tmp/provider-project-worktree");
const readModel = await harness.readModel();
expect(
Expand Down
Loading
Loading