| author | |
| committer | |
| log | c7c1dfe4df99edf65a49d8a31b39ef1361f37f44 |
| tree | 225c1fad845e5917f553ab4df56613fea9ef55b0 |
| parent | c75299ee2085a121bceb6df76796e971fe92b5b6 |
| signature | Signed by PGP key B5690EEEBB952194 |
6 files changed, 20 insertions(+), 159 deletions(-)
apps/web/src/components/chat/ComposerActivityStatus.tsx+1-1| ... | ... | @@ -6,7 +6,7 @@ export function ComposerActivityRow({ phase }: { readonly phase: ThreadSyncPhase |
| 6 | 6 | return ( |
| 7 | 7 | <ComposerBanner.Row> |
| 8 | 8 | <ComposerBanner.Icon> |
| 9 | <LoaderCircleIcon className="motion-safe:animate-spin" /> | |
| 9 | <LoaderCircleIcon /> | |
| 10 | 10 | </ComposerBanner.Icon> |
| 11 | 11 | <ComposerBanner.Content> |
| 12 | 12 | <span |
apps/web/src/components/chat/ComposerServerUpdateStatus.tsx+1-1| ... | ... | @@ -12,7 +12,7 @@ export function ComposerServerUpdateIcon({ |
| 12 | 12 | readonly status: ServerUpdateState["status"]; |
| 13 | 13 | }) { |
| 14 | 14 | if (status === "running") { |
| 15 | return <LoaderCircleIcon aria-hidden className="motion-safe:animate-spin" />; | |
| 15 | return <LoaderCircleIcon aria-hidden />; | |
| 16 | 16 | } |
| 17 | 17 | if (status === "failed") { |
| 18 | 18 | return <CircleAlertIcon aria-hidden className="text-error" />; |
apps/web/src/components/chat/MessagesTimeline.test.tsx+1-3| ... | ... | @@ -1203,7 +1203,7 @@ describe("MessagesTimeline", () => { |
| 1203 | 1203 | expect(markup).not.toContain('aria-label="Hidden work includes a failure"'); |
| 1204 | 1204 | }); |
| 1205 | 1205 | |
| 1206 | it("shows the animated one-line label for a live tool group", () => { | |
| 1206 | it("shows the one-line label for a live tool group", () => { | |
| 1207 | 1207 | const turnId = TurnId.make("turn-live"); |
| 1208 | 1208 | const markup = renderToStaticMarkup( |
| 1209 | 1209 | <MessagesTimeline |
| ... | ... | @@ -1240,7 +1240,6 @@ describe("MessagesTimeline", () => { |
| 1240 | 1240 | |
| 1241 | 1241 | expect(markup).toContain("Working for"); |
| 1242 | 1242 | expect(markup).toContain("Running pnpm"); |
| 1243 | expect(markup).toContain("live-activity-focus"); | |
| 1244 | 1243 | }); |
| 1245 | 1244 | |
| 1246 | 1245 | it("scopes a live row failure to the tool named by the row", () => { |
| ... | ... | @@ -1358,7 +1357,6 @@ describe("MessagesTimeline", () => { |
| 1358 | 1357 | |
| 1359 | 1358 | expect(markup).toContain("Running pnpm"); |
| 1360 | 1359 | expect(markup).toContain("lucide-terminal"); |
| 1361 | expect(markup).toContain("live-activity-focus"); | |
| 1362 | 1360 | expect(markup).not.toContain("Ran pnpm"); |
| 1363 | 1361 | expect(markup).not.toContain("Thinking"); |
| 1364 | 1362 | expect(markup).not.toContain('data-timeline-row-kind="thinking"'); |
apps/web/src/components/chat/MessagesTimeline.tsx+13-60| ... | ... | @@ -1614,17 +1614,9 @@ function WorkingTimelineRow({ row }: { row: Extract<TimelineRow, { kind: "workin |
| 1614 | 1614 | className="relative shrink-0 overflow-hidden whitespace-nowrap transition-opacity duration-150 starting:opacity-0 motion-reduce:transition-none" |
| 1615 | 1615 | > |
| 1616 | 1616 | {isPreparingWorktree ? ( |
| 1617 | <> | |
| 1618 | Setting up worktree… | |
| 1619 | <ActivityShimmerOverlay>Setting up worktree…</ActivityShimmerOverlay> | |
| 1620 | </> | |
| 1617 | "Setting up worktree…" | |
| 1621 | 1618 | ) : isCompacting ? ( |
| 1622 | <> | |
| 1623 | <CompactingLabel /> | |
| 1624 | <ActivityShimmerOverlay> | |
| 1625 | <CompactingLabel /> | |
| 1626 | </ActivityShimmerOverlay> | |
| 1627 | </> | |
| 1619 | <CompactingLabel /> | |
| 1628 | 1620 | ) : row.createdAt ? ( |
| 1629 | 1621 | <> |
| 1630 | 1622 | Working for <WorkingTimer createdAt={row.createdAt} /> |
| ... | ... | @@ -1878,19 +1870,6 @@ function ExpandedWorkGroupEntries({ |
| 1878 | 1870 | |
| 1879 | 1871 | const workEntryKey = (entry: TimelineWorkEntry) => entry.id; |
| 1880 | 1872 | |
| 1881 | function ActivityShimmerOverlay({ children }: { children: ReactNode }) { | |
| 1882 | return ( | |
| 1883 | <span | |
| 1884 | aria-hidden | |
| 1885 | className="live-activity-focus pointer-events-none absolute inset-y-0 select-none" | |
| 1886 | > | |
| 1887 | <span className="live-activity-focus-counter block"> | |
| 1888 | <span className="live-activity-focus-aligned block text-foreground">{children}</span> | |
| 1889 | </span> | |
| 1890 | </span> | |
| 1891 | ); | |
| 1892 | } | |
| 1893 | ||
| 1894 | 1873 | const failedToolIconClassName = "text-tool-error-icon/40"; |
| 1895 | 1874 | |
| 1896 | 1875 | /** Image icons and the gradient computer-use mark cannot take a currentColor |
| ... | ... | @@ -1914,7 +1893,7 @@ function LiveActivityRow({ |
| 1914 | 1893 | failed?: boolean; |
| 1915 | 1894 | }) { |
| 1916 | 1895 | return ( |
| 1917 | <div className="relative min-h-6 w-fit max-w-full min-w-0 overflow-hidden rounded-md text-sm leading-relaxed"> | |
| 1896 | <div className="min-h-6 w-fit max-w-full min-w-0 overflow-hidden rounded-md text-sm leading-relaxed"> | |
| 1918 | 1897 | <LiveActivityContent |
| 1919 | 1898 | label={label} |
| 1920 | 1899 | iconName={iconName} |
| ... | ... | @@ -1922,15 +1901,6 @@ function LiveActivityRow({ |
| 1922 | 1901 | failed={failed} |
| 1923 | 1902 | announceFailure={failed} |
| 1924 | 1903 | /> |
| 1925 | <ActivityShimmerOverlay> | |
| 1926 | <LiveActivityContent | |
| 1927 | label={label} | |
| 1928 | iconName={iconName} | |
| 1929 | toolIcon={toolIcon} | |
| 1930 | failed={failed} | |
| 1931 | highlighted | |
| 1932 | /> | |
| 1933 | </ActivityShimmerOverlay> | |
| 1934 | 1904 | </div> |
| 1935 | 1905 | ); |
| 1936 | 1906 | } |
| ... | ... | @@ -1941,14 +1911,12 @@ function LiveActivityContent({ |
| 1941 | 1911 | toolIcon, |
| 1942 | 1912 | failed = false, |
| 1943 | 1913 | announceFailure = false, |
| 1944 | highlighted = false, | |
| 1945 | 1914 | }: { |
| 1946 | 1915 | label: string; |
| 1947 | 1916 | iconName: WorkEntryIconName | undefined; |
| 1948 | 1917 | toolIcon?: ToolActivityIcon | undefined; |
| 1949 | 1918 | failed?: boolean; |
| 1950 | 1919 | announceFailure?: boolean; |
| 1951 | highlighted?: boolean; | |
| 1952 | 1920 | }) { |
| 1953 | 1921 | const showTrailingFailureMark = |
| 1954 | 1922 | failed && iconName !== undefined && !toolIconAcceptsTint(iconName, toolIcon); |
| ... | ... | @@ -1958,14 +1926,14 @@ function LiveActivityContent({ |
| 1958 | 1926 | className={cn( |
| 1959 | 1927 | "flex min-h-6 min-w-0 items-center gap-1.5 py-0.5", |
| 1960 | 1928 | iconName ? "px-0.5" : "px-1", |
| 1961 | highlighted ? "text-foreground" : "text-secondary-label", | |
| 1929 | "text-secondary-label", | |
| 1962 | 1930 | )} |
| 1963 | 1931 | > |
| 1964 | 1932 | {iconName ? ( |
| 1965 | 1933 | <span |
| 1966 | 1934 | className={cn( |
| 1967 | 1935 | "flex size-6 shrink-0 items-center justify-center", |
| 1968 | highlighted ? "text-foreground" : failed ? failedToolIconClassName : "text-icon-muted", | |
| 1936 | failed ? failedToolIconClassName : "text-icon-muted", | |
| 1969 | 1937 | )} |
| 1970 | 1938 | role={announceFailure ? "img" : undefined} |
| 1971 | 1939 | aria-label={announceFailure ? "Tool call failed" : undefined} |
| ... | ... | @@ -1974,16 +1942,13 @@ function LiveActivityContent({ |
| 1974 | 1942 | icon={toolIcon} |
| 1975 | 1943 | fallbackName={iconName} |
| 1976 | 1944 | className="block size-4 shrink-0 stroke-[1.8]" |
| 1977 | muted={!highlighted} | |
| 1945 | muted | |
| 1978 | 1946 | /> |
| 1979 | 1947 | </span> |
| 1980 | 1948 | ) : null} |
| 1981 | 1949 | <span className="min-w-0 flex-1 truncate">{label}</span> |
| 1982 | 1950 | {showTrailingFailureMark ? ( |
| 1983 | <XIcon | |
| 1984 | aria-hidden | |
| 1985 | className={cn("size-3 shrink-0", !highlighted && failedToolIconClassName)} | |
| 1986 | /> | |
| 1951 | <XIcon aria-hidden className={cn("size-3 shrink-0", failedToolIconClassName)} /> | |
| 1987 | 1952 | ) : null} |
| 1988 | 1953 | </span> |
| 1989 | 1954 | ); |
| ... | ... | @@ -2002,24 +1967,12 @@ function LiveWorkEntryTimelineRow({ row }: { row: Extract<TimelineRow, { kind: " |
| 2002 | 1967 | aria-expanded={row.expanded} |
| 2003 | 1968 | onClick={() => ctx.onToggleWorkGroup(row.groupId, row.id)} |
| 2004 | 1969 | > |
| 2005 | {row.active ? ( | |
| 2006 | <LiveActivityRow | |
| 2007 | label={label} | |
| 2008 | iconName={workEntryIconName(row.entry)} | |
| 2009 | toolIcon={row.entry.toolIcon ?? row.entry.toolSource?.icon} | |
| 2010 | failed={failed} | |
| 2011 | /> | |
| 2012 | ) : ( | |
| 2013 | <div className="min-h-6 w-fit max-w-full min-w-0 overflow-hidden rounded-md text-sm leading-relaxed"> | |
| 2014 | <LiveActivityContent | |
| 2015 | label={label} | |
| 2016 | iconName={workEntryIconName(row.entry)} | |
| 2017 | toolIcon={row.entry.toolIcon ?? row.entry.toolSource?.icon} | |
| 2018 | failed={failed} | |
| 2019 | announceFailure={failed} | |
| 2020 | /> | |
| 2021 | </div> | |
| 2022 | )} | |
| 1970 | <LiveActivityRow | |
| 1971 | label={label} | |
| 1972 | iconName={workEntryIconName(row.entry)} | |
| 1973 | toolIcon={row.entry.toolIcon ?? row.entry.toolSource?.icon} | |
| 1974 | failed={failed} | |
| 1975 | /> | |
| 2023 | 1976 | </button> |
| 2024 | 1977 | ); |
| 2025 | 1978 | } |
apps/web/src/index.css+2-94| ... | ... | @@ -425,80 +425,6 @@ html[data-mobile-composer-route-transition="true"]::view-transition-old(t3-mobil |
| 425 | 425 | } |
| 426 | 426 | } |
| 427 | 427 | |
| 428 | @keyframes live-activity-focus { | |
| 429 | 0% { | |
| 430 | transform: translateX(0); | |
| 431 | } | |
| 432 | 100% { | |
| 433 | transform: translateX(100%); | |
| 434 | } | |
| 435 | } | |
| 436 | ||
| 437 | @keyframes live-activity-focus-counter { | |
| 438 | 0% { | |
| 439 | transform: translateX(0); | |
| 440 | } | |
| 441 | 100% { | |
| 442 | transform: translateX(-100%); | |
| 443 | } | |
| 444 | } | |
| 445 | ||
| 446 | @utility live-activity-focus { | |
| 447 | --live-activity-focus-width: 4.5rem; | |
| 448 | ||
| 449 | right: auto; | |
| 450 | left: calc(-1 * var(--live-activity-focus-width)); | |
| 451 | width: calc(100% + var(--live-activity-focus-width) + var(--live-activity-focus-width)); | |
| 452 | -webkit-mask-image: linear-gradient( | |
| 453 | to right, | |
| 454 | transparent 0, | |
| 455 | rgb(0 0 0 / 12%) 0.675rem, | |
| 456 | rgb(0 0 0 / 55%) 1.575rem, | |
| 457 | black 2.25rem, | |
| 458 | rgb(0 0 0 / 55%) 2.925rem, | |
| 459 | rgb(0 0 0 / 12%) 3.825rem, | |
| 460 | transparent var(--live-activity-focus-width), | |
| 461 | transparent 100% | |
| 462 | ); | |
| 463 | -webkit-mask-repeat: no-repeat; | |
| 464 | mask-image: linear-gradient( | |
| 465 | to right, | |
| 466 | transparent 0, | |
| 467 | rgb(0 0 0 / 12%) 0.675rem, | |
| 468 | rgb(0 0 0 / 55%) 1.575rem, | |
| 469 | black 2.25rem, | |
| 470 | rgb(0 0 0 / 55%) 2.925rem, | |
| 471 | rgb(0 0 0 / 12%) 3.825rem, | |
| 472 | transparent var(--live-activity-focus-width), | |
| 473 | transparent 100% | |
| 474 | ); | |
| 475 | mask-repeat: no-repeat; | |
| 476 | animation: live-activity-focus 2.2s linear infinite; | |
| 477 | will-change: transform; | |
| 478 | ||
| 479 | @media (prefers-reduced-motion: reduce) { | |
| 480 | animation: none; | |
| 481 | opacity: 0; | |
| 482 | will-change: auto; | |
| 483 | } | |
| 484 | } | |
| 485 | ||
| 486 | @utility live-activity-focus-counter { | |
| 487 | width: 100%; | |
| 488 | animation: live-activity-focus-counter 2.2s linear infinite; | |
| 489 | will-change: transform; | |
| 490 | ||
| 491 | @media (prefers-reduced-motion: reduce) { | |
| 492 | animation: none; | |
| 493 | will-change: auto; | |
| 494 | } | |
| 495 | } | |
| 496 | ||
| 497 | @utility live-activity-focus-aligned { | |
| 498 | width: calc(100% - var(--live-activity-focus-width) - var(--live-activity-focus-width)); | |
| 499 | margin-left: var(--live-activity-focus-width); | |
| 500 | } | |
| 501 | ||
| 502 | 428 | @property --assistant-citation-highlight-opacity { |
| 503 | 429 | syntax: "<number>"; |
| 504 | 430 | inherits: true; |
| ... | ... | @@ -1906,24 +1832,6 @@ code { |
| 1906 | 1832 | color: var(--code-foreground) !important; |
| 1907 | 1833 | } |
| 1908 | 1834 | |
| 1909 | @keyframes ultrathink-rainbow { | |
| 1910 | 0% { | |
| 1911 | background-position: 0% 50%; | |
| 1912 | } | |
| 1913 | 100% { | |
| 1914 | background-position: 200% 50%; | |
| 1915 | } | |
| 1916 | } | |
| 1917 | ||
| 1918 | @keyframes ultrathink-chroma-shift { | |
| 1919 | 0% { | |
| 1920 | filter: hue-rotate(0deg) saturate(1.2); | |
| 1921 | } | |
| 1922 | 100% { | |
| 1923 | filter: hue-rotate(360deg) saturate(1.2); | |
| 1924 | } | |
| 1925 | } | |
| 1926 | ||
| 1927 | 1835 | .ultrathink-frame { |
| 1928 | 1836 | --ultrathink-spectrum: linear-gradient( |
| 1929 | 1837 | 120deg, |
| ... | ... | @@ -1946,6 +1854,7 @@ code { |
| 1946 | 1854 | padding: 2px; |
| 1947 | 1855 | background-image: var(--ultrathink-spectrum); |
| 1948 | 1856 | background-size: 220% 220%; |
| 1857 | background-position: 0% 50%; | |
| 1949 | 1858 | pointer-events: none; |
| 1950 | 1859 | filter: saturate(0.82) brightness(0.92); |
| 1951 | 1860 | -webkit-mask: |
| ... | ... | @@ -1956,7 +1865,6 @@ code { |
| 1956 | 1865 | linear-gradient(#000 0 0) content-box, |
| 1957 | 1866 | linear-gradient(#000 0 0); |
| 1958 | 1867 | mask-composite: exclude; |
| 1959 | animation: ultrathink-rainbow 10s linear infinite; | |
| 1960 | 1868 | } |
| 1961 | 1869 | |
| 1962 | 1870 | .ultrathink-frame > * { |
| ... | ... | @@ -1964,7 +1872,7 @@ code { |
| 1964 | 1872 | } |
| 1965 | 1873 | |
| 1966 | 1874 | .ultrathink-chroma { |
| 1967 | animation: ultrathink-chroma-shift 10s linear infinite; | |
| 1875 | filter: saturate(1.2); | |
| 1968 | 1876 | } |
| 1969 | 1877 | |
| 1970 | 1878 | @keyframes preview-loading-progress { |
docs/user/composer.md+2| ... | ... | @@ -174,6 +174,8 @@ On web and desktop, loading and syncing statuses fill the available banner width |
| 174 | 174 | stash tab. Task progress appears above the composer, while the timeline's working timer shows |
| 175 | 175 | only elapsed time. |
| 176 | 176 | |
| 177 | Loading, syncing, and server-update icons are static. Live tool labels do not shimmer. | |
| 178 | ||
| 177 | 179 | On web and desktop, additional notices peek out above the attached banner. Hover over the peek |
| 178 | 180 | to reveal them, or focus **Show other notices** with `Tab` and press `Enter` or `Space`. Press |
| 179 | 181 | `Escape` to close the stack and return focus to that control. On a touchscreen, tap the peek to |