Skip to content
Closed
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
15 changes: 8 additions & 7 deletions apps/web/src/hooks/useHandleNewThread.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,9 +104,10 @@ export function useNewThreadHandler() {
} = useComposerDraftStore.getState();
const currentRouteTarget = getCurrentRouteTarget();
// A new thread carries the user's working mode from the thread being
// viewed. The target project's configured model still wins; runtime and
// interaction modes carry independently. Branch, worktree, and env mode
// come from configured defaults unless the caller passes them explicitly.
// viewed: model, runtime mode, and interaction mode. The target project's
// default model only fills in when there is nothing to carry. Branch,
// worktree, and env mode come from configured defaults unless the caller
// passes them explicitly.
const carrySourceShell =
currentRouteTarget?.kind === "server"
? readThreadShell(currentRouteTarget.threadRef)
Expand Down Expand Up @@ -304,8 +305,8 @@ export function useNewThreadHandler() {
}
// Model intent: an explicit human pick always stands. Seeds and
// legacy entries alike re-resolve here — sticky first, mirroring
// the mint-fresh path, then the project default or carried
// selection on top. This runs even when the draft is already open:
// the mint-fresh path, then the carried selection or project
// default on top. This runs even when the draft is already open:
// without it, a changed pin could never reach the draft the user
// is looking at, because explicit picks are the only thing the
// flag protects.
Expand Down Expand Up @@ -457,8 +458,8 @@ export function useNewThreadHandler() {
applyStickyState(draftId);
const modelSelectionOverride = resolveModelSelectionOverride(draftId);
if (modelSelectionOverride) {
// Project defaults and carried selections both outrank global sticky
// state. The project default wins when both are present.
// Carried selections and project defaults both outrank global sticky
// state. The carried selection wins when both are present.
setModelSelection(draftId, modelSelectionOverride, { replaceOptions: true });
}
carryComposerContentTo(draftId);
Expand Down
21 changes: 20 additions & 1 deletion apps/web/src/lib/chatThreadActions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,14 +59,33 @@ describe("chatThreadActions", () => {
).toEqual(CARRIED_SELECTION);
});

it("keeps the project default above any carried selection", () => {
it("keeps the carried selection above the project default", () => {
expect(
resolveNewThreadModelSelectionOverride({
projectDefaultSelection: PROJECT_DEFAULT_SELECTION,
carrySelection: CARRIED_SELECTION,
carrySourceDraftId: "draft-a",
destinationDraftId: "draft-b",
}),
).toEqual(CARRIED_SELECTION);
});

it("falls back to the project default when there is nothing to carry", () => {
expect(
resolveNewThreadModelSelectionOverride({
projectDefaultSelection: PROJECT_DEFAULT_SELECTION,
carrySelection: null,
carrySourceDraftId: null,
destinationDraftId: "draft-b",
}),
).toEqual(PROJECT_DEFAULT_SELECTION);
expect(
resolveNewThreadModelSelectionOverride({
projectDefaultSelection: PROJECT_DEFAULT_SELECTION,
carrySelection: CARRIED_SELECTION,
carrySourceDraftId: "draft-a",
destinationDraftId: "draft-a",
}),
).toEqual(PROJECT_DEFAULT_SELECTION);
});

Expand Down
11 changes: 7 additions & 4 deletions apps/web/src/lib/chatThreadActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,16 +39,19 @@ export function resolveNewDraftStartFromOrigin(input: {
return input.envMode === "worktree" && input.newWorktreesStartFromOrigin;
}

// The model the user is looking at carries into the new thread. The project
// default only fills in when there is nothing to carry: every project gets a
// default seeded at creation, so letting it outrank the carried selection
// would reset the model on every new thread.
export function resolveNewThreadModelSelectionOverride(input: {
readonly projectDefaultSelection: ModelSelection | null;
readonly carrySelection: ModelSelection | null;
readonly carrySourceDraftId: string | null;
readonly destinationDraftId: string;
}): ModelSelection | null {
return (
input.projectDefaultSelection ??
(input.carrySourceDraftId === input.destinationDraftId ? null : input.carrySelection)
);
const carrySelection =
input.carrySourceDraftId === input.destinationDraftId ? null : input.carrySelection;
return carrySelection ?? input.projectDefaultSelection;
}

export function resolveThreadActionProjectRef(
Expand Down
Loading