Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
141 changes: 141 additions & 0 deletions public/dashboard/dashboard.css
Original file line number Diff line number Diff line change
Expand Up @@ -1383,3 +1383,144 @@ body {
line-height: 1.5;
color: color-mix(in oklab, var(--color-base-content) 65%, transparent);
}

/* Hooks tab (PR3): visual rule builder and event-grouped card list. */
.dash-hook-list { display: flex; flex-direction: column; gap: var(--space-5); }
.dash-hook-event-section {
border: 1px solid var(--color-base-300);
border-radius: var(--radius-lg);
background: var(--color-base-100);
padding: var(--space-4);
}
.dash-hook-event-header { margin-bottom: var(--space-3); }
.dash-hook-event-title {
font-size: 14px;
font-weight: 600;
color: var(--color-base-content);
margin: 0 0 4px;
font-family: 'JetBrains Mono', monospace;
}
.dash-hook-event-summary {
font-size: 12px;
color: color-mix(in oklab, var(--color-base-content) 60%, transparent);
margin: 0;
}
.dash-hook-event-cards { display: flex; flex-direction: column; gap: var(--space-2); }

.dash-hook-builder {
border: 1px solid var(--color-base-300);
border-radius: var(--radius-lg);
background: var(--color-base-100);
padding: var(--space-5);
}
.dash-hook-builder-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: var(--space-4);
}
.dash-hook-builder-title {
font-size: 16px;
font-weight: 600;
margin: 0;
}
.dash-hook-columns {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: var(--space-4);
margin-bottom: var(--space-4);
}
@media (max-width: 960px) { .dash-hook-columns { grid-template-columns: 1fr; } }
.dash-hook-column {
border: 1px solid var(--color-base-300);
border-radius: var(--radius-md);
padding: var(--space-3);
background: var(--color-base-200);
}
.dash-hook-column-eyebrow {
font-size: 10px;
font-weight: 600;
letter-spacing: 0.09em;
text-transform: uppercase;
color: color-mix(in oklab, var(--color-base-content) 50%, transparent);
margin-bottom: var(--space-1);
}
.dash-hook-column-title {
font-size: 13px;
font-weight: 600;
margin: 0 0 var(--space-3);
}
.dash-hook-column-help {
font-size: 11.5px;
color: color-mix(in oklab, var(--color-base-content) 60%, transparent);
margin-top: var(--space-2);
line-height: 1.5;
}
.dash-hook-column-disabled {
font-size: 12px;
color: color-mix(in oklab, var(--color-base-content) 50%, transparent);
font-style: italic;
}
.dash-hook-action-form { display: flex; flex-direction: column; gap: var(--space-3); margin-top: var(--space-3); }
.dash-hook-preview {
border: 1px solid var(--color-base-300);
border-radius: var(--radius-md);
background: var(--color-base-200);
padding: var(--space-3);
}
.dash-hook-preview-summary {
cursor: pointer;
font-size: 11px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.08em;
color: color-mix(in oklab, var(--color-base-content) 55%, transparent);
user-select: none;
}
.dash-hook-preview-code {
margin-top: var(--space-3);
font-family: 'JetBrains Mono', monospace;
font-size: 11.5px;
line-height: 1.55;
background: var(--color-base-100);
border-radius: var(--radius-sm);
padding: var(--space-3);
overflow-x: auto;
color: var(--color-base-content);
}
.dash-audit-row {
padding: 8px 10px;
border: 1px solid var(--color-base-300);
border-radius: var(--radius-sm);
background: var(--color-base-100);
margin-bottom: 6px;
}
.dash-audit-row-top { font-size: 12px; }
.dash-audit-row-body { font-size: 11px; color: color-mix(in oklab, var(--color-base-content) 60%, transparent); margin-top: 4px; }

