Skip to content
Merged
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
2 changes: 1 addition & 1 deletion apps/web/src/components/chat/ComposerActivityStatus.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ export function ComposerActivityRow({ phase }: { readonly phase: ThreadSyncPhase
return (
<ComposerBanner.Row>
<ComposerBanner.Icon>
<LoaderCircleIcon className="motion-safe:animate-spin" />
<LoaderCircleIcon />
</ComposerBanner.Icon>
<ComposerBanner.Content>
<span
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export function ComposerServerUpdateIcon({
readonly status: ServerUpdateState["status"];
}) {
if (status === "running") {
return <LoaderCircleIcon aria-hidden className="motion-safe:animate-spin" />;
return <LoaderCircleIcon aria-hidden />;
}
if (status === "failed") {
return <CircleAlertIcon aria-hidden className="text-error" />;
Expand Down
4 changes: 1 addition & 3 deletions apps/web/src/components/chat/MessagesTimeline.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1203,7 +1203,7 @@ describe("MessagesTimeline", () => {
expect(markup).not.toContain('aria-label="Hidden work includes a failure"');
});

it("shows the animated one-line label for a live tool group", () => {
it("shows the one-line label for a live tool group", () => {
const turnId = TurnId.make("turn-live");
const markup = renderToStaticMarkup(
<MessagesTimeline
Expand Down Expand Up @@ -1240,7 +1240,6 @@ describe("MessagesTimeline", () => {

expect(markup).toContain("Working for");
expect(markup).toContain("Running pnpm");
expect(markup).toContain("live-activity-focus");
});

it("scopes a live row failure to the tool named by the row", () => {
Expand Down Expand Up @@ -1358,7 +1357,6 @@ describe("MessagesTimeline", () => {

expect(markup).toContain("Running pnpm");
expect(markup).toContain("lucide-terminal");
expect(markup).toContain("live-activity-focus");
expect(markup).not.toContain("Ran pnpm");
expect(markup).not.toContain("Thinking");
expect(markup).not.toContain('data-timeline-row-kind="thinking"');
Expand Down
73 changes: 13 additions & 60 deletions apps/web/src/components/chat/MessagesTimeline.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1614,17 +1614,9 @@ function WorkingTimelineRow({ row }: { row: Extract<TimelineRow, { kind: "workin
className="relative shrink-0 overflow-hidden whitespace-nowrap transition-opacity duration-150 starting:opacity-0 motion-reduce:transition-none"
>
{isPreparingWorktree ? (
<>
Setting up worktree…
<ActivityShimmerOverlay>Setting up worktree…</ActivityShimmerOverlay>
</>
"Setting up worktree…"
) : isCompacting ? (
<>
<CompactingLabel />
<ActivityShimmerOverlay>
<CompactingLabel />
</ActivityShimmerOverlay>
</>
<CompactingLabel />
) : row.createdAt ? (
<>
Working for <WorkingTimer createdAt={row.createdAt} />
Expand Down Expand Up @@ -1878,19 +1870,6 @@ function ExpandedWorkGroupEntries({

const workEntryKey = (entry: TimelineWorkEntry) => entry.id;

function ActivityShimmerOverlay({ children }: { children: ReactNode }) {
return (
<span
aria-hidden
className="live-activity-focus pointer-events-none absolute inset-y-0 select-none"
>
<span className="live-activity-focus-counter block">
<span className="live-activity-focus-aligned block text-foreground">{children}</span>
</span>
</span>
);
}

const failedToolIconClassName = "text-tool-error-icon/40";

/** Image icons and the gradient computer-use mark cannot take a currentColor
Expand All @@ -1914,23 +1893,14 @@ function LiveActivityRow({
failed?: boolean;
}) {
return (
<div className="relative min-h-6 w-fit max-w-full min-w-0 overflow-hidden rounded-md text-sm leading-relaxed">
<div className="min-h-6 w-fit max-w-full min-w-0 overflow-hidden rounded-md text-sm leading-relaxed">
<LiveActivityContent
label={label}
iconName={iconName}
toolIcon={toolIcon}
failed={failed}
announceFailure={failed}
/>
<ActivityShimmerOverlay>
<LiveActivityContent
label={label}
iconName={iconName}
toolIcon={toolIcon}
failed={failed}
highlighted
/>
</ActivityShimmerOverlay>
</div>
);
}
Expand All @@ -1941,14 +1911,12 @@ function LiveActivityContent({
toolIcon,
failed = false,
announceFailure = false,
highlighted = false,
}: {
label: string;
iconName: WorkEntryIconName | undefined;
toolIcon?: ToolActivityIcon | undefined;
failed?: boolean;
announceFailure?: boolean;
highlighted?: boolean;
}) {
const showTrailingFailureMark =
failed && iconName !== undefined && !toolIconAcceptsTint(iconName, toolIcon);
Expand All @@ -1958,14 +1926,14 @@ function LiveActivityContent({
className={cn(
"flex min-h-6 min-w-0 items-center gap-1.5 py-0.5",
iconName ? "px-0.5" : "px-1",
highlighted ? "text-foreground" : "text-secondary-label",
"text-secondary-label",
)}
>
{iconName ? (
<span
className={cn(
"flex size-6 shrink-0 items-center justify-center",
highlighted ? "text-foreground" : failed ? failedToolIconClassName : "text-icon-muted",
failed ? failedToolIconClassName : "text-icon-muted",
)}
role={announceFailure ? "img" : undefined}
aria-label={announceFailure ? "Tool call failed" : undefined}
Expand All @@ -1974,16 +1942,13 @@ function LiveActivityContent({
icon={toolIcon}
fallbackName={iconName}
className="block size-4 shrink-0 stroke-[1.8]"
muted={!highlighted}
muted
/>
</span>
) : null}
<span className="min-w-0 flex-1 truncate">{label}</span>
{showTrailingFailureMark ? (
<XIcon
aria-hidden
className={cn("size-3 shrink-0", !highlighted && failedToolIconClassName)}
/>
<XIcon aria-hidden className={cn("size-3 shrink-0", failedToolIconClassName)} />
) : null}
</span>
);
Expand All @@ -2002,24 +1967,12 @@ function LiveWorkEntryTimelineRow({ row }: { row: Extract<TimelineRow, { kind: "
aria-expanded={row.expanded}
onClick={() => ctx.onToggleWorkGroup(row.groupId, row.id)}
>
{row.active ? (
<LiveActivityRow
label={label}
iconName={workEntryIconName(row.entry)}
toolIcon={row.entry.toolIcon ?? row.entry.toolSource?.icon}
failed={failed}
/>
) : (
<div className="min-h-6 w-fit max-w-full min-w-0 overflow-hidden rounded-md text-sm leading-relaxed">
<LiveActivityContent
label={label}
iconName={workEntryIconName(row.entry)}
toolIcon={row.entry.toolIcon ?? row.entry.toolSource?.icon}
failed={failed}
announceFailure={failed}
/>
</div>
)}
<LiveActivityRow
label={label}
iconName={workEntryIconName(row.entry)}
toolIcon={row.entry.toolIcon ?? row.entry.toolSource?.icon}
failed={failed}
/>
</button>
);
}
Expand Down
96 changes: 2 additions & 94 deletions apps/web/src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -425,80 +425,6 @@ html[data-mobile-composer-route-transition="true"]::view-transition-old(t3-mobil
}
}

@keyframes live-activity-focus {
0% {
transform: translateX(0);
}
100% {
transform: translateX(100%);
}
}

@keyframes live-activity-focus-counter {
0% {
transform: translateX(0);
}
100% {
transform: translateX(-100%);
}
}

@utility live-activity-focus {
--live-activity-focus-width: 4.5rem;

right: auto;
left: calc(-1 * var(--live-activity-focus-width));
width: calc(100% + var(--live-activity-focus-width) + var(--live-activity-focus-width));
-webkit-mask-image: linear-gradient(
to right,
transparent 0,
rgb(0 0 0 / 12%) 0.675rem,
rgb(0 0 0 / 55%) 1.575rem,
black 2.25rem,
rgb(0 0 0 / 55%) 2.925rem,
rgb(0 0 0 / 12%) 3.825rem,
transparent var(--live-activity-focus-width),
transparent 100%
);
-webkit-mask-repeat: no-repeat;
mask-image: linear-gradient(
to right,
transparent 0,
rgb(0 0 0 / 12%) 0.675rem,
rgb(0 0 0 / 55%) 1.575rem,
black 2.25rem,
rgb(0 0 0 / 55%) 2.925rem,
rgb(0 0 0 / 12%) 3.825rem,
transparent var(--live-activity-focus-width),
transparent 100%
);
mask-repeat: no-repeat;
animation: live-activity-focus 2.2s linear infinite;
will-change: transform;

@media (prefers-reduced-motion: reduce) {
animation: none;
opacity: 0;
will-change: auto;
}
}

@utility live-activity-focus-counter {
width: 100%;
animation: live-activity-focus-counter 2.2s linear infinite;
will-change: transform;

@media (prefers-reduced-motion: reduce) {
animation: none;
will-change: auto;
}
}

@utility live-activity-focus-aligned {
width: calc(100% - var(--live-activity-focus-width) - var(--live-activity-focus-width));
margin-left: var(--live-activity-focus-width);
}

@property --assistant-citation-highlight-opacity {
syntax: "<number>";
inherits: true;
Expand Down Expand Up @@ -1906,24 +1832,6 @@ code {
color: var(--code-foreground) !important;
}

@keyframes ultrathink-rainbow {
0% {
background-position: 0% 50%;
}
100% {
background-position: 200% 50%;
}
}

@keyframes ultrathink-chroma-shift {
0% {
filter: hue-rotate(0deg) saturate(1.2);
}
100% {
filter: hue-rotate(360deg) saturate(1.2);
}
}

.ultrathink-frame {
--ultrathink-spectrum: linear-gradient(
120deg,
Expand All @@ -1946,6 +1854,7 @@ code {
padding: 2px;
background-image: var(--ultrathink-spectrum);
background-size: 220% 220%;
background-position: 0% 50%;
pointer-events: none;
filter: saturate(0.82) brightness(0.92);
-webkit-mask:
Expand All @@ -1956,15 +1865,14 @@ code {
linear-gradient(#000 0 0) content-box,
linear-gradient(#000 0 0);
mask-composite: exclude;
animation: ultrathink-rainbow 10s linear infinite;
}

.ultrathink-frame > * {
position: relative;
}

.ultrathink-chroma {
animation: ultrathink-chroma-shift 10s linear infinite;
filter: saturate(1.2);
}

@keyframes preview-loading-progress {
Expand Down
2 changes: 2 additions & 0 deletions docs/user/composer.md
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,8 @@ On web and desktop, loading and syncing statuses fill the available banner width
stash tab. Task progress appears above the composer, while the timeline's working timer shows
only elapsed time.

Loading, syncing, and server-update icons are static. Live tool labels do not shimmer.

On web and desktop, additional notices peek out above the attached banner. Hover over the peek
to reveal them, or focus **Show other notices** with `Tab` and press `Enter` or `Space`. Press
`Escape` to close the stack and return focus to that control. On a touchscreen, tap the peek to
Expand Down
Loading