Skip to content

[Feature]: Wave animation on grid focus change + smooth filled terminal border #235

@forketyfork

Description

@forketyfork

Status Quo

When a user navigates to a different terminal in grid view, the newly focused terminal's blue border appears immediately with no transition. The wave animation (horizontal-squish strip effect in renderWaveStrips(), renderer.zig:829–886) fires only when agent status notifications arrive, triggered via SessionInteractionComponent.setAttention().

The terminal border (focused, unfocused, and attention states) is drawn by drawThickBorder() in gfx/primitives.zig:47–61, which calls the 1px-wide drawRoundedBorder() outline thickness times with each call inset by 1px but using the same corner radius (hardcoded 12). Because the arc radius doesn't shrink as the rect shrinks, the nested corner arcs aren't concentric — they diverge at the corners, producing jagged/uneven edges. The radius is also not DPI-scaled.

Objectives

When a developer moves focus between terminals in grid view, a brief wave animation acknowledges the navigation. The terminal border (selected and unselected) should have smooth, properly filled corners at all DPI settings.

User Flow

Trigger: User presses Cmd+Arrow or Cmd+1–9 to move focus to a different terminal in grid view.

  1. Focus moves to the new terminal; its smooth blue border appears.
  2. A brief wave animation (~400 ms, smaller amplitude than the agent notification wave) plays on the newly focused terminal.
  3. Animation completes; terminal displays normally with the clean border.

Result: Navigation feels responsive and polished; all terminal borders have smooth, filled corners.

Scope

In scope:

  • Wave animation (strip squish, same timing as the attention animation) triggered on focus change in grid view, with a smaller amplitude
  • Fix drawThickBorder to render a properly filled donut between outer and inner rounded rects (scanline approach), with radius as a parameter and DPI scaling at call sites
  • All three terminal border call sites in renderer.zig (focused, unfocused, attention) benefit from the fix

Out of scope:

  • Changing the existing attention animation (amplitude, timing, color)
  • Wave animation on focus change in full-screen mode
  • Changing any other drawRoundedBorder/fillRoundedRect call sites (buttons, modals, scrollbars — all use thin 1px borders or fills that are already correct)
  • Configurable border radii or animation durations

Implementation Plan

Affected Modules

  • src/gfx/primitives.zig: rewrite drawThickBorder to use scanline-fill donut approach; add radius parameter
  • src/render/renderer.zig: update drawThickBorder call sites to pass explicit radius (DPI-scaled); handle nav_wave_start_time alongside wave_start_time with separate amplitude
  • src/ui/session_view_state.zig: add nav_wave_start_time: i64 = 0 field
  • src/ui/components/session_interaction.zig: add nav_wave_amplitude constant and triggerNavWave() method
  • src/app/runtime.zig: call triggerNavWave() when focused_session changes in grid view

Tasks

  1. Rewrite drawThickBorder in gfx/primitives.zig to use a scanline-fill approach: for each scanline compute the span inside the outer rounded rect and outside the inner rounded rect (inset by thickness), fill only those two side-spans and the corner regions — add radius: c_int as a parameter instead of hardcoding 12 — src/gfx/primitives.zig
  2. Update the 3 drawThickBorder call sites in renderer.zig to pass an explicit DPI-scaled radius (e.g. dpi.scale(6, ui_scale)) — src/render/renderer.zig
  3. Add nav_wave_start_time: i64 = 0 to SessionViewStatesrc/ui/session_view_state.zig
  4. Add pub const nav_wave_amplitude: f32 = 0.04 constant and triggerNavWave(idx, now_ms) method (mirrors setAttention but only sets nav_wave_start_time, no border color change) — src/ui/components/session_interaction.zig
  5. Detect focus change in grid view in the frame loop and call triggerNavWave() on the newly focused session — src/app/runtime.zig
  6. In renderWaveStrips(), accept an amplitude: f32 parameter; in the per-session render path handle nav_wave_start_time alongside wave_start_time, passing nav_wave_amplitudesrc/render/renderer.zig
  7. Write tests: triggerNavWave() sets nav_wave_start_time, does not set attention or change status; setAttention() does not interfere with nav_wave_start_timesrc/ui/components/session_interaction.zig

Acceptance Criteria

  • All tasks completed
  • Navigating between terminals in grid view triggers a brief wave animation with visibly smaller amplitude than the agent-notification wave
  • Wave does not trigger when switching to/from full-screen mode
  • All three terminal border states (focused, unfocused, attention) have smooth, filled corners with no jagged artifacts
  • Border rendering is visually correct on Retina/HiDPI displays
  • Existing attention animation is visually unchanged
  • zig build, zig build test, and just lint all pass

Metadata

Metadata

Assignees

No one assigned

    Labels

    featureNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions