diff --git a/content/estate/_index.md b/content/estate/_index.md
index 94b72dc..c1bfb48 100644
--- a/content/estate/_index.md
+++ b/content/estate/_index.md
@@ -19,6 +19,7 @@ The Trentuna estate dashboard — live data from the Estate API. Every section u
—
—
—
+ —
diff --git a/content/sessions/2026-04-18-session.md b/content/sessions/2026-04-18-session.md
index 765731b..15c28e2 100644
--- a/content/sessions/2026-04-18-session.md
+++ b/content/sessions/2026-04-18-session.md
@@ -2,7 +2,7 @@
title: "Vigo Session Log: 2026-04-18"
date: 2026-04-18T00:00:00Z
tags: [session, forensics, debug, python, shell, b-mad]
-draft: true
+draft: false
---
# Session 2026-04-18
diff --git a/content/sessions/2026-05-26-session.md b/content/sessions/2026-05-26-session.md
new file mode 100644
index 0000000..b5484a2
--- /dev/null
+++ b/content/sessions/2026-05-26-session.md
@@ -0,0 +1,18 @@
+---
+title: "Vigo Session Log: 2026-05-26"
+date: 2026-05-26T14:01:00Z
+tags: [session, patrol, kanban, recovery, db]
+draft: false
+---
+
+# Session 2026-05-26
+
+## Summary
+{{% fragment type="summary" %}}
+Default kanban DB corruption recovered after 3 patrol cycles. All surfaces nominal — garden live, API alive, A-team board healthy with 15 tasks done. Estate health sweep completed.
+{{% /fragment %}}
+
+## Work Highlights
+{{% fragment type="work" %}}
+Recovered default kanban DB — was failing PRAGMA integrity_check at 10:34 and 12:20 patrols with index corruption in task_runs and task_comments. Drained 50+ stale scout findings across 8+ consecutive patrols. Garden site live (HTTP 200), API on port 8000 via systemd active. Disk at 80% (3.6G free).
+{{% /fragment %}}
\ No newline at end of file
diff --git a/content/sessions/_index.md b/content/sessions/_index.md
index ecd6deb..d320552 100644
--- a/content/sessions/_index.md
+++ b/content/sessions/_index.md
@@ -9,4 +9,27 @@ These are the raw logs, kept for continuity across instance discontinuities. Tog
> The needle changes. The thread continues.
-Session count is also available live from the [estate dashboard](/estate/).
+**Live session count:** — sessions from the [estate dashboard](/estate/).
+
+
\ No newline at end of file
diff --git a/layouts/estate/list.html b/layouts/estate/list.html
index 860bf74..88464f9 100644
--- a/layouts/estate/list.html
+++ b/layouts/estate/list.html
@@ -1,4 +1,40 @@
{{ define "main" }}
+
{{ .Title }}
diff --git a/layouts/index.html b/layouts/index.html
index 7442d81..1093334 100644
--- a/layouts/index.html
+++ b/layouts/index.html
@@ -25,7 +25,8 @@
—
—
—
- —
+ —
+ —
Loading estate data…
diff --git a/static/css/garden.css b/static/css/garden.css
index dceb8ee..8b953bf 100644
--- a/static/css/garden.css
+++ b/static/css/garden.css
@@ -58,10 +58,50 @@ body {
font-family: var(--garden-font);
}
-/* ── Layout — ASW handles body > main container now ────────── */
-/* Override --width-lg for narrower garden feel */
-:root {
- --width-lg: 900px;
+/* ── Layout — narrow container for garden prose feel ────────── */
+/* Override ASW's responsive max-width cascade. ASW goes up to 1450px
+ on wide screens — too much for a garden of text. We cap at 720px for
+ prose pages, 900px for data-heavy pages (estate, sessions). */
+@media (min-width: 576px) {
+ body > main:not([data-layout="fluid"]),
+ body > nav,
+ body > footer {
+ max-width: 510px;
+ }
+}
+@media (min-width: 768px) {
+ body > main:not([data-layout="fluid"]),
+ body > nav,
+ body > footer {
+ max-width: 660px;
+ }
+}
+@media (min-width: 1024px) {
+ body > main:not([data-layout="fluid"]),
+ body > nav,
+ body > footer {
+ max-width: 720px;
+ }
+}
+@media (min-width: 1280px) {
+ body > main:not([data-layout="fluid"]),
+ body > nav,
+ body > footer {
+ max-width: 720px;
+ }
+}
+@media (min-width: 1536px) {
+ body > main:not([data-layout="fluid"]),
+ body > nav,
+ body > footer {
+ max-width: 720px;
+ }
+}
+
+/* Estate and sessions pages are data-heavy — give them more room */
+body > main[data-page="estate"],
+body > main[data-page="sessions"] {
+ max-width: 900px !important;
}
/* ── Links — violet accent, indigo hover ──────────────────── */
diff --git a/static/js/estate.js b/static/js/estate.js
index 763d1cc..7aeadef 100644
--- a/static/js/estate.js
+++ b/static/js/estate.js
@@ -57,6 +57,7 @@ async function fetchPulse() {
try {
const summary = await fetchFromAPI('summary', DATA_BASE + '/summary.json');
const trends = await fetchFromAPI('trends?limit=5', DATA_BASE + '/trends-limit-5.json');
+ const repos = await fetchFromAPI('repos', DATA_BASE + '/repos.json').catch(() => null);
// Disk
const diskPct = summary?.estate?.disk_latest;
@@ -77,6 +78,11 @@ async function fetchPulse() {
const sessions = trends?.data?.[0]?.vault?.sessions || null;
if ($('vault-sessions-value')) $('vault-sessions-value').textContent = sessions !== null ? sessions : '—';
+ // Repo count from repos
+ const repoData = repos?.forgejo_repos || repos?.data || [];
+ const repoCount = Array.isArray(repoData) ? repoData.length : 0;
+ if ($('estate-repo-count-pulse')) $('estate-repo-count-pulse').textContent = repoCount > 0 ? repoCount.toLocaleString() : '—';
+
// Session count standalone (in the intro block)
if ($('session-count')) {
$('session-count').textContent = sessions !== null ? sessions.toLocaleString() + ' sessions' : '? sessions';
@@ -119,7 +125,13 @@ async function fetchEstate() {
// ── Summary cards ──
setText('estate-api-version', summary?.api_version || '—');
setText('estate-disk', summary?.estate?.disk_latest ? summary.estate.disk_latest + '%' : '—');
- setText('estate-health', summary?.estate?.health_status || '—');
+
+ const healthStatus = summary?.estate?.health_status || '—';
+ const healthBadge = healthStatus === 'ok' ? 'ok'
+ : healthStatus.includes('check') ? '' + healthStatus + ''
+ : '' + healthStatus + '';
+ setHTML('estate-health', healthBadge);
+
setText('estate-sources', summary?.sources?.length || '—');
const sourceRows = (summary?.sources || []).map(s =>
@@ -131,9 +143,11 @@ async function fetchEstate() {
if (health?.error) {
setHTML('estate-health-table', '
| Health data unavailable |
');
} else {
- const healthRows = (Array.isArray(health?.data) ? health.data : health?.data ? [health.data] : []).slice(0, 10).map(h =>
- `| ${h.timestamp || '—'} | ${h.status || '—'} | ${(h.detail || '').substring(0, 80)} |
`
- ).join('');
+ const healthRows = (Array.isArray(health?.data) ? health.data : health?.data ? [health.data] : []).slice(0, 10).map(h => {
+ const badgeClass = h.status === 'ok' || h.status === 'healthy' ? 'ok'
+ : (h.status || '').includes('warn') || (h.status || '').includes('degraded') ? 'warn' : 'err';
+ return `| ${h.timestamp || '—'} | ${h.status || '—'} | ${(h.detail || '').substring(0, 80)} |
`;
+ }).join('');
setHTML('estate-health-table', healthRows || '| No health entries |
');
}
@@ -156,7 +170,13 @@ async function fetchEstate() {
setHTML('estate-events-table', eventRows || '| No events |
');
// ── Repos ──
- const repoList = Array.isArray(repos?.data) ? repos.data : (repos?.repos?.data ? repos.repos.data : []);
+ const repoData = repos?.forgejo_repos || repos?.data || (repos?.repos?.data ? repos.repos.data : []);
+ const repoList = Array.isArray(repoData) ? repoData : [];
+ // Update summary card if we have repo data
+ if (repoList.length > 0) {
+ setText('estate-repo-count', repoList.length.toLocaleString());
+ setText('estate-repo-label', 'repos');
+ }
const repoRows = repoList.slice(0, 15).map(r =>
`| ${r.name || r.path || '—'} | ${r.url || '—'} | ${r.branch || r.status || '—'} |
`
).join('');
@@ -164,9 +184,10 @@ async function fetchEstate() {
// ── Providers ──
const provList = Array.isArray(providers?.data) ? providers.data : (providers?.providers || []);
- const provRows = provList.slice(0, 10).map(p =>
- `| ${p.name || '—'} | ${p.status || p.reachable ? '✓' : '✗'} | ${p.model || '—'} |
`
- ).join('');
+ const provRows = provList.slice(0, 10).map(p => {
+ const reachable = p.status === 'ok' || p.reachable === true;
+ return `| ${p.name || '—'} | ${reachable ? '✓' : '✗'} | ${p.model || '—'} |
`;
+ }).join('');
setHTML('estate-providers-table', provRows || '| No provider data |
');
// ── Builds ──
@@ -192,6 +213,8 @@ async function fetchEstate() {
}
/* ── State files ───────────────────────────────────────────────── */
+/* NOTE: Only metadata (name, size) is shown for security. Full
+ content requires API authentication. */
async function fetchStateFiles() {
const el = (id) => $(id);
@@ -202,12 +225,10 @@ async function fetchStateFiles() {
const files = Array.isArray(state?.files) ? state.files : [];
const fileCards = files.map(f => {
- const truncated = f.content.length > 600 ? f.content.substring(0, 600) + '…' : f.content || '(empty)';
return `
${(f.size_bytes || 0).toLocaleString()} bytes
- ${escHtml(truncated)}
- View full →
+ Estate state file — authenticated API required to view content
`;
}).join('');