/* Settings tab (PR3): grouped sections and a bottom save bar. */
.dash-settings-section {
border: 1px solid var(--color-base-300);
border-radius: var(--radius-lg);
background: var(--color-base-100);
padding: var(--space-4);
margin-bottom: var(--space-4);
}
.dash-settings-section header { margin-bottom: var(--space-3); }
.dash-save-bar {
position: sticky;
bottom: 0;
display: flex;
align-items: center;
justify-content: space-between;
padding: var(--space-3) var(--space-4);
background: var(--color-base-200);
border-top: 1px solid var(--color-base-300);
margin-top: var(--space-4);
backdrop-filter: blur(6px);
}
.dash-save-bar-status {
font-size: 12px;
color: color-mix(in oklab, var(--color-base-content) 65%, transparent);
}
.dash-save-bar-actions { display: flex; gap: var(--space-2); }
71 changes: 59 additions & 12 deletions public/dashboard/dashboard.js
Original file line number Diff line number Diff line change
Expand Up @@ -181,35 +181,30 @@
container.setAttribute("data-active", "true");
var labels = {
sessions: {
eyebrow: "PR2",
eyebrow: "soon",
title: "Sessions",
body: "A live view of every session the agent has had, with channels, costs, turn counts, and outcomes. Click through for full transcripts and the memories consolidated from each run.",
},
cost: {
eyebrow: "PR2",
eyebrow: "soon",
title: "Cost",
body: "Daily and weekly cost breakdowns with model-level detail. Charts across time so you can see where the agent's budget actually goes, and alerts when anything drifts out of its baseline.",
},
scheduler: {
eyebrow: "PR3",
eyebrow: "soon",
title: "Scheduler",
body: "Every cron and one-shot job the agent has created, with next-run times, recent outcomes, and the ability to edit or pause a schedule without asking the agent to do it for you.",
},
evolution: {
eyebrow: "PR3",
eyebrow: "soon",
title: "Evolution timeline",
body: "The 6-step self-evolution pipeline rendered as a timeline: reflections, judges, validated changes, version bumps, and rollback points. You see exactly how the agent is changing itself over time.",
},
memory: {
eyebrow: "PR4",
eyebrow: "soon",
title: "Memory explorer",
body: "A read view over every episode, fact, and procedure the agent has consolidated. Search, filter by decay, inspect provenance, and watch memories get reinforced as they get reused.",
},
settings: {
eyebrow: "PR3",
title: "Settings",
body: "A curated form over the agent's Claude Code settings: permissions, MCP servers, hooks, and the knobs that actually change how it thinks. Raw JSON escape hatch for the power users.",
},
};
var meta = labels[name] || { eyebrow: "Soon", title: name, body: "Coming in a later PR." };
container.innerHTML = (
Expand Down Expand Up @@ -246,8 +241,8 @@
var name = parsed.route;
deactivateAllRoutes();

var liveRoutes = ["skills", "memory-files", "plugins"];
var comingSoon = ["sessions", "cost", "scheduler", "evolution", "memory", "settings"];
var liveRoutes = ["skills", "memory-files", "plugins", "subagents", "hooks", "settings"];
var comingSoon = ["sessions", "cost", "scheduler", "evolution", "memory"];

if (liveRoutes.indexOf(name) >= 0 && routes[name]) {
var containerId = "route-" + name;
Expand Down Expand Up @@ -304,9 +299,61 @@
if (el) el.textContent = new Date().toISOString().split("T")[0];
}

// Server-sent events for live dashboard updates. PR3 adds a
// "plugin_init_snapshot" event that fires when the agent sees the SDK init
// message and resolves the enabled-plugin set. The plugins module flips
// optimistically-installed cards to their real state.
//
// Connection health: the browser auto-reconnects on transport errors
// but not on HTTP 401 or 503, which happens on session expiry or
// cold boot. We paint a small status dot in the sidebar that shows
// live / reconnecting / disconnected so the operator knows whether
// live updates are actually arriving.
function updateSSEDot(statusClass, label) {
var dot = document.getElementById("dashboard-sse-dot");
if (!dot) return;
dot.setAttribute("data-status", statusClass);
dot.setAttribute("title", label);
}

function openEventStream() {
if (!window.EventSource) return null;
try {
var es = new EventSource("/ui/api/events");
es.addEventListener("open", function () {
updateSSEDot("live", "Live updates connected");
});
es.addEventListener("plugin_init_snapshot", function (e) {
try {
var data = JSON.parse(e.data);
if (window.PhantomPluginsModule && typeof window.PhantomPluginsModule.onInitSnapshot === "function") {
window.PhantomPluginsModule.onInitSnapshot(data);
}
} catch (_) {
// SSE payload was malformed; nothing useful to show the user.
}
});
es.onerror = function (e) {
// EventSource auto-reconnects on transport errors. Log a
// soft warning for debuggers; do not toast the user.
console.warn("SSE error, browser will attempt reconnect", e);
if (es.readyState === EventSource.CONNECTING) {
updateSSEDot("reconnecting", "Live updates reconnecting");
} else if (es.readyState === EventSource.CLOSED) {
updateSSEDot("disconnected", "Live updates disconnected");
}
};
return es;
} catch (_) {
updateSSEDot("disconnected", "Live updates unavailable");
return null;
}
}

function init() {
setNavDate();
initThemeToggle();
openEventStream();

window.addEventListener("beforeunload", function (e) {
if (anyDirty()) {
Expand Down
Loading
Loading