What changed
What you'll notice
Loading now looks the same everywhere in the app, and the screen stops jumping around while it loads.
Before, almost every part of the app drew its own "loading" look. Some places pulsed, some sat as flat grey blocks that never moved, one showed a spinner, and several showed nothing at all until their data arrived. Worse, those placeholders rarely matched what replaced them, so the home feed shuffled and resettled as each section finished loading.
Now every loading state is the same shimmer, and the placeholder is the real layout — same cards, same heights — so when your data lands nothing moves. If you were mid-round when you last closed the app, the "Resume" banner is already there when you open it, instead of dropping in a moment later and pushing everything down.
Worth checking on device: cold-launch the dashboard on a slow connection. Every rail should shimmer in the same way, nothing should shift vertically as sections fill in, and pull-to-refresh should not grey out content that's already on screen.
What changed, technically
The skeleton is then the real layout by construction, which removes the whole class of layout-shift bugs rather than patching each one.
Foundation
- One builder renders both states, so they cannot drift apart. Collapses with an animation when data resolves empty or errors, and owns its own bottom spacing so a hidden section leaves no dangling gap. Deliberately does *not* re-skeletonise a refresh that still holds a previous value.
Layout shifts fixed
- Now one skeleton for the whole card.
- The fixed spacers between home sections were siblings, so they survived a hidden section and left dangling 16px gaps. Each section now owns its bottom gap.
Banner caching
- Cached rounds carry a timestamp and expire after 8h — a round is a few hours of golf, and confidently offering "Resume" for one that ended yesterday is worse than a beat's delay.
- Only resolved values write to the cache, so a flaky network can't wipe a good banner.
- The cache stores only what each card draws (four strings / two fields / four booleans), and the cards were narrowed to those projections — which is what makes a cached banner render identically to a live one.
Coverage
All 12 dashboard home rails, the four dashboard tabs, the four tribe rails, member and ranking lists, the leaderboard boards, Fairdrop, the course picker, the contacts sheet and the conversations list.
The unpaid-tournaments sheet and the battle player picker went the same way; in the picker the section label and search field stay live and usable above the skeleton rows, since neither needs data to work.
Known remainder
19 files still show a spinner where content will appear — mostly sheets and screens (tribe settings, schedule create, inter-tribe pairing, message thread, GPS panel). Each needs its own placeholder model and none is a list with an obvious shape, so they're better done alongside work on those screens than in a sweep.