summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPeter Stone <thepeterstone@gmail.com>2026-07-04 17:08:50 +0000
committerPeter Stone <thepeterstone@gmail.com>2026-07-04 17:08:50 +0000
commitcc6b3234c6ad7f0f6acef83f6ccb3556581e80cb (patch)
tree6288fa70d8a871cbcbd727af967f17df66f65143
parentd8f79c1912eccdc2c65679c4f60603f98f5eece2 (diff)
feat(web): simplify nav to tracker board, model dashboard, drops, settings+roles
Remove the old queue/interrupted/ready/running/budget/roles tabs. Keep: stories (tracker board, now the default tab), stats (model visibility dashboard), drops (file drop), settings. Role configuration moves into the Settings tab as a section below the general settings, rendered by renderSettingsPanel calling renderRolesPanel with a sub-container. renderRolesPanel now accepts an optional container parameter and uses safe DOM methods instead of innerHTML for error/empty states. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DNDHfCtTZDbQueEV5sUBiD
-rw-r--r--web/app.js83
-rw-r--r--web/index.html34
2 files changed, 40 insertions, 77 deletions
diff --git a/web/app.js b/web/app.js
index 894aaec..fc6864c 100644
--- a/web/app.js
+++ b/web/app.js
@@ -583,7 +583,7 @@ export function setTaskFilterTab(tab) {
}
export function getActiveMainTab() {
- return localStorage.getItem('activeMainTab') ?? 'queue';
+ return localStorage.getItem('activeMainTab') ?? 'stories';
}
export function setActiveMainTab(tab) {
@@ -1310,7 +1310,7 @@ let lastHistoryFetch = 0;
function getActiveTab() {
const active = document.querySelector('.tab.active');
- return active ? active.dataset.tab : 'queue';
+ return active ? active.dataset.tab : 'stories';
}
function getRefreshInterval() {
@@ -1327,26 +1327,11 @@ async function fetchHealth() {
function renderActiveTab(allTasks) {
const activeTab = getActiveTab();
switch (activeTab) {
- case 'queue':
- renderQueuePanel(allTasks);
- break;
- case 'interrupted':
- renderInterruptedPanel(allTasks);
- break;
- case 'ready':
- renderReadyPanel(allTasks);
- break;
- case 'running':
- renderRunningView(allTasks);
- if (Date.now() - lastHistoryFetch > 60_000) {
- lastHistoryFetch = Date.now();
- fetchRecentExecutions(BASE_PATH, fetch)
- .then(execs => renderRunningHistory(execs))
- .catch(() => {
- const histEl = document.querySelector('.running-history');
- if (histEl) histEl.innerHTML = '<p class="task-meta">Could not load execution history.</p>';
- });
- }
+ case 'stories':
+ // Guard against yanking the board out from under an in-progress
+ // HTML5 drag (see draggingStoryId) — the next poll tick after the
+ // drag ends will pick up any server-side change.
+ if (!draggingStoryId) renderStoriesPanel();
break;
case 'stats':
Promise.all([
@@ -1357,21 +1342,9 @@ function renderActiveTab(allTasks) {
.then(([execs, agentData, dashStats]) => renderStatsPanel(allTasks, execs, agentData, dashStats))
.catch(() => {});
break;
- case 'stories':
- // Guard against yanking the board out from under an in-progress
- // HTML5 drag (see draggingStoryId) — the next poll tick after the
- // drag ends will pick up any server-side change.
- if (!draggingStoryId) renderStoriesPanel();
- break;
case 'drops':
renderDropsPanel();
break;
- case 'budget':
- renderBudgetPanel();
- break;
- case 'roles':
- renderRolesPanel();
- break;
case 'settings':
renderSettingsPanel();
break;
@@ -1444,28 +1417,30 @@ if (typeof window !== 'undefined') {
-function renderSettingsPanel() {
+async function renderSettingsPanel() {
const panel = document.querySelector('[data-panel="settings"]');
if (!panel) return;
panel.innerHTML = '';
+
+ // ── General settings ──
const section = document.createElement('div');
section.className = 'stats-section';
section.style.padding = '1rem';
const heading = document.createElement('h2');
- heading.textContent = 'User Settings';
+ heading.textContent = 'Settings';
section.appendChild(heading);
const refreshLabel = document.createElement('label');
refreshLabel.style.display = 'block';
refreshLabel.style.marginBottom = '0.5rem';
refreshLabel.textContent = 'Auto-Refresh Interval';
-
+
const refreshSelect = document.createElement('select');
refreshSelect.className = 'agent-selector';
refreshSelect.style.width = '100%';
-
+
const options = [
{ label: '5 seconds', value: '5000' },
{ label: '10 seconds (default)', value: '10000' },
@@ -1485,12 +1460,22 @@ function renderSettingsPanel() {
refreshSelect.addEventListener('change', () => {
localStorage.setItem('refreshInterval', refreshSelect.value);
- startPolling(); // restart with new interval
+ startPolling();
});
refreshLabel.appendChild(refreshSelect);
section.appendChild(refreshLabel);
panel.appendChild(section);
+
+ // ── Role Configuration ──
+ const rolesSection = document.createElement('div');
+ rolesSection.className = 'stats-section';
+ rolesSection.style.padding = '1rem';
+ const rolesHeading = document.createElement('h2');
+ rolesHeading.textContent = 'Role Configuration';
+ rolesSection.appendChild(rolesHeading);
+ panel.appendChild(rolesSection);
+ await renderRolesPanel(rolesSection);
}
// ── WebSocket (real-time events) ──────────────────────────────────────────────
@@ -4627,7 +4612,7 @@ async function handleActivateRoleVersionClick(roleName, version, btn) {
const body = await res.json().catch(() => ({}));
throw new Error(body.error || `HTTP ${res.status}`);
}
- await renderRolesPanel();
+ await renderSettingsPanel();
} catch (err) {
btn.disabled = false;
btn.textContent = original;
@@ -4705,15 +4690,17 @@ function renderRoleCard(roleName, versions, doc = document) {
return card;
}
-async function renderRolesPanel() {
- const panel = document.querySelector('[data-panel="roles"]');
- if (!panel) return;
+async function renderRolesPanel(container = document.querySelector('[data-panel="roles"]')) {
+ if (!container) return;
try {
const namesRes = await fetch(`${BASE_PATH}/api/roles`);
const names = namesRes.ok ? await namesRes.json() : [];
if (!names || names.length === 0) {
- panel.innerHTML = '<div class="task-empty">No role configs yet.</div>';
+ const empty = document.createElement('div');
+ empty.className = 'task-empty';
+ empty.textContent = 'No role configs yet.';
+ container.appendChild(empty);
return;
}
@@ -4721,12 +4708,14 @@ async function renderRolesPanel() {
fetch(`${BASE_PATH}/api/roles/${encodeURIComponent(name)}/versions`).then(r => r.ok ? r.json() : []),
));
- panel.innerHTML = '';
names.forEach((name, i) => {
- panel.appendChild(renderRoleCard(name, versionsByRole[i] || []));
+ container.appendChild(renderRoleCard(name, versionsByRole[i] || []));
});
} catch (err) {
- panel.innerHTML = `<div class="panel-fetch-error">Failed to load roles: ${err.message}</div>`;
+ const errEl = document.createElement('div');
+ errEl.className = 'panel-fetch-error';
+ errEl.textContent = `Failed to load roles: ${err.message}`;
+ container.appendChild(errEl);
}
}
diff --git a/web/index.html b/web/index.html
index 32c029e..0704cb8 100644
--- a/web/index.html
+++ b/web/index.html
@@ -40,35 +40,13 @@
<div id="budget-bar" class="budget-bar"></div>
</header>
<nav class="tab-bar">
- <button class="tab active" data-tab="queue" title="Queue">⏳</button>
- <button class="tab" data-tab="interrupted" title="Interrupted">⚠️<span class="tab-count-badge" hidden></span></button>
- <button class="tab" data-tab="ready" title="Ready">✅<span class="tab-count-badge" hidden></span></button>
- <button class="tab" data-tab="running" title="Running">▶️<span class="tab-count-badge" hidden></span></button>
- <button class="tab" data-tab="stories" title="Stories">📋</button>
+ <button class="tab active" data-tab="stories" title="Tracker">📋</button>
+ <button class="tab" data-tab="stats" title="Model Dashboard">📊</button>
<button class="tab" data-tab="drops" title="Drops">📁</button>
- <button class="tab" data-tab="stats" title="Stats">📊</button>
- <button class="tab" data-tab="budget" title="Budget &amp; Escalation">💰</button>
- <button class="tab" data-tab="roles" title="Roles">🎛️</button>
<button class="tab" data-tab="settings" title="Settings">⚙️</button>
</nav>
<main id="app">
- <div data-panel="queue">
- <div class="panel-task-list">
- <div class="task-empty">Loading…</div>
- </div>
- </div>
- <div data-panel="interrupted" hidden>
- <div class="panel-task-list"></div>
- </div>
- <div data-panel="ready" hidden>
- <div class="panel-task-list"></div>
- <div class="ready-completed-history"></div>
- </div>
- <div data-panel="running" hidden>
- <div class="running-current"></div>
- <div class="running-history"></div>
- </div>
- <div data-panel="stories" hidden>
+ <div data-panel="stories">
<div class="stories-toolbar">
<div class="view-toggle" role="tablist" aria-label="Stories view">
<button class="view-toggle-btn active" data-view="board" type="button">Board</button>
@@ -83,11 +61,7 @@
<div class="drops-panel"></div>
</div>
<div data-panel="stats" hidden></div>
- <div data-panel="budget" hidden></div>
- <div data-panel="roles" hidden></div>
- <div data-panel="settings" hidden>
- <p class="task-meta" style="padding:1rem">Settings coming soon.</p>
- </div>
+ <div data-panel="settings" hidden></div>
</main>
<!-- Side panel backdrop -->