Web: Activity loads older events as you scroll, no Show older button

Preference 172. ScrollSentinel watches the end of a cursor-paged list and
asks for the next page a screen early. It asks once per cursor, keeps going
when a page lands without pushing the end off screen, and stops on a failure
until Try again (an observer re-firing on a failure is a request loop). A
failed older page now gets its own error beside the retry, instead of
replacing the list's load error.

Adapted from fabledstash's ScrollSentinel (snippet 3760).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-08 16:54:19 -04:00
co-authored by Claude Opus 5.5
parent 962637ea8b
commit eb6a1ff3ef
3 changed files with 177 additions and 14 deletions
+16 -14
View File
@@ -1,9 +1,10 @@
<script setup lang="ts">
import { onMounted, ref } from "vue";
import { computed, onMounted, ref } from "vue";
import { api } from "../api/client";
import { errorMessage } from "../api/errors";
import { formatShortDateTime } from "../notes/datetime";
import { useLoad } from "../composables/useAction";
import ScrollSentinel from "./ScrollSentinel.vue";
// Admin: what has happened to accounts on this instance, newest first (#2939 §5).
// Kept for `audit_retention_days` (Settings → Security).
@@ -52,6 +53,9 @@ const { loading, error, load } = useLoad(async () => {
more.value = res.more;
}, "Couldn't load activity.");
const loadingMore = ref(false);
const moreError = ref("");
// The next page starts before the oldest event shown; empty once there is none.
const cursor = computed(() => (more.value ? (events.value[events.value.length - 1]?.at ?? "") : ""));
async function page(before?: string) {
const query = before ? `?before=${encodeURIComponent(before)}` : "";
@@ -59,15 +63,15 @@ async function page(before?: string) {
}
async function loadMore() {
const last = events.value[events.value.length - 1];
if (!last) return;
if (!cursor.value || loadingMore.value) return;
loadingMore.value = true;
moreError.value = "";
try {
const res = await page(last.at);
const res = await page(cursor.value);
events.value.push(...res.events);
more.value = res.more;
} catch (e) {
error.value = errorMessage(e, "Couldn't load older activity.");
moreError.value = errorMessage(e, "Couldn't load older activity.");
} finally {
loadingMore.value = false;
}
@@ -106,14 +110,12 @@ onMounted(() => {
</li>
</ul>
<button
v-if="more"
type="button"
class="self-start btn-sm"
:disabled="loadingMore"
@click="loadMore"
>
Show older
</button>
<ScrollSentinel
v-if="!loading"
:cursor="cursor"
:loading="loadingMore"
:error="moreError"
@more="loadMore"
/>
</section>
</template>