Skip to content

Add chat pet component fixtures - #329460

Open
Henning Dieterichs (hediet) wants to merge 1 commit into
mainfrom
hediet/chat-pet-component-fixtures
Open

Add chat pet component fixtures#329460
Henning Dieterichs (hediet) wants to merge 1 commit into
mainfrom
hediet/chat-pet-component-fixtures

Conversation

@hediet

@hediet Henning Dieterichs (hediet) commented Aug 6, 2026

Copy link
Copy Markdown
Member

Summary

  • separate chat pet behavior from rendering while keeping both classes in chatPetWidget.ts
  • add themed component fixtures for the pet animation states with deterministic timeline controls
  • loop enabled animations and provide a draggable gaze target for cursor-aware states
  • expose fixture animation enablement so pet fixtures can pause and scrub canvas and CSS animations

Testing

  • npm run typecheck-client
  • targeted ESLint for the changed files
  • npm run transpile-client
  • .\scripts\test.bat --run src\vs\workbench\contrib\chat\test\browser\widget\chatPetWidget.test.ts (19 passing)
chrome_GCMoFIZhw6

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: d9a5064b-06cf-4444-99ae-be62859a1c46
Copilot AI balanced review requested due to automatic review settings August 6, 2026 18:05
view.setResisting(true);
break;
}
if (options.position === 'left') {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI Review: RenderingSpeechBubbleLeft sets position: 'left', which moves the pet to the left edge. The speech bubble only flips left when the pet is near the right boundary, so this fixture never exercises the state its name promises. Could this place the pet at the right edge instead (or be renamed if the current layout is intentional)?

const sources = getSpeechSpriteSources(this._variant, this._options.resourceBaseUrl);
const source = this._motionReduced ? sources.reducedMotion : sources.animated;
if (!isChatPetImageSource(this._speechBubble.image, source.url)) {
this._speechAnimation.clear();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI Review: When the speech source changes, this clears the disposable but leaves _activeSpeechAnimation pointing at the previous source until the replacement image loads. Because whenReady() only checks that this field is defined, it can resolve immediately and miss a subsequent load error. Please clear _activeSpeechAnimation synchronously here and add coverage for speaking-state variant reload readiness.

@justschen Justin Chen (justschen) left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

awesome! as mentioned, gonna hold off while we hash out the last bit of the animations, but okay to merge next week once we finalize how we want the pet to work

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

Note

This error may be related to your runner configuration. You can now configure runners for Copilot code review separately from Copilot cloud agent by creating a copilot-code-review.yml file with your setup steps. Read the docs for details.

@dmitrivMS
Dmitriy Vasyura (dmitrivMS) requested a balanced review from Copilot August 6, 2026 20:28

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

Note

This error may be related to your runner configuration. You can now configure runners for Copilot code review separately from Copilot cloud agent by creating a copilot-code-review.yml file with your setup steps. Read the docs for details.

@dmitrivMS
Dmitriy Vasyura (dmitrivMS) requested a balanced review from Copilot August 6, 2026 21:26

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

Note

This error may be related to your runner configuration. You can now configure runners for Copilot code review separately from Copilot cloud agent by creating a copilot-code-review.yml file with your setup steps. Read the docs for details.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review details

Suppressed comments (5)

src/vs/workbench/test/browser/componentFixtures/fixtureUtils.ts:393

  • The schema now supports enableAnimationsByDefault, but the render path still uses parseFixtureInput(context.input) (which defaults enableAnimations to false when the property is missing). If context.input doesn’t already include schema defaults, fixtures that set enableAnimationsByDefault: true may still initialize with animations disabled. Consider parsing via this Zod schema (so defaults are applied) or updating parseFixtureInput to accept/derive the default value.
function createFixtureInputSchema(enableAnimationsByDefault: boolean) {
	return z.object({
		reverseStylesheets: z.boolean().default(false).describe('Reverse the order of the bundled CSS documents to surface cascade-order dependencies.'),
		reverseStylesheetsRange: z.object({
			fromIndex: z.number(),
			toIndex: z.number(),
		}).optional().describe('Reverse the bundled CSS documents in this half-open index range.'),
		enableAnimations: z.boolean().default(enableAnimationsByDefault).describe('Enable CSS animations and transitions.'),
		outputTimeTrace: z.boolean().default(false).describe('Return the render\'s virtual-time trace as its output.'),
		outputStylesheetFiles: z.boolean().default(false).describe('Return the bundled stylesheet files as the render output.'),
	});
}

src/vs/workbench/contrib/chat/browser/widget/chatPetWidget.ts:543

  • String-concatenating resourceBaseUrl and resource can produce malformed URLs (e.g. double slashes when resourceBaseUrl ends with /, or awkward results if resourceBaseUrl is a full URL). Consider normalizing (trim trailing slash) or using a URI/path join helper so resourceBaseUrl can be safely configured for fixtures and future callers.
function resolveChatPetResource(resource: AppResourcePath, resourceBaseUrl: string | undefined): string {
	return resourceBaseUrl === undefined ? FileAccess.asBrowserUri(resource).toString(true) : `${resourceBaseUrl}/${resource}`;
}

src/vs/workbench/contrib/chat/browser/widget/chatPetWidget.ts:1140

  • This now clears/redraws the canvas on every scheduled tick, even when frame.frameIndex hasn’t changed. The previous implementation avoided unnecessary draws by tracking the current frame index. Reintroducing a simple lastFrameIndex check (per sprite animation) would reduce CPU/GPU work during long-running animations, especially when frame delays are small or when the timer fires slightly early/late.
	private _drawAnimationFrame(source: ChatPetSpriteSource, sprite: ChatPetSpriteElement, elapsed: number): ChatPetAnimationFrame {
		const context = sprite.canvas.getContext('2d');
		const frame = getChatPetAnimationFrame(source.frameDurations, elapsed, this._options.loopAnimations ? Infinity : source.iterations);
		if (!context) {
			return frame;
		}
		context.imageSmoothingEnabled = false;
		context.clearRect(0, 0, CHAT_PET_SOURCE_SIZE, CHAT_PET_SOURCE_SIZE);
		context.drawImage(
			sprite.image,
			frame.frameIndex * CHAT_PET_SOURCE_SIZE,
			0,
			CHAT_PET_SOURCE_SIZE,
			CHAT_PET_SOURCE_SIZE,
			0,
			0,
			CHAT_PET_SOURCE_SIZE,
			CHAT_PET_SOURCE_SIZE
		);
		return frame;
	}

src/vs/workbench/contrib/chat/browser/widget/chatPetWidget.ts:1020

  • The new error doesn’t include enough context to diagnose which render triggered it (e.g. pending state, variant, and/or whether it was the speech bubble vs main sprite). Consider including this._pendingState, this._variant, and the relevant source URL/type in the error message to make fixture failures and telemetry/debugging actionable.
	private _onImageError(image: HTMLImageElement): void {
		this._renderError = new Error(`Failed to load chat pet resource: ${image.src}`);
		this._onDidRender.fire();
	}

src/vs/workbench/contrib/chat/browser/widget/chatPetWidget.ts:794

  • The new externally-controlled animation mode (pausing/resuming sprite + CSS animations based on animationTime) is complex and can regress easily. Please add targeted tests that: (1) assert animations are paused and scrubbed when animationTime is set, (2) assert _resumeAnimations() restarts animations when animationTime becomes undefined, and (3) validate onDidCompleteAnimation behavior in controlled mode (completion reported once per completion).
		this._register(autorun(reader => {
			const animationTime = this._animationTime.read(reader);
			if (animationTime === undefined) {
				if (this._animationControlled) {
					this._animationControlled = false;
					this._resumeAnimations();
				}
				return;
			}
			this._animationControlled = true;
			this._spriteAnimation.clear();
			this._speechAnimation.clear();
			this._renderControlledAnimation(this._activeSpriteAnimation, animationTime);
			this._renderControlledAnimation(this._activeSpeechAnimation, animationTime);
			this._updateCssAnimationTime(animationTime);
		}));
  • Files reviewed: 3/3 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants