Conversation
There was a problem hiding this comment.
Pull request overview
Adds an “aquarium” animation overlay to the Agents window new-session screen, with a persistent toggle button in the chat bar and animated VS Code-logo “fish”/food interactions.
Changes:
- Registers a new sessions workbench contribution that mounts an aquarium overlay and toggle button (AfterRestored).
- Implements animated fish/food/water layers and supporting SVG generation + styling.
- Adds supporting context key, i18n resource mapping, and stylelint allowlisting for new CSS variables.
Show a summary per file
| File | Description |
|---|---|
| src/vs/sessions/sessions.common.main.ts | Loads the new aquarium contribution in the sessions workbench entrypoint. |
| src/vs/sessions/contrib/aquarium/browser/aquarium.contribution.ts | Registers the aquarium workbench contribution and instantiates the overlay. |
| src/vs/sessions/contrib/aquarium/browser/aquariumOverlay.ts | Implements overlay lifecycle, DOM layers, interactions, and RAF-driven animation loop. |
| src/vs/sessions/contrib/aquarium/browser/fish.ts | Builds fish DOM/SVG (strip-clipped VS Code logo) and shared SVG defs. |
| src/vs/sessions/contrib/aquarium/browser/media/aquarium.css | Adds water/fish/food/toggle styling, animations, and reduced-motion handling. |
| src/vs/sessions/common/contextkeys.ts | Introduces sessionsAquariumActive context key. |
| build/lib/stylelint/vscode-known-variables.json | Allowlists new CSS custom properties used by the aquarium feature. |
| build/lib/i18n.resources.json | Adds a sessions i18n project mapping for vs/sessions/contrib/aquarium. |
Copilot's findings
Comments suppressed due to low confidence (1)
src/vs/sessions/contrib/aquarium/browser/aquariumOverlay.ts:343
- Food drops are wired to
EventType.MOUSE_DOWN. On iOS this event doesn’t fire (pointer events are used), so users won’t be able to drop food. Please useaddDisposableGenericMouseDownListener(or pointer events) for cross-platform input.
store.add(addDisposableListener(mainContainer, EventType.MOUSE_DOWN, (e: MouseEvent) => {
// Only spawn food on plain left clicks against background-ish surfaces.
if (e.button !== 0) {
return;
}
- Files reviewed: 8/8 changed files
- Comments generated: 7
There was a problem hiding this comment.
Copilot's findings
Comments suppressed due to low confidence (3)
src/vs/sessions/contrib/aquarium/browser/aquariumOverlay.ts:324
createActiveAquarium()mixestargetWindow.document(for fish) with the globaldocument(forwater,fishLayer,foodLayer). In multi-window scenarios this can create nodes in the wrong document/realm. Prefer consistently usingtargetWindow.document.createElement(...)here so the overlay is built in the same window asmainContainer.
const water = document.createElement('div');
water.className = 'agents-aquarium-water';
// Insert as the FIRST child so all subsequent chat bar content paints over it.
chatBar.insertBefore(water, chatBar.firstChild);
store.add(toDisposable(() => water.remove()));
const fishLayer = document.createElement('div');
fishLayer.className = 'agents-aquarium-fish-layer';
water.appendChild(fishLayer);
const foodLayer = document.createElement('div');
foodLayer.className = 'agents-aquarium-food-layer';
water.appendChild(foodLayer);
src/vs/sessions/contrib/aquarium/browser/aquariumOverlay.ts:470
spawnFood()uses the globaldocument.createElement('div')even though this aquarium instance is scoped totargetWindow. For consistency (and to avoid cross-window DOM issues), usetargetWindow.document.createElement(...)here too.
const el = document.createElement('div');
el.className = 'agents-aquarium-food';
el.style.transform = `translate(${fx}px, ${fy}px)`;
foodLayer.appendChild(el);
food.push({ element: el, x: fx, y: fy, vy: randomBetween(20, 35) });
src/vs/sessions/contrib/aquarium/browser/aquariumOverlay.ts:406
- Disposing the shared SVG defs container as part of each aquarium instance's disposal is unsafe when there can be overlap (e.g., deactivate triggers an exit animation + delayed dispose, then activate creates a new aquarium before the delay completes). When the old instance disposes it will remove defs still referenced by the new fish. Consider ref-counting the shared defs (increment on activation, decrement on disposal) or tying defs lifetime to
AquariumOverlayinstead of the per-activationstore.
// Tear down the shared SVG defs container along with the last
// active aquarium so we don't leak it across reloads.
disposeSharedFishDefs();
}));
- Files reviewed: 8/8 changed files
- Comments generated: 6
There was a problem hiding this comment.
Copilot's findings
Comments suppressed due to low confidence (4)
src/vs/sessions/contrib/aquarium/browser/aquariumOverlay.ts:381
- Listening for
'scroll'ontargetWindowwith{ capture: true }will fire for scrolls anywhere in the document (since scroll doesn't bubble but is capturable). CallinggetBoundingClientRect()inupdateBounds()on every scroll can force frequent layouts and cause jank. Consider throttling this (e.g. schedule oneupdateBounds()per animation frame), or scope the listener to the specific scroll container(s) that can actually move the chat bar.
// Listen on the main container so we always know cursor position even
// when over the chat input (water has pointer-events:none).
store.add(addDisposableListener(targetWindow, EventType.RESIZE, updateBounds, { passive: true }));
store.add(addDisposableListener(targetWindow, 'scroll', updateBounds, { passive: true, capture: true }));
src/vs/sessions/contrib/aquarium/browser/aquariumOverlay.ts:447
- When
prefers-reduced-motionis enabled, the RAF loop continues to schedule frames indefinitely even thoughupdateFood/updateFishare skipped. This still consumes CPU/battery. Consider not starting the RAF loop at all (or stopping it) whenreduceMotionis true, and keep the aquarium in its initial static state.
const reduceMotion = targetWindow.matchMedia?.('(prefers-reduced-motion: reduce)').matches ?? false;
let lastFrame = performance.now();
let rafDisposable: IDisposable | undefined;
const tick = () => {
const now = performance.now();
const dtMs = Math.min(now - lastFrame, 100); // clamp big stalls
const dt = dtMs / 1000;
lastFrame = now;
// Skip work when window is hidden (RAF stays alive lazily).
const visible = targetWindow.document.visibilityState !== 'hidden';
if (visible && !reduceMotion) {
updateFood(dt);
updateFish(dt);
}
rafDisposable = scheduleAtNextAnimationFrame(targetWindow, tick);
};
src/vs/sessions/contrib/aquarium/browser/aquariumOverlay.ts:291
- The aquarium DOM is purely decorative, but the created elements (
water, fish/food layers) are not marked as such. Addaria-hidden="true"(and/orrole="presentation") to these containers (and likely individual fish/food elements) so they don't appear in the accessibility tree.
const water = doc.createElement('div');
water.className = 'agents-aquarium-water';
// First child so subsequent chat bar content paints over it.
chatBar.insertBefore(water, chatBar.firstChild);
store.add(toDisposable(() => water.remove()));
const fishLayer = doc.createElement('div');
fishLayer.className = 'agents-aquarium-fish-layer';
water.appendChild(fishLayer);
const foodLayer = doc.createElement('div');
foodLayer.className = 'agents-aquarium-food-layer';
water.appendChild(foodLayer);
src/vs/sessions/contrib/aquarium/browser/aquariumOverlay.ts:276
createActiveAquariumreturns a no-op aquarium when the chat bar container isn't available/visible, butactivate()still treats it as active (context key set, toggle shows active, and user preference may be persisted). This can leave the UI in an “enabled but nothing happens” state. Consider havingcreateActiveAquariumreturnundefined(or throw a known error) when the chat bar isn't ready, and haveactivate()bail out without updating context/button state (optionally retry once the chat bar becomes visible).
// Host inside the chat bar so chat input UI naturally paints on top —
// no z-index gymnastics required.
const chatBar = layoutService.getContainer(targetWindow, Parts.CHATBAR_PART);
if (!chatBar || !layoutService.isVisible(Parts.CHATBAR_PART, targetWindow)) {
return {
dispose: () => store.dispose(),
exit: () => { store.dispose(); return store; },
};
}
- Files reviewed: 9/9 changed files
- Comments generated: 2
an easter egg :)
builds an svg structure, then animates with css!!!!