Skip to content

feat(chat): ShimmerText and ThinkingLabel with rotating verbs (CL-8481) - #43

Merged
TheGreatAxios merged 7 commits into
mainfrom
cl-8481-shimmer-thinking
Sep 21, 2026
Merged

TheGreatAxios merged 7 commits into
mainfrom
cl-8481-shimmer-thinking

Conversation

@TheGreatAxios

Copy link
Copy Markdown
Contributor

Summary

  • ShimmerText: a shimmer sweep over inline text via a background-clip: text gradient built from --muted-foreground/--foreground only (no new hex); optional role="status".
  • ThinkingLabel: rotates caller-supplied verbs on an interval (default 3s), rendered with ShimmerText, role="status" aria-live="polite".
  • New corbits-shimmer-sweep keyframe in src/theme.css; reduced motion collapses the sweep via the existing global rule and stops verb rotation via a matchMedia check.
  • Stories under stories/primitives/.

Test plan

  • bun run typecheck
  • bun run lint
  • bun test
  • bun run build (generate, swc, tsc, tailwind, contrast gate all pass)
  • bun run dep-guard

@TheGreatAxios

Copy link
Copy Markdown
Contributor Author

Review

Verdict: approve with nits. New ShimmerText / ThinkingLabel, reduced-motion handled. No unit tests for empty verbs, rotation, or reduced-motion skipping the interval. No CI. No prior reviews.

@TheGreatAxios

Copy link
Copy Markdown
Contributor Author

Re-review

Verdict: approve. SHA 18e34c1.

ThinkingLabel tests: empty verbs, reduced-motion skips setInterval, rotation after intervalMs. bun test 339 pass, typecheck/lint 0.

@TheGreatAxios

Copy link
Copy Markdown
Contributor Author

Review (Draper + Emil)

Verdict: request changes (a11y). Intent is good.

  • Reduced-motion: global rule sets animation to 0.01ms; ShimmerText stays text-transparent. That can leave unreadable text. Under motion-reduce / forced-colors, solid text-muted-foreground, no clip.
  • ThinkingLabel uses one-shot matchMedia. Use live usePrefersReducedMotion() (already in the library).
  • Visual verb rotation is fine; aria-live="polite" every 3s floods SRs. Announce once, not on the timer.
  • Infinite 1.8s shimmer is not the 150/200/300ms scale. Don’t add a fourth “thinking” language next to TypingIndicator.

Fixes in flight for the first three.

@TheGreatAxios

Copy link
Copy Markdown
Contributor Author

Re-review

A11y nits at eac1b88: reduced-motion/forced-colors solid text; live usePrefersReducedMotion; status “Working” once, verbs aria-hidden. Full bun test still has Toaster leftovers (isolation is on #42 — rebase after #42 or cherry-pick dismiss).

@TheGreatAxios TheGreatAxios left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ShimmerText is clean — token-only gradient, no new hexes, keyframes in theme.css where they belong.

One fix: thinking-label.tsx hand-rolls a one-shot prefersReducedMotion() helper. That's the stale-matchMedia pattern usePrefersReducedMotion was extracted to kill — if the OS preference flips mid-session the verb rotation never stops. Use the hook (PR #44 already does).

Minor: role="status" + aria-live="polite" on the same element is redundant — status implies polite.

@TheGreatAxios

Copy link
Copy Markdown
Contributor Author

Re-review

Toaster isolation cherry-picked. SHA 9f83ccb. bun test 342 pass.

@TheGreatAxios
TheGreatAxios force-pushed the cl-8481-shimmer-thinking branch from 9f83ccb to a6724c9 Compare September 21, 2026 18:27
@TheGreatAxios

Copy link
Copy Markdown
Contributor Author

Review pass — rebased onto post-prune main + fixes applied

Rebased after the prune (#55): generated package.json/src/index.ts resolved via regeneration. The usePrefersReducedMotion fix was already in the branch — verified live, not a stale matchMedia snapshot.

Persona review findings — all resolved

  • Hard-cut + reflow on verb rotation (draper): the active verb swapped by replacing text, so every tick changed element width and jumped neighboring layout. Rewritten as a stacked inline-grid — every verb occupies the same cell, width stabilizes to the widest verb, and swaps crossfade via transition-opacity. Screen-reader behavior unchanged: a single role="status" announces once, rotation stays aria-hidden and decorative.
  • Hollow cleanup coverage (emil): the interval stub now records clearInterval calls; the reduced-motion-live test asserts cleanup actually fires.
  • Naming/string override (draper, product decision): the hardcoded "Working" announcement is now a statusLabel prop — sensible default, trivially overridable.
  • CHANGELOG entry added for both new modules.

Validation

231 tests, typecheck, lint, dep-guard, full build, contrast gate — all green at 8048266.

…tion (CL-8481)

Empty verbs render nothing; prefers-reduced-motion never starts the interval;
the verb advances when the interval callback fires after intervalMs.
ShimmerText falls back to solid muted text under prefers-reduced-motion
and forced-colors. ThinkingLabel uses usePrefersReducedMotion and
announces Working once instead of aria-live on every verb tick.
@TheGreatAxios
TheGreatAxios force-pushed the cl-8481-shimmer-thinking branch from 8048266 to e1c6567 Compare September 21, 2026 23:28
@TheGreatAxios
TheGreatAxios merged commit 6a939ee into main Sep 21, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant