fix: resolve source control and chat UX bugs

- make staging real in the git panel: new /stage and /unstage endpoints,
  /status now reports the actual index via porcelain -z (handles renames,
  conflicts, and paths with spaces), and Stage/Unstage All run real git
  commands instead of toggling client-side state
- add branch search to the header dropdown and Branches tab
- persist the chosen permission mode per provider so a brand-new chat
  keeps it once the session id arrives, instead of reverting to default
- replace cmdk's fuzzy model search with strict token matching so
  "chatgpt" no longer surfaces unrelated models
- unit tests for the git status parser
This commit is contained in:
Haileyesus
2026-07-06 15:33:57 +03:00
parent 5dedf873e6
commit 09a21b3754
10 changed files with 437 additions and 78 deletions

View File

@@ -451,17 +451,19 @@ export function useChatProviderState({ selectedSession, selectedProject: _select
}, [providerEfforts, providerModels, reconcileStoredEffort]);
useEffect(() => {
if (!selectedSession?.id) {
return;
}
const savedMode = localStorage.getItem(`permissionMode-${selectedSession.id}`) as PermissionMode | null;
const validModes = getPermissionModesForProvider(provider);
setPermissionMode(
savedMode && validModes.includes(savedMode)
? savedMode
: getDefaultPermissionModeForProvider(provider),
const sessionSavedMode = selectedSession?.id
? (localStorage.getItem(`permissionMode-${selectedSession.id}`) as PermissionMode | null)
: null;
// Fall back to the last mode picked for this provider: a brand-new chat
// only receives its session id after the first send, so without this the
// mode chosen beforehand would snap back to the default as soon as the
// session id appears.
const providerSavedMode = localStorage.getItem(`permissionMode-last-${provider}`) as PermissionMode | null;
const savedMode = [sessionSavedMode, providerSavedMode].find(
(mode): mode is PermissionMode => Boolean(mode && validModes.includes(mode)),
);
setPermissionMode(savedMode ?? getDefaultPermissionModeForProvider(provider));
}, [selectedSession?.id, provider, getDefaultPermissionModeForProvider, getPermissionModesForProvider]);
useEffect(() => {
@@ -511,6 +513,10 @@ export function useChatProviderState({ selectedSession, selectedProject: _select
const nextMode = modes[nextIndex];
setPermissionMode(nextMode);
// Persist per provider as well as per session: a brand-new chat has no
// session id yet, and the per-provider key keeps the choice sticky when
// the real id arrives (and for future sessions of this provider).
localStorage.setItem(`permissionMode-last-${provider}`, nextMode);
if (selectedSession?.id) {
localStorage.setItem(`permissionMode-${selectedSession.id}`, nextMode);
}

View File

@@ -34,6 +34,17 @@ const PROVIDER_META: { id: LLMProvider; name: string }[] = [
const MOD_KEY =
typeof navigator !== "undefined" && /Mac|iPhone|iPad/.test(navigator.platform) ? "⌘" : "Ctrl";
// cmdk's default filter is fuzzy (loose character-subsequence scoring), which
// surfaces unrelated models — e.g. searching "chatgpt" also matched "Fable".
// Require every whitespace-separated search token to appear as a literal
// substring instead, so "claude 4.5" still matches "Anthropic Claude Haiku 4.5"
// but "chatgpt" only matches models that actually contain it.
function modelSearchFilter(value: string, search: string): number {
const haystack = value.toLowerCase();
const tokens = search.toLowerCase().split(/\s+/).filter(Boolean);
return tokens.every((token) => haystack.includes(token)) ? 1 : 0;
}
type ProviderSelectionEmptyStateProps = {
selectedSession: ProjectSession | null;
currentSessionId: string | null;
@@ -234,7 +245,7 @@ export default function ProviderSelectionEmptyState({
<div className="border-b border-border/60 bg-muted/20 px-4 py-3">
<p className="text-sm font-semibold text-foreground">Choose a model</p>
</div>
<Command>
<Command filter={modelSearchFilter}>
<CommandInput
placeholder={t("providerSelection.searchModels", {
defaultValue: "Search models...",