Skip to content

Commit 50febf2

Browse files
committed
Adds compose/review working animation
- Adds a reduced-motion-aware categorizing animation behind AI loading states and surfaces review finding counts Improves the "back/foward" button/bar in compose/review - Replaces small resume chips with clearer full-width resume bars that preview saved compose/review results - Tracks and clears resume metadata with the saved workflow state so stale counts don't linger
1 parent 252a8a2 commit 50febf2

11 files changed

Lines changed: 662 additions & 76 deletions

src/webviews/apps/plus/graph/components/detailsActions.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1899,6 +1899,7 @@ export class DetailsActions {
18991899
this.state.activeModeContext.set(null);
19001900
this.resources.compose.reset();
19011901
this.state.composeForwardAvailable.set(false);
1902+
this.state.composeBackPreview.set(undefined);
19021903
this.refreshWip();
19031904
void this.fetchDetails(sha, repoPath, graphReachability);
19041905
}
@@ -1933,6 +1934,7 @@ export class DetailsActions {
19331934
this.state.activeModeContext.set(null);
19341935
this.resources.compose.reset();
19351936
this.state.composeForwardAvailable.set(false);
1937+
this.state.composeBackPreview.set(undefined);
19361938
this.refreshWip();
19371939
void this.fetchDetails(sha, repoPath, graphReachability);
19381940
}

