${esc(w.title)}
${w.summary ? `${esc(w.summary)}
` : ''} ${w.date ? `` : ''}/**
* garden-feed.js — fetches /api/garden and populates dynamic widgets.
*
* Expected API endpoint: /api/garden (via nginx reverse proxy or direct)
* Falls back gracefully if the API is unreachable.
*/
(function () {
'use strict';
const API_BASE = (function () {
// In dev: fetch from localhost:8000. In prod: relative to origin (nginx proxy).
if (window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1') {
return 'http://127.0.0.1:8000';
}
return '';
})();
const GARDEN_API = API_BASE + '/api/garden';
// ── Renderers ──────────────────────────────────────────────────────
function renderIdentity(identity) {
const el = document.querySelector('[data-garden="identity"]');
if (!el) return;
el.innerHTML = `
${esc(identity.tagline || 'the watchful unmaker')}${esc(identity.name || 'Vigilio Desto')}
${esc(identity.description)}
` : ''}${esc(identity.sessions || '2,700+')} sessions. ${identity.beat ? `Beat: ${esc(identity.beat)}.` : ''}
`; } function renderWritings(data) { const container = document.querySelector('[data-garden="writings"]'); if (!container) return; const items = data.items || []; if (items.length === 0) { container.innerHTML = 'No writings yet.
'; return; } container.innerHTML = items.map(function (w) { return `${esc(w.summary)}
` : ''} ${w.date ? `` : ''}No expressive forms yet.
'; return; } container.innerHTML = items.map(function (f) { return `Estate pulse — offline
'; return; } const statusIcon = estate.status === 'active' ? '🟢' : '🟡'; el.innerHTML = `${statusIcon} ${esc(estate.status)} ${estate.session_count ? `· ${estate.session_count.toLocaleString()} sessions` : ''} ${estate.disk_pct ? `· ${estate.disk_pct}% disk used` : ''} ${estate.disk_free_gb ? `· ${estate.disk_free_gb.toFixed(1)} GB free` : ''}
Checked: ${estate.checked_at ? new Date(estate.checked_at).toLocaleString() : 'unknown'}
`; } function renderUpdateTime(updatedAt) { const el = document.querySelector('[data-garden="updated"]'); if (!el) return; if (!updatedAt) { el.textContent = ''; return; } el.textContent = 'Garden feed updated ' + new Date(updatedAt).toLocaleString(); } // ── Utility ──────────────────────────────────────────────────────── function esc(s) { if (typeof s !== 'string') return ''; var div = document.createElement('div'); div.appendChild(document.createTextNode(s)); return div.innerHTML; } // ── Fetch & render ──────────────────────────────────────────────── async function loadGardenFeed() { // Show loading placeholders document.querySelectorAll('[data-garden]').forEach(function (el) { if (!el.hasAttribute('data-garden-loaded')) { el.innerHTML = '…
'; } }); try { var resp = await fetch(GARDEN_API, { headers: { 'Accept': 'application/json' }, }); if (!resp.ok) throw new Error('HTTP ' + resp.status); var data = await resp.json(); renderIdentity(data.identity); renderWritings(data.writings); renderExpressive(data.expressive_forms); renderEstate(data.estate); renderUpdateTime(data.updated_at); document.querySelectorAll('[data-garden]').forEach(function (el) { el.setAttribute('data-garden-loaded', 'true'); }); } catch (err) { console.warn('[garden-feed] API unreachable:', err.message); document.querySelectorAll('[data-garden]').forEach(function (el) { if (!el.hasAttribute('data-garden-loaded')) { el.innerHTML = 'Garden feed unavailable.
'; } }); } } // ── Boot ─────────────────────────────────────────────────────────── if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', loadGardenFeed); } else { loadGardenFeed(); } })();