Report repo mix context failures (#104)
All checks were successful
CI / lint (push) Successful in 9s
Release / release-candidate (push) Successful in 4s
CI / build-frontend (push) Successful in 4s

Closes #103
This commit is contained in:
rockachopa 2026-08-06 12:17:51 +00:00
commit acc60d11e7
3 changed files with 50 additions and 14 deletions

View File

@ -161,6 +161,7 @@ textarea { resize: vertical; min-height: 120px; }
<script src="static/markdown.js"></script> <script src="static/markdown.js"></script>
<script src="static/commands.js"></script> <script src="static/commands.js"></script>
<script src="static/widgets.js"></script>
<script> <script>
(function(){ (function(){
const qs = (s, el=document) => el.querySelector(s); const qs = (s, el=document) => el.querySelector(s);
@ -361,20 +362,7 @@ textarea { resize: vertical; min-height: 120px; }
tickWidgets(); tickWidgets();
/* Widgets */ /* Widgets */
async function updateRepoMix() { updateRepoMix(qs('#repo-mix'), fetch);
const el = qs('#repo-mix');
try {
const res = await fetch('api/v1/context', { headers: { Accept: 'application/json' } });
const data = await res.json();
const repos = data.repos || [];
const issues = (data.issues||[]).filter(i=>i.state==='open');
const prs = data.pull_requests || [];
el.innerHTML = '<div class="kv"><div class="label">Repos</div><div class="value">' + repos.length + '</div><div class="label">Open issues</div><div class="value">' + issues.length + '</div><div class="label">Open PRs</div><div class="value">' + prs.length + '</div></div>';
} catch (e) {
el.innerHTML = '<div class="muted">Widget unavailable.</div>';
}
}
updateRepoMix();
function widgetTick() { const el=qs('#widget-clock'); if(el) el.textContent = fmt(new Date()); } function widgetTick() { const el=qs('#widget-clock'); if(el) el.textContent = fmt(new Date()); }
setInterval(widgetTick, 1000); setInterval(widgetTick, 1000);

19
frontend/widgets.js Normal file
View File

@ -0,0 +1,19 @@
async function updateRepoMix(element, fetchContext = fetch) {
try {
const response = await fetchContext('api/v1/context', {
headers: { Accept: 'application/json' },
});
if (!response.ok) throw new Error('HTTP ' + response.status);
const data = await response.json();
const repos = data.repos || [];
const issues = (data.issues || []).filter((issue) => issue.state === 'open');
const pullRequests = data.pull_requests || [];
element.innerHTML = '<div class="kv"><div class="label">Repos</div><div class="value">' + repos.length + '</div><div class="label">Open issues</div><div class="value">' + issues.length + '</div><div class="label">Open PRs</div><div class="value">' + pullRequests.length + '</div></div>';
} catch (error) {
element.innerHTML = '<div class="muted">Widget unavailable.</div>';
}
}
if (typeof module !== 'undefined' && module.exports) {
module.exports = updateRepoMix;
}

View File

@ -1,8 +1,15 @@
import json
import subprocess
from pathlib import Path
import pytest import pytest
from src.views import dashboard from src.views import dashboard
WIDGETS = Path(__file__).parents[1] / "frontend" / "widgets.js"
@pytest.mark.anyio @pytest.mark.anyio
async def test_dashboard_renders_repo_mix_placeholder_before_javascript_runs(): async def test_dashboard_renders_repo_mix_placeholder_before_javascript_runs():
html = await dashboard() html = await dashboard()
@ -16,3 +23,25 @@ async def test_dashboard_marks_fallback_context_as_degraded_instead_of_live():
html = await dashboard() html = await dashboard()
assert "data.error ? 'Degraded · ' + data.error : 'Live · updated just now'" in html assert "data.error ? 'Degraded · ' + data.error : 'Live · updated just now'" in html
def test_repo_mix_reports_unavailable_for_failed_context_response():
script = f"""
const updateRepoMix = require({json.dumps(str(WIDGETS))});
const element = {{ innerHTML: 'stale counts' }};
const failedFetch = async () => ({{
ok: false,
status: 503,
json: async () => ({{ error: 'upstream unavailable' }}),
}});
updateRepoMix(element, failedFetch).then(() => {{
if (!element.innerHTML.includes('Widget unavailable.')) {{
throw new Error(`expected unavailable state, got: ${{element.innerHTML}}`);
}}
}});
"""
subprocess.run(
["node", "-e", script], check=True, capture_output=True, text=True
)