src/webviews/apps/plus/graph/components/detailsState.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,13 @@ function createTransientState() {
258258
const reviewForwardAvailable = signal(false);
259259
const composeForwardAvailable = signal(false);
260260

261+
// Preview metadata for the resume bar shown above the idle panel — derived from the
262+
// orchestrator's back-snapshot so the bar can show counts (commits/files, focus areas)
263+
// without re-deriving them from data the panel doesn't own. Cleared in lockstep with
264+
// the *ForwardAvailable signals above.
265+
const composeBackPreview = signal<{ commitCount: number; fileCount: number } | undefined>(undefined);
266+
const reviewBackPreview = signal<{ findingCount: number; fileCount: number } | undefined>(undefined);
267+
261268
// Compose progress + apply state. `composeProgressMessage` mirrors the latest phase label
262269
// streamed by the library while compose is running (cleared to undefined when the run ends).
263270
// `composeApplying` is true between an apply-plan click and the IPC's resolution — drives
@@ -315,6 +322,8 @@ function createTransientState() {
315322
wipStale: wipStale,
316323
reviewForwardAvailable: reviewForwardAvailable,
317324
composeForwardAvailable: composeForwardAvailable,
325+
composeBackPreview: composeBackPreview,
326+
reviewBackPreview: reviewBackPreview,
318327
composeProgressMessage: composeProgressMessage,
319328
composeApplying: composeApplying,
320329

src/webviews/apps/plus/graph/components/detailsWorkflowController.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -419,6 +419,22 @@ export class DetailsWorkflowController implements ReactiveController {
419419
if (value != null) {
420420
this._reviewBackSnapshot = value;
421421
this.actions.state.reviewForwardAvailable.set(true);
422+
if ('result' in value) {
423+
const filesSet = new Set<string>();
424+
let findingCount = 0;
425+
for (const area of value.result.focusAreas) {
426+
findingCount += area.findings?.length ?? 0;
427+
for (const f of area.files) {
428+
filesSet.add(f);
429+
}
430+
}
431+
this.actions.state.reviewBackPreview.set({
432+
findingCount: findingCount,
433+
fileCount: filesSet.size,
434+
});
435+
} else {
436+
this.actions.state.reviewBackPreview.set(undefined);
437+
}
422438
}
423439
}
424440
this.actions.resources.review.reset();
@@ -434,6 +450,7 @@ export class DetailsWorkflowController implements ReactiveController {
434450
invalidateSnapshot: (): void => {
435451
this._reviewBackSnapshot = undefined;
436452
this.actions.state.reviewForwardAvailable.set(false);
453+
this.actions.state.reviewBackPreview.set(undefined);
437454
},
438455
};
439456

@@ -466,6 +483,11 @@ export class DetailsWorkflowController implements ReactiveController {
466483
if (value != null && 'result' in value) {
467484
this._composeBackSnapshot = value;
468485
this.actions.state.composeForwardAvailable.set(true);
486+
const totalFiles = value.result.commits.reduce((sum, c) => sum + c.files.length, 0);
487+
this.actions.state.composeBackPreview.set({
488+
commitCount: value.result.commits.length,
489+
fileCount: totalFiles,
490+
});
469491
}
470492
}
471493
this.actions.resources.compose.reset();
@@ -479,6 +501,7 @@ export class DetailsWorkflowController implements ReactiveController {
479501
invalidateSnapshot: (): void => {
480502
this._composeBackSnapshot = undefined;
481503
this.actions.state.composeForwardAvailable.set(false);
504+
this.actions.state.composeBackPreview.set(undefined);
482505
},
483506
};
484507

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
import { css } from 'lit';
2+
3+
export const categorizingLoadingAnimationStyles = css`
4+
:host {
5+
display: block;
6+
position: relative;
7+
width: 100%;
8+
height: 100%;
9+
overflow: hidden;
10+
opacity: 0;
11+
transition: opacity 0.6s ease-in;
12+
--gl-loading-accent: var(--vscode-charts-purple, #c084fc);
13+
}
14+
15+
:host([variant='review']) {
16+
--gl-loading-accent: var(--vscode-charts-yellow, #facc15);
17+
}
18+
19+
:host([data-ready]) {
20+
opacity: 1;
21+
}
22+
23+
.stage {
24+
position: absolute;
25+
inset: 0;
26+
}
27+
28+
.bucket {
29+
position: absolute;
30+
border-bottom: 0.2rem solid currentColor;
31+
border-radius: 0.6rem;
32+
opacity: 0.55;
33+
background: linear-gradient(180deg, transparent 0%, color-mix(in srgb, currentColor 12%, transparent) 100%);
34+
}
35+
36+
.lens {
37+
position: absolute;
38+
border-top: 1px solid color-mix(in srgb, var(--vscode-foreground) 12%, transparent);
39+
border-bottom: 1px solid color-mix(in srgb, var(--vscode-foreground) 12%, transparent);
40+
background: linear-gradient(
41+
90deg,
42+
transparent 0%,
43+
color-mix(in srgb, var(--vscode-foreground) 4%, transparent) 50%,
44+
transparent 100%
45+
);
46+
overflow: hidden;
47+
}
48+
49+
.lens__scanline {
50+
position: absolute;
51+
left: 0;
52+
right: 0;
53+
top: 0;
54+
height: 1px;
55+
background: linear-gradient(
56+
90deg,
57+
transparent 0%,
58+
color-mix(in srgb, var(--gl-loading-accent) 70%, transparent) 50%,
59+
transparent 100%
60+
);
61+
animation: gl-categorizing-scanline 1.4s ease-in-out infinite alternate;
62+
will-change: top, opacity;
63+
}
64+
65+
.particle {
66+
position: absolute;
67+
top: 0;
68+
left: 0;
69+
width: 0.8rem;
70+
height: 0.8rem;
71+
border-radius: 50%;
72+
background: color-mix(in srgb, var(--vscode-foreground) 35%, transparent);
73+
filter: blur(1px);
74+
opacity: 0;
75+
will-change: transform, opacity;
76+
}
77+
78+
.particle--categorized {
79+
filter: none;
80+
width: 0.6rem;
81+
height: 0.6rem;
82+
}
83+
84+
@keyframes gl-categorizing-scanline {
85+
0% {
86+
top: 0;
87+
opacity: 0.35;
88+
}
89+
50% {
90+
opacity: 1;
91+
}
92+
100% {
93+
top: calc(100% - 1px);
94+
opacity: 0.35;
95+
}
96+
}
97+
98+
@media (prefers-reduced-motion: reduce) {
99+
:host {
100+
display: none;
101+
}
102+
}
103+
`;

0 commit comments

Comments
 (0)