Batch recent activity six at a time
Deploy production / deploy (push) Successful in 2s

This commit is contained in:
2026-08-12 00:09:20 -07:00
parent 2964bdaf24
commit aae9836b99
6 changed files with 68 additions and 4 deletions
+4
View File
@@ -63,6 +63,10 @@ making changes; it is the authoritative operations and deployment runbook.
mustard labels and are excluded from discovery.
- Keep project devlog entries in the homepage Recent Activity feed alongside
projects, blog entries, and articles, ordered by publication date.
- Keep Recent Activity batching as a progressive enhancement: render the full
semantic feed, show six items initially with JavaScript, reveal six more per
activation, announce the visible count, and leave all items available when
JavaScript is disabled.
- When `README.md` changes, keep the README-derived example blog entry aligned
with the runbook content it demonstrates.
+4 -1
View File
@@ -199,7 +199,10 @@ The public routes are `/projects/thinkloom/`,
native Lektor records and approved images. This site owns their models,
templates, layout, repository-information card, and import policy. Imported
devlog entries also join the homepage Recent Activity feed, ordered with
projects, articles, and blog entries by publication date.
projects, articles, and blog entries by publication date. The feed initially
shows six items and reveals up to six more per button activation. This is a
progressive enhancement implemented by `assets/static/activity.js`; without
JavaScript, the complete semantic feed remains visible.
`configs/project-sources.ini` is the allowlist. Each source declares a stable
project ID, credential-free HTTPS Git URL, public web/API URL, and branch. Add
+34
View File
@@ -0,0 +1,34 @@
(() => {
for (const feed of document.querySelectorAll("[data-activity-feed]")) {
const items = [...feed.querySelectorAll("[data-activity-item]")];
const batchSize = Number.parseInt(feed.dataset.batchSize || "6", 10);
const controls = feed.parentElement.querySelector("[data-activity-controls]");
const status = controls?.querySelector("[data-activity-status]");
const button = controls?.querySelector("[data-activity-more]");
if (!controls || !status || !button || items.length <= batchSize) continue;
let visibleCount = Math.min(batchSize, items.length);
const render = () => {
for (const [index, item] of items.entries()) {
item.hidden = index >= visibleCount;
}
const remaining = items.length - visibleCount;
status.textContent = `Showing ${visibleCount} of ${items.length} recent activity items.`;
button.disabled = remaining === 0;
button.textContent = remaining
? `Show ${Math.min(batchSize, remaining)} more`
: "All activity shown";
controls.hidden = false;
};
button.addEventListener("click", () => {
visibleCount = Math.min(visibleCount + batchSize, items.length);
render();
});
render();
}
})();
+18
View File
@@ -57,6 +57,23 @@ h1 { max-width: 650px; margin: 0; font-size: clamp(30px, 4vw, 45px); font-style:
.timeline-entry h2, .entry-card h2 { margin: 10px 0 5px; font-size: 21px; line-height: 1.3; }
.timeline-entry p, .entry-card p { max-width: 680px; margin: 0 0 10px; color: var(--muted); }
.read-more { color: var(--magenta); font-size: 10px; }
.activity-controls { display: flex; justify-content: space-between; align-items: center; gap: 18px; padding: 24px 0 0 37px; }
.activity-controls[hidden] { display: none; }
.activity-status { margin: 0; color: var(--muted); font: 500 9px var(--mono); letter-spacing: .08em; text-transform: uppercase; }
.activity-more-button {
border: 1px solid var(--rule);
background: transparent;
padding: 7px 10px;
color: var(--muted);
cursor: pointer;
font: 500 9px var(--mono);
letter-spacing: .08em;
white-space: nowrap;
text-transform: uppercase;
}
.activity-more-button:hover,
.activity-more-button:focus-visible { border-color: var(--teal); color: var(--teal); }
.activity-more-button:disabled { cursor: default; opacity: .55; }
.card-grid { display: grid; grid-template-columns: repeat(2, minmax(0,1fr)); gap: 1px; background: var(--rule); border: 1px solid var(--rule); }
.entry-card { min-height: 245px; padding: 28px; background: var(--bg); }
@@ -286,6 +303,7 @@ h1 { max-width: 650px; margin: 0; font-size: clamp(30px, 4vw, 45px); font-style:
.card-grid { grid-template-columns: 1fr; }
.tag-directory { grid-template-columns: 1fr; }
.tag-filter { padding-block: 18px; }
.activity-controls { align-items: flex-start; padding-left: 30px; }
.tag-directory-card { min-height: 0; }
.project-hero { padding-block: 44px; }
.project-hero-inner,
+2 -1
View File
@@ -9,8 +9,9 @@
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600&family=Inter:wght@400;500&family=Playfair+Display:ital,wght@0,600;0,700;1,500;1,600&display=swap" rel="stylesheet">
<link rel="icon" href="{{ '/static/favicon.svg'|url }}" type="image/svg+xml">
<link rel="stylesheet" href="{{ '/static/style.css'|url }}?v=6">
<link rel="stylesheet" href="{{ '/static/style.css'|url }}?v=7">
<script src="{{ '/static/tags.js'|url }}?v=1" defer></script>
<script src="{{ '/static/activity.js'|url }}?v=1" defer></script>
</head>
<body>
<header class="site-header">
+6 -2
View File
@@ -12,7 +12,7 @@
<span>Recent Activity</span>
<span>Sorted by date</span>
</div>
<div class="timeline">
<div class="timeline" id="recent-activity-list" data-activity-feed data-batch-size="6">
{% set entries = [] %}
{% for section_id in ['projects', 'articles', 'blog'] %}
{% for item in site.get('/' ~ section_id).children %}
@@ -30,7 +30,7 @@
{% endif %}
{% endfor %}
{% for item in entries|sort(attribute='date', reverse=true) %}
<article class="timeline-entry accent-{{ item.kicker|lower }}">
<article class="timeline-entry accent-{{ item.kicker|lower }}" data-activity-item>
<div class="entry-meta">
<span class="label">{{ item.kicker }}</span>
<time datetime="{{ item.date }}">{{ item.date|dateformat('YYYY.MM.dd') }}</time>
@@ -41,5 +41,9 @@
</article>
{% endfor %}
</div>
<div class="activity-controls" data-activity-controls hidden>
<p class="activity-status" data-activity-status aria-live="polite"></p>
<button class="activity-more-button" type="button" data-activity-more aria-controls="recent-activity-list">Show 6 more</button>
</div>
</div>
</section>