feat(ui): wave animation on grid focus change, smooth filled terminal borders#236
Merged
forketyfork merged 4 commits intomainfrom Feb 22, 2026
Merged
feat(ui): wave animation on grid focus change, smooth filled terminal borders#236forketyfork merged 4 commits intomainfrom
forketyfork merged 4 commits intomainfrom
Conversation
… borders Issue: Cmd+Arrow grid navigation had no visual feedback. Terminal borders were nested 1px rounded outlines with a fixed corner radius, so the arcs diverged at corners and looked jagged. The DPI scaling helper was stuck in src/ui/scale.zig, unreachable from the rendering layer without breaking the architecture rules. Solution: Cmd+Arrow now triggers a brief squish-wave on the destination terminal at half the amplitude of the agent-notification wave. drawThickBorder was rewritten to scanline-fill the donut between outer and inner rounded rects, giving uniform border thickness with clean corners. The inner corner radius shrinks by the border thickness to keep the frame consistent all the way around. The DPI helper moved to src/dpi.zig alongside geom and colors, where the renderer can reach it cleanly.
Issue: Waving terminal was hidden under neighbours in grid view; nav wave animation was too slow and too prominent; border was too thin. Solution: Two-pass grid rendering ensures waving terminals draw last (on top). Nav wave duration reduced to 250ms and amplitude to 0.02. Border thickness increased from 3 to 6px. renderWaveStrips now takes total_ms so attention and nav waves can have independent durations.
There was a problem hiding this comment.
Pull request overview
This PR adds a distinct “navigation wave” animation when grid focus changes via Cmd+Arrow, improves terminal border rendering to use a scanline-filled rounded-rect donut for smoother corners, and relocates the DPI scaling helper so the render layer can import it.
Changes:
- Add
nav_wave_start_timeto session view state and plumb a new nav-wave timing/amplitude through grid rendering. - Rewrite
drawThickBorderto fill the border area (donut) between outer/inner rounded rects for consistent corner thickness; add an explicitradiusparameter. - Move DPI scaling helper to
src/dpi.zigand update UI component imports accordingly.
Reviewed changes
Copilot reviewed 21 out of 22 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| src/ui/session_view_state.zig | Adds nav_wave_start_time to support independent nav-wave animation state. |
| src/ui/components/session_interaction.zig | Introduces nav-wave constants, triggerNavWave(), frame scheduling, and new tests. |
| src/render/renderer.zig | Two-pass grid rendering to draw waving sessions on top; uses nav wave in renderGridSessionCached; passes DPI-scaled border radius into thick border drawing. |
| src/gfx/primitives.zig | Replaces nested-outline thick border with scanline-filled donut border; adds roundedRectXSpan helper. |
| src/dpi.zig | New shared DPI scaling helper module (previously under src/ui/). |
| src/app/runtime.zig | Triggers nav wave after Cmd+Arrow grid navigation. |
| src/ui/components/worktree_overlay.zig | Updates DPI import path to new src/dpi.zig. |
| src/ui/components/story_overlay.zig | Updates DPI import path to new src/dpi.zig. |
| src/ui/components/scrollbar.zig | Updates DPI import path to new src/dpi.zig. |
| src/ui/components/recent_folders_overlay.zig | Updates DPI import path to new src/dpi.zig. |
| src/ui/components/reader_overlay.zig | Updates DPI import path to new src/dpi.zig. |
| src/ui/components/quit_confirm.zig | Updates DPI import path to new src/dpi.zig. |
| src/ui/components/hotkey_indicator.zig | Updates DPI import path to new src/dpi.zig. |
| src/ui/components/help_overlay.zig | Updates DPI import path to new src/dpi.zig. |
| src/ui/components/fullscreen_overlay.zig | Updates DPI import path to new src/dpi.zig. |
| src/ui/components/flowing_line.zig | Updates DPI import path to new src/dpi.zig. |
| src/ui/components/expanding_overlay.zig | Updates DPI import path to new src/dpi.zig. |
| src/ui/components/escape_hold.zig | Updates DPI import path to new src/dpi.zig. |
| src/ui/components/diff_overlay.zig | Updates DPI import path to new src/dpi.zig. |
| src/ui/components/cwd_bar.zig | Updates DPI import path to new src/dpi.zig. |
| src/ui/components/confirm_dialog.zig | Updates DPI import path to new src/dpi.zig. |
| src/ui/components/button.zig | Updates DPI import path to new src/dpi.zig. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Issue: Redundant markDirty() after triggerNavWave(); border thickness not DPI-scaled; PR description out of sync with actual constants. Solution: Remove the redundant markDirty() call since triggerNavWave() already calls it. Apply dpi.scale() to attention_thickness at the draw site in renderSessionOverlays so border thickness scales correctly on Retina displays. Update PR description to reflect current values (250ms, 0.02 amplitude, 6px logical border, z-order fix).
Issue: dpi.zig (moved from ui/scale.zig) was missing from shared utilities documentation; Cmd+Arrow grid navigation was undocumented in README. Solution: Add dpi to the shared utilities lists in ARCHITECTURE.md. Add Cmd+Arrow navigation with wave animation to the keyboard navigation line in README.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Cmd+Arrow navigation in grid view now triggers a short squish-wave on the destination terminal. The nav wave runs for 250ms (vs 400ms for the attention wave) with amplitude 0.02 (vs 0.08), so it reads as "you moved here" rather than "something happened here." The waving terminal renders on top of its neighbours during the animation so the expanded strips aren't occluded. Terminal borders are redrawn with a scanline donut fill instead of stacked 1px arcs, which gets rid of the jagged corners and makes thickness consistent all the way around. Border thickness is now DPI-scaled (6 logical px × display density). The DPI scaling helper moved from
src/ui/scale.zigtosrc/dpi.zigso the rendering layer can reach it without breaking the architecture layering rules.Closes #235
Changes
src/gfx/primitives.zig:drawThickBorderused to drawthicknessnested 1px outlines with the same corner radius. As each outline shrank inward, the arcs drifted apart and the corners looked uneven. The new version scanline-fills the donut between an outer rounded rect and an inner rounded rect inset bythickness, with inner radius clamped tomax(0, outer_radius - thickness). The border is now the same width everywhere, corners included. Aradiusparameter replaces the hardcoded 12px value.src/dpi.zig:scale.zigwas insrc/ui/, off-limits to the rendering layer. Moved tosrc/dpi.zignext togeom.zigandcolors.zig. The 16 UI component imports were updated.src/ui/components/session_interaction.zig,src/render/renderer.zig,src/app/runtime.zig:SessionViewStategets anav_wave_start_timefield alongsidewave_start_time.triggerNavWave()sets it without touching attention state or session status.runtime.zigcalls it afternavigateGrid()for Cmd+Arrow in grid mode only, not for Cmd+1-9 (which expands to full-screen) or mouse clicks. The renderer reads the nav wave inrenderGridSessionCachedand passesnav_wave_amplitude(0.02, duration 250ms) torenderWaveStrips. If both waves are active at once, the attention wave wins. The.Gridrender loop uses two passes so the waving terminal always draws on top. Border thickness is computed asdpi.scale(attention_thickness, ui_scale)at the draw site.Test plan