Skip to content

Web Lab 2: smooth Instructions ↔ AI Tutor tab transition#73282

Draft
breville wants to merge 3 commits into
stagingfrom
weblab2-tab-switch-animation
Draft

Web Lab 2: smooth Instructions ↔ AI Tutor tab transition#73282
breville wants to merge 3 commits into
stagingfrom
weblab2-tab-switch-animation

Conversation

@breville

@breville breville commented Jun 16, 2026

Copy link
Copy Markdown
Member

Background

In weblab2, the info panel has separate Instructions and AI Tutor tabs. Both can show the level's instructions; the AI Tutor tab shows the instructions in a collapsible drawer above the chat. Because each tab rendered its own copy of the instructions and switching swapped the two panels, the transition was rough — instructions that were visible on both tabs would re-fade, the chat would grow in, and the instructions would replay their slide-in animation as if remounting.

This PR makes switching feel stable: nothing moves when it shouldn't, and the one case that benefits from motion — switching while the instructions drawer is open — animates smoothly.

What changed (user-visible)

  • Switching with the instructions drawer closed (the default) is instant. The AI Tutor chat is already mounted and laid out, so switching reveals it with nothing growing or sliding; switching back shows the instructions exactly as they were, with no remount and no slide-in replay.
  • Switching with the instructions drawer open animates. The instructions smoothly resize between full-height and drawer-height while the chat is revealed in place (the message input stays put).
  • The Show/Hide Instructions toggle still animates in both directions.
  • No layout flash on entering the AI Tutor tab.
  • Respects prefers-reduced-motion.

Video

weblab2-instructions-tutor-animation-1.mov

How it works

  • The Instructions and AI Tutor tabs now share a single persistent AiTutorChatWithInstructionDrawer instance (driven by aiTutorActive = currentTab === Tabs.AiTutor) instead of each rendering its own instructions, so the instructions element never crossfades — it only resizes.
  • The instructions drawer stays mounted even when hidden (height 0, padding 0, inert), so switching tabs never remounts it and replays the instructions' slidein keyframe.
  • Animation is gated on drawer state: animateLayout = !isCollapsed || <within the close-settle window>. When the drawer is settled closed, an .instant class drops the height/opacity/top transitions so tab switches are instant.
  • The hidden drawer/toggle are driven to height/top 0 synchronously (not via the effect-driven instructionsHeight, which lagged a frame and briefly flashed the full-height drawer).
  • focus({preventScroll: true}) when focusing tab content, so auto-focusing the chat input no longer scrolls the panel up for a frame.
  • The chat is laid out at a fixed, bottom-anchored height (chatContentHeight) and revealed by an animating clip window rather than reflowing.

Scope

Gated to the weblab2 instructions-drawer case (hasInstructionsDrawer && aiTutorVisible && longInstructions). Other tabs/labs are unaffected; they keep the existing opacity crossfade on .tabContent.

Notes

  • Rebased/merged on latest staging, which defaults the AI Tutor drawer to collapsed and the initial weblab2 tab to Instructions; this PR's behavior is built on top of that. The merge reconciled staging's AiTutorChatWithInstructionDrawer refactor (removed isCollapsedByDefault, added initialWelcomeMessage) with this work.

Testing

  • Verified frame-by-frame in a local browser on a weblab2 level with long instructions:
  • Closed-drawer switch → AI Tutor: chat is full-size from the first frame (no grow); synchronous measurement right after the click shows the drawer already at height 0 (no stale full-height frame).
  • Closed-drawer switch → Instructions: the instructions DOM node persists (no remount), slidein does not replay (margin/opacity stable), height is correct immediately (no slide).
  • Open-drawer switch: drawer eases between drawer-height and full over ~200ms, chat revealed in place.
  • Show/Hide Instructions still animates; collapsed-vs-open and the inert/tabbability of hidden content checked.
  • yarn typecheck, eslint, stylelint, and the pre-commit hook pass.

🤖 Generated with Claude Code

The Instructions and AI Tutor tabs both render the level's instructions, so
crossfading the two tab panels re-faded instructions that were already visible.
Render a single persistent AiTutorChatWithInstructionDrawer instance shared by
both tabs instead: the instructions element stays mounted and only resizes,
while the chat and the Hide/Show Instructions toggle fade and slide in.

- ResourcePanel renders one shared drawer panel covering both tabs, driven by
  aiTutorActive = (currentTab === AiTutor). Other tabs keep the opacity
  crossfade on .tabContent.
- The drawer height, chat, and toggle transition (height/opacity/top);
  suppressed mid-drag and under prefers-reduced-motion.
- The chat is laid out at a fixed, bottom-anchored height (chatContentHeight)
  and revealed by an animating clip window, so it is unveiled in place rather
  than reflowing/sliding under the toggle.
- focus({preventScroll}) when focusing tab content, so auto-focusing the chat
  input no longer scrolls the panel up for a frame on entering the AI Tutor tab.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@breville breville added the AI generated This PR has been substantially generated using AI. label Jun 16, 2026
@breville breville marked this pull request as draft June 16, 2026 18:10
breville and others added 2 commits June 16, 2026 18:45
# Conflicts:
#	apps/src/lab2/views/components/Instructions/ResourcePanel/AiTutorChatWithInstructionsDrawer/AiTutorChatWithInstructionDrawer.tsx
…open

Staging now defaults the AI Tutor instructions drawer to collapsed, so the
default Instructions <-> AI Tutor switch is full-instructions <-> chat-only.
Animating that morph looked bad: the chat grew in, and switching back remounted
the instructions and replayed their slide-in. Gate the animation on drawer state
instead.

- animateLayout = !isCollapsed || <within close-settle window>; when the drawer
  is settled closed, an .instant class drops the height/opacity/top transitions
  so tab switches are instant (nothing grows or slides). Open-drawer switches and
  the open/close toggle still animate.
- Keep the instructions drawer mounted even when hidden (height 0, padding 0,
  inert) instead of unmounting it, so switching tabs never remounts it and
  replays the instructions' slidein keyframe.
- Drive the hidden drawer/toggle to height/top 0 synchronously (drawerHeight)
  rather than the effect-driven instructionsHeight, which lagged a frame and
  flashed the full-height drawer for one frame on switching to a closed tab.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

AI generated This PR has been substantially generated using AI.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant