Summary
When navigating to the Mods or Tools pages, there's a noticeable delay while data is fetched from Google Cloud Firestore. During this time, the user sees either a blank page or a flash of empty content before the data appears. Adding skeleton loading states and a styled Turbo progress bar would give users immediate visual feedback that content is loading.
Related: This revisits closed issue #24 ("Create a loading screen to show while we fetch data from firestore") with a more modern approach using skeleton UI instead of a generic loading spinner.
Current Behavior
Looking at the actual codebase:
app/models/mod.rb fetches all mods via Rails.cache.fetch("firestore/mods", expires_in: 5.minutes) — on cache miss, this hits Firestore which can take several seconds
app/views/mods/_mods.html.erb wraps the table in a turbo_frame_tag "mods" — Turbo handles frame updates but with no visual loading indicator
app/views/mods/index.html.erb has search with data-action="input->mods#search" and 200ms debounce — during the Turbo frame fetch, no loading state is shown
app/views/tools/index.html.erb has no Turbo frame at all — full page load with no feedback
- The mods page has an empty state ("No mods match your query") but no loading state
- On first visit or after cache expires, the blank period can be 2-5 seconds
Proposed Solution
1. Style the Turbo Progress Bar
Turbo has a built-in progress bar that appears at the top of the page during navigation. It just needs CSS customization to match the gold theme.
In app/assets/stylesheets/application.css:
.turbo-progress-bar {
height: 3px;
background-color: #f1ad1c; /* icarus-500 gold */
}
This is the easiest win — zero JS, zero markup changes. Every Turbo navigation (clicking Mods, Tools, Info, mod detail pages) gets a gold progress bar automatically.
Turbo shows the progress bar after a 500ms delay by default. To lower it for more responsive feel:
// app/javascript/application.js
Turbo.setProgressBarDelay(200)
2. Turbo Frame Loading Indicator for Search
When the search Turbo Frame is loading, Turbo adds [busy] and aria-busy="true" to the frame automatically. Use CSS to style this:
turbo-frame[busy] {
position: relative;
}
turbo-frame[busy]::before {
content: "";
display: block;
height: 2px;
background: linear-gradient(90deg, transparent, #f1ad1c, transparent);
animation: shimmer 1.5s ease-in-out infinite;
}
turbo-frame[busy] table {
opacity: 0.5;
pointer-events: none;
transition: opacity 0.2s;
}
@keyframes shimmer {
0% { transform: translateX(-100%); }
100% { transform: translateX(100%); }
}
This dims the existing table and shows a gold shimmer bar while search results are loading — all via CSS, no JS needed.
3. Skeleton Table Rows for Initial Load
Add skeleton placeholder rows inside _mods.html.erb that show while the real data hasn't loaded yet. Use a Stimulus controller to swap them out:
<%# In _mods.html.erb, inside the <tbody> %>
<% if mods.nil? %>
<% 8.times do %>
<tr class="animate-pulse">
<td class="p-2 border-t border-slate-600" colspan="2">
<div class="h-4 w-48 rounded bg-slate-700"></div>
</td>
<td class="hidden p-2 border-t sm:table-cell border-slate-600">
<div class="h-4 w-24 rounded bg-slate-700"></div>
</td>
<td class="hidden p-2 border-t md:table-cell border-slate-600">
<div class="h-4 w-12 rounded bg-slate-700"></div>
</td>
<td class="hidden p-2 border-t xl:table-cell border-slate-600">
<div class="h-4 w-16 rounded bg-slate-700"></div>
</td>
<td class="hidden p-2 border-t lg:table-cell border-slate-600">
<div class="h-4 w-64 rounded bg-slate-700"></div>
</td>
</tr>
<% end %>
<% end %>
The skeleton rows match the actual table column structure (Name, Download, Author, Version, Week, Description) with the same responsive visibility classes (hidden sm:table-cell, etc.).
4. Tools Page Skeleton Cards
The tools page uses flex-wrapped cards, not a table. Add skeleton cards matching the actual tool card layout from tools/index.html.erb:
<% if @tools.nil? || @tools.empty? %>
<% 4.times do %>
<div class="flex flex-col flex-auto w-1/3 p-2 m-2 border rounded-xl border-icarus-500 bg-slate-200 dark:bg-slate-800 min-h-96 animate-pulse">
<div class="flex items-start justify-between flex-1">
<div class="flex flex-col flex-1 gap-2">
<div class="h-6 w-48 rounded bg-slate-700"></div>
<div class="h-4 w-24 rounded bg-slate-700"></div>
</div>
<div class="h-9 w-24 rounded-md bg-slate-700"></div>
</div>
<div class="space-y-2 mt-4">
<div class="h-3 w-full rounded bg-slate-700"></div>
<div class="h-3 w-5/6 rounded bg-slate-700"></div>
<div class="h-3 w-2/3 rounded bg-slate-700"></div>
</div>
</div>
<% end %>
<% end %>
Files to Modify
| File |
Change |
app/assets/stylesheets/application.css |
Add Turbo progress bar color, Turbo frame busy styles, shimmer animation |
app/javascript/application.js |
Set Turbo.setProgressBarDelay(200) |
app/views/mods/_mods.html.erb |
Add skeleton table rows for loading state |
app/views/tools/index.html.erb |
Add skeleton cards for loading state |
Technical Notes
- The 5-minute Firestore cache (
Rails.cache.fetch) means most page loads are fast — skeletons mainly help on first visit, cache expiry, or when Firestore is slow
- Turbo progress bar and frame
[busy] styling are pure CSS — no additional JS dependencies
- Skeleton rows use
animate-pulse which is built into Tailwind — no custom animation needed
- The skeleton layout must match the real table/card layout exactly (same column count, same responsive breakpoints) to prevent layout shift when real data loads
Design Notes
- Skeleton placeholder color:
bg-slate-700 in dark mode, bg-slate-300 in light mode — use dark: variants
- Turbo progress bar in gold (
#f1ad1c) for consistent branding
animate-pulse provides a subtle breathing effect — much more modern than a spinner
- The dimmed table during search (
opacity: 0.5) tells users their current results are stale while new ones load
Testing
Summary
When navigating to the Mods or Tools pages, there's a noticeable delay while data is fetched from Google Cloud Firestore. During this time, the user sees either a blank page or a flash of empty content before the data appears. Adding skeleton loading states and a styled Turbo progress bar would give users immediate visual feedback that content is loading.
Related: This revisits closed issue #24 ("Create a loading screen to show while we fetch data from firestore") with a more modern approach using skeleton UI instead of a generic loading spinner.
Current Behavior
Looking at the actual codebase:
app/models/mod.rbfetches all mods viaRails.cache.fetch("firestore/mods", expires_in: 5.minutes)— on cache miss, this hits Firestore which can take several secondsapp/views/mods/_mods.html.erbwraps the table in aturbo_frame_tag "mods"— Turbo handles frame updates but with no visual loading indicatorapp/views/mods/index.html.erbhas search withdata-action="input->mods#search"and 200ms debounce — during the Turbo frame fetch, no loading state is shownapp/views/tools/index.html.erbhas no Turbo frame at all — full page load with no feedbackProposed Solution
1. Style the Turbo Progress Bar
Turbo has a built-in progress bar that appears at the top of the page during navigation. It just needs CSS customization to match the gold theme.
In
app/assets/stylesheets/application.css:This is the easiest win — zero JS, zero markup changes. Every Turbo navigation (clicking Mods, Tools, Info, mod detail pages) gets a gold progress bar automatically.
Turbo shows the progress bar after a 500ms delay by default. To lower it for more responsive feel:
2. Turbo Frame Loading Indicator for Search
When the search Turbo Frame is loading, Turbo adds
[busy]andaria-busy="true"to the frame automatically. Use CSS to style this:This dims the existing table and shows a gold shimmer bar while search results are loading — all via CSS, no JS needed.
3. Skeleton Table Rows for Initial Load
Add skeleton placeholder rows inside
_mods.html.erbthat show while the real data hasn't loaded yet. Use a Stimulus controller to swap them out:The skeleton rows match the actual table column structure (Name, Download, Author, Version, Week, Description) with the same responsive visibility classes (
hidden sm:table-cell, etc.).4. Tools Page Skeleton Cards
The tools page uses flex-wrapped cards, not a table. Add skeleton cards matching the actual tool card layout from
tools/index.html.erb:Files to Modify
app/assets/stylesheets/application.cssapp/javascript/application.jsTurbo.setProgressBarDelay(200)app/views/mods/_mods.html.erbapp/views/tools/index.html.erbTechnical Notes
Rails.cache.fetch) means most page loads are fast — skeletons mainly help on first visit, cache expiry, or when Firestore is slow[busy]styling are pure CSS — no additional JS dependenciesanimate-pulsewhich is built into Tailwind — no custom animation neededDesign Notes
bg-slate-700in dark mode,bg-slate-300in light mode — usedark:variants#f1ad1c) for consistent brandinganimate-pulseprovides a subtle breathing effect — much more modern than a spinneropacity: 0.5) tells users their current results are stale while new ones loadTesting