Skip to content

fix(components): make spinners rotate in place instead of orbiting - #14

Merged
lodystage[bot] merged 1 commit into
mainfrom
fix/spinner-rotates-in-place
Aug 8, 2026
Merged

lodystage[bot] merged 1 commit into
mainfrom
fix/spinner-rotates-in-place

Conversation

@zxch3n

@zxch3n zxch3n commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

The mobile home status pill's "刷新中…" spinner did not spin about its own center — it swept a circle around a point beside it.

Root cause

Investigated by measuring the rotation pivot in real WebKit (the engine the mobile app renders in) rather than reasoning from the code. That first disproved the obvious suspects: the Loader2 artwork is exactly centered in its viewBox, and CSS animate-spin on a root <svg> is pixel-identical to SVG-native rotation about (12,12). The icon itself was fine.

Two mechanisms did reproduce the symptom, both rooted in transform-box defaulting to view-box — so transform-origin: 50% 50% is only the box center via a spec fallback that nothing in the markup asserts:

  1. Squished box (the reported spinner). The pill is a capped-width flex row whose label truncates. Without shrink-0, a long label compressed the glyph from 14×14 → 13.55×15.43, so the rotation swept an ellipse and the glyph wobbled.
  2. Wrapper-box mismatch (the latent version). Two sites animate an HTML <span> wrapper instead of the glyph, to keep an always-on spinner on the compositor. That is only equivalent while the wrapper's box is the glyph's box — a wrapper that also held its "Syncing" label orbited with 64px of travel.

Changes

  • index.css — one @layer base rule pinning transform-box / transform-origin (and flex-shrink: 0) for spinners, stating the intent once globally. In base, so an explicit origin-* / shrink utility at a call site still wins.
  • mobile-connection-status.tsx — shrink-0 on all three spinner states (refreshing / reconnecting / loading). Both halves are needed: the CSS rule corrects the pivot but cannot restore a squished box.
  • spinner-rotates-in-place.test.tsx — 5 new tests.

Audit

Classified all 233 animate-spin sites: 231 are on the glyph directly and are covered by the new rule; the 2 wrapper sites are documented and constrained by test.

Verification

  • Typecheck (tsgo --noEmit) clean; prettier clean.
  • New tests verified to genuinely catch the bug — with the fix reverted the 3 pill tests fail, with it all 5 pass. 34 tests pass across the new file plus the two related existing suites.
  • jsdom has no layout, so the tests assert the structural invariants that produce the correct geometry rather than re-measuring pixels.

Two things to flag

  • Pre-existing unrelated failures. The full suite has 26 failing tests across 9 files (workspace-target-router, useOrganization, onboarding-flow, others). I stashed my changes and confirmed they fail identically on the clean tree.
  • pnpm check could not be run. apps/cli depends on acp-extension-claude@workspace:*, absent from this public tree, so the root install aborts. I used a filtered install of @lody/components and ran typecheck + tests there.
  • Verification was WebKit 26.5 via Playwright, not a physical iOS device. An on-device confirmation would close the loop.

🤖 Generated with Claude Code

…isk:low]

The mobile home status pill's "刷新中…" spinner did not spin about its own
center; it swept a circle around a point beside it.

Measured in WebKit rather than inferred. Two mechanisms reproduce it, both
rooted in `transform-box` defaulting to `view-box`, so `animate-spin`'s
`transform-origin: 50% 50%` is only the box center via a spec fallback that
nothing in the markup asserts:

- The pill is a capped-width flex row whose label truncates. Without
  `shrink-0` a long label compressed the glyph from 14x14 to 13.55x15.43, so
  the rotation swept an ellipse and the glyph wobbled.
- Two sites animate an HTML wrapper instead of the glyph to keep an
  always-on spinner on the compositor. That is only equivalent while the
  wrapper's box is the glyph's box; a wrapper that also held its "Syncing"
  label orbited with 64px of travel.

Not the cause, and verified as such before fixing: the Loader2 artwork is
exactly centered in its viewBox, and CSS rotation of a root <svg> is
pixel-identical to SVG-native rotation about (12,12).

Fixes the pivot globally in `@layer base` (below utilities, so an explicit
`origin-*` / `shrink` utility at a call site still wins) and adds `shrink-0`
to the three pill spinner states, since the CSS rule corrects the pivot but
cannot restore a squished box.

Audited all 233 `animate-spin` sites: 231 are on the glyph and are covered
by the new rule; the 2 wrapper sites are documented and constrained by test.

Tests assert the structural invariants that produce the geometry, since
jsdom has no layout. Verified to fail without the fix.

Model: claude-opus-5[1m]

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@lodystage
lodystage Bot merged commit 0df0742 into main Aug 8, 2026
Leeeon233 added a commit that referenced this pull request Aug 26, 2026
Model: gpt-5

Co-authored-by: Leeeon233 <leeeon233@gmail.com>
Leeeon233 added a commit to ladydd/Lody that referenced this pull request Sep 9, 2026
Use merged acp-extension-dsh LodyAI#14 to fix the no-floating-promises CI error without weakening lint checks.

Model: gpt-6
Leeeon233 added a commit that referenced this pull request Sep 9, 2026
* fix: keep forked Codex sessions subscribed

Pin acp-extension-codex to fc91dce (LodyAI/acp-extension-codex#37) so
thread/fork children stay subscribed for turn/completed. Pin
acp-extension-core 0.1.2 because that adapter already depends on it.

This does not take Lody#534 host worktree identity changes. Adapter #35
is already on Codex main and comes along with #37.

Closes #543

* docs: record Codex fork subscription pin

Agent Note for pinning acp-extension-codex fc91dce and Core 0.1.2
without taking Lody #534 host identity changes.

Model: grok-4.6

* fix: pin DSH main with awaited smoke tests

Use merged acp-extension-dsh #14 to fix the no-floating-promises CI error without weakening lint checks.

Model: gpt-6

---------

Co-authored-by: Leon Zhao <leeeon233@gmail.com>
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