Skip to content

feat(app): replace wave loading indicator with spherical-harmonic morphing dot #261

Closed

Description

@jeonghun-jj-lee

Important

Problem — The current working indicator is split across two surfaces: a standing-wave SVG with cycling gerund text ("Fidelitymaxxing...") in the ThinkingLine, and a separate yellow breathing dot on the thought rail. The wave+word indicator is visually noisy, occupies vertical space in the transcript, and doesn't connect to the brand's identity (harmonic modes of a physical system). The rail dot — which already marks the active step — is underutilized.

Approach — Remove the wave and gerund from ThinkingLine (keep only the elapsed/token metadata), and promote the thought-rail dot into a morphing spherical-harmonic indicator: when running, the dot grows from 7px to ~13px and its shape cycles through 2D polar-projected silhouettes of Y_l^m modes (circle → dumbbell → pinched → clover), rendered as SVG path morphs via SMIL <animate>. A slow CSS rotation (~12s period) adds organic life. The metadata line stays in its current position below the message.

Scope — in: ThinkingLine simplification, harmonic-dot geometry module, HarmonicDot component, thought-rail integration, grow/shrink transition, reduced-motion support · out: wave file deletion (kept for potential reuse), tab/sidebar status indicators, done-dot styling

Acceptance Criteria

  • When a turn is streaming, the tail step's rail dot renders as a ~13px SVG that morphs through four spherical-harmonic silhouettes on a ~2.3s hold per mode
  • The morph uses SMIL path interpolation — all four paths have identical point counts (64 samples)
  • The dot slowly rotates (~12s per full turn) via CSS transform on a parent <g>
  • The dot grows from 7px (done size) to ~13px with a smooth CSS transition (~150ms ease-out) when it enters the running state
  • When the step completes, the dot shrinks back to 7px ink circle (the existing done styling)
  • The ThinkingLine no longer renders the wave SVG or cycling gerund — only the meta row (elapsed, tokens, esc hint)
  • Under prefers-reduced-motion: no morph animation, no rotation; the running dot renders as a static 13px yellow circle
  • The morphing dot fits within the existing pl-6 (24px) content inset — no layout shift to surrounding content
  • Both light and dark themes render correctly (accent fill is visible on both grounds; accent-edge border defines the shape on light where yellow alone is ~1.2:1 contrast)

Testing Decisions

  • Visual verification in the running app (localhost:4444 dev server) across light/dark themes
  • Verify prefers-reduced-motion via DevTools emulation → Rendering → Emulate CSS media feature
  • Confirm no layout shift with the browser's Layout Shift debugger or by observing content reflow
  • Unit-testable: the geometry module (harmonic-geometry.ts) should be testable standalone — correct point counts, normalized radii, valid SVG path syntax

Key Decisions

  • SVG path morph via SMIL — all four harmonic outlines are sampled at 64 angular points (equal M/L command counts), enabling native <animate attributeName="d"> interpolation without JS frame loops or third-party libraries
  • 13px running size — centered on LINE_X (11px), extends 4.5–17.5px, stays inside the 24px content gutter with no gutter-width changes needed
  • Slow rotation on parent <g> — CSS transform: rotate() on the <g> wrapper, separate from the SMIL d morph on the <path>, so the two animations compose cleanly
  • Keep wave files — amico-wave.tsx and wave-geometry.ts stay in the tree (potential reuse on marketing surfaces) but are no longer imported by ThinkingLine
  • Meta line retained as-is — elapsed time, token count, and interrupt hint continue rendering in their current DOM position below the message; only the wave+word above them is removed

Constraints & Invariants

  • The rail's geometry rules (documented in thought-rail.tsx header) remain intact — dot centre alignment, segment cap logic, adjacency-based completion
  • Done dots remain 7px ink circles — the morph is exclusively a running-state behavior
  • The NODE constant (7px) stays for done-state rendering; a new RUNNING_NODE (~13px) applies only inside HarmonicDot
  • No new runtime dependencies — pure SVG + CSS + SMIL, framework-agnostic geometry module
  • The ThinkingLine's metadata row must not reflow or jump when the wave is removed (the grid layout simplifies but the meta's position is stable)

Prior Art

  • packages/ui/src/amicode/wave-geometry.ts — the standing-wave approach (modal paths, timing constants, pure geometry module pattern)
  • packages/app/src/pages/session/timeline/thought-rail.tsx — the rail's six geometry rules, especially Rule 5 (gutter sizing) and Rule 6 (lone running dot)
  • packages/ui/src/amicode/amico-wave.tsx — SMIL-compatible multi-mode SVG animation pattern

Notes

  • The four harmonic modes chosen (Y_0^0, Y_1^0, Y_2^0, Y_2^2) are recognizable at small sizes: circle, peanut, elongated pinch, and four-lobe clover. Higher harmonics (l=3+) have too many lobes to resolve at 13px.
  • The mode cadence (2.3s) matches the wave's existing MODE_HOLD_MS, maintaining the same temporal rhythm the eye is already trained on.
  • The grow transition (7→13px) serves double duty: it signals "this step just started" and it gives the morph enough pixels to be legible.

Metadata

Metadata

Assignees

No one assigned

    Labels

    afkImplement + merge unattended

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions