Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
c9cf6d3
docs: add sidebar performance plan
Jun 15, 2026
6ded222
fix(git): highlight ahead/behind counts in sync button
Jun 16, 2026
89dd2b3
fix(shortcuts): remove Tab as default agent cycle shortcut
Jun 18, 2026
8c508d9
fix(providers): use correct endpoint for provider disconnect
Jun 18, 2026
b4b8bfc
fix(agents): use isPrimaryMode filter for agent picker
Jun 18, 2026
39095f2
fix(agents): use isPrimaryMode consistently across all agent pickers
Jun 18, 2026
e715572
fix(sidebar): increase virtualizer buffer for expanded parents
Jun 18, 2026
4eb2c27
fix(sidebar): correct expansion-key format for virtualizer buffer
Jun 18, 2026
7fdb00b
fix(sidebar): preserve pinned sessions and folder refs on empty sessi…
Jun 18, 2026
a692bb9
fix(agents): apply agent's pinned model + variant on explicit switch
Jun 18, 2026
71f654d
fix(auth): honor OPENCODE_SERVER_USERNAME env var for basic auth
Jun 18, 2026
a9c0801
fix(providers): hide/show all buttons respect active model filter
Jun 18, 2026
511e09b
fix(session): bind new sessions to selected project
Jun 18, 2026
0360108
test(session): add unit test for openNewSessionDraft project binding
Jun 18, 2026
de9d74d
fix(sync): reflect share status from global store after cancel
Jun 18, 2026
4370672
fix(sync): extract shared mergeLiveSessionWithGlobalSession helper
Jun 18, 2026
ed632b5
test(sync): add unit tests for mergeLiveSessionWithGlobalSession helper
Jun 18, 2026
0cdc6c0
fix(chat): preserve tool duration across session switches
Jun 18, 2026
f0436e4
fix(sync): preserve tool state.time in materialization merge
Jun 18, 2026
61f3176
fix(behavior): read AGENTS.md before settings.json so external edits …
Jun 19, 2026
befe4d3
fix(agents): send null to clear temperature/topP overrides on update
Jun 18, 2026
e6e4b4d
feat(agents): expose thinking variant configuration in agent settings
Jun 18, 2026
a1b682b
fix(sync): preserve tool state.time in materialization merge
Jun 18, 2026
e120ab5
chore: trigger re-review
Jun 18, 2026
354a3fa
fix(agents): use thinking variant selector in settings
Jun 20, 2026
105411b
fix(agents): preserve thinking variant values
Jun 20, 2026
f4665b7
ci: add Build Windows Electron workflow
Jun 17, 2026
752ddc3
ci: pin GitHub Actions by SHA per repo convention
Jun 18, 2026
b3f61b0
ci: default windows build to selected branch
Jun 20, 2026
c7f066c
ci: name branch build artifacts distinctly
Jun 20, 2026
fd5190c
fix(worktree): gate sessions on bootstrap readiness
Jun 22, 2026
dc211aa
fix(git): materialize draft session for generate
Jun 22, 2026
3b0cebe
docs: add issue 1737 connection status plan
Jun 21, 2026
cedad66
feat(ui): aggregated connection status indicator (#1737, #1556)
bashrusakh Jun 22, 2026
690548c
fix(ui): address connection status review feedback
Jun 22, 2026
7620837
fix(sync): preserve terminal disconnect state
Jun 22, 2026
8ad5b81
fix(sync): keep disconnect state consistent
Jun 22, 2026
b20cbbb
fix(ui): show per-hop status dots in tooltip
Jun 22, 2026
f68c3fb
fix(ui): align connection tooltip hop dots
Jun 22, 2026
40cac74
chore: remove planning artifacts from PR branch
Jun 22, 2026
8af3cce
fix(ui): remove unused connectionStatus.tooltip.hopLabel i18n key
Jun 22, 2026
cfee39b
feat(chat): add setting to strip slash on submit (#1775)
Jun 23, 2026
ba5e430
fix(chat): address review feedback for strip-slash setting
Jun 23, 2026
8ac5053
fix(chat): handle leading whitespace in strip-slash regex
Jun 23, 2026
139abc5
Update pr-review.yml
bashrusakh Jun 22, 2026
68366fe
Update pr-review.yml
bashrusakh Jun 22, 2026
f0307c3
feat: support OpenCode steer delivery / follow-up behavior settings
Jun 23, 2026
33eee1c
fix(#1766): make steer mode actually steer
Jun 23, 2026
42ecc99
refactor(#1766): flatten nested ternary in followUpBehavior resolution
Jun 23, 2026
f903175
Merge branch 'main' into win-artifact-pr-fixes-v2
bashrusakh Jun 26, 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
6 changes: 3 additions & 3 deletions .github/workflows/pr-review.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ name: pr-review

on:
pull_request_target:
types: [opened, synchronize, reopened, ready_for_review]
types: []
issue_comment:
types: [created]
types: []
pull_request_review_comment:
types: [created]
types: []

concurrency:
# PR conversation comments arrive as `issue_comment` events, so their PR number
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# Agent memory
.graymatter/

# Logs
logs
*.log
Expand Down
186 changes: 186 additions & 0 deletions SIDEBAR_PERF_PLAN.md

Large diffs are not rendered by default.

45 changes: 34 additions & 11 deletions packages/ui/src/components/chat/ChatInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1065,6 +1065,7 @@ const ChatInputComponent: React.FC<ChatInputProps> = ({ onOpenSettings, scrollTo
const inputBarOffset = useUIStore((state) => state.inputBarOffset);
const persistChatDraft = useUIStore((state) => state.persistChatDraft);
const inputSpellcheckEnabled = useUIStore((state) => state.inputSpellcheckEnabled);
const stripSlashOnSubmit = useUIStore((state) => state.stripSlashOnSubmit);
const isExpandedInput = useUIStore((state) => state.isExpandedInput);
const setExpandedInput = useUIStore((state) => state.setExpandedInput);
const setTimelineDialogOpen = useUIStore((state) => state.setTimelineDialogOpen);
Expand Down Expand Up @@ -1420,7 +1421,7 @@ const ChatInputComponent: React.FC<ChatInputProps> = ({ onOpenSettings, scrollTo
} | null>(null);

// Message queue
const queueModeEnabled = useMessageQueueStore((state) => state.queueModeEnabled);
const followUpBehavior = useMessageQueueStore((state) => state.followUpBehavior);
const queuedMessages = useMessageQueueStore(
React.useCallback(
(state) => {
Expand Down Expand Up @@ -1697,6 +1698,7 @@ const ChatInputComponent: React.FC<ChatInputProps> = ({ onOpenSettings, scrollTo
type SubmitOptions = {
queuedOnly?: boolean;
queuedMessageId?: string;
delivery?: 'steer';
};
const handleSubmitRef = React.useRef<(options?: SubmitOptions) => Promise<void>>(async () => {});

Expand Down Expand Up @@ -1746,7 +1748,8 @@ const ChatInputComponent: React.FC<ChatInputProps> = ({ onOpenSettings, scrollTo
}, []);

const handleQueuedMessageSend = React.useCallback((messageId: string) => {
void handleSubmitRef.current({ queuedOnly: true, queuedMessageId: messageId });
// Force-sending from the queue during a busy session counts as steer
void handleSubmitRef.current({ queuedOnly: true, queuedMessageId: messageId, delivery: 'steer' });
}, []);

const handleOpenAgentPanel = React.useCallback(() => {
Expand All @@ -1768,6 +1771,7 @@ const ChatInputComponent: React.FC<ChatInputProps> = ({ onOpenSettings, scrollTo
const handleSubmit = async (options?: SubmitOptions) => {
const queuedOnly = options?.queuedOnly ?? false;
const queuedMessageId = options?.queuedMessageId;
const delivery = options?.delivery === 'steer' && sessionPhase !== 'idle' ? 'steer' : undefined;
const inputSnapshot = getCurrentInputSnapshot();
const queuedMessagesToSend = queuedMessageId
? queuedMessages.filter((message) => message.id === queuedMessageId)
Expand Down Expand Up @@ -1971,7 +1975,14 @@ const ChatInputComponent: React.FC<ChatInputProps> = ({ onOpenSettings, scrollTo

// Handle local slash commands only in normal mode
const normalizedCommand = primaryText.trimStart();
if (inputMode === 'normal' && normalizedCommand.startsWith('/')) {
// When stripSlashOnSubmit is enabled (and we're in normal mode), skip
// all built-in slash command handling — strip the leading slash and
// fall through to send as plain text so the user sees what they typed
// (no skill-prompt expansion). Shell mode is left untouched because
// paths like /usr/bin/foo must keep their leading slash.
if (inputMode === 'normal' && stripSlashOnSubmit && normalizedCommand.startsWith('/')) {
primaryText = primaryText.replace(/^(\s*)\/+/, '$1');
} else if (inputMode === 'normal' && normalizedCommand.startsWith('/')) {
const commandName = normalizedCommand
.slice(1)
.trim()
Expand Down Expand Up @@ -2024,6 +2035,7 @@ const ChatInputComponent: React.FC<ChatInputProps> = ({ onOpenSettings, scrollTo
[{ text: instructionsText, synthetic: true }],
variantToSend,
inputMode,
sendMessageOptions,
);
scrollToBottom?.();
} catch (error) {
Expand All @@ -2046,6 +2058,7 @@ const ChatInputComponent: React.FC<ChatInputProps> = ({ onOpenSettings, scrollTo
[{ text: instructionsText, synthetic: true }],
variantToSend,
inputMode,
sendMessageOptions,
);
scrollToBottom?.();
} catch (error) {
Expand All @@ -2072,6 +2085,7 @@ const ChatInputComponent: React.FC<ChatInputProps> = ({ onOpenSettings, scrollTo
[{ text: instructionsText, synthetic: true }],
variantToSend,
inputMode,
sendMessageOptions,
);
scrollToBottom?.();
} catch (error) {
Expand All @@ -2094,6 +2108,7 @@ const ChatInputComponent: React.FC<ChatInputProps> = ({ onOpenSettings, scrollTo
[{ text: instructionsText, synthetic: true }],
variantToSend,
inputMode,
sendMessageOptions,
);
scrollToBottom?.();
} catch (error) {
Expand All @@ -2116,6 +2131,7 @@ const ChatInputComponent: React.FC<ChatInputProps> = ({ onOpenSettings, scrollTo
[{ text: instructionsText, synthetic: true }],
variantToSend,
inputMode,
sendMessageOptions,
);
scrollToBottom?.();
} catch (error) {
Expand All @@ -2138,6 +2154,7 @@ const ChatInputComponent: React.FC<ChatInputProps> = ({ onOpenSettings, scrollTo
[{ text: instructionsText, synthetic: true }],
variantToSend,
inputMode,
sendMessageOptions,
);
scrollToBottom?.();
} catch (error) {
Expand All @@ -2160,6 +2177,7 @@ const ChatInputComponent: React.FC<ChatInputProps> = ({ onOpenSettings, scrollTo
[{ text: instructionsText, synthetic: true }],
variantToSend,
inputMode,
sendMessageOptions,
);
scrollToBottom?.();
} catch (error) {
Expand Down Expand Up @@ -2208,7 +2226,8 @@ const ChatInputComponent: React.FC<ChatInputProps> = ({ onOpenSettings, scrollTo
agentMentionName,
additionalParts.length > 0 ? additionalParts : undefined,
variantToSend,
inputMode
inputMode,
sendMessageOptions,
);

if (typeof window === 'undefined') {
Expand Down Expand Up @@ -2279,12 +2298,14 @@ const ChatInputComponent: React.FC<ChatInputProps> = ({ onOpenSettings, scrollTo
// Update ref with latest handleSubmit on every render
handleSubmitRef.current = handleSubmit;

// Primary action for send button - respects queue mode setting
// Primary action for send/queue button respects selected follow-up behavior
const handlePrimaryAction = React.useCallback(() => {
const inputSnapshot = getCurrentInputSnapshot();
const canQueue = inputMode === 'normal' && inputSnapshot.hasContent && currentSessionId && (sessionPhase !== 'idle' || autoReviewRunning);
if (queueModeEnabled && canQueue) {
handleQueueMessage();
} else if (followUpBehavior === 'steer' && canQueue) {
void handleSubmitRef.current({ delivery: 'steer' });
} else {
void handleSubmitRef.current();
}
Expand Down Expand Up @@ -2527,7 +2548,7 @@ const ChatInputComponent: React.FC<ChatInputProps> = ({ onOpenSettings, scrollTo
return;
}

// Handle Enter/Ctrl+Enter based on queue mode
// Handle Enter/Ctrl+Enter based on selected follow-up behavior.
if (e.key === 'Enter' && !e.shiftKey && (!isMobile || e.ctrlKey || e.metaKey)) {
e.preventDefault();

Expand All @@ -2539,20 +2560,22 @@ const ChatInputComponent: React.FC<ChatInputProps> = ({ onOpenSettings, scrollTo
// For new sessions (draft), always send immediately
const canQueue = inputMode === 'normal' && hasContent && currentSessionId && (sessionPhase !== 'idle' || autoReviewRunning);

if (queueModeEnabled) {
if (followUpBehavior === 'queue') {
if (isCtrlEnter || !canQueue) {
// Ctrl+Enter sends, or Enter when can't queue (new session)
handleSubmit();
} else {
// Enter queues when we have a session
handleQueueMessage();
}
} else if (followUpBehavior === 'steer') {
if (isCtrlEnter || !canQueue) {
handleSubmit();
} else {
handleSubmit({ delivery: 'steer' });
}
} else {
if (isCtrlEnter && canQueue) {
// Ctrl+Enter queues when we have a session
handleQueueMessage();
} else {
// Enter sends
handleSubmit();
}
}
Expand Down
8 changes: 8 additions & 0 deletions packages/ui/src/components/chat/ModelControls.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -814,6 +814,10 @@ export const ModelControls: React.FC<ModelControlsProps> = ({
return;
}

if (explicitAgentSwitchRef.current === currentAgentName) {
return;
}

const restoreKey = [
currentSessionId,
latestLoadedUserChoice.id,
Expand Down Expand Up @@ -883,6 +887,10 @@ export const ModelControls: React.FC<ModelControlsProps> = ({
return;
}

if (explicitAgentSwitchRef.current === currentAgentName) {
return;
}

const applySavedSelections = (): 'resolved' | 'waiting' | 'continue' => {
const savedSessionModel = getSessionModelSelection(currentSessionId);
const savedAgentName = currentSessionId
Expand Down
Loading