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
22 changes: 13 additions & 9 deletions desktop/src/features/agents/ui/AgentAiConfigurationMode.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,16 @@ import type { InheritedDefault } from "./bakedEnvHelpers";

export type { AgentAiConfigurationMode } from "./agentAiConfigurationPolicy";

/**
* Motion shared by the sliding pill and the labels it slides under. Both halves
* must use it: a bare `transition-colors` falls back to Tailwind's 150ms
* default, so the label finishes recoloring ~100ms before the 250ms pill
* arrives beneath it, and on a different curve.
*/
const TAB_MOTION = "duration-[250ms] ease-out motion-reduce:transition-none";

const TAB_TRIGGER_CLASS = `relative z-10 h-full rounded-md bg-transparent text-xs font-medium shadow-none transition-colors ${TAB_MOTION} data-[state=active]:bg-transparent data-[state=active]:shadow-none`;

export function HarnessModelDefaultNotice({
harness,
model,
Expand Down Expand Up @@ -86,24 +96,18 @@ export function AgentAiConfigurationModeField({
<TabsList className="relative isolate grid h-9 w-full grid-cols-2 overflow-hidden rounded-lg bg-muted p-0.5">
<div
aria-hidden="true"
className="absolute bottom-0.5 left-0.5 top-0.5 z-0 rounded-md bg-background shadow-sm transition-transform duration-[250ms] ease-out"
className={`absolute bottom-0.5 left-0.5 top-0.5 z-0 rounded-md bg-background shadow-sm transition-transform ${TAB_MOTION}`}
style={{
transform: `translateX(${mode === "custom" ? 100 : 0}%)`,
width: "calc((100% - 4px) / 2)",
}}
/>
<TabsTrigger
className="relative z-10 h-full rounded-md bg-transparent text-xs font-medium shadow-none transition-colors data-[state=active]:bg-transparent data-[state=active]:shadow-none"
value="defaults"
>
<TabsTrigger className={TAB_TRIGGER_CLASS} value="defaults">
{needsProviderSelection
? "Use agent defaults"
: "Use harness defaults"}
</TabsTrigger>
<TabsTrigger
className="relative z-10 h-full rounded-md bg-transparent text-xs font-medium shadow-none transition-colors data-[state=active]:bg-transparent data-[state=active]:shadow-none"
value="custom"
>
<TabsTrigger className={TAB_TRIGGER_CLASS} value="custom">
Customize for this agent
</TabsTrigger>
</TabsList>
Expand Down
5 changes: 4 additions & 1 deletion desktop/src/features/agents/ui/AgentDefinitionDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -834,7 +834,10 @@ export function AgentDefinitionDialog({
) : null}

<div
className="space-y-5"
// Keyed to fade the incoming mode in: this section's height changes
// instantly while the pill slides 250ms. Values live in parent state.
key={aiConfigurationMode}
className="animate-in space-y-5 fade-in-0 duration-[250ms] ease-out motion-reduce:animate-none"
data-testid={`agent-${aiConfigurationMode}-configuration-section`}
>
{aiConfigurationMode === "custom" ? (
Expand Down
18 changes: 13 additions & 5 deletions desktop/src/features/huddle/components/HuddleBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -493,21 +493,29 @@ export function HuddleBar({
async function handleLeave() {
if (isLeaving) return;
const leavingChannelId = barState?.ephemeral_channel_id ?? null;
// Snapshot for rollback: leaving is only optimistic if a failed teardown can
// put the bar back exactly as it was.
const previousState = state;
stateGenerationRef.current += 1;
locallyLeavingChannelRef.current = leavingChannelId;
setIsLeaving(true);
// Close the drawer now rather than after teardown. leaveHuddle() stops the
// worklet, stops the mic track, and round-trips Rust `leave_huddle`; waiting
// on all three reads as an unresponsive click. `locallyLeavingChannelRef`
// already suppresses in-flight "still active" states for this channel, so
// nothing reopens the drawer behind us.
setState(null);
try {
const backendClean = await leaveHuddle();
if (backendClean) {
setState(null);
} else {
// If cleanup failed, restore the bar so the user can retry.
if (!(await leaveHuddle())) {
locallyLeavingChannelRef.current = null;
stateGenerationRef.current += 1;
setState(previousState);
}
// If cleanup failed, keep the bar visible so the user can retry.
} catch (e) {
locallyLeavingChannelRef.current = null;
stateGenerationRef.current += 1;
setState(previousState);
console.error("Failed to leave huddle:", e);
} finally {
setIsLeaving(false);
Expand Down