diff --git a/monitor/frontend/dist/bundle.js b/monitor/frontend/dist/bundle.js index e4d3274..2e4ac33 100644 --- a/monitor/frontend/dist/bundle.js +++ b/monitor/frontend/dist/bundle.js @@ -66038,7 +66038,7 @@ var require_elk_bundled = __commonJS({ Qb(a); zo(this.a, new Lp(a)); }; - _.xc = function rp(a) { + _.xc = function rp2(a) { return lp(this, a); }; _.ec = function sp() { @@ -123275,79 +123275,182 @@ var baseStylesheet = [ } ]; +// src/palette.ts +var rp = { + base: "#191724", + surface: "#1f1d2e", + overlay: "#26233a", + muted: "#6e6a86", + subtle: "#908caa", + text: "#e0def4", + love: "#eb6f92", + gold: "#f6c177", + rose: "#ebbcba", + pine: "#31748f", + foam: "#9ccfd8", + iris: "#c4a7e7", + highlightLow: "#21202e", + highlightMed: "#403d52", + highlightHigh: "#524f67" +}; +var rpMoon = { + rose: "#ea9a97", + pine: "#3e8fb0" +}; + // src/style.ts -var monitorOverlayStyles = [ - // SM node dark-mode override: readable on dark background +var monitorDarkModeOverrides = [ + // Base node: rosé pine dark background + { + selector: "node", + style: { + "background-color": rp.surface, + "background-opacity": 1, + color: rp.text, + "border-color": rp.highlightMed, + "text-outline-width": 0 + } + }, + // Per-category border colours (override data(colour) from server) + { + selector: 'node[category = "arithmetic"]', + style: { "border-color": rp.foam } + }, + { + selector: 'node[category = "logic"]', + style: { "border-color": rp.pine } + }, + { + selector: 'node[category = "comparison"]', + style: { "border-color": rp.gold } + }, + { + selector: 'node[category = "routing"]', + style: { "border-color": rp.iris } + }, + { + selector: 'node[category = "memory"]', + style: { "border-color": rpMoon.rose } + }, + { + selector: 'node[category = "io"]', + style: { "border-color": rpMoon.pine } + }, + { + selector: 'node[category = "config"]', + style: { "border-color": rp.muted } + }, + // SM node: warm gold/rose tones { selector: 'node[category = "structure_memory"]', style: { - "background-color": "#2e2416", + "background-color": rp.overlay, "background-opacity": 0.9, - "border-color": "#ff9800", - color: "#e0c8a0" + "border-color": rp.gold, + color: rp.rose } }, - // Synthetic edge dark-mode override + // Compound node (PE clusters): rosé pine overlay { - selector: "edge.synthetic", + selector: "$node > node", + style: { + "background-color": rp.overlay, + "background-opacity": 0.4, + "border-color": rp.iris, + "border-style": "dashed", + "border-width": 1, + color: rp.iris + } + }, + // Base edge: muted on dark + { + selector: "edge", + style: { + "line-color": rp.subtle, + "target-arrow-color": rp.subtle, + color: rp.muted, + "text-background-color": rp.base, + "text-background-opacity": 0.9 + } + }, + // Error node + { + selector: "node.error", + style: { + "border-color": rp.love + } + }, + // Error edge + { + selector: "edge.error", style: { - "line-color": "#ff9800", - "target-arrow-color": "#ff9800" + "line-color": rp.love, + "target-arrow-color": rp.love } }, - // Active node (AC3.3): bright border glow when token received + // Synthetic edge (SM request/return) + { + selector: "edge.synthetic", + style: { + "line-color": rp.gold, + "target-arrow-color": rp.gold + } + } +]; +var monitorOverlayStyles = [ + // Active node: bright gold border when token received { selector: "node.active", style: { - "border-color": "#ffc107", + "border-color": rp.gold, "border-width": 4 } }, - // Executed node (AC3.3): brief green highlight + // Executed node: pine green highlight { selector: "node.executed", style: { - "background-color": "#4caf50", + "background-color": rp.pine, "background-opacity": 0.3 } }, - // Matched node: brief cyan highlight + // Matched node: foam cyan border { selector: "node.matched", style: { - "border-color": "#00bcd4", + "border-color": rp.foam, "border-width": 3 } }, - // Half-matched node (AC3.12): one operand waiting + // Half-matched node: one operand waiting { selector: "node.half-matched", style: { "border-style": "dashed", "border-width": 2, - "border-color": "#00bcd4", + "border-color": rp.foam, "background-opacity": 0.5 } }, - // Token flow edge (AC3.4): animated dashed line + // Token flow edge: animated gold dashed line { selector: "edge.token-flow", style: { - "line-color": "#ff9800", + "line-color": rp.gold, "line-dash-pattern": [6, 3], width: 3 } }, - // SM cell written: brief orange highlight on SM node + // SM cell written: rose highlight { selector: "node.cell-written", style: { - "border-color": "#ff9800", + "border-color": rp.rose, "border-width": 3, "background-opacity": 0.6 } }, - // Finished indicator: dim all nodes when simulation complete + // Finished indicator: dim all nodes { selector: "node.finished", style: { @@ -123355,8 +123458,135 @@ var monitorOverlayStyles = [ } } ]; +var physicalEdgeStyles = [ + // PE cluster node + { + selector: "node.pe-cluster", + style: { + shape: "roundrectangle", + width: "label", + height: "label", + "border-width": 1.5, + "border-color": rp.iris, + "background-color": rp.overlay, + "background-opacity": 0.4, + padding: "16px", + "text-valign": "top", + "text-halign": "center", + label: "data(label)", + "font-size": 9, + "font-weight": "bold", + color: rp.iris + } + }, + // Port node (bus topology endpoints) + { + selector: "node.port-node", + style: { + shape: "ellipse", + width: 10, + height: 10, + "background-color": rp.love, + "background-opacity": 1, + "border-width": 0, + label: "" + } + }, + // Intra-PE edge + { + selector: "edge.intra-pe", + style: { + width: 0.75, + "line-color": rp.subtle, + "target-arrow-color": rp.subtle, + "source-label": "", + "target-label": "data(targetLabel)", + "target-text-offset": 5, + "target-text-margin-y": -6, + "font-size": 7, + "font-family": "monospace", + color: rp.muted, + "text-background-color": rp.base, + "text-background-opacity": 0.9, + "text-background-padding": "1px" + } + }, + // Exit segment (node to exit port, no arrow) + { + selector: "edge.exit-segment", + style: { + width: 0.75, + "line-color": rp.subtle, + "target-arrow-shape": "none", + "target-label": "", + "source-label": "" + } + }, + // Entry segment (entry port to destination) + { + selector: "edge.entry-segment", + style: { + width: 0.75, + "line-color": rp.subtle, + "target-arrow-color": rp.subtle, + "target-arrow-shape": "triangle", + "source-label": "", + "target-label": "data(targetLabel)", + "target-text-offset": 5, + "target-text-margin-y": -6, + "font-size": 7, + "font-family": "monospace", + color: rp.muted, + "text-background-color": rp.base, + "text-background-opacity": 0.9, + "text-background-padding": "1px" + } + }, + // Bus segment (cross-PE thick edge) + { + selector: "edge.bus-segment", + style: { + width: 2, + "line-color": rp.love, + "target-arrow-color": rp.love, + "target-arrow-shape": "triangle", + "arrow-scale": 0.8, + label: "data(label)", + "target-label": "", + "source-label": "", + "text-margin-y": -8, + "font-size": 7, + "font-family": "monospace", + color: rp.love, + "text-background-color": rp.base, + "text-background-opacity": 0.9, + "text-background-padding": "1px" + } + }, + // Seed edge (dashed, muted) + { + selector: "edge.seed-edge", + style: { + width: 1, + "line-style": "dashed", + "line-color": rp.muted, + "target-arrow-color": rp.muted, + "target-text-offset": 5, + "target-text-margin-y": -6 + } + }, + // Physical edges default to bezier + { + selector: "edge.physical", + style: { + "curve-style": "bezier" + } + } +]; var monitorStylesheet = [ ...baseStylesheet, + ...monitorDarkModeOverrides, + ...physicalEdgeStyles, ...monitorOverlayStyles ]; @@ -123523,61 +123753,45 @@ function createConnection(options2) { var currentFilter = { component: null, eventType: null }; var allEvents = []; var eventTypeColors = { - Matched: "#00d4aa", - // Cyan - Executed: "#4caf50", - // Green - TokenReceived: "#2196f3", - // Blue - CellWritten: "#ff9800", - // Orange - DeferredRead: "#9c27b0", - // Purple - Emitted: "#ff5252", - // Red - TokenStored: "#ffeb3b" - // Yellow -}; -var defaultEventColor = "#b0bec5"; + Matched: rp.foam, + Executed: rp.pine, + TokenReceived: rp.iris, + CellWritten: rp.gold, + DeferredRead: rp.rose, + Emitted: rp.love, + TokenStored: rp.gold +}; function formatTime(time2) { return `[${time2.toFixed(3)}]`; } function renderEventEntry(event3) { const entry = document.createElement("div"); entry.className = "event-entry"; - entry.style.padding = "8px"; - entry.style.borderBottom = "1px solid #333"; - entry.style.fontFamily = "monospace"; - entry.style.fontSize = "12px"; - entry.style.cursor = "pointer"; - entry.style.transition = "background-color 0.2s"; entry.addEventListener("mouseenter", () => { - entry.style.backgroundColor = "#2a2a3e"; + entry.style.backgroundColor = rp.overlay; }); entry.addEventListener("mouseleave", () => { entry.style.backgroundColor = "transparent"; }); const timeSpan = document.createElement("span"); timeSpan.textContent = formatTime(event3.time); - timeSpan.style.color = "#00d4aa"; + timeSpan.style.color = rp.foam; entry.appendChild(timeSpan); entry.appendChild(document.createTextNode(" ")); const componentSpan = document.createElement("span"); componentSpan.textContent = event3.component; - componentSpan.style.color = "#ffeb3b"; + componentSpan.style.color = rp.gold; entry.appendChild(componentSpan); entry.appendChild(document.createTextNode(": ")); const eventTypeSpan = document.createElement("span"); eventTypeSpan.textContent = event3.type; - const color = eventTypeColors[event3.type] || defaultEventColor; - eventTypeSpan.style.color = color; + eventTypeSpan.style.color = eventTypeColors[event3.type] || rp.subtle; entry.appendChild(eventTypeSpan); if (Object.keys(event3.details).length > 0) { entry.appendChild(document.createTextNode(" \u2014 ")); - const detailsStr = JSON.stringify(event3.details); const detailsSpan = document.createElement("span"); - detailsSpan.textContent = detailsStr; - detailsSpan.style.color = "#888"; + detailsSpan.textContent = JSON.stringify(event3.details); + detailsSpan.style.color = rp.muted; entry.appendChild(detailsSpan); } entry.addEventListener("click", () => { @@ -123630,7 +123844,7 @@ function renderPEState(peId, state) { summary.textContent = `PE ${peId}`; summary.style.cursor = "pointer"; summary.style.fontWeight = "bold"; - summary.style.color = "#2196f3"; + summary.style.color = rp.foam; section.appendChild(summary); const content = document.createElement("div"); content.style.paddingLeft = "16px"; @@ -123640,20 +123854,20 @@ function renderPEState(peId, state) { iramDiv.style.marginBottom = "8px"; const iramLabel = document.createElement("strong"); iramLabel.textContent = "IRAM:"; - iramLabel.style.color = "#4caf50"; + iramLabel.style.color = rp.pine; iramDiv.appendChild(iramLabel); iramDiv.appendChild(document.createElement("br")); const iramEntries = Object.entries(state.iram || {}); if (iramEntries.length === 0) { const empty2 = document.createElement("span"); empty2.textContent = "(empty)"; - empty2.style.color = "#888"; + empty2.style.color = rp.muted; iramDiv.appendChild(empty2); } else { for (const [offset, instr] of iramEntries) { const line = document.createElement("div"); line.textContent = ` [${offset}]: ${JSON.stringify(instr)}`; - line.style.color = "#aaa"; + line.style.color = rp.subtle; iramDiv.appendChild(line); } } @@ -123662,14 +123876,14 @@ function renderPEState(peId, state) { msDiv.style.marginBottom = "8px"; const msLabel = document.createElement("strong"); msLabel.textContent = "Matching Store:"; - msLabel.style.color = "#4caf50"; + msLabel.style.color = rp.pine; msDiv.appendChild(msLabel); msDiv.appendChild(document.createElement("br")); const matchingStore = state.matching_store || []; if (matchingStore.length === 0) { const empty2 = document.createElement("span"); empty2.textContent = "(empty)"; - empty2.style.color = "#888"; + empty2.style.color = rp.muted; msDiv.appendChild(empty2); } else { for (let ctx = 0; ctx < matchingStore.length; ctx++) { @@ -123677,7 +123891,7 @@ function renderPEState(peId, state) { if (!ctx_slots || ctx_slots.length === 0) continue; const ctxDiv = document.createElement("div"); ctxDiv.textContent = ` ctx[${ctx}]:`; - ctxDiv.style.color = "#ffeb3b"; + ctxDiv.style.color = rp.gold; msDiv.appendChild(ctxDiv); for (let offset = 0; offset < ctx_slots.length; offset++) { const entry = ctx_slots[offset]; @@ -123687,7 +123901,7 @@ function renderPEState(peId, state) { const portStr = entry.port || "\u2014"; const dataStr = entry.data !== null ? entry.data.toString() : "\u2014"; entryDiv.textContent = `[${offset}]: data=${dataStr}, port=${portStr}`; - entryDiv.style.color = "#aaa"; + entryDiv.style.color = rp.subtle; msDiv.appendChild(entryDiv); } } @@ -123697,20 +123911,20 @@ function renderPEState(peId, state) { genDiv.style.marginBottom = "8px"; const genLabel = document.createElement("strong"); genLabel.textContent = "Gen Counters:"; - genLabel.style.color = "#4caf50"; + genLabel.style.color = rp.pine; genDiv.appendChild(genLabel); genDiv.appendChild(document.createElement("br")); const genCounters = state.gen_counters || []; if (genCounters.length === 0) { const empty2 = document.createElement("span"); empty2.textContent = "(none)"; - empty2.style.color = "#888"; + empty2.style.color = rp.muted; genDiv.appendChild(empty2); } else { for (let i = 0; i < genCounters.length; i++) { const line = document.createElement("div"); line.textContent = ` ctx[${i}]: ${genCounters[i]}`; - line.style.color = "#aaa"; + line.style.color = rp.subtle; genDiv.appendChild(line); } } @@ -123718,12 +123932,12 @@ function renderPEState(peId, state) { const statsDiv = document.createElement("div"); const queueSpan = document.createElement("span"); queueSpan.textContent = `Input Queue: ${state.input_queue_depth}`; - queueSpan.style.color = "#aaa"; + queueSpan.style.color = rp.subtle; statsDiv.appendChild(queueSpan); statsDiv.appendChild(document.createElement("br")); const outputSpan = document.createElement("span"); outputSpan.textContent = `Output Count: ${state.output_count}`; - outputSpan.style.color = "#aaa"; + outputSpan.style.color = rp.subtle; statsDiv.appendChild(outputSpan); content.appendChild(statsDiv); section.appendChild(content); @@ -123736,7 +123950,7 @@ function renderSMState(smId, state) { summary.textContent = `SM ${smId}`; summary.style.cursor = "pointer"; summary.style.fontWeight = "bold"; - summary.style.color = "#ff9800"; + summary.style.color = rp.gold; section.appendChild(summary); const content = document.createElement("div"); content.style.paddingLeft = "16px"; @@ -123746,7 +123960,7 @@ function renderSMState(smId, state) { cellsDiv.style.marginBottom = "8px"; const cellsLabel = document.createElement("strong"); cellsLabel.textContent = "Cells:"; - cellsLabel.style.color = "#4caf50"; + cellsLabel.style.color = rp.pine; cellsDiv.appendChild(cellsLabel); cellsDiv.appendChild(document.createElement("br")); const cells = state.cells || {}; @@ -123757,7 +123971,7 @@ function renderSMState(smId, state) { if (cellAddrs.length === 0) { const empty2 = document.createElement("span"); empty2.textContent = "(all empty)"; - empty2.style.color = "#888"; + empty2.style.color = rp.muted; cellsDiv.appendChild(empty2); } else { for (const addr of cellAddrs) { @@ -123766,7 +123980,7 @@ function renderSMState(smId, state) { const line = document.createElement("div"); const dataStr = cell.data_l !== null ? cell.data_l : cell.data_r !== null ? cell.data_r : "\u2014"; line.textContent = ` [${addr}]: ${cell.presence} = ${dataStr}`; - line.style.color = "#aaa"; + line.style.color = rp.subtle; cellsDiv.appendChild(line); } } @@ -123776,24 +123990,24 @@ function renderSMState(smId, state) { deferredDiv.style.marginBottom = "8px"; const deferredLabel = document.createElement("strong"); deferredLabel.textContent = "Deferred Read:"; - deferredLabel.style.color = "#9c27b0"; + deferredLabel.style.color = rp.iris; deferredDiv.appendChild(deferredLabel); deferredDiv.appendChild(document.createElement("br")); const drLine = document.createElement("div"); drLine.textContent = ` cell_addr: ${state.deferred_read.cell_addr}`; - drLine.style.color = "#aaa"; + drLine.style.color = rp.subtle; deferredDiv.appendChild(drLine); content.appendChild(deferredDiv); } const statsDiv = document.createElement("div"); const t0Span = document.createElement("span"); t0Span.textContent = `T0 Store Size: ${state.t0_store_size}`; - t0Span.style.color = "#aaa"; + t0Span.style.color = rp.subtle; statsDiv.appendChild(t0Span); statsDiv.appendChild(document.createElement("br")); const queueSpan = document.createElement("span"); queueSpan.textContent = `Input Queue: ${state.input_queue_depth}`; - queueSpan.style.color = "#aaa"; + queueSpan.style.color = rp.subtle; statsDiv.appendChild(queueSpan); content.appendChild(statsDiv); section.appendChild(content); @@ -123809,7 +124023,7 @@ function updateStateInspector(state, options2) { peSection.style.marginBottom = "16px"; const peTitle = document.createElement("h3"); peTitle.textContent = "Processing Elements"; - peTitle.style.color = "#2196f3"; + peTitle.style.color = rp.foam; peTitle.style.marginTop = "0"; peTitle.style.marginBottom = "8px"; peTitle.style.fontSize = "14px"; @@ -123818,7 +124032,7 @@ function updateStateInspector(state, options2) { if (Object.keys(pes).length === 0) { const empty2 = document.createElement("span"); empty2.textContent = "(none)"; - empty2.style.color = "#888"; + empty2.style.color = rp.muted; empty2.style.fontSize = "11px"; peSection.appendChild(empty2); } else { @@ -123830,7 +124044,7 @@ function updateStateInspector(state, options2) { const smSection = document.createElement("div"); const smTitle = document.createElement("h3"); smTitle.textContent = "Structure Memories"; - smTitle.style.color = "#ff9800"; + smTitle.style.color = rp.gold; smTitle.style.marginTop = "0"; smTitle.style.marginBottom = "8px"; smTitle.style.fontSize = "14px"; @@ -123839,7 +124053,7 @@ function updateStateInspector(state, options2) { if (Object.keys(sms).length === 0) { const empty2 = document.createElement("span"); empty2.textContent = "(none)"; - empty2.style.color = "#888"; + empty2.style.color = rp.muted; empty2.style.fontSize = "11px"; smSection.appendChild(empty2); } else { @@ -123855,12 +124069,12 @@ function displayNodeMatchingStore(peId, ctx, offset, entry, container2) { detailSection.id = "node-detail"; detailSection.style.marginTop = "16px"; detailSection.style.padding = "12px"; - detailSection.style.backgroundColor = "#262641"; - detailSection.style.border = "2px solid #2196f3"; + detailSection.style.backgroundColor = rp.overlay; + detailSection.style.border = `2px solid ${rp.foam}`; detailSection.style.borderRadius = "4px"; const title = document.createElement("h4"); title.textContent = `Selected Node: PE${peId} IRAM[${offset}]`; - title.style.color = "#2196f3"; + title.style.color = rp.foam; title.style.margin = "0 0 8px 0"; title.style.fontSize = "13px"; detailSection.appendChild(title); @@ -123871,11 +124085,11 @@ function displayNodeMatchingStore(peId, ctx, offset, entry, container2) { statusDiv.style.marginBottom = "8px"; const statusLabel = document.createElement("span"); statusLabel.textContent = "Status: "; - statusLabel.style.color = "#4caf50"; + statusLabel.style.color = rp.pine; statusDiv.appendChild(statusLabel); const statusValue = document.createElement("span"); statusValue.textContent = entry.occupied ? "OCCUPIED" : "EMPTY"; - statusValue.style.color = entry.occupied ? "#ff9800" : "#888"; + statusValue.style.color = entry.occupied ? rp.gold : rp.muted; statusDiv.appendChild(statusValue); contentDiv.appendChild(statusDiv); if (entry.occupied) { @@ -123883,21 +124097,21 @@ function displayNodeMatchingStore(peId, ctx, offset, entry, container2) { dataDiv.style.marginBottom = "8px"; const dataLabel = document.createElement("span"); dataLabel.textContent = "Data: "; - dataLabel.style.color = "#4caf50"; + dataLabel.style.color = rp.pine; dataDiv.appendChild(dataLabel); const dataValue = document.createElement("span"); dataValue.textContent = entry.data !== null ? entry.data.toString() : "(null)"; - dataValue.style.color = "#ffeb3b"; + dataValue.style.color = rp.gold; dataDiv.appendChild(dataValue); contentDiv.appendChild(dataDiv); const portDiv = document.createElement("div"); const portLabel = document.createElement("span"); portLabel.textContent = "Waiting Port: "; - portLabel.style.color = "#4caf50"; + portLabel.style.color = rp.pine; portDiv.appendChild(portLabel); const portValue = document.createElement("span"); portValue.textContent = entry.port || "(none)"; - portValue.style.color = entry.port ? "#2196f3" : "#888"; + portValue.style.color = entry.port ? rp.foam : rp.muted; portDiv.appendChild(portValue); contentDiv.appendChild(portDiv); } @@ -123918,12 +124132,12 @@ function displaySMNodeState(smId, state, container2) { detailSection.id = "node-detail"; detailSection.style.marginTop = "16px"; detailSection.style.padding = "12px"; - detailSection.style.backgroundColor = "#2e2416"; - detailSection.style.border = "2px solid #ff9800"; + detailSection.style.backgroundColor = rp.overlay; + detailSection.style.border = `2px solid ${rp.gold}`; detailSection.style.borderRadius = "4px"; const title = document.createElement("h4"); title.textContent = `Selected: SM ${smId}`; - title.style.color = "#ff9800"; + title.style.color = rp.gold; title.style.margin = "0 0 8px 0"; title.style.fontSize = "13px"; detailSection.appendChild(title); @@ -123941,17 +124155,17 @@ function displaySMNodeState(smId, state, container2) { emptyDiv.style.marginBottom = "8px"; const label = document.createElement("strong"); label.textContent = "Cells: "; - label.style.color = "#4caf50"; + label.style.color = rp.pine; emptyDiv.appendChild(label); const value = document.createElement("span"); value.textContent = "(all empty)"; - value.style.color = "#888"; + value.style.color = rp.muted; emptyDiv.appendChild(value); contentDiv.appendChild(emptyDiv); } else { const cellsHeader = document.createElement("strong"); cellsHeader.textContent = "Cells:"; - cellsHeader.style.color = "#4caf50"; + cellsHeader.style.color = rp.pine; contentDiv.appendChild(cellsHeader); contentDiv.appendChild(document.createElement("br")); for (const addr of nonEmptyCells) { @@ -123961,22 +124175,22 @@ function displaySMNodeState(smId, state, container2) { line.style.marginBottom = "2px"; const addrSpan = document.createElement("span"); addrSpan.textContent = `[${addr}] `; - addrSpan.style.color = "#ffeb3b"; + addrSpan.style.color = rp.gold; line.appendChild(addrSpan); const presSpan = document.createElement("span"); presSpan.textContent = cell.presence; - presSpan.style.color = cell.presence === "FULL" ? "#4caf50" : cell.presence === "WAITING" ? "#9c27b0" : cell.presence === "RESERVED" ? "#ff9800" : "#888"; + presSpan.style.color = cell.presence === "FULL" ? rp.pine : cell.presence === "WAITING" ? rp.iris : cell.presence === "RESERVED" ? rp.gold : rp.muted; line.appendChild(presSpan); if (cell.data_l !== null) { const dataSpan = document.createElement("span"); dataSpan.textContent = ` L=${cell.data_l}`; - dataSpan.style.color = "#aaa"; + dataSpan.style.color = rp.subtle; line.appendChild(dataSpan); } if (cell.data_r !== null) { const dataSpan = document.createElement("span"); dataSpan.textContent = ` R=${cell.data_r}`; - dataSpan.style.color = "#aaa"; + dataSpan.style.color = rp.subtle; line.appendChild(dataSpan); } contentDiv.appendChild(line); @@ -123987,11 +124201,11 @@ function displaySMNodeState(smId, state, container2) { drDiv.style.marginTop = "8px"; const drLabel = document.createElement("strong"); drLabel.textContent = "Deferred Read: "; - drLabel.style.color = "#9c27b0"; + drLabel.style.color = rp.iris; drDiv.appendChild(drLabel); const drValue = document.createElement("span"); drValue.textContent = `cell[${state.deferred_read.cell_addr}]`; - drValue.style.color = "#aaa"; + drValue.style.color = rp.subtle; drDiv.appendChild(drValue); contentDiv.appendChild(drDiv); } @@ -123999,13 +124213,13 @@ function displaySMNodeState(smId, state, container2) { t0Div.style.marginTop = "8px"; const t0Label = document.createElement("span"); t0Label.textContent = `T0 Store: ${state.t0_store_size} entries`; - t0Label.style.color = "#888"; + t0Label.style.color = rp.muted; t0Div.appendChild(t0Label); contentDiv.appendChild(t0Div); const queueDiv = document.createElement("div"); const queueLabel = document.createElement("span"); queueLabel.textContent = `Input Queue: ${state.input_queue_depth}`; - queueLabel.style.color = "#888"; + queueLabel.style.color = rp.muted; queueDiv.appendChild(queueLabel); contentDiv.appendChild(queueDiv); detailSection.appendChild(contentDiv); @@ -124024,81 +124238,244 @@ function displaySMNodeState(smId, state, container2) { // src/main.ts cytoscape2.use(import_cytoscape_elk.default); cytoscape2.use(import_cytoscape_svg.default); +var ROUTING_CATEGORY = "routing"; var cy; var isPhysicalLayout = true; var currentState = null; -function buildElements(nodes3, edges3, layout4) { - const elements = []; - if (layout4 === "physical") { - const peIds = /* @__PURE__ */ new Set(); - for (const node of nodes3) { - if (node.pe !== null) { - peIds.add(node.pe); - } +var currentNodes = []; +var currentEdges = []; +function routeEdges(selector) { + const nodes3 = cy.nodes().not(":parent").not(".port-node"); + const nodePositions = []; + nodes3.forEach((n) => { + nodePositions.push({ x: n.position("x"), y: n.position("y") }); + }); + let leftCount = 0; + let rightCount = 0; + const edges3 = selector ? cy.edges(selector) : cy.edges(); + edges3.forEach((edge) => { + const sy = edge.source().position("y"); + const ty = edge.target().position("y"); + const sx = edge.source().position("x"); + const tx = edge.target().position("x"); + const span = Math.abs(ty - sy); + if (span < 80) { + edge.style({ "curve-style": "bezier" }); + return; } - for (const peId of peIds) { - elements.push({ - data: { - id: `pe-${peId}`, - label: `PE ${peId}` - }, - classes: "pe-cluster" - }); + const blocked = nodePositions.some((p2) => { + return p2.y > Math.min(sy, ty) + 25 && p2.y < Math.max(sy, ty) - 25; + }); + if (!blocked) { + edge.style({ "curve-style": "bezier" }); + return; } - for (const node of nodes3) { - const label = node.label ? node.label : node.const !== null ? `${node.opcode} + const avgX = (sx + tx) / 2; + const centerX = (Math.min(...nodePositions.map((p2) => p2.x)) + Math.max(...nodePositions.map((p2) => p2.x))) / 2; + const goLeft = avgX >= centerX; + const baseOffset = 30; + const stagger = goLeft ? 12 * leftCount : 12 * rightCount; + const offset = (goLeft ? -1 : 1) * (baseOffset + stagger); + if (goLeft) leftCount++; + else rightCount++; + edge.style({ + "curve-style": "unbundled-bezier", + "control-point-distances": [offset * 0.6, offset, offset * 0.6], + "control-point-weights": [0.2, 0.5, 0.8] + }); + }); +} +function buildLogicalElements(nodes3, edges3) { + const elements = []; + for (const node of nodes3) { + const label = node.label ? node.label : node.const !== null ? `${node.opcode} ${node.const}` : node.opcode; - const el = { - data: { - id: node.id, - label, - colour: node.colour, - category: node.category, - pe: node.pe, - iram_offset: node.iram_offset, - ctx: node.ctx, - synthetic: node.synthetic ?? false, - sm_id: node.sm_id ?? null - } - }; - if (!node.synthetic && node.pe !== null) { - el.data.parent = `pe-${node.pe}`; + elements.push({ + data: { + id: node.id, + label, + colour: node.colour, + category: node.category, + pe: node.pe, + iram_offset: node.iram_offset, + ctx: node.ctx, + synthetic: node.synthetic ?? false, + sm_id: node.sm_id ?? null } - elements.push(el); - } - } else { - for (const node of nodes3) { - const label = node.label ? node.label : node.const !== null ? `${node.opcode} -${node.const}` : node.opcode; - const el = { - data: { - id: node.id, - label, - colour: node.colour, - category: node.category, - pe: node.pe, - iram_offset: node.iram_offset, - ctx: node.ctx, - synthetic: node.synthetic ?? false, - sm_id: node.sm_id ?? null - } - }; - elements.push(el); - } + }); } for (const edge of edges3) { + const sourceNode = nodes3.find((n) => n.id === edge.source); + let sourceLabel; + if (sourceNode && sourceNode.category === ROUTING_CATEGORY && edge.source_port) { + sourceLabel = edge.source_port === "L" ? "T" : "F"; + } elements.push({ data: { - id: `${edge.source}->${edge.target}`, + id: `${edge.source}->${edge.target}:${edge.port}`, source: edge.source, target: edge.target, - targetLabel: edge.port + targetLabel: edge.port, + sourceLabel: sourceLabel ?? "" }, classes: edge.synthetic ? "synthetic" : void 0 }); } return elements; } +function buildPhysicalElements(nodes3, edges3) { + const elements = []; + const edgeTargets = /* @__PURE__ */ new Set(); + for (const edge of edges3) { + edgeTargets.add(edge.target); + } + const seedConstIds = /* @__PURE__ */ new Set(); + const nodePeMap = /* @__PURE__ */ new Map(); + const peIds = /* @__PURE__ */ new Set(); + for (const node of nodes3) { + nodePeMap.set(node.id, node.pe); + if (node.pe !== null) peIds.add(node.pe); + if (node.category === "config" && node.opcode === "const" && !edgeTargets.has(node.id)) { + seedConstIds.add(node.id); + } + } + for (const peId of peIds) { + elements.push({ + data: { id: `pe-${peId}`, label: `PE ${peId}` }, + classes: "pe-cluster" + }); + } + for (const node of nodes3) { + const isSeedConst = seedConstIds.has(node.id); + const label = node.label ? node.label : node.const !== null ? `${node.opcode} +${node.const}` : node.opcode; + const el = { + data: { + id: node.id, + label, + colour: node.colour, + category: node.category, + pe: node.pe, + iram_offset: node.iram_offset, + ctx: node.ctx, + synthetic: node.synthetic ?? false, + sm_id: node.sm_id ?? null + }, + classes: [isSeedConst ? "seed-const" : void 0].filter(Boolean).join(" ") || void 0 + }; + if (!node.synthetic && !isSeedConst && node.pe !== null) { + el.data.parent = `pe-${node.pe}`; + } + elements.push(el); + } + const pePairKey = (src, tgt) => `${src}->${tgt}`; + const crossPePairs = /* @__PURE__ */ new Map(); + const busEdgeCounts = /* @__PURE__ */ new Map(); + for (const edge of edges3) { + if (seedConstIds.has(edge.source) || edge.synthetic) continue; + const srcPe = nodePeMap.get(edge.source) ?? null; + const tgtPe = nodePeMap.get(edge.target) ?? null; + if (srcPe !== null && tgtPe !== null && srcPe !== tgtPe) { + const key = pePairKey(srcPe, tgtPe); + if (!crossPePairs.has(key)) { + crossPePairs.set(key, { srcPe, tgtPe }); + } + busEdgeCounts.set(key, (busEdgeCounts.get(key) ?? 0) + 1); + } + } + for (const [, pair] of crossPePairs) { + elements.push({ + data: { + id: `port-${pair.srcPe}-to-${pair.tgtPe}-exit`, + parent: `pe-${pair.srcPe}` + }, + classes: "port-node" + }); + elements.push({ + data: { + id: `port-${pair.srcPe}-to-${pair.tgtPe}-entry`, + parent: `pe-${pair.tgtPe}` + }, + classes: "port-node" + }); + } + for (const [key, pair] of crossPePairs) { + const count = busEdgeCounts.get(key) ?? 1; + elements.push({ + data: { + id: `bus-${pair.srcPe}-to-${pair.tgtPe}`, + source: `port-${pair.srcPe}-to-${pair.tgtPe}-exit`, + target: `port-${pair.srcPe}-to-${pair.tgtPe}-entry`, + label: count > 1 ? `\xD7${count}` : "" + }, + classes: "physical bus-segment" + }); + } + for (const edge of edges3) { + const srcPe = nodePeMap.get(edge.source) ?? null; + const tgtPe = nodePeMap.get(edge.target) ?? null; + if (edge.synthetic) { + elements.push({ + data: { + id: `${edge.source}->${edge.target}:${edge.port}`, + source: edge.source, + target: edge.target, + targetLabel: edge.port, + sourceLabel: "" + }, + classes: "physical synthetic" + }); + } else if (seedConstIds.has(edge.source)) { + elements.push({ + data: { + id: `${edge.source}->${edge.target}:${edge.port}`, + source: edge.source, + target: edge.target, + targetLabel: edge.port, + sourceLabel: "" + }, + classes: "seed-edge physical" + }); + } else if (srcPe !== null && tgtPe !== null && srcPe !== tgtPe) { + elements.push({ + data: { + id: `${edge.source}->exit-${srcPe}-to-${tgtPe}:${edge.port}`, + source: edge.source, + target: `port-${srcPe}-to-${tgtPe}-exit`, + sourceLabel: "", + targetLabel: "" + }, + classes: "physical exit-segment" + }); + elements.push({ + data: { + id: `entry-${srcPe}-to-${tgtPe}->${edge.target}:${edge.port}`, + source: `port-${srcPe}-to-${tgtPe}-entry`, + target: edge.target, + targetLabel: edge.port + }, + classes: "physical entry-segment" + }); + } else { + const sourceNode = nodes3.find((n) => n.id === edge.source); + let sourceLabel; + if (sourceNode && sourceNode.category === ROUTING_CATEGORY && edge.source_port) { + sourceLabel = edge.source_port === "L" ? "T" : "F"; + } + elements.push({ + data: { + id: `${edge.source}->${edge.target}:${edge.port}`, + source: edge.source, + target: edge.target, + targetLabel: edge.port, + sourceLabel: sourceLabel ?? "" + }, + classes: "physical intra-pe" + }); + } + } + return elements; +} function initializeCytoscape() { const container2 = document.getElementById("graph"); if (!container2) { @@ -124114,12 +124491,24 @@ function initializeCytoscape() { }); } function renderGraph(nodes3, edges3) { - const layout4 = isPhysicalLayout ? "physical" : "logical"; - const elements = buildElements(nodes3, edges3, layout4); - cy.elements().remove(); - cy.add(elements); + currentNodes = nodes3; + currentEdges = edges3; + const elements = isPhysicalLayout ? buildPhysicalElements(nodes3, edges3) : buildLogicalElements(nodes3, edges3); + cy.batch(() => { + cy.elements().remove(); + cy.add(elements); + }); const layoutOptions = isPhysicalLayout ? physicalLayout() : logicalLayout(); - cy.layout(layoutOptions).run(); + const layout4 = cy.layout(layoutOptions); + layout4.on("layoutstop", () => { + if (isPhysicalLayout) { + routeEdges(".intra-pe, .seed-edge"); + } else { + routeEdges(); + } + cy.fit(void 0, 40); + }); + layout4.run(); } function updateGraphExecution(nodes3, edges3, state) { cy.nodes().removeClass(["active", "executed", "matched", "half-matched", "cell-written"]); @@ -124155,10 +124544,16 @@ function updateGraphExecution(nodes3, edges3, state) { } for (const monitorEdge of edges3) { if (monitorEdge.token_flow) { - const edgeId = `${monitorEdge.source}->${monitorEdge.target}`; - const cyEdge = cy.getElementById(edgeId); - if (cyEdge) { + const logicalId = `${monitorEdge.source}->${monitorEdge.target}`; + const physicalId = `${monitorEdge.source}->${monitorEdge.target}:${monitorEdge.port}`; + const cyEdge = cy.getElementById(physicalId); + if (cyEdge && !cyEdge.empty()) { cyEdge.addClass("token-flow"); + } else { + const fallback = cy.getElementById(logicalId); + if (fallback && !fallback.empty()) { + fallback.addClass("token-flow"); + } } } } @@ -124216,16 +124611,9 @@ function updateSimTime(time2, finished) { const timeDisplay = document.getElementById("sim-time"); if (!timeDisplay) return; timeDisplay.textContent = `t=${time2.toFixed(3)}`; - if (finished) { - const finishedBadge = document.getElementById("finished-badge"); - if (finishedBadge) { - finishedBadge.style.display = "inline"; - } - } else { - const finishedBadge = document.getElementById("finished-badge"); - if (finishedBadge) { - finishedBadge.style.display = "none"; - } + const finishedBadge = document.getElementById("finished-badge"); + if (finishedBadge) { + finishedBadge.style.display = finished ? "inline" : "none"; } } function toggleLayout() { @@ -124234,29 +124622,9 @@ function toggleLayout() { if (button) { button.textContent = isPhysicalLayout ? "Logical Layout" : "Physical Layout"; } - const elements = cy.elements().jsons(); - const nodes3 = elements.filter((e) => !e.data.source); - const edges3 = elements.filter((e) => e.data.source); - const layout4 = isPhysicalLayout ? "physical" : "logical"; - if (isPhysicalLayout) { - const peIds = /* @__PURE__ */ new Set(); - for (const node of nodes3) { - if (node.data.pe !== null) { - peIds.add(node.data.pe); - } - } - for (const node of nodes3) { - if (node.data.pe !== null && !node.data.id.startsWith("pe-")) { - node.data.parent = `pe-${node.data.pe}`; - } - } - } else { - for (const node of nodes3) { - delete node.data.parent; - } + if (currentNodes.length > 0) { + renderGraph(currentNodes, currentEdges); } - const layoutOptions = isPhysicalLayout ? physicalLayout() : logicalLayout(); - cy.layout(layoutOptions).run(); } function setupNodeClickHandler() { cy.on("tap", "node", (event3) => { @@ -124451,7 +124819,7 @@ function main() { const statusEl = document.getElementById("connection-status"); if (statusEl) { statusEl.textContent = "Connected"; - statusEl.style.color = "#4caf50"; + statusEl.style.color = rp.foam; } }, onDisconnect: () => { @@ -124459,7 +124827,7 @@ function main() { const statusEl = document.getElementById("connection-status"); if (statusEl) { statusEl.textContent = "Disconnected"; - statusEl.style.color = "#f44336"; + statusEl.style.color = rp.love; } } }); diff --git a/monitor/frontend/index.html b/monitor/frontend/index.html index 71e10c1..e00eb74 100644 --- a/monitor/frontend/index.html +++ b/monitor/frontend/index.html @@ -16,14 +16,14 @@ height: 100vh; display: flex; flex-direction: column; - background: #1a1a2e; - color: #eee; + background: #191724; + color: #e0def4; } #toolbar { padding: 8px 16px; - background: #0d0d1a; - border-bottom: 1px solid #333; + background: #26233a; + border-bottom: 1px solid #403d52; display: flex; align-items: center; gap: 12px; @@ -35,12 +35,13 @@ font-size: 16px; font-weight: 600; margin-right: auto; + color: #e0def4; } #toolbar button { - background: #2a2a4a; - color: #eee; - border: 1px solid #444; + background: #1f1d2e; + color: #e0def4; + border: 1px solid #403d52; padding: 6px 12px; border-radius: 4px; cursor: pointer; @@ -49,24 +50,25 @@ } #toolbar button:hover { - background: #3a3a5a; + background: #403d52; } #layout-toggle { - background: #5c6bc0; + background: #31748f; border: none; font-weight: 500; + color: #e0def4; } #layout-toggle:hover { - background: #3f51b5; + background: #286b82; } #toolbar input[type="text"], #toolbar input[type="number"] { - background: #1a1a2e; - color: #eee; - border: 1px solid #444; + background: #191724; + color: #e0def4; + border: 1px solid #403d52; padding: 4px 8px; border-radius: 4px; font-size: 12px; @@ -82,19 +84,20 @@ align-items: center; gap: 4px; font-size: 12px; + color: #908caa; } #sim-time { font-family: monospace; font-size: 12px; - color: #00d4aa; + color: #9ccfd8; margin-left: 16px; } #finished-badge { display: none; - background: #f44336; - color: white; + background: #eb6f92; + color: #191724; padding: 2px 8px; border-radius: 3px; font-size: 11px; @@ -105,7 +108,7 @@ #connection-status { font-size: 11px; margin-left: 12px; - color: #f44336; + color: #eb6f92; } #main-content { @@ -118,7 +121,7 @@ #graph { flex: 2; position: relative; - background: #1a1a2e; + background: #191724; } #sidebar { @@ -126,8 +129,8 @@ max-width: 450px; display: flex; flex-direction: column; - background: #0d0d1a; - border-left: 1px solid #333; + background: #1f1d2e; + border-left: 1px solid #403d52; overflow: hidden; } @@ -135,34 +138,34 @@ flex: 1; display: flex; flex-direction: column; - border-bottom: 1px solid #333; + border-bottom: 1px solid #26233a; overflow: hidden; } #event-log-header { padding: 8px 12px; - background: #1a1a2e; - border-bottom: 1px solid #333; + background: #26233a; + border-bottom: 1px solid #403d52; display: flex; align-items: center; gap: 8px; font-size: 12px; font-weight: 600; - color: #00d4aa; + color: #9ccfd8; } #event-log-controls { display: flex; gap: 8px; padding: 6px 12px; - background: #1a1a2e; - border-bottom: 1px solid #333; + background: #26233a; + border-bottom: 1px solid #403d52; } #event-log-controls select { - background: #2a2a4a; - color: #eee; - border: 1px solid #444; + background: #1f1d2e; + color: #e0def4; + border: 1px solid #403d52; padding: 4px 8px; border-radius: 3px; font-size: 11px; @@ -179,13 +182,13 @@ .event-entry { padding: 6px 8px; - border-bottom: 1px solid #222; + border-bottom: 1px solid #26233a; cursor: pointer; transition: background-color 0.15s; } .event-entry:hover { - background-color: #2a2a3e; + background-color: #26233a; } #state-inspector-section { @@ -197,11 +200,11 @@ #state-inspector-header { padding: 8px 12px; - background: #1a1a2e; - border-bottom: 1px solid #333; + background: #26233a; + border-bottom: 1px solid #403d52; font-size: 12px; font-weight: 600; - color: #ff9800; + color: #f6c177; } #state-inspector { @@ -216,6 +219,7 @@ margin-top: 12px; margin-bottom: 6px; font-size: 12px; + color: #c4a7e7; } details { @@ -227,14 +231,16 @@ user-select: none; padding: 4px; border-radius: 3px; + color: #908caa; } summary:hover { - background: rgba(255, 255, 255, 0.05); + background: rgba(224, 222, 244, 0.05); } details[open] > summary { margin-bottom: 4px; + color: #e0def4; } .toolbar-group { @@ -242,7 +248,7 @@ align-items: center; gap: 8px; padding: 0 4px; - border-right: 1px solid #333; + border-right: 1px solid #26233a; } .toolbar-group:last-child { @@ -271,18 +277,18 @@ #event-log::-webkit-scrollbar-track, #state-inspector::-webkit-scrollbar-track { - background: #1a1a2e; + background: #191724; } #event-log::-webkit-scrollbar-thumb, #state-inspector::-webkit-scrollbar-thumb { - background: #3a3a5a; + background: #403d52; border-radius: 4px; } #event-log::-webkit-scrollbar-thumb:hover, #state-inspector::-webkit-scrollbar-thumb:hover { - background: #4a4a6a; + background: #524f67; } diff --git a/monitor/frontend/src/main.ts b/monitor/frontend/src/main.ts index 9602a07..5500745 100644 --- a/monitor/frontend/src/main.ts +++ b/monitor/frontend/src/main.ts @@ -11,114 +11,321 @@ import { exportSvg, exportPng, copyPng } from "@common/export"; import { createConnection } from "./connection"; import type { MonitorConnection } from "./connection"; import { clearEventLog, updateEventLog, setEventFilter, updateStateInspector, displayNodeMatchingStore, displaySMNodeState } from "./panels"; +import { rp } from "./palette"; // Register plugins cytoscape.use(elk); cytoscape.use(svg); +const ROUTING_CATEGORY = "routing"; + // Global state let cy: cytoscape.Core; let isPhysicalLayout = true; let currentState: SystemState | null = null; +let currentNodes: ReadonlyArray = []; +let currentEdges: ReadonlyArray = []; + +/** + * Route edges with intelligent bezier curves to avoid node collisions. + * Ported from dfgraph/frontend/src/main.ts. + */ +function routeEdges(selector?: string): void { + const nodes = cy.nodes().not(":parent").not(".port-node"); + const nodePositions: Array<{ x: number; y: number }> = []; + nodes.forEach((n) => { + nodePositions.push({ x: n.position("x"), y: n.position("y") }); + }); + + let leftCount = 0; + let rightCount = 0; + + const edges = selector ? cy.edges(selector) : cy.edges(); + edges.forEach((edge) => { + const sy = edge.source().position("y"); + const ty = edge.target().position("y"); + const sx = edge.source().position("x"); + const tx = edge.target().position("x"); + const span = Math.abs(ty - sy); + + if (span < 80) { + edge.style({ "curve-style": "bezier" }); + return; + } + + const blocked = nodePositions.some((p) => { + return p.y > Math.min(sy, ty) + 25 && p.y < Math.max(sy, ty) - 25; + }); + + if (!blocked) { + edge.style({ "curve-style": "bezier" }); + return; + } + + const avgX = (sx + tx) / 2; + const centerX = (Math.min(...nodePositions.map((p) => p.x)) + Math.max(...nodePositions.map((p) => p.x))) / 2; + const goLeft = avgX >= centerX; + + const baseOffset = 30; + const stagger = goLeft ? 12 * leftCount : 12 * rightCount; + const offset = (goLeft ? -1 : 1) * (baseOffset + stagger); + + if (goLeft) leftCount++; + else rightCount++; + + edge.style({ + "curve-style": "unbundled-bezier", + "control-point-distances": [offset * 0.6, offset, offset * 0.6], + "control-point-weights": [0.2, 0.5, 0.8], + }); + }); +} /** - * Build Cytoscape element definitions from graph data. + * Build Cytoscape elements for logical view (flat, no PE clustering). */ -function buildElements( +function buildLogicalElements( nodes: ReadonlyArray, edges: ReadonlyArray, - layout: "physical" | "logical" ): cytoscape.ElementDefinition[] { const elements: cytoscape.ElementDefinition[] = []; - if (layout === "physical") { - // Build PE cluster parents - const peIds = new Set(); - for (const node of nodes) { - if (node.pe !== null) { - peIds.add(node.pe); - } - } + for (const node of nodes) { + const label = node.label + ? node.label + : node.const !== null + ? `${node.opcode}\n${node.const}` + : node.opcode; - for (const peId of peIds) { - elements.push({ - data: { - id: `pe-${peId}`, - label: `PE ${peId}`, - }, - classes: "pe-cluster", - }); + elements.push({ + data: { + id: node.id, + label, + colour: node.colour, + category: node.category, + pe: node.pe, + iram_offset: node.iram_offset, + ctx: node.ctx, + synthetic: node.synthetic ?? false, + sm_id: (node as any).sm_id ?? null, + }, + }); + } + + for (const edge of edges) { + const sourceNode = nodes.find((n) => n.id === edge.source); + let sourceLabel: string | undefined; + if (sourceNode && sourceNode.category === ROUTING_CATEGORY && edge.source_port) { + sourceLabel = edge.source_port === "L" ? "T" : "F"; } - // Add nodes - for (const node of nodes) { - const label = node.label - ? node.label - : node.const !== null - ? `${node.opcode}\n${node.const}` - : node.opcode; + elements.push({ + data: { + id: `${edge.source}->${edge.target}:${edge.port}`, + source: edge.source, + target: edge.target, + targetLabel: edge.port, + sourceLabel: sourceLabel ?? "", + }, + classes: edge.synthetic ? "synthetic" : undefined, + }); + } - const el: cytoscape.ElementDefinition = { - data: { - id: node.id, - label, - colour: node.colour, - category: node.category, - pe: node.pe, - iram_offset: node.iram_offset, - ctx: node.ctx, - synthetic: node.synthetic ?? false, - sm_id: (node as any).sm_id ?? null, - }, - }; + return elements; +} - // Synthetic SM nodes float freely (not parented to any PE cluster) - if (!node.synthetic && node.pe !== null) { - el.data.parent = `pe-${node.pe}`; - } +/** + * Build Cytoscape elements for physical view with bus topology. + * Ported from dfgraph/frontend/src/main.ts buildPhysicalElements(). + */ +function buildPhysicalElements( + nodes: ReadonlyArray, + edges: ReadonlyArray, +): cytoscape.ElementDefinition[] { + const elements: cytoscape.ElementDefinition[] = []; + + // Build edge target set for seed const detection + const edgeTargets = new Set(); + for (const edge of edges) { + edgeTargets.add(edge.target); + } - elements.push(el); + const seedConstIds = new Set(); + const nodePeMap = new Map(); + const peIds = new Set(); + + for (const node of nodes) { + nodePeMap.set(node.id, node.pe); + if (node.pe !== null) peIds.add(node.pe); + + if (node.category === "config" && node.opcode === "const" && !edgeTargets.has(node.id)) { + seedConstIds.add(node.id); } - } else { - // Logical layout: no PE clustering - for (const node of nodes) { - const label = node.label - ? node.label - : node.const !== null - ? `${node.opcode}\n${node.const}` - : node.opcode; - - const el: cytoscape.ElementDefinition = { - data: { - id: node.id, - label, - colour: node.colour, - category: node.category, - pe: node.pe, - iram_offset: node.iram_offset, - ctx: node.ctx, - synthetic: node.synthetic ?? false, - sm_id: (node as any).sm_id ?? null, - }, - }; + } + + // Create PE cluster parent nodes + for (const peId of peIds) { + elements.push({ + data: { id: `pe-${peId}`, label: `PE ${peId}` }, + classes: "pe-cluster", + }); + } + + // Create operation nodes + for (const node of nodes) { + const isSeedConst = seedConstIds.has(node.id); + const label = node.label + ? node.label + : node.const !== null + ? `${node.opcode}\n${node.const}` + : node.opcode; + + const el: cytoscape.ElementDefinition = { + data: { + id: node.id, + label, + colour: node.colour, + category: node.category, + pe: node.pe, + iram_offset: node.iram_offset, + ctx: node.ctx, + synthetic: node.synthetic ?? false, + sm_id: (node as any).sm_id ?? null, + }, + classes: + [isSeedConst ? "seed-const" : undefined].filter(Boolean).join(" ") || undefined, + }; - elements.push(el); + // Synthetic SM nodes and seed consts float freely + if (!node.synthetic && !isSeedConst && node.pe !== null) { + el.data.parent = `pe-${node.pe}`; } + + elements.push(el); } - // Add edges + // Scan cross-PE edges to find PE pairs for bus topology + type PePair = { srcPe: number; tgtPe: number }; + const pePairKey = (src: number, tgt: number) => `${src}->${tgt}`; + const crossPePairs = new Map(); + const busEdgeCounts = new Map(); + for (const edge of edges) { + if (seedConstIds.has(edge.source) || edge.synthetic) continue; + const srcPe = nodePeMap.get(edge.source) ?? null; + const tgtPe = nodePeMap.get(edge.target) ?? null; + if (srcPe !== null && tgtPe !== null && srcPe !== tgtPe) { + const key = pePairKey(srcPe, tgtPe); + if (!crossPePairs.has(key)) { + crossPePairs.set(key, { srcPe, tgtPe }); + } + busEdgeCounts.set(key, (busEdgeCounts.get(key) ?? 0) + 1); + } + } + + // Create port nodes for each directed PE pair + for (const [, pair] of crossPePairs) { elements.push({ data: { - id: `${edge.source}->${edge.target}`, - source: edge.source, - target: edge.target, - targetLabel: edge.port, + id: `port-${pair.srcPe}-to-${pair.tgtPe}-exit`, + parent: `pe-${pair.srcPe}`, }, - classes: edge.synthetic ? "synthetic" : undefined, + classes: "port-node", + }); + elements.push({ + data: { + id: `port-${pair.srcPe}-to-${pair.tgtPe}-entry`, + parent: `pe-${pair.tgtPe}`, + }, + classes: "port-node", + }); + } + + // Create bus edges (one per PE pair) + for (const [key, pair] of crossPePairs) { + const count = busEdgeCounts.get(key) ?? 1; + elements.push({ + data: { + id: `bus-${pair.srcPe}-to-${pair.tgtPe}`, + source: `port-${pair.srcPe}-to-${pair.tgtPe}-exit`, + target: `port-${pair.srcPe}-to-${pair.tgtPe}-entry`, + label: count > 1 ? `×${count}` : "", + }, + classes: "physical bus-segment", }); } + // Create edges with proper splitting + for (const edge of edges) { + const srcPe = nodePeMap.get(edge.source) ?? null; + const tgtPe = nodePeMap.get(edge.target) ?? null; + + if (edge.synthetic) { + // Synthetic edges (SM request/return) — never bundled + elements.push({ + data: { + id: `${edge.source}->${edge.target}:${edge.port}`, + source: edge.source, + target: edge.target, + targetLabel: edge.port, + sourceLabel: "", + }, + classes: "physical synthetic", + }); + } else if (seedConstIds.has(edge.source)) { + // Seed const: direct edge, never bundled + elements.push({ + data: { + id: `${edge.source}->${edge.target}:${edge.port}`, + source: edge.source, + target: edge.target, + targetLabel: edge.port, + sourceLabel: "", + }, + classes: "seed-edge physical", + }); + } else if (srcPe !== null && tgtPe !== null && srcPe !== tgtPe) { + // Cross-PE: split into exit segment + entry segment (bus already created) + elements.push({ + data: { + id: `${edge.source}->exit-${srcPe}-to-${tgtPe}:${edge.port}`, + source: edge.source, + target: `port-${srcPe}-to-${tgtPe}-exit`, + sourceLabel: "", + targetLabel: "", + }, + classes: "physical exit-segment", + }); + elements.push({ + data: { + id: `entry-${srcPe}-to-${tgtPe}->${edge.target}:${edge.port}`, + source: `port-${srcPe}-to-${tgtPe}-entry`, + target: edge.target, + targetLabel: edge.port, + }, + classes: "physical entry-segment", + }); + } else { + // Intra-PE or unplaced + const sourceNode = nodes.find((n) => n.id === edge.source); + let sourceLabel: string | undefined; + if (sourceNode && sourceNode.category === ROUTING_CATEGORY && edge.source_port) { + sourceLabel = edge.source_port === "L" ? "T" : "F"; + } + + elements.push({ + data: { + id: `${edge.source}->${edge.target}:${edge.port}`, + source: edge.source, + target: edge.target, + targetLabel: edge.port, + sourceLabel: sourceLabel ?? "", + }, + classes: "physical intra-pe", + }); + } + } + return elements; } @@ -146,18 +353,34 @@ function initializeCytoscape(): void { */ function renderGraph( nodes: ReadonlyArray, - edges: ReadonlyArray + edges: ReadonlyArray, ): void { - const layout = isPhysicalLayout ? "physical" : "logical"; - const elements = buildElements(nodes, edges, layout); + currentNodes = nodes; + currentEdges = edges; - cy.elements().remove(); - cy.add(elements); + const elements = isPhysicalLayout + ? buildPhysicalElements(nodes, edges) + : buildLogicalElements(nodes, edges); + + cy.batch(() => { + cy.elements().remove(); + cy.add(elements); + }); const layoutOptions = isPhysicalLayout ? physicalLayout() : logicalLayout(); - cy.layout(layoutOptions).run(); + + const layout = cy.layout(layoutOptions); + layout.on("layoutstop", () => { + if (isPhysicalLayout) { + routeEdges(".intra-pe, .seed-edge"); + } else { + routeEdges(); + } + cy.fit(undefined, 40); + }); + layout.run(); } /** @@ -166,7 +389,7 @@ function renderGraph( function updateGraphExecution( nodes: ReadonlyArray, edges: ReadonlyArray, - state: SystemState + state: SystemState, ): void { // Clear previous execution classes cy.nodes().removeClass(["active", "executed", "matched", "half-matched", "cell-written"]); @@ -208,13 +431,20 @@ function updateGraphExecution( } } - // Apply token flow to edges + // Apply token flow to edges — need to handle both edge ID formats for (const monitorEdge of edges) { if (monitorEdge.token_flow) { - const edgeId = `${monitorEdge.source}->${monitorEdge.target}`; - const cyEdge = cy.getElementById(edgeId); - if (cyEdge) { + // Try both ID formats (logical uses source->target, physical uses source->target:port) + const logicalId = `${monitorEdge.source}->${monitorEdge.target}`; + const physicalId = `${monitorEdge.source}->${monitorEdge.target}:${monitorEdge.port}`; + const cyEdge = cy.getElementById(physicalId); + if (cyEdge && !cyEdge.empty()) { cyEdge.addClass("token-flow"); + } else { + const fallback = cy.getElementById(logicalId); + if (fallback && !fallback.empty()) { + fallback.addClass("token-flow"); + } } } } @@ -310,16 +540,9 @@ function updateSimTime(time: number, finished: boolean): void { timeDisplay.textContent = `t=${time.toFixed(3)}`; - if (finished) { - const finishedBadge = document.getElementById("finished-badge"); - if (finishedBadge) { - finishedBadge.style.display = "inline"; - } - } else { - const finishedBadge = document.getElementById("finished-badge"); - if (finishedBadge) { - finishedBadge.style.display = "none"; - } + const finishedBadge = document.getElementById("finished-badge"); + if (finishedBadge) { + finishedBadge.style.display = finished ? "inline" : "none"; } } @@ -333,40 +556,10 @@ function toggleLayout(): void { button.textContent = isPhysicalLayout ? "Logical Layout" : "Physical Layout"; } - // Re-render graph with new layout - const elements = cy.elements().jsons(); - const nodes = elements.filter((e: any) => !e.data.source); - const edges = elements.filter((e: any) => e.data.source); - - const layout = isPhysicalLayout ? "physical" : "logical"; - - // Rebuild with appropriate layout - if (isPhysicalLayout) { - // Add PE clusters back - const peIds = new Set(); - for (const node of nodes) { - if (node.data.pe !== null) { - peIds.add(node.data.pe); - } - } - - // Reassign parents - for (const node of nodes) { - if (node.data.pe !== null && !node.data.id.startsWith("pe-")) { - node.data.parent = `pe-${node.data.pe}`; - } - } - } else { - // Remove parents - for (const node of nodes) { - delete node.data.parent; - } + // Re-render graph with stored data + if (currentNodes.length > 0) { + renderGraph(currentNodes, currentEdges); } - - const layoutOptions = isPhysicalLayout - ? physicalLayout() - : logicalLayout(); - cy.layout(layoutOptions).run(); } /** @@ -610,7 +803,7 @@ function main(): void { const statusEl = document.getElementById("connection-status"); if (statusEl) { statusEl.textContent = "Connected"; - statusEl.style.color = "#4caf50"; + statusEl.style.color = rp.foam; } }, onDisconnect: () => { @@ -618,7 +811,7 @@ function main(): void { const statusEl = document.getElementById("connection-status"); if (statusEl) { statusEl.textContent = "Disconnected"; - statusEl.style.color = "#f44336"; + statusEl.style.color = rp.love; } }, }); diff --git a/monitor/frontend/src/palette.ts b/monitor/frontend/src/palette.ts new file mode 100644 index 0000000..6386c3e --- /dev/null +++ b/monitor/frontend/src/palette.ts @@ -0,0 +1,22 @@ +export const rp = { + base: "#191724", + surface: "#1f1d2e", + overlay: "#26233a", + muted: "#6e6a86", + subtle: "#908caa", + text: "#e0def4", + love: "#eb6f92", + gold: "#f6c177", + rose: "#ebbcba", + pine: "#31748f", + foam: "#9ccfd8", + iris: "#c4a7e7", + highlightLow: "#21202e", + highlightMed: "#403d52", + highlightHigh: "#524f67", +} as const; + +export const rpMoon = { + rose: "#ea9a97", + pine: "#3e8fb0", +} as const; diff --git a/monitor/frontend/src/panels.ts b/monitor/frontend/src/panels.ts index 2c02d6e..95a9da9 100644 --- a/monitor/frontend/src/panels.ts +++ b/monitor/frontend/src/panels.ts @@ -2,26 +2,18 @@ import type { SimEventJSON, SystemState, PEState, SMState, SMCellState, MatchingEntry, } from "./types"; +import { rp } from "./palette"; -/** - * Event filter for the event log panel. - */ type EventFilter = { readonly component: string | null; readonly eventType: string | null; }; -/** - * Options for the event log panel. - */ type EventLogOptions = { readonly container: HTMLElement; readonly maxEvents: number; }; -/** - * Options for the state inspector panel. - */ type StateInspectorOptions = { readonly container: HTMLElement; }; @@ -29,97 +21,64 @@ type StateInspectorOptions = { /** * Module-level state for the event log panel. * - * IMPORTANT: These variables maintain implicit state shared between updateEventLog() and setEventFilter(). - * - * - `allEvents`: The complete list of events received from the server. Updated by updateEventLog(). - * - `currentFilter`: The active filter applied to the event log. Updated by setEventFilter(). - * - * When setEventFilter() is called, it updates `currentFilter` and calls updateEventLog() with the - * stored `allEvents`, which re-renders only entries matching the new filter. This state coupling - * allows filtering to work across multiple update calls without re-passing all events. - * - * If you need to clear events (e.g., on reset), ensure both variables are cleared together. + * `allEvents` accumulates all events; `currentFilter` controls visible subset. + * setEventFilter() updates the filter and re-renders with stored events. */ let currentFilter: EventFilter = { component: null, eventType: null }; let allEvents: ReadonlyArray = []; -/** - * Color scheme for event types and components. - */ const eventTypeColors: Record = { - Matched: "#00d4aa", // Cyan - Executed: "#4caf50", // Green - TokenReceived: "#2196f3", // Blue - CellWritten: "#ff9800", // Orange - DeferredRead: "#9c27b0", // Purple - Emitted: "#ff5252", // Red - TokenStored: "#ffeb3b", // Yellow + Matched: rp.foam, + Executed: rp.pine, + TokenReceived: rp.iris, + CellWritten: rp.gold, + DeferredRead: rp.rose, + Emitted: rp.love, + TokenStored: rp.gold, }; -const defaultEventColor = "#b0bec5"; - -/** - * Format a timestamp value as [time] - */ function formatTime(time: number): string { return `[${time.toFixed(3)}]`; } -/** - * Render a single event entry with color-coded sections. - */ function renderEventEntry(event: SimEventJSON): HTMLElement { const entry = document.createElement("div"); entry.className = "event-entry"; - entry.style.padding = "8px"; - entry.style.borderBottom = "1px solid #333"; - entry.style.fontFamily = "monospace"; - entry.style.fontSize = "12px"; - entry.style.cursor = "pointer"; - entry.style.transition = "background-color 0.2s"; - - // Hover effect + entry.addEventListener("mouseenter", () => { - entry.style.backgroundColor = "#2a2a3e"; + entry.style.backgroundColor = rp.overlay; }); entry.addEventListener("mouseleave", () => { entry.style.backgroundColor = "transparent"; }); - // Time span (cyan) const timeSpan = document.createElement("span"); timeSpan.textContent = formatTime(event.time); - timeSpan.style.color = "#00d4aa"; + timeSpan.style.color = rp.foam; entry.appendChild(timeSpan); entry.appendChild(document.createTextNode(" ")); - // Component span (yellow) const componentSpan = document.createElement("span"); componentSpan.textContent = event.component; - componentSpan.style.color = "#ffeb3b"; + componentSpan.style.color = rp.gold; entry.appendChild(componentSpan); entry.appendChild(document.createTextNode(": ")); - // Event type span (color based on type) const eventTypeSpan = document.createElement("span"); eventTypeSpan.textContent = event.type; - const color = eventTypeColors[event.type] || defaultEventColor; - eventTypeSpan.style.color = color; + eventTypeSpan.style.color = eventTypeColors[event.type] || rp.subtle; entry.appendChild(eventTypeSpan); - // Details (if any) if (Object.keys(event.details).length > 0) { entry.appendChild(document.createTextNode(" — ")); - const detailsStr = JSON.stringify(event.details); const detailsSpan = document.createElement("span"); - detailsSpan.textContent = detailsStr; - detailsSpan.style.color = "#888"; + detailsSpan.textContent = JSON.stringify(event.details); + detailsSpan.style.color = rp.muted; entry.appendChild(detailsSpan); } - // Click handler: dispatch custom event entry.addEventListener("click", () => { const evt = new CustomEvent("event-selected", { detail: event, @@ -128,15 +87,11 @@ function renderEventEntry(event: SimEventJSON): HTMLElement { entry.dispatchEvent(evt); }); - // Store event data for filtering (entry as any)._eventData = event; return entry; } -/** - * Check if an event matches the current filter. - */ function eventMatchesFilter(event: SimEventJSON, filter: EventFilter): boolean { if (filter.component !== null && event.component !== filter.component) { return false; @@ -147,9 +102,6 @@ function eventMatchesFilter(event: SimEventJSON, filter: EventFilter): boolean { return true; } -/** - * Update the event log panel with new events and apply the current filter. - */ function clearEventLog(): void { allEvents = []; } @@ -158,18 +110,14 @@ function updateEventLog( events: ReadonlyArray, options: EventLogOptions ): void { - // Append new events to the running log if (events.length > 0) { allEvents = [...allEvents, ...events]; } - // Clear the container for re-render options.container.innerHTML = ""; - // Limit to maxEvents const visibleEvents = Array.from(allEvents).slice(-options.maxEvents); - // Filter and render for (const event of visibleEvents) { if (eventMatchesFilter(event, currentFilter)) { const entry = renderEventEntry(event); @@ -177,27 +125,20 @@ function updateEventLog( } } - // Auto-scroll to bottom options.container.scrollTop = options.container.scrollHeight; } -/** - * Set the event filter and re-render. - */ function setEventFilter( filter: EventFilter, logContainer: HTMLElement | null ): void { currentFilter = filter; if (logContainer) { - const maxEvents = 1000; // Default limit + const maxEvents = 1000; updateEventLog(allEvents, { container: logContainer, maxEvents }); } } -/** - * Render the PE state section. - */ function renderPEState(peId: string, state: PEState): HTMLElement { const section = document.createElement("details"); section.style.marginBottom = "12px"; @@ -206,7 +147,7 @@ function renderPEState(peId: string, state: PEState): HTMLElement { summary.textContent = `PE ${peId}`; summary.style.cursor = "pointer"; summary.style.fontWeight = "bold"; - summary.style.color = "#2196f3"; + summary.style.color = rp.foam; section.appendChild(summary); const content = document.createElement("div"); @@ -219,7 +160,7 @@ function renderPEState(peId: string, state: PEState): HTMLElement { iramDiv.style.marginBottom = "8px"; const iramLabel = document.createElement("strong"); iramLabel.textContent = "IRAM:"; - iramLabel.style.color = "#4caf50"; + iramLabel.style.color = rp.pine; iramDiv.appendChild(iramLabel); iramDiv.appendChild(document.createElement("br")); @@ -227,13 +168,13 @@ function renderPEState(peId: string, state: PEState): HTMLElement { if (iramEntries.length === 0) { const empty = document.createElement("span"); empty.textContent = "(empty)"; - empty.style.color = "#888"; + empty.style.color = rp.muted; iramDiv.appendChild(empty); } else { for (const [offset, instr] of iramEntries) { const line = document.createElement("div"); line.textContent = ` [${offset}]: ${JSON.stringify(instr)}`; - line.style.color = "#aaa"; + line.style.color = rp.subtle; iramDiv.appendChild(line); } } @@ -244,7 +185,7 @@ function renderPEState(peId: string, state: PEState): HTMLElement { msDiv.style.marginBottom = "8px"; const msLabel = document.createElement("strong"); msLabel.textContent = "Matching Store:"; - msLabel.style.color = "#4caf50"; + msLabel.style.color = rp.pine; msDiv.appendChild(msLabel); msDiv.appendChild(document.createElement("br")); @@ -252,7 +193,7 @@ function renderPEState(peId: string, state: PEState): HTMLElement { if (matchingStore.length === 0) { const empty = document.createElement("span"); empty.textContent = "(empty)"; - empty.style.color = "#888"; + empty.style.color = rp.muted; msDiv.appendChild(empty); } else { for (let ctx = 0; ctx < matchingStore.length; ctx++) { @@ -261,7 +202,7 @@ function renderPEState(peId: string, state: PEState): HTMLElement { const ctxDiv = document.createElement("div"); ctxDiv.textContent = ` ctx[${ctx}]:`; - ctxDiv.style.color = "#ffeb3b"; + ctxDiv.style.color = rp.gold; msDiv.appendChild(ctxDiv); for (let offset = 0; offset < ctx_slots.length; offset++) { @@ -273,7 +214,7 @@ function renderPEState(peId: string, state: PEState): HTMLElement { const portStr = entry.port || "—"; const dataStr = entry.data !== null ? entry.data.toString() : "—"; entryDiv.textContent = `[${offset}]: data=${dataStr}, port=${portStr}`; - entryDiv.style.color = "#aaa"; + entryDiv.style.color = rp.subtle; msDiv.appendChild(entryDiv); } } @@ -285,7 +226,7 @@ function renderPEState(peId: string, state: PEState): HTMLElement { genDiv.style.marginBottom = "8px"; const genLabel = document.createElement("strong"); genLabel.textContent = "Gen Counters:"; - genLabel.style.color = "#4caf50"; + genLabel.style.color = rp.pine; genDiv.appendChild(genLabel); genDiv.appendChild(document.createElement("br")); @@ -293,13 +234,13 @@ function renderPEState(peId: string, state: PEState): HTMLElement { if (genCounters.length === 0) { const empty = document.createElement("span"); empty.textContent = "(none)"; - empty.style.color = "#888"; + empty.style.color = rp.muted; genDiv.appendChild(empty); } else { for (let i = 0; i < genCounters.length; i++) { const line = document.createElement("div"); line.textContent = ` ctx[${i}]: ${genCounters[i]}`; - line.style.color = "#aaa"; + line.style.color = rp.subtle; genDiv.appendChild(line); } } @@ -309,13 +250,13 @@ function renderPEState(peId: string, state: PEState): HTMLElement { const statsDiv = document.createElement("div"); const queueSpan = document.createElement("span"); queueSpan.textContent = `Input Queue: ${state.input_queue_depth}`; - queueSpan.style.color = "#aaa"; + queueSpan.style.color = rp.subtle; statsDiv.appendChild(queueSpan); statsDiv.appendChild(document.createElement("br")); const outputSpan = document.createElement("span"); outputSpan.textContent = `Output Count: ${state.output_count}`; - outputSpan.style.color = "#aaa"; + outputSpan.style.color = rp.subtle; statsDiv.appendChild(outputSpan); content.appendChild(statsDiv); @@ -323,9 +264,6 @@ function renderPEState(peId: string, state: PEState): HTMLElement { return section; } -/** - * Render the SM state section. - */ function renderSMState(smId: string, state: SMState): HTMLElement { const section = document.createElement("details"); section.style.marginBottom = "12px"; @@ -334,7 +272,7 @@ function renderSMState(smId: string, state: SMState): HTMLElement { summary.textContent = `SM ${smId}`; summary.style.cursor = "pointer"; summary.style.fontWeight = "bold"; - summary.style.color = "#ff9800"; + summary.style.color = rp.gold; section.appendChild(summary); const content = document.createElement("div"); @@ -347,7 +285,7 @@ function renderSMState(smId: string, state: SMState): HTMLElement { cellsDiv.style.marginBottom = "8px"; const cellsLabel = document.createElement("strong"); cellsLabel.textContent = "Cells:"; - cellsLabel.style.color = "#4caf50"; + cellsLabel.style.color = rp.pine; cellsDiv.appendChild(cellsLabel); cellsDiv.appendChild(document.createElement("br")); @@ -360,7 +298,7 @@ function renderSMState(smId: string, state: SMState): HTMLElement { if (cellAddrs.length === 0) { const empty = document.createElement("span"); empty.textContent = "(all empty)"; - empty.style.color = "#888"; + empty.style.color = rp.muted; cellsDiv.appendChild(empty); } else { for (const addr of cellAddrs) { @@ -370,7 +308,7 @@ function renderSMState(smId: string, state: SMState): HTMLElement { const line = document.createElement("div"); const dataStr = cell.data_l !== null ? cell.data_l : (cell.data_r !== null ? cell.data_r : "—"); line.textContent = ` [${addr}]: ${cell.presence} = ${dataStr}`; - line.style.color = "#aaa"; + line.style.color = rp.subtle; cellsDiv.appendChild(line); } } @@ -382,13 +320,13 @@ function renderSMState(smId: string, state: SMState): HTMLElement { deferredDiv.style.marginBottom = "8px"; const deferredLabel = document.createElement("strong"); deferredLabel.textContent = "Deferred Read:"; - deferredLabel.style.color = "#9c27b0"; + deferredLabel.style.color = rp.iris; deferredDiv.appendChild(deferredLabel); deferredDiv.appendChild(document.createElement("br")); const drLine = document.createElement("div"); drLine.textContent = ` cell_addr: ${state.deferred_read.cell_addr}`; - drLine.style.color = "#aaa"; + drLine.style.color = rp.subtle; deferredDiv.appendChild(drLine); content.appendChild(deferredDiv); @@ -398,13 +336,13 @@ function renderSMState(smId: string, state: SMState): HTMLElement { const statsDiv = document.createElement("div"); const t0Span = document.createElement("span"); t0Span.textContent = `T0 Store Size: ${state.t0_store_size}`; - t0Span.style.color = "#aaa"; + t0Span.style.color = rp.subtle; statsDiv.appendChild(t0Span); statsDiv.appendChild(document.createElement("br")); const queueSpan = document.createElement("span"); queueSpan.textContent = `Input Queue: ${state.input_queue_depth}`; - queueSpan.style.color = "#aaa"; + queueSpan.style.color = rp.subtle; statsDiv.appendChild(queueSpan); content.appendChild(statsDiv); @@ -412,17 +350,12 @@ function renderSMState(smId: string, state: SMState): HTMLElement { return section; } -/** - * Update the state inspector panel with the current system state. - */ function updateStateInspector( state: SystemState, options: StateInspectorOptions ): void { - // Clear the container options.container.innerHTML = ""; - // Create a scrollable container const scrollContainer = document.createElement("div"); scrollContainer.style.overflowY = "auto"; scrollContainer.style.height = "100%"; @@ -434,7 +367,7 @@ function updateStateInspector( const peTitle = document.createElement("h3"); peTitle.textContent = "Processing Elements"; - peTitle.style.color = "#2196f3"; + peTitle.style.color = rp.foam; peTitle.style.marginTop = "0"; peTitle.style.marginBottom = "8px"; peTitle.style.fontSize = "14px"; @@ -444,7 +377,7 @@ function updateStateInspector( if (Object.keys(pes).length === 0) { const empty = document.createElement("span"); empty.textContent = "(none)"; - empty.style.color = "#888"; + empty.style.color = rp.muted; empty.style.fontSize = "11px"; peSection.appendChild(empty); } else { @@ -459,7 +392,7 @@ function updateStateInspector( const smTitle = document.createElement("h3"); smTitle.textContent = "Structure Memories"; - smTitle.style.color = "#ff9800"; + smTitle.style.color = rp.gold; smTitle.style.marginTop = "0"; smTitle.style.marginBottom = "8px"; smTitle.style.fontSize = "14px"; @@ -469,7 +402,7 @@ function updateStateInspector( if (Object.keys(sms).length === 0) { const empty = document.createElement("span"); empty.textContent = "(none)"; - empty.style.color = "#888"; + empty.style.color = rp.muted; empty.style.fontSize = "11px"; smSection.appendChild(empty); } else { @@ -482,10 +415,6 @@ function updateStateInspector( options.container.appendChild(scrollContainer); } -/** - * Display matching store entry for a selected node. - * Shows occupied slots, stored data, and which port is waiting for a partner (AC3.11). - */ function displayNodeMatchingStore( peId: number, ctx: number, @@ -493,18 +422,17 @@ function displayNodeMatchingStore( entry: MatchingEntry, container: HTMLElement ): void { - // Find the state inspector and add a detail section const detailSection = document.createElement("div"); detailSection.id = "node-detail"; detailSection.style.marginTop = "16px"; detailSection.style.padding = "12px"; - detailSection.style.backgroundColor = "#262641"; - detailSection.style.border = "2px solid #2196f3"; + detailSection.style.backgroundColor = rp.overlay; + detailSection.style.border = `2px solid ${rp.foam}`; detailSection.style.borderRadius = "4px"; const title = document.createElement("h4"); title.textContent = `Selected Node: PE${peId} IRAM[${offset}]`; - title.style.color = "#2196f3"; + title.style.color = rp.foam; title.style.margin = "0 0 8px 0"; title.style.fontSize = "13px"; detailSection.appendChild(title); @@ -513,50 +441,47 @@ function displayNodeMatchingStore( contentDiv.style.fontFamily = "monospace"; contentDiv.style.fontSize = "11px"; - // Display matching store status const statusDiv = document.createElement("div"); statusDiv.style.marginBottom = "8px"; const statusLabel = document.createElement("span"); statusLabel.textContent = "Status: "; - statusLabel.style.color = "#4caf50"; + statusLabel.style.color = rp.pine; statusDiv.appendChild(statusLabel); const statusValue = document.createElement("span"); statusValue.textContent = entry.occupied ? "OCCUPIED" : "EMPTY"; - statusValue.style.color = entry.occupied ? "#ff9800" : "#888"; + statusValue.style.color = entry.occupied ? rp.gold : rp.muted; statusDiv.appendChild(statusValue); contentDiv.appendChild(statusDiv); if (entry.occupied) { - // Display data const dataDiv = document.createElement("div"); dataDiv.style.marginBottom = "8px"; const dataLabel = document.createElement("span"); dataLabel.textContent = "Data: "; - dataLabel.style.color = "#4caf50"; + dataLabel.style.color = rp.pine; dataDiv.appendChild(dataLabel); const dataValue = document.createElement("span"); dataValue.textContent = entry.data !== null ? entry.data.toString() : "(null)"; - dataValue.style.color = "#ffeb3b"; + dataValue.style.color = rp.gold; dataDiv.appendChild(dataValue); contentDiv.appendChild(dataDiv); - // Display port const portDiv = document.createElement("div"); const portLabel = document.createElement("span"); portLabel.textContent = "Waiting Port: "; - portLabel.style.color = "#4caf50"; + portLabel.style.color = rp.pine; portDiv.appendChild(portLabel); const portValue = document.createElement("span"); portValue.textContent = entry.port || "(none)"; - portValue.style.color = entry.port ? "#2196f3" : "#888"; + portValue.style.color = entry.port ? rp.foam : rp.muted; portDiv.appendChild(portValue); contentDiv.appendChild(portDiv); @@ -564,13 +489,11 @@ function displayNodeMatchingStore( detailSection.appendChild(contentDiv); - // Remove any existing node detail section const existing = container.querySelector("#node-detail"); if (existing) { existing.remove(); } - // Append the new detail section (add it at the top of the inspector) const scrollContainer = container.querySelector("div"); if (scrollContainer) { scrollContainer.insertBefore(detailSection, scrollContainer.firstChild); @@ -579,10 +502,6 @@ function displayNodeMatchingStore( } } -/** - * Display SM state detail when an SM node is clicked. - * Shows cell contents with presence, data values, deferred reads, and T0 store size. - */ function displaySMNodeState( smId: number, state: SMState, @@ -592,13 +511,13 @@ function displaySMNodeState( detailSection.id = "node-detail"; detailSection.style.marginTop = "16px"; detailSection.style.padding = "12px"; - detailSection.style.backgroundColor = "#2e2416"; - detailSection.style.border = "2px solid #ff9800"; + detailSection.style.backgroundColor = rp.overlay; + detailSection.style.border = `2px solid ${rp.gold}`; detailSection.style.borderRadius = "4px"; const title = document.createElement("h4"); title.textContent = `Selected: SM ${smId}`; - title.style.color = "#ff9800"; + title.style.color = rp.gold; title.style.margin = "0 0 8px 0"; title.style.fontSize = "13px"; detailSection.appendChild(title); @@ -620,17 +539,17 @@ function displaySMNodeState( emptyDiv.style.marginBottom = "8px"; const label = document.createElement("strong"); label.textContent = "Cells: "; - label.style.color = "#4caf50"; + label.style.color = rp.pine; emptyDiv.appendChild(label); const value = document.createElement("span"); value.textContent = "(all empty)"; - value.style.color = "#888"; + value.style.color = rp.muted; emptyDiv.appendChild(value); contentDiv.appendChild(emptyDiv); } else { const cellsHeader = document.createElement("strong"); cellsHeader.textContent = "Cells:"; - cellsHeader.style.color = "#4caf50"; + cellsHeader.style.color = rp.pine; contentDiv.appendChild(cellsHeader); contentDiv.appendChild(document.createElement("br")); @@ -642,27 +561,27 @@ function displaySMNodeState( const addrSpan = document.createElement("span"); addrSpan.textContent = `[${addr}] `; - addrSpan.style.color = "#ffeb3b"; + addrSpan.style.color = rp.gold; line.appendChild(addrSpan); const presSpan = document.createElement("span"); presSpan.textContent = cell.presence; - presSpan.style.color = cell.presence === "FULL" ? "#4caf50" - : cell.presence === "WAITING" ? "#9c27b0" - : cell.presence === "RESERVED" ? "#ff9800" - : "#888"; + presSpan.style.color = cell.presence === "FULL" ? rp.pine + : cell.presence === "WAITING" ? rp.iris + : cell.presence === "RESERVED" ? rp.gold + : rp.muted; line.appendChild(presSpan); if (cell.data_l !== null) { const dataSpan = document.createElement("span"); dataSpan.textContent = ` L=${cell.data_l}`; - dataSpan.style.color = "#aaa"; + dataSpan.style.color = rp.subtle; line.appendChild(dataSpan); } if (cell.data_r !== null) { const dataSpan = document.createElement("span"); dataSpan.textContent = ` R=${cell.data_r}`; - dataSpan.style.color = "#aaa"; + dataSpan.style.color = rp.subtle; line.appendChild(dataSpan); } @@ -676,11 +595,11 @@ function displaySMNodeState( drDiv.style.marginTop = "8px"; const drLabel = document.createElement("strong"); drLabel.textContent = "Deferred Read: "; - drLabel.style.color = "#9c27b0"; + drLabel.style.color = rp.iris; drDiv.appendChild(drLabel); const drValue = document.createElement("span"); drValue.textContent = `cell[${state.deferred_read.cell_addr}]`; - drValue.style.color = "#aaa"; + drValue.style.color = rp.subtle; drDiv.appendChild(drValue); contentDiv.appendChild(drDiv); } @@ -690,7 +609,7 @@ function displaySMNodeState( t0Div.style.marginTop = "8px"; const t0Label = document.createElement("span"); t0Label.textContent = `T0 Store: ${state.t0_store_size} entries`; - t0Label.style.color = "#888"; + t0Label.style.color = rp.muted; t0Div.appendChild(t0Label); contentDiv.appendChild(t0Div); @@ -698,13 +617,12 @@ function displaySMNodeState( const queueDiv = document.createElement("div"); const queueLabel = document.createElement("span"); queueLabel.textContent = `Input Queue: ${state.input_queue_depth}`; - queueLabel.style.color = "#888"; + queueLabel.style.color = rp.muted; queueDiv.appendChild(queueLabel); contentDiv.appendChild(queueDiv); detailSection.appendChild(contentDiv); - // Remove any existing node detail section const existing = container.querySelector("#node-detail"); if (existing) { existing.remove(); diff --git a/monitor/frontend/src/style.ts b/monitor/frontend/src/style.ts index 2086ee4..44f73ef 100644 --- a/monitor/frontend/src/style.ts +++ b/monitor/frontend/src/style.ts @@ -1,78 +1,160 @@ import { baseStylesheet } from "@common/style"; import type cytoscape from "cytoscape"; +import { rp, rpMoon } from "./palette"; -const monitorOverlayStyles: cytoscape.StylesheetStyle[] = [ - // SM node dark-mode override: readable on dark background +const monitorDarkModeOverrides: cytoscape.StylesheetStyle[] = [ + // Base node: rosé pine dark background + { + selector: "node", + style: { + "background-color": rp.surface, + "background-opacity": 1, + color: rp.text, + "border-color": rp.highlightMed, + "text-outline-width": 0, + }, + }, + // Per-category border colours (override data(colour) from server) + { + selector: 'node[category = "arithmetic"]', + style: { "border-color": rp.foam }, + }, + { + selector: 'node[category = "logic"]', + style: { "border-color": rp.pine }, + }, + { + selector: 'node[category = "comparison"]', + style: { "border-color": rp.gold }, + }, + { + selector: 'node[category = "routing"]', + style: { "border-color": rp.iris }, + }, + { + selector: 'node[category = "memory"]', + style: { "border-color": rpMoon.rose }, + }, + { + selector: 'node[category = "io"]', + style: { "border-color": rpMoon.pine }, + }, + { + selector: 'node[category = "config"]', + style: { "border-color": rp.muted }, + }, + // SM node: warm gold/rose tones { selector: 'node[category = "structure_memory"]', style: { - "background-color": "#2e2416", + "background-color": rp.overlay, "background-opacity": 0.9, - "border-color": "#ff9800", - color: "#e0c8a0", + "border-color": rp.gold, + color: rp.rose, }, }, - // Synthetic edge dark-mode override + // Compound node (PE clusters): rosé pine overlay + { + selector: "$node > node", + style: { + "background-color": rp.overlay, + "background-opacity": 0.4, + "border-color": rp.iris, + "border-style": "dashed", + "border-width": 1, + color: rp.iris, + }, + }, + // Base edge: muted on dark + { + selector: "edge", + style: { + "line-color": rp.subtle, + "target-arrow-color": rp.subtle, + color: rp.muted, + "text-background-color": rp.base, + "text-background-opacity": 0.9, + }, + }, + // Error node + { + selector: "node.error", + style: { + "border-color": rp.love, + }, + }, + // Error edge + { + selector: "edge.error", + style: { + "line-color": rp.love, + "target-arrow-color": rp.love, + }, + }, + // Synthetic edge (SM request/return) { selector: "edge.synthetic", style: { - "line-color": "#ff9800", - "target-arrow-color": "#ff9800", + "line-color": rp.gold, + "target-arrow-color": rp.gold, }, }, - // Active node (AC3.3): bright border glow when token received +]; + +const monitorOverlayStyles: cytoscape.StylesheetStyle[] = [ + // Active node: bright gold border when token received { selector: "node.active", style: { - "border-color": "#ffc107", + "border-color": rp.gold, "border-width": 4, }, }, - // Executed node (AC3.3): brief green highlight + // Executed node: pine green highlight { selector: "node.executed", style: { - "background-color": "#4caf50", + "background-color": rp.pine, "background-opacity": 0.3, }, }, - // Matched node: brief cyan highlight + // Matched node: foam cyan border { selector: "node.matched", style: { - "border-color": "#00bcd4", + "border-color": rp.foam, "border-width": 3, }, }, - // Half-matched node (AC3.12): one operand waiting + // Half-matched node: one operand waiting { selector: "node.half-matched", style: { "border-style": "dashed", "border-width": 2, - "border-color": "#00bcd4", + "border-color": rp.foam, "background-opacity": 0.5, }, }, - // Token flow edge (AC3.4): animated dashed line + // Token flow edge: animated gold dashed line { selector: "edge.token-flow", style: { - "line-color": "#ff9800", + "line-color": rp.gold, "line-dash-pattern": [6, 3], width: 3, }, }, - // SM cell written: brief orange highlight on SM node + // SM cell written: rose highlight { selector: "node.cell-written", style: { - "border-color": "#ff9800", + "border-color": rp.rose, "border-width": 3, "background-opacity": 0.6, }, }, - // Finished indicator: dim all nodes when simulation complete + // Finished indicator: dim all nodes { selector: "node.finished", style: { @@ -81,7 +163,135 @@ const monitorOverlayStyles: cytoscape.StylesheetStyle[] = [ }, ]; +const physicalEdgeStyles: cytoscape.StylesheetStyle[] = [ + // PE cluster node + { + selector: "node.pe-cluster", + style: { + shape: "roundrectangle", + width: "label", + height: "label", + "border-width": 1.5, + "border-color": rp.iris, + "background-color": rp.overlay, + "background-opacity": 0.4, + padding: "16px", + "text-valign": "top", + "text-halign": "center", + label: "data(label)", + "font-size": 9, + "font-weight": "bold", + color: rp.iris, + }, + }, + // Port node (bus topology endpoints) + { + selector: "node.port-node", + style: { + shape: "ellipse", + width: 10, + height: 10, + "background-color": rp.love, + "background-opacity": 1, + "border-width": 0, + label: "", + }, + }, + // Intra-PE edge + { + selector: "edge.intra-pe", + style: { + width: 0.75, + "line-color": rp.subtle, + "target-arrow-color": rp.subtle, + "source-label": "", + "target-label": "data(targetLabel)", + "target-text-offset": 5, + "target-text-margin-y": -6, + "font-size": 7, + "font-family": "monospace", + color: rp.muted, + "text-background-color": rp.base, + "text-background-opacity": 0.9, + "text-background-padding": "1px", + }, + }, + // Exit segment (node to exit port, no arrow) + { + selector: "edge.exit-segment", + style: { + width: 0.75, + "line-color": rp.subtle, + "target-arrow-shape": "none", + "target-label": "", + "source-label": "", + }, + }, + // Entry segment (entry port to destination) + { + selector: "edge.entry-segment", + style: { + width: 0.75, + "line-color": rp.subtle, + "target-arrow-color": rp.subtle, + "target-arrow-shape": "triangle", + "source-label": "", + "target-label": "data(targetLabel)", + "target-text-offset": 5, + "target-text-margin-y": -6, + "font-size": 7, + "font-family": "monospace", + color: rp.muted, + "text-background-color": rp.base, + "text-background-opacity": 0.9, + "text-background-padding": "1px", + }, + }, + // Bus segment (cross-PE thick edge) + { + selector: "edge.bus-segment", + style: { + width: 2, + "line-color": rp.love, + "target-arrow-color": rp.love, + "target-arrow-shape": "triangle", + "arrow-scale": 0.8, + label: "data(label)", + "target-label": "", + "source-label": "", + "text-margin-y": -8, + "font-size": 7, + "font-family": "monospace", + color: rp.love, + "text-background-color": rp.base, + "text-background-opacity": 0.9, + "text-background-padding": "1px", + }, + }, + // Seed edge (dashed, muted) + { + selector: "edge.seed-edge", + style: { + width: 1, + "line-style": "dashed", + "line-color": rp.muted, + "target-arrow-color": rp.muted, + "target-text-offset": 5, + "target-text-margin-y": -6, + }, + }, + // Physical edges default to bezier + { + selector: "edge.physical", + style: { + "curve-style": "bezier", + }, + }, +]; + export const monitorStylesheet: ReadonlyArray = [ ...baseStylesheet, + ...monitorDarkModeOverrides, + ...physicalEdgeStyles, ...monitorOverlayStyles, ];