authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-08-31 11:44:56-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-08-31 11:44:56-07:00
log5ce92c2f192040bf77c0211fa33bf03c74c031ef
treeb87db9b1648398d2c60adb4bac5584dc8c511f61
parent35da5813315dc4e0c20602ed1918ceccc916b7eb
signature Signed by PGP key B5690EEEBB952194

fix(mobile): shimmer active tool rows (#8932)

Co-authored-by: Julius Marminge <julius@mac.lan>

3 files changed, 25 insertions(+), 7 deletions(-)

apps/mobile/src/features/threads/thread-work-log.tsx+3-2
...@@ -9,6 +9,7 @@ import {...@@ -9,6 +9,7 @@ import {
9 type ColorValue,9 type ColorValue,
10 Pressable,10 Pressable,
11 ScrollView,11 ScrollView,
12 StyleSheet,
12 View,13 View,
13} from "react-native";14} from "react-native";
14import Svg, { Defs, LinearGradient, Rect, Stop } from "react-native-svg";15import Svg, { Defs, LinearGradient, Rect, Stop } from "react-native-svg";
...@@ -143,7 +144,7 @@ function ShimmeringWorkContent(props: {...@@ -143,7 +144,7 @@ function ShimmeringWorkContent(props: {
143144
144 return (145 return (
145 <View146 <View
146 className="min-w-0 flex-1"147 className="min-w-0 flex-1 overflow-hidden"
147 onLayout={(event) => setAvailableWidth(event.nativeEvent.layout.width)}148 onLayout={(event) => setAvailableWidth(event.nativeEvent.layout.width)}
148 >149 >
149 <ShimmerWorkContent150 <ShimmerWorkContent
...@@ -164,7 +165,7 @@ function ShimmeringWorkContent(props: {...@@ -164,7 +165,7 @@ function ShimmeringWorkContent(props: {
164 style={[{ width: SHIMMER_WIDTH }, sweepStyle]}165 style={[{ width: SHIMMER_WIDTH }, sweepStyle]}
165 >166 >
166 <MaskedView167 <MaskedView
167 className="absolute inset-0"168 style={StyleSheet.absoluteFill}
168 maskElement={169 maskElement={
169 <Svg width="100%" height="100%">170 <Svg width="100%" height="100%">
170 <Defs>171 <Defs>
apps/mobile/src/lib/threadActivity.test.ts+20-1
...@@ -942,8 +942,27 @@ describe("buildThreadFeed", () => {...@@ -942,8 +942,27 @@ describe("buildThreadFeed", () => {
942 summary: "Running pnpm",942 summary: "Running pnpm",
943 summaryKind: "command",943 summaryKind: "command",
944 live: true,944 live: true,
945 shimmer: false,945 shimmer: true,
946 });946 });
947 expect(rows[0]).toMatchObject({ live: false, shimmer: false });
948
949 const stoppedRows = deriveThreadFeedPresentation(feed, latestTurn, new Set());
950 expect(stoppedRows.filter((entry) => entry.type === "work-toggle")).toMatchObject([
951 { live: false, shimmer: false },
952 { live: false, shimmer: false },
953 ]);
954
955 const completedRows = deriveThreadFeedPresentation(
956 feed,
957 { ...latestTurn, state: "completed", completedAt: "2026-04-01T00:00:04.000Z" },
958 new Set([turnId]),
959 new Set(),
960 latestTurn.startedAt,
961 );
962 expect(completedRows.filter((entry) => entry.type === "work-toggle")).toMatchObject([
963 { live: false, shimmer: false },
964 { live: false, shimmer: false },
965 ]);
947 });966 });
948967
949 it("does not revive cached in-progress tools after work stops", () => {968 it("does not revive cached in-progress tools after work stops", () => {
apps/mobile/src/lib/threadActivity.ts+2-4
...@@ -1512,10 +1512,8 @@ function appendToolGroupRows(...@@ -1512,10 +1512,8 @@ function appendToolGroupRows(
1512 ),1512 ),
1513 hasFailure: activities.findLast((activity) => activity.toolLike)?.status === "failure",1513 hasFailure: activities.findLast((activity) => activity.toolLike)?.status === "failure",
1514 live,1514 live,
1515 shimmer:1515 // Match the live label until the turn or contiguous tool run settles.
1516 isWorking &&1516 shimmer: live,
1517 latestActivity.lifecycleStatus === "inProgress" &&
1518 latestActivity.turnId === unsettledTurnId,
1519 });1517 });
1520 if (!expanded) {1518 if (!expanded) {
1521 return;1519 return;