From fca1738bd3d63c4c16db8eb3144fe0d97b34f181 Mon Sep 17 00:00:00 2001
From: Ricardo
Date: Tue, 3 Mar 2026 15:48:59 +0100
Subject: [PATCH] feat: skeleton loaders replace loading text (Release 6)
Animated card-shaped placeholders with shimmer effect shown during
content loading instead of plain "Loading..." text. Applied to reader,
tag timeline, and explore tabs (both first-load and load-more states).
Confab-Link: http://localhost:8080/sessions/e9d666ac-3c90-4298-9e92-9ac9d142bc06
---
assets/reader.css | 75 +++++++++++++++++++++++++++++
package.json | 2 +-
views/activitypub-explore.njk | 44 ++++++++++-------
views/activitypub-reader.njk | 10 ++--
views/activitypub-tag-timeline.njk | 10 ++--
views/partials/ap-skeleton-card.njk | 15 ++++++
6 files changed, 132 insertions(+), 24 deletions(-)
create mode 100644 views/partials/ap-skeleton-card.njk
diff --git a/assets/reader.css b/assets/reader.css
index 8fcbd66..9f896a4 100644
--- a/assets/reader.css
+++ b/assets/reader.css
@@ -1595,6 +1595,81 @@
cursor: pointer;
}
+/* ==========================================================================
+ Skeleton Loaders
+ ========================================================================== */
+
+@keyframes ap-skeleton-shimmer {
+ 0% { background-position: 200% 0; }
+ 100% { background-position: -200% 0; }
+}
+
+.ap-skeleton {
+ background: linear-gradient(90deg,
+ var(--color-offset) 25%,
+ var(--color-background) 50%,
+ var(--color-offset) 75%);
+ background-size: 200% 100%;
+ animation: ap-skeleton-shimmer 1.5s ease-in-out infinite;
+ border-radius: var(--border-radius-small);
+}
+
+.ap-card--skeleton {
+ pointer-events: none;
+}
+
+.ap-card--skeleton .ap-card__author {
+ display: flex;
+ align-items: center;
+ gap: var(--space-s);
+}
+
+.ap-skeleton--avatar {
+ width: 2.5rem;
+ height: 2.5rem;
+ border-radius: 50%;
+ flex-shrink: 0;
+}
+
+.ap-skeleton-lines {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ gap: 0.4rem;
+}
+
+.ap-skeleton--name {
+ height: 0.85rem;
+ width: 40%;
+}
+
+.ap-skeleton--handle {
+ height: 0.7rem;
+ width: 25%;
+}
+
+.ap-skeleton-body {
+ display: flex;
+ flex-direction: column;
+ gap: 0.5rem;
+ margin-top: var(--space-s);
+}
+
+.ap-skeleton--line {
+ height: 0.75rem;
+ width: 100%;
+}
+
+.ap-skeleton--short {
+ width: 60%;
+}
+
+.ap-skeleton-group {
+ display: flex;
+ flex-direction: column;
+ gap: var(--space-m);
+}
+
/* ==========================================================================
Responsive
========================================================================== */
diff --git a/package.json b/package.json
index 0b26c5f..3bb2fee 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "@rmdes/indiekit-endpoint-activitypub",
- "version": "2.5.4",
+ "version": "2.5.5",
"description": "ActivityPub federation endpoint for Indiekit via Fedify. Adds full fediverse support: actor, inbox, outbox, followers, following, syndication, and Mastodon migration.",
"keywords": [
"indiekit",
diff --git a/views/activitypub-explore.njk b/views/activitypub-explore.njk
index 0c89b76..f00c489 100644
--- a/views/activitypub-explore.njk
+++ b/views/activitypub-explore.njk
@@ -256,10 +256,14 @@
x-data="apInfiniteScroll()"
x-init="init()">
-
- {# Loading spinner — first load, no content yet #}
-
- {{ __("activitypub.reader.pagination.loading") }}
+ {% include "partials/ap-skeleton-card.njk" %}
+ {% include "partials/ap-skeleton-card.njk" %}
+ {% include "partials/ap-skeleton-card.njk" %}
{# Error state with retry #}
@@ -368,7 +377,7 @@
x-html="tabState[tab._id] ? tabState[tab._id].html : ''">
- {# Load more button + loading spinner for subsequent pages #}
+ {# Load more button + skeleton loaders for subsequent pages #}
{{ __("activitypub.reader.pagination.loadMore") }}
-
- {{ __("activitypub.reader.pagination.loading") }}
-
+ {% include "partials/ap-skeleton-card.njk" %}
+ {% include "partials/ap-skeleton-card.njk" %}
+
{# Empty state — no instance tabs pinned yet #}
diff --git a/views/activitypub-reader.njk b/views/activitypub-reader.njk
index b84100a..365495b 100644
--- a/views/activitypub-reader.njk
+++ b/views/activitypub-reader.njk
@@ -150,10 +150,14 @@
x-data="apInfiniteScroll()"
x-init="init()">
-
- {{ __("activitypub.reader.pagination.loadMore") }}
- {{ __("activitypub.reader.pagination.loading") }}
+
+ {{ __("activitypub.reader.pagination.loadMore") }}
+
+ {% include "partials/ap-skeleton-card.njk" %}
+ {% include "partials/ap-skeleton-card.njk" %}
+ {% include "partials/ap-skeleton-card.njk" %}
+
{{ __("activitypub.reader.pagination.noMore") }}
{% endif %}
diff --git a/views/activitypub-tag-timeline.njk b/views/activitypub-tag-timeline.njk
index c72ab80..6a9988f 100644
--- a/views/activitypub-tag-timeline.njk
+++ b/views/activitypub-tag-timeline.njk
@@ -75,10 +75,14 @@
x-data="apInfiniteScroll()"
x-init="init()">
-
- {{ __("activitypub.reader.pagination.loadMore") }}
- {{ __("activitypub.reader.pagination.loading") }}
+
+ {{ __("activitypub.reader.pagination.loadMore") }}
+
+ {% include "partials/ap-skeleton-card.njk" %}
+ {% include "partials/ap-skeleton-card.njk" %}
+ {% include "partials/ap-skeleton-card.njk" %}
+
{{ __("activitypub.reader.pagination.noMore") }}
{% endif %}
diff --git a/views/partials/ap-skeleton-card.njk b/views/partials/ap-skeleton-card.njk
new file mode 100644
index 0000000..3776985
--- /dev/null
+++ b/views/partials/ap-skeleton-card.njk
@@ -0,0 +1,15 @@
+{# Skeleton loading card — animated placeholder while content loads #}
+